Researched 2026-10-12. Spec version cited: MCP 2025-11-25. Anything not confirmed on a primary page is marked [unverified].
https://trust.arnao.ai/mcp) must accept POST and GET. Clients POST every JSON-RPC message with Accept: application/json, text/event-stream; the server replies with JSON or opens an SSE stream. GET is optional (server-to-client stream) and may return 405.Origin header (403 on invalid, to stop DNS rebinding); bind to localhost when local; SHOULD authenticate every connection.MCP-Session-Id header; client must send MCP-Protocol-Version. For a read-mostly knowledge server, run stateless (no session IDs) so any worker can answer. FastMCP: stateless_http=True or env FASTMCP_STATELESS_HTTP=true (FastMCP HTTP deployment)./sse endpoints exist only for old clients (DeepWiki says its SSE endpoint is being phased out: https://docs.devin.ai/work-with-devin/deepwiki-mcp). Do not build SSE-only.The authorization section is OPTIONAL in the spec, and for HTTP transports it says implementations SHOULD follow it. Source: https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization
| Option | When | Notes |
|---|---|---|
Static bearer token per consumer app (Authorization: Bearer <token>) | Byron's own apps and agents calling Ria over Tailscale/HTTPS. Recommended starting point. | The spec requires bearer format on every HTTP request and forbids tokens in the query string. A static key is not the full OAuth flow, so third-party clients that auto-discover OAuth will not work, but any client that lets you set headers (Claude Code --mcp-config, Agent SDK, custom code) will. Issue one token per app, store only hashes, log token id per call, support revocation. |
| OAuth 2.1 resource server | If you want claude.ai connectors, Claude Desktop, or any third-party client to connect without hand-set headers. | Server MUST publish RFC 9728 Protected Resource Metadata (via WWW-Authenticate: Bearer resource_metadata=... on 401 or /.well-known/oauth-protected-resource), validate tokens' audience (RFC 8707 resource), accept PKCE S256 clients, never pass tokens through to upstream APIs. Client registration: Client ID Metadata Documents preferred, pre-registration, or Dynamic Client Registration as fallback. Use an external IdP (Auth0/Clerk/Keycloak) rather than writing an authorization server. |
| Network-level only (Tailscale ACL, no app auth) | Strictly single-user, tailnet-only. | Still add a bearer token; the spec says servers SHOULD authenticate. |
FastMCP supports bearer verifiers (StaticTokenVerifier for dev/test, per the docs) and OAuth providers (GitHub etc.), with a production OAuth proxy needing an explicit jwt_signing_key and encrypted persistent storage (docs). For a production bearer scheme, write a small custom TokenVerifier that checks hashed tokens against a table instead of using the dev verifier. API details beyond the doc summary: read the FastMCP auth docs before coding [unverified].
Recommendation: Streamable HTTP, stateless, single /mcp endpoint, bearer token per consuming app now; add OAuth 2.1 resource-server metadata later if claude.ai/third-party connectors are wanted. Put it behind Cloudflare/Tailscale Funnel or an arnao.ai host with TLS.
From the tools spec (https://modelcontextprotocol.io/specification/2025-11-25/server/tools):
A-Za-z0-9_-., unique per server. Dots are allowed, so namespace: incidents.search, incidents.get.title, description, inputSchema (JSON Schema 2020-12 default), and an outputSchema plus structuredContent; also return the JSON as a text block for compatibility.isError: true results with an actionable message so the model can self-correct; protocol errors are for malformed requests.resource_link items instead of big payloads; link to resources and let the client fetch.untrusted_content: true) and strip instructions-like markup.Pagination (https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/pagination): opaque cursor / nextCursor; server chooses page size; applies to resources/list, resources/templates/list, prompts/list, tools/list; bad cursor returns -32602. For your own data tools (which are not covered by that mechanism) copy the same convention: limit (cap 50), cursor in, next_cursor out, base64 of (sort_key, id); results sorted by a stable key such as (published_at DESC, id).
Proposed surface
Tools (read-only unless noted):
incidents.search(query, since?, severity?, tags?, limit?, cursor?), incidents.get(id), incidents.timeline(entity_id)briefs.latest(), briefs.get(date), briefs.search(query, limit?, cursor?)sources.search(query), sources.get(id) (source index: URL, type, reliability grade, last fetched)entities.get(slug), entities.search(query) (labs, people, models, orgs)models.lineup(provider?) (model lineup with release dates; every row cites a URL)ria.submit_lead(url, note) (the only write tool; queue for review, never direct write to corpus)Resources (URI templates, so clients can attach context):
ria://brief/{yyyy-mm-dd}, ria://incident/{id}, ria://entity/{slug}, ria://source/{id}, ria://models/currentresources/subscribe on ria://brief/latest is a good fit for push when the daily brief lands.Prompts: ria.explain_incident, ria.weekly_rollup.
Cache-Control: max-age at the HTTP edge for resource reads; version records (updated_at, revision) rather than mutating.tools/list is stable; emit listChanged only when the surface changes.read_wiki_structure, read_wiki_contents, ask_question; https://mcp.deepwiki.com/mcp; no auth for public repos. https://docs.devin.ai/work-with-devin/deepwiki-mcphttps://mcp.context7.com/mcp Streamable HTTP with OAuth, API key for higher limits. https://www.mintlify.com/upstash/context7/mcp/overviewsearch_nodes, read_graph, open_nodes): https://github.com/modelcontextprotocol/serversMount(), pass mcp_app.lifespan to the parent app or sessions will not initialise (docs).Anthropic API
usage object (input, output, cache_creation_input_tokens, cache_read_input_tokens). Compute USD yourself from a price table. Pricing: https://platform.claude.com/docs/en/about-claude/pricingGET https://api.anthropic.com/v1/organizations/usage_report/messages with starting_at, ending_at, bucket_width (1m/1h/1d), group_by[] (model, workspace, api key, service tier, etc.), filters; max buckets 1,440 / 168 / 31.GET https://api.anthropic.com/v1/organizations/cost_report daily buckets only, USD as decimal strings in cents, group by workspace_id / description. Priority Tier costs are excluded from the cost endpoint; use the usage endpoint.sk-ant-admin01-...) or equivalent admin credential; workspace API keys do not work. Unavailable for individual accounts; needs an Organization set up in Console. Header anthropic-version: 2023-06-01.has_more / next_page.workspace_id / api_key_id isolates its spend (default workspace shows null).OpenRouter
prompt_tokens, completion_tokens, total_tokens, cost (credits charged), cost_details.upstream_inference_cost, completion_tokens_details.reasoning_tokens, prompt_tokens_details.cached_tokens/cache_write_tokens. The old usage: {include: true} flag is deprecated and has no effect. https://openrouter.ai/docs/guides/guides/usage-accountingGET https://openrouter.ai/api/v1/generation?id=gen-... (Bearer key) returns total_cost, usage, upstream_inference_cost, cache_discount, normalized tokens_prompt/completion and native token counts, latency, generation_time, provider_name, model, request_id, is_byok, cancelled. https://openrouter.ai/docs/api-reference/get-a-generation . Per the usage-accounting page, upstream cost from this endpoint is only populated for BYOK requests.Local Ollama
prompt_eval_count, eval_count) and durations; I did not re-verify field names against current docs this session [unverified]: check https://github.com/ollama/ollama/blob/main/docs/api.md . USD is $0 marginal; record tokens and wall time, and optionally an electricity estimate (watts x seconds) for honest comparison.LiteLLM as a single meter
claude -p)Docs: https://code.claude.com/docs/en/headless and https://code.claude.com/docs/en/agent-sdk/cost-tracking
--output-format json returns a JSON object whose text is in result, plus session_id, usage, and total_cost_usd with a per-model breakdown (modelUsage: costUSD, input/output/cache tokens). I did not see the full field list on the headless page (e.g. duration_ms, num_turns) so verify those against a real run [unverified].--output-format stream-json (with --verbose) ends with a result message carrying final text, cost and session metadata. --bare is recommended for scripted runs.total_cost_usd is a client-side estimate from a bundled price table, not billing data; use the Usage and Cost API for authoritative figures and do not make financial decisions from it.--continue or --resume, the result reports the whole session total, including earlier runs. Do not sum across resumed calls; take the latest or compute deltas (since v2.1.277 per docs).usage excludes subagent tokens; total_cost_usd and modelUsage include them. Use modelUsage for token accounting.error_max_budget_usd, usage omits the response that crossed the cap while total_cost_usd includes it. After a process crash the cost fields may be zeroed.--max-budget-usd CLI flag exists (the headless page references it); the Agent SDK equivalent max_budget_usd counts only the call's own spend. Use it as a per-run backstop, not the daily cap.CREATE TABLE ledger (
id INTEGER PRIMARY KEY,
run_id TEXT NOT NULL, -- uuid per scheduled run
parent_run_id TEXT, -- for subtasks / retries
task TEXT NOT NULL, -- 'daily_brief','moltbook_probe','ingest_feed',...
provider TEXT NOT NULL, -- 'anthropic' | 'openrouter' | 'ollama'
model TEXT NOT NULL,
input_tokens INTEGER NOT NULL DEFAULT 0,
output_tokens INTEGER NOT NULL DEFAULT 0,
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
usd REAL NOT NULL, -- estimate at write time
usd_source TEXT NOT NULL, -- 'claude_cli_estimate'|'openrouter_cost'|'price_table'|'zero_local'
wall_ms INTEGER NOT NULL,
status TEXT NOT NULL, -- ok|error|killed|capped
external_id TEXT, -- claude session_id / openrouter gen-id
reconciled_usd REAL, -- filled by nightly reconcile from admin APIs
started_at TEXT NOT NULL, -- UTC ISO8601
UNIQUE(provider, external_id)
);
CREATE INDEX ledger_day ON ledger(substr(started_at,1,10));
Write one row per model call (or per claude -p run using modelUsage rows), including failures. A nightly job fills reconciled_usd from the Anthropic cost report and OpenRouter, and alerts when drift exceeds e.g. 15%.
claude -p): SELECT SUM(usd) FROM ledger WHERE day=today plus an in-flight reservation (worst-case estimate for this call). If spent + reservation > DAILY_CAP_USD (say 80% soft, 100% hard), refuse and log status=capped. Mirrors the "blocking guardrail runs before spend" idea in the OpenAI Agents SDK guardrails (docs).--max-budget-usd to claude -p, set provider max_tokens, and a wall-clock timeout; budget per task type, not just per day.~/ria/KILL checked by the gate and by a Claude Code PreToolUse hook (hooks run deterministically and can block tool calls: https://code.claude.com/docs/en/hooks-guide). If present, every run exits immediately. Also expose it as an MCP admin tool, not on the public surface.max_budget. These catch bugs in your own ledger. Confirm the exact settings in each console [unverified].