Meridian MCP documentation

This page documents the Meridian MCP (the orbital task router) β€” flagship of the fleet on this domain. The Grok connector setup section below has a picker for the other two MCPs (Finance Β· Pharmacy); for their tool surfaces and architecture, see their READMEs.

Meridian is a orbital task router β€” local stdio MCP for Claude Code/Cursor/Windsurf, hosted remote MCP at mcp.ask-meridian.uk for Grok/ChatGPT/Claude.ai. It generates candidates 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-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. The bundled JS orbital classifier ranks a static corpus offline; toggle πŸͺ dynamic to also POST to /v1/route for fresh Llama-3.3-70B candidates, scored alongside the static set.

Grok connector setup

Grok's "Add custom connector" dialog takes an MCP server URL plus four OAuth fields. Pick which MCP from the fleet you want to add β€” the values below update accordingly:

Task β†’ orbital classification. route_task tool.
FieldValue
Server URL https://mcp.ask-meridian.uk/mcp
Authorization endpointhttps://mcp.ask-meridian.uk/authorize
Token endpoint https://mcp.ask-meridian.uk/token
Client ID grok
Client secret (empty)
Token auth method none  (PKCE only)
Scopes route_task

The flow (same for any MCP in the fleet):

  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 Bearer back (TTL: 1 h for Meridian, 7 d for Finance & Pharmacy).
  4. The MCP's tools appear in Grok's tools list. For Meridian it's route_task; for Pharmacy it's search_products / add_to_cart / prepare_checkout + 8 more; for Finance it's the read-only balance + portfolio surface.

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 candidates 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 candidates 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 candidates
    {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 candidate 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

  • task required β€” missing or empty.
  • task too long β€” over 800 chars.
  • GitHub Models error β€” token missing, rate limited, or upstream failure. Surfaced verbatim to the agent.

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 task 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

  • https://ask-meridian.uk
  • https://meridian.ask-meridian.uk (shared origin: lens, helix, miniapp, vision-lab)
  • https://photon.ask-meridian.uk

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-candidate 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 candidate (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 25-feature vector per candidate: 9 physics scalars (the 8 originals plus coherence_time added in 3.1.0), 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 a bounded sigmoid:

$$\mathrm{score}_{\mathrm{final}} \;=\; \mathrm{score}_{\mathrm{heuristic}} \cdot \bigl(1 + \tanh(K \cdot \mathbf{w} \cdot \mathbf{x})\bigr) \;\;\in\; [0,\; 2 \cdot \mathrm{score}_{\mathrm{heuristic}}]$$

Cold start: \(\mathbf{w} = \mathbf{0}\), multiplier \(= 1\), pure heuristic. \(K = 0.6\) so the multiplier only saturates at \(|\mathbf{w}\!\cdot\!\mathbf{x}| > 2\) β€” leaves room for fitted weights to grow. The bound to \([0, 2]\) means no individual candidate can be silently boosted beyond 2Γ— heuristic, even under feedback flooding.

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 candidate is assigned exactly one class β€” argmax over six per-class scores:

$$\begin{aligned} \mathrm{score}_{\mathrm{planet}} \;&=\; \min(m,\, s,\, i)^{1.5} \\[2pt] \mathrm{score}_{\mathrm{moon}} \;&=\; 2 \cdot \max\!\bigl(0,\; \tfrac{1}{2} - i\bigr) \cdot \mathbb{1}_{\mathrm{parent}} \cdot \bigl(1 - \tfrac{m}{2}\bigr) \\[2pt] \mathrm{score}_{\mathrm{trojan}} \;&=\; d_r \cdot \mathbb{1}_{\mathrm{parent}} \cdot (1 - f) \\[2pt] \mathrm{score}_{\mathrm{asteroid}} \;&=\; 2.5 \cdot \max\!\bigl(0,\; 0.55 - m\bigr) \cdot s \cdot i \\[2pt] \mathrm{score}_{\mathrm{comet}} \;&=\; \delta \cdot c_d \cdot (1 - d_r) \\[2pt] \mathrm{score}_{\mathrm{irregular}} \;&=\; 0.85 \cdot c_d \cdot f \\[6pt] \mathrm{class}(\mathbf{p}) \;&=\; \arg\max_{c}\; \mathrm{score}_c(\mathbf{p}) \end{aligned}$$

Variables: \(m\) mass, \(s\) scope, \(i\) independence, \(c_d\) cross-domain affinity, \(f\) fragmentation, \(\delta\) drag, \(d_r\) dep_ratio, \(\mathbb{1}_{\mathrm{parent}}\) is the indicator that the candidate has a parent in the sibling set.

ClassDecision ruleScore boost
Planet Domain anchor β€” high mass Γ— scope Γ— independence. Loads as a primary candidate. Γ—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 Satellite of 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-candidate features derived from content alone (no curated lookup tables):

$$m \;=\; \mathrm{clamp}_{[0,1]}\!\left(\, 0.6 \cdot \frac{\log_{10}\!\bigl(\max(50,\,|\mathrm{body}|)\bigr) - \log_{10}(200)}{\log_{10}(3000) - \log_{10}(200)} \,+\, 0.4 \cdot \frac{|\mathrm{kws}| - 3}{12 - 3} \,\right)$$
  • mass \(m\) β€” see formula above. Tuned to current LLM body lengths (200–3000 chars) and keyword counts (3–12). Median LLM output hits \(m \approx 0.5\).
  • scope \(s\) β€” \(\min(0.7,\; |\mathrm{kws}|/12) + 0.3 \cdot c_d\). Keyword breadth plus cross-domain contribution.
  • independence \(i\) β€” \(\mathrm{clamp}(1 - 0.7\, d_r + 0.2\, m)\). 1 βˆ’ Jaccard with siblings, modulated by mass.
  • cross_domain \(c_d\) β€” Shannon entropy of system-affinity distribution across forge / signal / mind term sets, normalised by \(\log 3\).
  • fragmentation \(f\) β€” keyword length standard deviation + cross-domain contribution.
  • drag \(\delta\) β€” proportion of long / hyphenated specialised terms.
  • dep_ratio \(d_r\) β€” relatedness to siblings in the batch: max(token-Jaccard Γ— 1.5, keyword-Jaccard Γ— 2.2), clamped to [0,1].
  • lagrange_potential β€” boosted minimum of the two strongest system affinities: min(topβ‚‚) Γ— 1.4, clamped to [0,1].

Star systems

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

  • Forge β€” devops/backend: api Β· docker Β· deploy Β· network Β· backend Β· nginx Β· ssh Β· ci/cd Β· build Β· server Β· database Β· redis Β· cache Β· auth Β· test Β· kubernetes Β· tunnel Β· vpn Β· observability
  • Signal β€” growth/marketing: seo Β· serp Β· keyword Β· content Β· email Β· marketing Β· campaign Β· analytics Β· conversion Β· funnel Β· brand Β· publish Β· backlink Β· cohort Β· attribution Β· linkedin Β· podcast Β· persona
  • Mind β€” AI/research: llm Β· prompt Β· reasoning Β· agent Β· embedding Β· vector Β· rag Β· evaluation Β· orchestration Β· memory Β· knowledge Β· openai Β· anthropic Β· inference Β· fine-tune Β· transcript Β· synthesis

Candidates 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 candidates the LLM generates per call (3–8).
MERIDIAN_TIMEOUT_MS 90000 Abort the inference request after this many ms.

Latency & quota

  • Per call β€” typical 5–15 s. Most of the budget is GitHub Models inference; the local classifier runs in < 5 ms.
  • Free tier β€” GitHub Models has a free per-account daily request budget that resets at 00:00 UTC. Limits depend on the model tier (Llama-3.3-70B is in the higher-cost tier; smaller models burn fewer requests). See GitHub Models limits.
  • Failure mode β€” when the token is missing, expired, or rate-limited, the MCP returns a clear error to the agent instead of silently falling back.

Self-hosting and version history

The current meridian-mcp v3.x package is a self-contained stdio MCP shim β€” install once, point any MCP-capable client at it, and the package handles GitHub-Models-based candidate generation plus the JS orbital classifier without a backend service. See the repo README for the full install/config flow.

Earlier lines are no longer recommended: 0.x shipped a Python skill_orbit.py + 88-entry curated corpus + MiniLM ONNX embeddings (closed-domain, offline); 1.x POSTed to a Cloudflare Worker that has since been retired. Either pin 0.3.2 for offline-only use or upgrade to 3.x.