Meridian documentation

Meridian is a skill router for AI agents — local stdio MCP for Claude Code/Cursor/Windsurf, hosted remote MCP at mcp.ask-meridian.uk for Grok/ChatGPT/Claude.ai. It generates candidate skills with an LLM, then ranks them with a local orbital classifier that assigns each one a celestial class (planet · moon · trojan · asteroid · comet · irregular).

Install

1. Stdio MCP — Claude Code, Cursor, Windsurf, Goose, Continue

npm install -g meridian-skills-mcp
claude mcp add meridian meridian-mcp \
  -e MERIDIAN_GITHUB_TOKEN=ghp_yourTokenHere

Same install works in any client that speaks stdio MCP. The package is one Node entrypoint plus the bundled orbital classifier — no native deps.

2. Remote MCP — Grok, ChatGPT, Claude.ai connectors

Pick the host below, paste the URL, no install required. Full step-by-step in the Grok connector section.

https://mcp.ask-meridian.uk/mcp

3. Get a GitHub Models token (stdio install only)

The stdio MCP calls GitHub Models using your own PAT. Create a fine-grained one at github.com/settings/tokens with account permissions → Models: Read. That single permission is enough; the token sees nothing else on your account. Pass it as MERIDIAN_GITHUB_TOKEN (or GITHUB_TOKEN).

The remote variant (mcp.ask-meridian.uk) handles its own GitHub Models auth — connector users never see a PAT.

4. Browser miniapp (no install)

Open ask-meridian.uk/miniapp. Type a task or hit 📷 Scan an object to use real-time camera object detection as the input. Same orbital classifier, runs in-browser, routes against the bundled 88-skill corpus.

Grok connector setup

Grok's "Add custom connector" dialog takes an MCP server URL plus four OAuth fields. Paste these:

FieldValue
Server URLhttps://mcp.ask-meridian.uk/mcp
Authorization endpointhttps://mcp.ask-meridian.uk/authorize
Token endpointhttps://mcp.ask-meridian.uk/token
Client IDgrok
Client secret(empty)
Token auth methodnone  (PKCE only)
Scopesroute_task

The flow:

  1. Grok stores the connector and shows an Authorize button.
  2. Clicking it opens mcp.ask-meridian.uk/authorize in a new tab — a single-button confirmation page. No PAT pasting, no GitHub jargon.
  3. You click Authorize, the page 302s back to Grok with a one-time auth code, Grok exchanges it at /token with PKCE verification, and gets a 1-hour Bearer back.
  4. route_task appears in Grok's tools list. Ask something like "route a task to ranked skills: build a WebSocket-backed presence indicator" and Grok invokes it.

The hosted Worker uses an operator-pays auth model: it holds a single GitHub Models PAT and uses it for every inference call, so connector users see zero credential prompts. Tokens expire after 1 h and can be re-authorized at any time. Same URL works in ChatGPT custom MCPs and Claude.ai connectors — they speak the same MCP Streamable-HTTP + OAuth 2.1 spec.

Source: cf-worker/ in the npm repo. ~250 lines, runs on Cloudflare Workers, deploys automatically on every push to main.

Quickstart

Once the MCP is registered, your AI client auto-discovers the tool. Just ask:

Use meridian to find skills for setting up a public API rate limiter.

The client calls meridian.route_task({ task, limit }). The package generates 5 LLM candidates via GitHub Models, classifies them orbitally, and returns ranked skills with full markdown bodies — which your client lifts straight into its context window.

Pipeline

meridian.route_task({ task, limit })
            │
            ↓
  GitHub Models inference
  (default: meta/llama-3.3-70b-instruct)
  → 5 candidate skills
    {slug, description, keywords, body}
            │
            ↓
  Local orbital classifier (JS, <5 ms)
            │
   ─────────┴──────────
   physics:   mass · scope · independence ·
              cross_domain · fragmentation · drag · dep_ratio
   class:     planet | moon | trojan | asteroid | comet | irregular
   parent:    nearest sibling by Jaccard
   system:    forge | signal | mind  (term-set affinity)
   lagrange:  bridge potential between two strongest systems
            │
            ↓
   route_score = (kw·10 + desc·5 + body·1)
                × diversity · class_boost · versatility
            │
            ↓
        ranked array

Two ways to reach this pipeline. External AI clients (Grok / ChatGPT / Claude.ai) register the OAuth-gated MCP at mcp.ask-meridian.uk/mcp and call the route_task tool — covered in the next section. First-party browser front-ends (lens, the miniapp, Photon) skip OAuth entirely and POST mcp.ask-meridian.uk/v1/route — covered in Browser endpoint below.

MCP tool

The package exposes one tool:

route_task(task: string, limit?: integer)

Generates candidates, classifies them orbitally, and returns the ranked list as a single agent-readable markdown block. Each entry ships with its full skill body so the caller LLM doesn't need a second tool call to read it.

Input

{
  "task":  "string (≤ 800 chars, required)",
  "limit": 5     // 1-10 (default 5)
}

Errors

Browser endpoint

First-party Meridian front-ends (the browser miniapp, Lens, Photon) skip the OAuth dance and POST mcp.ask-meridian.uk/v1/route directly. The endpoint is operator-paid — the Cloudflare Worker holds a single GitHub PAT in a secret and uses it for every inference call — and Origin-restricted, so only requests carrying an allowlisted Origin header are accepted. There is no auth header for the caller to manage; the server enforces both the allowlist and the upstream credential.

Use this when you're shipping a browser app on an ask-meridian.uk sub-property and want skill routing without making the user paste a PAT or run an OAuth callback. Use /mcp instead when the caller is an external connector host (Grok, ChatGPT, Claude.ai).

Allowlisted origins

Bring up a new sub-property by editing BROWSER_ORIGIN_ALLOWLIST in cf-worker/worker.mjs and pushing — the Worker re-deploys automatically.

Request

curl -X POST https://mcp.ask-meridian.uk/v1/route \
  -H 'Origin: https://ask-meridian.uk' \
  -H 'Content-Type: application/json' \
  -d '{"task": "rate limit a public API", "limit": 5}'

Response

Same pipeline as route_task, but returns the structured classifier output (not the markdown wrapper) so front-ends can render orbits, score bars, and per-skill physics panels directly:

{
  "task":                 "rate limit a public API",
  "confidence":           "strong",
  "top_score":            109.3,
  "candidates_generated": 5,
  "selected": [
    /* same shape as the per-entry block in `Response shape` below */
  ],
  "timing": { "llm_ms": 4200, "classify_ms": 12, "total_ms": 4212 }
}

Errors come back as { "error": "..." } with a 4xx/5xx status: 403 for a non-allowlisted Origin, 500 if the operator's PAT secret is missing, 502 for upstream LLM failure.

Online learning

The browser endpoint /v1/route applies a fitted-correction layer on top of the heuristic ranking. The correction is trained by a separate POST endpoint that front-ends fire whenever a user engages a skill (clicks a planet in Lens, opens a detail panel in the miniapp, etc.). Constant per-request cost (~1 ms), no batch training, no GPU, no local execution.

POST /v1/feedback

curl -X POST https://mcp.ask-meridian.uk/v1/feedback \
  -H 'Origin: https://ask-meridian.uk' \
  -H 'Content-Type: application/json' \
  -d '{
    "query":       "rate limit a public API",
    "selected":    [ /* same shape as /v1/route's selected[] */ ],
    "chosen_slug": "redis-token-bucket",
    "action":      "click"
  }'

Each POST runs one pairwise-ranking SGD step (logistic ranking loss over (chosen, every-other) pairs, lr 0.02, L2 0.001) against a 24-feature vector per skill: 8 physics scalars, 6 class one-hot, 3 star-system one-hot, 3 token-hit features, route_score normalised by batch max, rank position, has-parent, habitable-zone. The fitted correction multiplies route_score by 1 + tanh(K · w·x) — bounded to [0, 2] so no individual skill can be silently boosted beyond 2× heuristic.

action values: click, detail_open, copy, thumbs_up, bootstrap all train (positive signal). dismiss is recorded but does not update weights — avoids penalising candidates the user simply didn't have time to look at.

GET /v1/model-info — read-only model state for dashboards and the eval cron:

curl https://mcp.ask-meridian.uk/v1/model-info
# {
#   "version": "v1",
#   "n_updates": 64,
#   "n_pairs":   1213,
#   "cold_start": false,
#   "updated_at": "2026-05-07T17:47:14.815Z"
# }

The MCP tool path (/mcp, OAuth 2.1 + PKCE) does not apply the fitted correction. External connector hosts (Grok, ChatGPT, Claude.ai) get the deterministic heuristic ranking so per-call behaviour is reproducible. Only the first-party browser endpoint /v1/route learns from feedback.

For the full story — including the calibration panel that surfaced the bug and the two textbook physics frameworks (Vallado CRTBP, Sears & Zemansky spectral) we tried and abandoned — see the blog post: From 17% to 81%.

Response shape

The MCP renders results as markdown for the agent. The underlying ranked entries look like:

{
  "slug":        "redis-token-bucket",
  "name":        "redis-token-bucket",
  "description": "Atomic Lua-driven token bucket in Redis for sub-millisecond rate limiting.",
  "body":        "## Use It For\n- ...\n\n## Workflow\n1. ...\n",
  "keywords":    ["redis", "lua", "token-bucket", "..."],
  "route_score": 109.3,

  "classification": {
    "class":              "trojan",
    "class_scores":       { "planet": 0.41, "moon": 0.07, "trojan": 0.62, "..." : 0 },
    "physics": {
      "mass":          0.58,
      "scope":         0.71,
      "independence":  0.34,
      "cross_domain":  0.18,
      "fragmentation": 0.12,
      "drag":          0.21,
      "dep_ratio":     0.64,
      "lagrange_potential": 0.22,
      "star_system":   "forge",
      "star_affinity": { "forge": 0.78, "signal": 0.06, "mind": 0.04 }
    },
    "parent":             "api-rate-limiting",
    "star_system":        "forge",
    "lagrange_systems":   ["forge"],
    "lagrange_potential": 0.22,
    "decision_rule":      "Companion at L4/L5 of api-rate-limiting — ...",
    "habitable_zone":     true,
    "tidal_lock":         true
  },

  "breakdown": {
    "kw_hits":        3,
    "desc_hits":      2,
    "body_hits":      11,
    "diversity_mult": 1.36,
    "class_mult":     1.20,
    "lagrange_mult":  1.11,
    "tokens":         ["rate", "limiting", "redis"]
  },
  "why": "3 keyword · 2 desc · 11 body · trojan×1.20 · ..."
}

Celestial classes

Each generated skill is assigned exactly one class — argmax over six per-class scores:

ClassDecision ruleScore boost
Planet Domain anchor — high mass × scope × independence. Loads as a primary skill. ×1.30
Trojan Companion at L4/L5 of a parent — high dep_ratio, low fragmentation. Co-activates permanently with its parent. ×1.20
Irregular Cross-domain bridge — spans multiple star systems with high fragmentation. ×1.10
Moon Sub-skill orbiting a parent — low independence, dep_ratio drives loading. ×1.05
Asteroid Narrow-scope niche tool — low mass but independently useful. ×0.85
Comet Specialised / occasional — high drag, high cross_domain, low dep_ratio. Triggers rarely. ×0.80

Physics signature

Per-skill features derived from content alone (no curated lookup tables):

Star systems

Three system term sets determine which gravitational well a skill orbits:

Skills with strong affinity in ≥ 2 systems get a versatility boost (1 + min(0.30, lagrange_potential × 0.5)) on the route score.

Configuration

Environment variables for the MCP:

VariableDefaultPurpose
MERIDIAN_GITHUB_TOKEN (or GITHUB_TOKEN) (required) Fine-grained PAT with Models: Read. Used to call GitHub Models.
MERIDIAN_MODEL meta/llama-3.3-70b-instruct Override the model. Any GitHub Models catalog id works (e.g. openai/gpt-4o).
MERIDIAN_MODELS_ENDPOINT https://models.github.ai/inference/chat/completions Override the inference endpoint (e.g. for a self-hosted gateway).
MERIDIAN_CANDIDATES 5 How many candidate skills the LLM generates per call (3–8).
MERIDIAN_TIMEOUT_MS 90000 Abort the inference request after this many ms.

Latency & quota

Self-hosting (closed-domain, offline)

If you need local model + curated corpus with no network calls, pin to the legacy line:

npm install -g meridian-skills-mcp@0.3.2

That ships:

Tradeoff: closed-domain (only the curated 88 skills classify), but no internet required. Versions 1.x ran the LLM and classifier on a Cloudflare Worker — that backend was retired during the GitHub Pages migration; 1.0.1 POSTs to a dead endpoint and is no longer supported.