Cost estimates, recommended models and the MCP server
Quote a request before sending it, recommended models per use case, and the MCP server that puts the router in VS Code, Cursor, Claude and any agent.
Three ways to ask the router "what will this cost and what should I use?" before spending tokens:
a quote API and page, a recommended-models digest, and a Model Context Protocol
server that puts all of it inside VS Code, Cursor, Claude, Windsurf or any agent framework. The
same pieces exist in the SDK (opensmartroute.estimate, opensmartroute.mcp_server, osr estimate,
osr mcp) and on the hosted platform (POST /api/v1/estimate, GET /api/v1/models/recommended,
/mcp, the /estimate page and Dashboard -> Integrations).
1. Quotes
A quote routes the request exactly as route() would - signals, policy, strategies, learned
quality - and then prices every ranked candidate instead of executing one:
- Tokens. Input tokens come from the text (word / digit / punctuation / CJK / code heuristic,
estimate_tokens, calibrated against billed gpt-4.1 prompt tokens on twelve text types - mean ratio 1.02, every type within about 20 %) plus the history (estimate_messages_tokens, 4 tokens of framing per message). Output tokens are the caller'soutput_tokens, else the signals' predicted answer length, elseDEFAULT_OUTPUT_TOKENS(256);output_tokens_sourcesays which. - Prices.
usd_per_1k_input/usd_per_1k_outputon the target when declared, else the blendedusd_per_1k_tokens; a fixedusd_per_callis added for agents, tools and humans. On the platform a target that resolves to a reference model is priced from the live catalogue (priced_from: "catalogue"), so quotes follow vendor list prices. - Picks.
recommendedis the router's decision;best_qualitythe highest quality estimate;cheapestandfastestare chosen among candidates withinquality_tolerance(0.1) of the best that fit their context window, so a free-but-useless target never wins.savings_usdis the gap between the dearest candidate and the recommended one. - Nothing is executed and the text is never stored.
from opensmartroute.estimate import estimate
q = estimate(router, "Summarise this contract in five bullets", output_tokens=300)
print(q.input_tokens, q.output_tokens, q.output_tokens_source) # 9 300 caller
print(q.recommended.name, q.recommended.cost_usd) # Small model 0.000062
for t in q.targets: # every candidate, ranked
print(t.rank, t.target_id, f"${t.cost_usd:.6f}", t.quality_estimate, t.latency_ms, t.fits_context)
print(q.cheapest.target_id, q.best_quality.target_id, q.fastest.target_id, q.savings_usd)
q.to_dict() # the JSON the API returnsestimate(router, request, *, output_tokens=None, kinds=None, prices=None, quality_tolerance=0.1, decision=None) accepts a RouteRequest or a string, a kinds filter and a prices(target) -> (usd_per_1k_in, usd_per_1k_out) | None hook for live price feeds; pass decision= to quote what an
earlier route() already ranked.
osr -t targets.yaml estimate "Translate this contract into German" --monthly 100000
osr -t targets.yaml estimate "..." --output-tokens 500 --cost-weight 1.0 --jsonPOST /api/v1/estimate
Same body as POST /api/v1/route (text, history, objective, constraints, kinds) plus
output_tokens and monthly_requests. Without a key the endpoint is rate limited per client (20 per
minute, 8 000 characters) and ignores tenant rules; with a key it is metered as estimate, applies
your plan and tenants and returns X-Request-Id.
curl -s https://<platform>/api/v1/estimate -H "Authorization: Bearer $OSR_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Summarise this contract...", "monthly_requests": 50000}'{
"request_id": "…", "input_tokens": 9, "output_tokens": 256, "output_tokens_source": "signals",
"total_tokens": 265, "confidence": 0.71, "savings_usd": 0.00209,
"recommended": {"target_id": "llm-small", "name": "Small model", "vendor": "openai", "model": "openai/gpt-4.1-nano",
"input_cost_usd": 0.0000009, "output_cost_usd": 0.0001, "cost_usd": 0.000103,
"latency_ms": 400, "quality_estimate": 0.62, "fits_context": true, "priced_from": "catalogue",
"rationale": {"semantic": "…", "cost": "…"}},
"cheapest": {"…": "…"}, "best_quality": {"…": "…"}, "fastest": {"…": "…"},
"targets": [{"…": "…"}],
"signals": {"complexity": 0.31, "domains": ["legal"], "task_type": "summarise", "contains_pii": false, "…": "…"},
"policy_rejections": {},
"monthly": {"requests": 50000, "recommended_usd": 5.15, "cheapest_usd": 5.15, "best_quality_usd": 109.6,
"dearest_usd": 109.6, "savings_usd": 104.45, "tokens": 13250000},
"anonymous": false
}The /estimate page is the same call with a form: paste a prompt, choose what to optimise for, set the
expected answer length and monthly volume, and read the four picks, the signals the router saw and the
full candidate table with input / output cost bars.
2. Recommended models
GET /api/v1/models/recommended (no key) quotes a representative prompt per use case - chat, code,
reasoning, summarise, extraction, pii - against the live catalogue and returns, for each, the
recommended, cheapest, best_quality and fastest pick with cost per request, quality estimate,
latency, 30-day traffic, the reference model's intelligence index and the reasons (why). A
leaderboard counts how often each target was the router's pick. The digest is recomputed at most
once a minute and follows the deployment's targets, prices and learned quality rather than a
hand-maintained list. It renders on /models ("Recommended per use case") and on /estimate.
3. MCP server
An MCP client - VS Code, Cursor, Claude, Windsurf or an agent framework - calls the router's tools over JSON-RPC and gets a decision (or a quote, or the executed answer) back:
sequenceDiagram
participant C as MCP client (Cursor / Claude / agent)
participant S as MCPServer(router)
participant R as Router
C->>S: tools/list
S-->>C: route, estimate, recommend, explain, list_targets, feedback, ask
C->>S: tools/call route "translate this contract"
S->>R: route(request)
R-->>S: decision + trace
S-->>C: target, confidence, alternatives, reasons
Note over C,R: ask also runs the chosen target and returns the answer, tokens and costWhat it exposes
| Tool | Purpose | Side effects |
|---|---|---|
route | Pick the best target: decision, confidence, alternatives, signals, ranked candidates with reasons. Takes everything Router.route does - objective weights (quality, cost, latency, energy, carbon, quality_floor), hard constraints (region, data boundary, tenant, PII, cost and latency caps, allow / deny lists), kinds, plan, top_k, exclude (an agent handing the request back), context, history and session_id; with a marketplace hook also marketplace: [slug, ...] - listings considered as candidates for this request only, nothing installed | none |
estimate | The quote above for a request | none |
recommend | One-paragraph recommendation for a task; priority = balanced, cost, quality or speed | none |
explain | Human-readable trace: signals, policy filtering, per-strategy breakdown | none |
list_targets | The catalogue (optionally one kind) with prices, latency, quality prior, boundary | none |
feedback | Report an outcome (request_id, target_id, success, quality, cost_usd, latency_ms) | learners update |
ask | Route and run the chosen target / plan (same objective, constraints, history, context, session_id as route); returns the answer, tokens, cost and the plan's steps | executes |
marketplace_search, marketplace_get | Search listings; fetch one with its manifest and snippets (install: true also loads it into a local router's catalogue) | installs on request |
Resources: osr://targets (the catalogue as JSON) and osr://stats (feedback statistics). The
server speaks MCP 2025-06-18 (also accepts 2025-03-26 and 2024-11-05) over JSON-RPC 2.0:
initialize, ping, tools/list, tools/call, resources/list, resources/read,
prompts/list, logging/setLevel (validated, applied to the package logger); notifications get no reply; unknown
methods return -32601, unknown tools -32602, and every tool failure - invalid arguments, no admissible target, a
provider that is down, a marketplace listing that does not exist - comes back as an isError result the model can
read and act on, never as a protocol error.
ask and marketplace_* appear only when the server can honour them: osr serve and the platform provide both;
osr mcp (local, stdio) offers ask when something in the catalogue can run - an LLM with a chat handler
(OSR_LLM_BASE_URL) or a bound manifest endpoint such as an A2A agent or MCP server synced from the marketplace -
and the marketplace tools through MarketplaceClient.mcp_hook (--no-marketplace leaves them out); a bare
MCPServer(router) exposes the read-only tools and feedback.
Hosted platform
GET /mcp describes the server (tools, resources, protocol version, connection snippets) without a
key. POST /mcp takes one JSON-RPC message or a batch with the same Authorization: Bearer <api key>
(or X-API-Key) header as the REST API; notifications return 202. Every routed call carries the
workspace tenant and is metered as mcp against the plan's quotas; feedback needs the feedback
feature and ask the plan feature (Pro and above). Quota, plan and routing errors surface as tool
errors such as 429: rate limit exceeded. Dashboard -> Integrations renders ready-to-paste
configuration for each client with the workspace's key.
HTTP transport (the client talks to https://<platform>/mcp directly):
// VS Code: .vscode/mcp.json
{"servers": {"opensmartroute": {"type": "http", "url": "https://<platform>/mcp",
"headers": {"Authorization": "Bearer osr_..."}}}}
// Cursor: .cursor/mcp.json
{"mcpServers": {"opensmartroute": {"url": "https://<platform>/mcp",
"headers": {"Authorization": "Bearer osr_..."}}}}
// Windsurf: ~/.codeium/windsurf/mcp_config.json
{"mcpServers": {"opensmartroute": {"serverUrl": "https://<platform>/mcp",
"headers": {"Authorization": "Bearer osr_..."}}}}claude mcp add --transport http opensmartroute https://<platform>/mcp --header "Authorization: Bearer osr_..."Stdio bridge for clients that only launch local processes (Claude Desktop and older clients): the CLI forwards each line to the platform with your key.
{"mcpServers": {"opensmartroute": {"command": "osr",
"args": ["mcp", "--url", "https://<platform>/mcp", "--api-key", "osr_..."]}}}Streamable HTTP: sessions, streams and notifications
The endpoint implements the Streamable HTTP transport of the MCP specification, not only request / reply:
- Sessions. The reply to
initializecarries anMcp-Session-Id; send it back on later calls. The id is signed with the deployment secret and bound to the workspace, so every replica accepts it without shared state; an id of another workspace, an expired one (24 hours) or one that was ended answers404- re-initialize.DELETE /mcpwith the header ends the session (204). - Streamed replies. Send
Accept: text/event-streamonPOST /mcpand the answer is a server-sent-events stream:notifications/progressfor a_meta.progressToken(askreports routing, running on<target>, recording),notifications/messagelog lines at the level set withlogging/setLevel, then the JSON-RPC result as the lastmessageevent. Without that header the reply is plain JSON as before. - Notification stream.
GET /mcpwithAccept: text/event-streamand your credential opens the server's own stream:notifications/tools/list_changedandnotifications/resources/list_changedwhen the catalogue, your installed listings or the tools change, a: pingcomment every 15 seconds, closed aftermax_sseconds (default and ceilingOSR_PLATFORM_MCP_STREAM_MAX_S, one hour). Every frame has anid; reconnect withLast-Event-IDand the stream starts with alist_changedso a change during the gap is not missed.initializeadvertiseslistChanged: truefor tools and resources because of it. - Guards. A browser
Originmust be one of the deployment's (the web app, the API's own origin,OSR_PLATFORM_CORS_ORIGINS, loopback) - the DNS-rebinding protection the specification asks for; non-browser clients send none. AnMCP-Protocol-Versionheader must name a revision the server speaks (2025-06-18,2025-03-26,2024-11-05), else400. Bodies are capped at 256 KB.
Signing in with your identity provider (OAuth 2.1, on-behalf-of)
Besides workspace API keys, /mcp accepts a bearer token your organization's identity provider
issued for this resource - the OAuth 2.1 resource-server flow MCP clients implement (VS Code,
Claude, Cursor ...): the client is refused with 401 and
WWW-Authenticate: Bearer resource_metadata="https://<platform>/.well-known/oauth-protected-resource/mcp",
reads that document (RFC 9728: resource, authorization_servers, scopes_supported), signs the
person in at the authorization server and comes back with a JWT. The platform verifies the signature
against the issuer's JWKS (RS256 / PS256 / ES256 families, keys refreshed on rotation), the issuer,
the audience (https://<platform>/mcp, or the app's client id / api://<client id>), expiry and
not-before, then maps the token to the platform user: a linked single sign-on identity whose subject is
the token's sub or oid first, else the user whose address the token names (preferred_username,
email, upn; OSR_PLATFORM_MCP_OAUTH_EMAIL_MATCH=false turns that off). Someone the platform does
not know gets 403 with a pointer to sign in on the site once. The call then runs in the person's
workspace, metered and audited like a keyed one, with their role. The endpoint is reachable both on the
API host and on the web origin (https://<site>/mcp, proxied with the session, protocol-version and
stream headers intact); the metadata, the challenge and the accepted audience name whichever URL the
client used, and Dashboard -> Integrations shows the key-less configuration when the deployment has an
identity provider.
Configuration defaults from the microsoft single sign-on provider (OSR_PLATFORM_SSO_MICROSOFT_*): its
tenant names the issuer (https://login.microsoftonline.com/<tenant>/v2.0; a multi-tenant app keeps
Entra's {tenantid} placeholder and matches any tenant), its client id the audience, its credentials the
on-behalf-of client. Any OIDC issuer works explicitly:
| Variable | Meaning |
|---|---|
OSR_PLATFORM_MCP_OAUTH_ISSUERS | Comma-separated issuers whose tokens are accepted (discovery at <issuer>/.well-known/openid-configuration) |
OSR_PLATFORM_MCP_OAUTH_AUDIENCES | Audiences accepted besides <base_url>/mcp |
OSR_PLATFORM_MCP_OBO_CLIENT_ID, OSR_PLATFORM_MCP_OBO_CLIENT_SECRET | The confidential client the on-behalf-of exchange authenticates as |
OSR_PLATFORM_MCP_OBO_TOKEN_URL | Token endpoint for the exchange (default: the token's issuer's) |
OSR_PLATFORM_MCP_OAUTH_EMAIL_MATCH | Whether a verified address claim may identify the user (default true) |
OSR_PLATFORM_MCP_STREAM_MAX_S | Longest GET /mcp notification stream (default 3600) |
On-behalf-of. A model behind the organization's own identity - Azure OpenAI or Azure AI Foundry
with Entra authentication, a corporate gateway - can be a provider whose credential is obo:<scope>
instead of a key (obo:https://cognitiveservices.azure.com/.default), in the deployment file, the
operator console or a workspace's own providers. When a call that arrived with an identity provider's
token reaches such a provider, the platform exchanges that token for one with the scope - Entra's
jwt-bearer grant with requested_token_use=on_behalf_of for a Microsoft issuer, the RFC 8693 token
exchange for any other - and calls the model with Authorization: Bearer <downstream token>: the person's
own permissions apply, no shared key exists, and the exchange is cached per user and scope until shortly
before it expires and recorded in the audit log (mcp.obo_exchange, no token material). A request that
came with an API key has nobody to act for: an obo: provider is unavailable to it and the fallback
loop moves on to the next executable target. The health probe of such a provider checks reachability
without a credential.
// VS Code: no key in the file - the editor signs you in at the authorization server
{"servers": {"opensmartroute": {"type": "http", "url": "https://<platform>/mcp"}}}Self-hosted and local
osr serve mounts the same server at POST /mcp (and GET /mcp for the description) next to
/route and /v1, with ask backed by the configured providers - and the same Streamable HTTP
transport as the hosted platform: a signed Mcp-Session-Id on initialize (bound to the access token
it was issued to; DELETE /mcp ends it), event-stream replies with notifications/progress and log
messages when the client accepts text/event-stream, a GET /mcp notification stream that announces
tools/list_changed / resources/list_changed when the catalogue moves (a marketplace sync, a
/targets write; max_s bounds it, ceiling OSR_SERVER_MCP_STREAM_MAX_S), the protocol-version check
and the browser-origin guard (loopback always passes; OSR_SERVER_MCP_ORIGINS names other origins).
Without a service, the CLI runs the
server over stdio straight from a catalogue - useful for a laptop IDE setup; set OSR_LLM_BASE_URL (and
OSR_LLM_API_KEY) and ask runs the routed model for real, exactly as osr serve would:
osr -t targets.yaml -r rules.yaml mcp # newline-delimited JSON-RPC on stdin/stdout
osr -t targets.yaml mcp --list-tools # what the IDE will see
osr -t targets.yaml mcp --marketplace-url https://api.example.test # marketplace_* against another marketplace
osr -t targets.yaml mcp --no-marketplace # local tools only
osr mcp --url https://<platform>/mcp --api-key osr_... # bridge to a hosted platform
osr mcp --remote --list-tools # tools of the platform you signed in to (osr login){"servers": {"opensmartroute": {"type": "stdio", "command": "osr",
"args": ["-t", "targets.yaml", "-r", "rules.yaml", "mcp"]}}}When the IDE's environment does not have osr on PATH (a project virtualenv, uv run), use the interpreter
instead: "command": "python", "args": ["-m", "opensmartroute", "-t", "targets.yaml", "mcp"]. Both transports are
exercised in the test suite with the official mcp Python client: initialize,
tools/list, tools/call, resources/list over stdio against python -m opensmartroute ... mcp, and over
Streamable HTTP against osr serve's POST /mcp with a bearer token.
In Python
from opensmartroute.mcp_server import MCPServer, serve_stdio
server = MCPServer(router, execute=my_execute, marketplace=my_marketplace, prices=my_prices)
server.tools() # tool specs (tools/list)
server.call("estimate", {"text": "…"}) # {"content": [...], "structuredContent": {...}}
reply = server.handle(
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "recommend", "arguments": {"task": "…", "priority": "cost"}},
}
)
serve_stdio(server) # block on stdin/stdoutexecute(request) -> dict enables ask; marketplace(action, args) with action in search /
get / target (the last returns a RouteTarget for a slug and powers the route tool's marketplace
candidates) enables the marketplace tools; prices is the quote hook above. RemoteMCP(url, api_key) and
bridge_stdio(url, api_key) are the client side used by osr mcp --url. Everything is standard
library only; osr serve needs the server extra (FastAPI).
Importing other MCP servers as targets
The router is also an MCP client: a server's tools, resources and prompts become routable targets.
from opensmartroute.adapters import connect_mcp, connect_mcp_http
client, targets = connect_mcp(["npx", "-y", "@modelcontextprotocol/server-filesystem", "."], resources=True)
http, remote = connect_mcp_http("https://tools.example.com/mcp", api_key="...", resources=True, prompts=True)
for t in [*targets, *remote]:
registry.upsert(t)connect_mcp spawns a stdio server; connect_mcp_http (adapters.HttpMCPClient) speaks the Streamable HTTP
transport (JSON or SSE replies, Mcp-Session-Id) to remote servers over https. Tools become tool targets
whose handler runs tools/call; with resources=True every resource becomes a read-only tool target that
reads it (resources_from_mcp), with prompts=True every prompt becomes an instructions-only skill slot
whose handler renders the template (prompts_from_mcp). Tool descriptions are still scored for injection
risk at import time and signed manifests still verify as before.
Things to ask your assistant
- "Estimate what this prompt will cost and which model to use."
- "Recommend the cheapest model that can refactor this file."
- "Explain how OpenSmartRoute would route this request."
- "Search the marketplace for a SQL reporting skill and show me its manifest."
- "Route and answer this with the best model, then record that the answer was good."
Something wrong or out of date on this page? Tell us - the page is named for you, so a one-line note is enough.
Marketplace
Find, install, buy and publish agents, skills, personas, prompts and stack templates; ratings and the review lifecycle.
Content publishing for your website
Your feeds and briefs become posts written through the router, reviewed in the dashboard and delivered to your site - a signed webhook, WordPress or Ghost - or pulled as RSS; the same tools on your MCP server.