MCP server feature catalog · 2026-05-20
Audit + expansion of the Insiders Trades Sigma MCP (Model Context Protocol) server. Goal: expose every queryable surface of the platform (declarations, signals, strategy proof, audits, glossary, markets, blog) as MCP tools, resources, and prompts so that any compatible agent (Claude Desktop, Cursor, Windsurf, Continue, LangChain) can drive the platform without writing SQL.
TL;DR
| Capability | Count | Status |
|---|---|---|
| Tools | 29 | live (was 20) |
| Resources | 5 static + 1 templated (audits://{slug}) | new |
| Prompts | 5 | new |
JSON-RPC methods supported: initialize, initialized, ping, tools/list,
tools/call, resources/list, resources/templates/list, resources/read,
prompts/list, prompts/get.
Auth model unchanged: Authorization: Bearer <sit_live_...> (or X-Api-Key,
or ?apiKey= query fallback) consumes one quota unit per request, except
initialize and ping which are public.
Audit · prior state
Before this expansion the server had:
- 20 tools across 4 families (discovery / enrichment / system / composite).
- No resources capability advertised.
- No prompts capability advertised.
initializecapabilities only declaredtools: { listChanged: false }.
Auth was already implemented (Bearer, X-Api-Key, ?apiKey query). Rate-limit
quotas were already enforced upstream by resolveApiKey + bumpKeyUsage.
Delta · what was added
9 new tools
Category labels reflect existing TOOL_BY_NAME taxonomy.
| Tool | Category | What it does |
|---|---|---|
get_strategy_proof |
system | live STRATEGY_PROOF + WINNING_STRATEGY config (4y yearly returns, Sharpe annualized / cross-sectional / deflated, win rate Wilson CI95, max DD) |
get_market_overview |
system | per-MIC aggregates (companiesCount, declarationsCount over N days, avgSignalScore, top companies). All-markets fallback when mic omitted. |
search_blog_articles |
discovery | full-text search on published blog articles by query, category, locale |
list_audits |
system | index of docs/method-review/*.md |
get_audit |
system | full body of a single audit (markdown or json), capped at 50 KB with truncated flag |
get_glossary |
system | bilingual FR + EN glossary, full or single-slug |
get_top_insiders |
discovery | ranking of most active insiders over N days by count / avgScore / maxScore |
explain_signal_score |
enrichment | structured breakdown of a declaration's signalScore components (cluster, role bucket, market-cap bucket, pubDelay, acquisition, flow metrics) |
5 static resources + 1 templated
URIs follow the convention <topic>://<key>:
| URI | MIME | Content |
|---|---|---|
strategy://proof |
application/json | STRATEGY_PROOF object (lives in src/lib/winning-strategy.ts) |
strategy://winning-filters |
application/json | WINNING_STRATEGY config object |
markets://list |
application/json | 32 regulator entries (MIC + ISO 3166 country + ISO 4217 currency) |
audits://list |
application/json | full index of method-review docs |
audits://{slug} |
text/markdown | single audit body (templated, listed via resources/templates/list) |
glossary://terms |
application/json | full glossary set |
Implementation: src/lib/mcp/resources.ts, readResource() dispatcher with
path-traversal guard on audits://.
5 prompts
Reusable LLM templates exposed via prompts/list and resolved server-side
via prompts/get with argument interpolation. Each template points the LLM
at concrete MCP tools to call instead of embedding stale data.
| Prompt | Args | Workflow |
|---|---|---|
explain_signal |
amfId (req) | get_declaration → get_company_full_profile → 200-word FR brief |
compare_insiders |
slugA, slugB (req) | get_insider_activity_summary x2 → markdown table + verdict |
daily_brief |
limit (opt) | get_system_health → get_winning_strategy_signals → find_clustered_trades |
weekly_alpha_report |
lookbackDays (opt) | get_site_stats → get_backtest_stats BUY+SELL → search_top_signals |
company_deep_dive |
slug (req) | get_company_full_profile → get_company_declarations(50) → FR markdown |
Strict instructions in every template:
- "Do not invent numbers"
- For weekly_alpha_report: only cite
sharpeAnnualizedfromstrategy://proof, never the cross-sectional Sharpe alone (matchesSTRATEGY_PROOF.dsrNoteguidance).
Architecture changes
Files touched:
src/lib/mcp/tools.ts— added 9 tool defs; bumped catalog from 20 to 29.src/lib/mcp/execute.ts— added 8 executor functions + wired router.src/lib/mcp/resources.ts— new, registry + dispatcher.src/lib/mcp/prompts.ts— new, registry + builders.src/app/api/mcp/route.ts— added handlers forresources/list,resources/templates/list,resources/read,prompts/list,prompts/get; extendedinitializecapabilities object; updated GET welcome page.src/app/docs/mcp/page.tsx— updated displayed tool count (20 → 29), per-category subtitles, JSON-RPC method table.
Constraints honored
- Per-response size cap:
get_audittruncates at 50 KB with explicittruncated: trueflag. - Auth: every method except
initialize/pingrequires a valid API key, same path as the REST API. - Error model:
McpError-style JSON-RPC error envelopes with codes -32600 / -32601 / -32602 / -32603 / -32000. - No em-dashes / en-dashes / emoji in the description strings the LLM will
see.
npm run lint:emdashandnpm run lint:emojiboth pass. - TypeScript:
npx tsc --noEmitclean.
Local smoke tests
Dev server on port 3789, key from .api-test-key.
tools/call get_strategy_proof
$ curl -sL http://localhost:3789/api/mcp/ -X POST -H "Authorization: Bearer ..." \
-d '{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"get_strategy_proof","arguments":{}}}'
{"jsonrpc":"2.0","id":10,"result":{"content":[{"type":"text","text":"{
\"strategy\": { \"minScore\": 40, \"minMarketCapEur\": 200000000, ..., \"useVolTargeting\": true },
\"proof\": {
\"years\": [
{ \"year\": 2022, \"strategy\": -7.9, \"cac40\": -10.3, \"alpha\": 2.4, \"sampleSize\": 30, \"beats\": true },
{ \"year\": 2023, \"strategy\": 16, \"cac40\": 14.4, \"alpha\": 1.6, \"sampleSize\": 25, \"beats\": true },
...
tools/call get_market_overview (mic=XPAR, 30d)
{
"mic": "XPAR",
"lookbackDays": 30,
"companiesCount": 583,
"declarationsCount": 167,
"avgSignalScore": 26.59,
"topCompanies": [
{ "name": "Publicis Groupe SA", "declarationsCount": 11, "marketCap": 19286532096 },
{ "name": "ABC Arbitrage", "declarationsCount": 10, "marketCap": 305618176 },
{ "name": "Unibail-Rodamco-Westfield SE", "declarationsCount": 9 },
...
]
}
tools/call list_audits
{
"count": 87,
"audits": [
{ "slug": "01-quant-challenge", "num": "01", "title": "Quant challenge ...", "ext": "md", "url": "..." },
...
]
}
resources/read markets://list
{ "count": 32, "markets": [ { "regulator": "SEC", "mic": "XNAS", "country": "US", "currency": "USD", ... }, ... ] }
prompts/get daily_brief (limit=3)
Returns a single user message that instructs the LLM to call
get_system_health, get_winning_strategy_signals { lookbackDays: 3, limit: 3 },
find_clustered_trades, then assemble a 3-section FR brief.
Verification
npx tsc --noEmit· clean (no errors).npm run lint:emdash· OK, no em-dashes in user-facing copy.npm run lint:emoji· OK, 461 files scanned.- Local dev server starts and answers all 10 JSON-RPC methods.
- 5 representative endpoints tested live against Neon prod DB.
What's not in scope here
- Streaming HTTP / SSE transport. Current transport is plain HTTP JSON-RPC, which Claude Desktop ≥ 3.7, Cursor, Windsurf, Continue all support. Migration to streamable HTTP can land in a separate PR if a future client requires it.
- Sampling capability (server → client LLM calls). Not needed for read-only query layer.
- Tool-level rate limiting beyond the per-key quota. The Bearer key already
has a daily quota enforced by
resolveApiKey; we kept that single layer.