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:
route_task tool.| Field | Value |
|---|---|
| Server URL | https://mcp.ask-meridian.uk/mcp |
| Authorization endpoint | https://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):
- Grok stores the connector and shows an Authorize button.
- Clicking it opens mcp.ask-meridian.uk/authorize in a new tab β a single-button confirmation page. No PAT pasting, no GitHub jargon.
- You click Authorize, the page 302s back to Grok with a
one-time auth code, Grok exchanges it at
/tokenwith PKCE verification, and gets a Bearer back (TTL: 1 h for Meridian, 7 d for Finance & Pharmacy). - The MCP's tools appear in Grok's tools list. For Meridian it's
route_task; for Pharmacy it'ssearch_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.ukhttps://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:
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:
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.
| Class | Decision rule | Score 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):
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:
| Variable | Default | Purpose |
|---|---|---|
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.