mcp / mcp-config
The local MCP server, its 33-read and 37-guardrails profiles, authentication, work bounds, and launch descriptor.
solwyn mcp serves Solwyn's operations as tools to any MCP client over stdio. solwyn mcp-config prints the command and arguments a client needs to launch it. solwyn doctor --mcp checks that the wiring works. For how an agent should use the tools, see Agent integration.
mcp
solwyn [--project <project_id>] [--api-url <url>] mcp [--profile read|guardrails]| Option | Type | Default | Purpose |
|---|---|---|---|
--profile | read | guardrails | guardrails | The fixed tool surface for this launch. |
| Contract | |
|---|---|
| Auth | resolved per tool call, never at startup |
| Exit codes | 0 clean shutdown · 1 startup or protocol failure · 2 usage |
| Transport | stdio only; stdout is reserved for JSON-RPC framing |
Put the global --project and --api-url before mcp and --profile after it. The output flags (--json, --format, --no-color) are accepted and ignored. The server advertises tools only — no prompts, no resources — and the profile is fixed for the life of the process; no tool argument can change it.
Credentials are read from SOLWYN_API_KEY or the OS keychain on every tool call, so a rotated key is picked up mid-session and listing tools never touches a credential. An empty or malformed SOLWYN_API_KEY blocks keychain fallback rather than silently selecting another identity.
Profiles
| Profile | Tools | Use when |
|---|---|---|
read | 33 read-only tools | Observe projects, accounts, costs, billing estimates, team access and delivery. The default for setup, mcp-config, doctor --mcp, and the shipped example clients. |
guardrails | All 37, adding budget_check, budget_confirm, budget_check_batch, record_call | The agent makes un-instrumented provider calls itself and must run the check → confirm loop. The default for bare solwyn mcp. |
Recommend --profile read unless the agent genuinely spends. A tool excluded by the profile is not merely denied — it is never registered, so calling it fails as an unknown tool before any argument, credential, or network is touched. Profiles expose tools; they never grant permissions. A full-scope key does not promise that a future budget decision will allow spend.
The tool catalogue
| Tool | CLI equivalent | Mutates | Credential |
|---|---|---|---|
budget_status | budget status | no | either |
budget_check | budget check | yes | full-scope project key |
budget_confirm | budget confirm | yes | full-scope project key |
budget_check_batch | budget check --batch | yes | project key; read-only suffices for recover_only |
record_call | record | yes | full-scope project key |
get_project_status | status | no | either |
get_forecast | forecast | no | either |
get_costs | costs | no | either; account=true requires JWT |
get_cost_total | costs total | no | either |
list_budget_rules | budget rules list | no | either; with_last_fired=true requires JWT |
list_tags | tags list | no | either; Team and above |
list_tag_values | tags values | no | either; Team and above |
list_agent_runs | runs list | no | either |
get_agent_run | runs show | no | either |
list_alert_events | alerts list | no | either; account: true requires JWT |
list_projects | projects list | no | JWT |
get_overview | overview | no | JWT |
list_cost_events | costs events | no | either |
get_cost_baseline | costs baseline | no | either |
get_unit_economics | costs unit-economics | no | either |
get_cost_drift | costs drift | no | either |
list_priced_models | costs priced-models | no | either |
simulate_costs | costs simulate | no | either |
list_provider_failovers | providers failovers | no | either |
list_untracked_surfaces | providers untracked | no | either |
list_audit_events | audit | no | JWT |
plan_status | plan | no | JWT or project key with account context; no project required |
plan_catalog | plan list | no | JWT or project key with authenticated user context; no project required |
get_whoami | whoami | no | either; a key returns local context only |
get_account_phone | account phone show | no | JWT |
list_alert_activity | alerts activity | no | JWT |
list_open_alert_deliveries | alerts pending | no | JWT |
budget_uncounted | budget uncounted | no | either |
get_account_alert | alerts account show | no | JWT; optional with_last_fired |
plan_preview | plan change --dry-run | no | JWT at any account role; no project required |
list_team_members | team members list | no | JWT at any account role; no project required |
list_team_invites | team invites list | no | owner/admin JWT; no project required |
Every read tool is annotated readOnlyHint: true, idempotentHint: true; the four writers carry destructiveHint: true. Tool inputs mirror the CLI flags in snake case — get_costs takes the full shared cost-filter set and from_/to windows, list_tag_values takes key, prefix, limit, cursor, and so on — and every input schema is closed, so an unknown key is rejected as invalid_arguments. The three budget tools' descriptions are generated from the same text as solwyn agent-context, so the MCP guidance and the CLI brief cannot drift apart.
All 33 reads appear in both profiles. The four writers appear only in guardrails; recovery-only batch access still requires that profile. Reads carry destructiveHint: false; writers carry readOnlyHint: false and idempotentHint: false. All tools carry openWorldHint: true because they contact Core. Annotations describe behavior and do not enforce authorization.
plan_preview(tier="team", interval="year") uses a read-only POST, creates no invoice or subscription change, and returns solwyn.plan.preview.v1 with immediate/scheduled mode, dates, signed integer cents, currency and proration lines. An estimate is not a price lock. Cost simulation and opt-in latest-alert lookup also use read-only POSTs.
Billing, team/member writes, account rename/recovery and invitation token/password workflows remain CLI-only. Rule, channel, project, key and alert writes also remain CLI/dashboard operations.
Selection and authorization
Credential order is environment project key, stored project key, then stored JWT. A selected key masks JWT and a JWT-only tool refuses it; the server never silently switches identities. Unset SOLWYN_API_KEY and deliberately use logout then login to clear a stored key when user login is needed.
For project reads, an explicit project_id overrides launch --project, then SOLWYN_PROJECT_ID, then default_project. Writes always use the key's own project. Project selection does not change key scope, account role or account membership. Core also checks access and plan entitlements.
get_costs(account=true) defaults to project grouping, requires JWT and rejects explicit project_id; configured default projects are irrelevant. Only model, provider, modality and preset/paired custom windows apply in account mode. list_alert_events(account=true) reads account-scope history and ignores project_id; list_alert_activity reads all projects plus account scope. Roster reads allow any member; invitation listings require owner/admin and preserve expired open rows and delivery statuses. Listing or resending an invitation does not prove email delivery.
MCP 2026-07-28 discovery advertises ttlMs=60000, cacheScope="private"; legacy clients receive the same tools without those fields. Hosts may cache discovery for one minute per launch/profile. Tool results have no cache hint: discovery freshness does not cache credentials, authorize spend or extend reservations.
The result envelope
Every tool returns one typed object, serialized identically into the text content and the structured content of the result:
{"schema":"solwyn.mcp.budget-check.v1","ok":true,"data":{"allowed":false,"reservation_id":null,"denied_by_period":"monthly","call_id":"…","recorded":{"status":"ingested"}},"error":null}{"schema":"solwyn.mcp.budget-status.v1","ok":false,"data":null,"error":{"code":"authentication_required","message":"Solwyn authentication is required.","hint":"Configure a project key or run solwyn login, then retry.","status_code":null}}isError is set exactly when ok is false. Two consequences matter for a model:
- A budget denial is a successful decision, not an error:
ok: true,isError: false,data.allowed: false. Fail-open isallowed: truewithreservation_id: null, and a stopped run is an ordinary denial. Denials record attribution by default unlessno_recordis set. - Error messages are fixed strings. Server error codes pass through only from an allowlist; anything else becomes
api_error. Validation hints name a schema path, never an input value. Tracebacks, raw server text, secrets, and the names of unknown methods are never returned.
An HTTP-200 result whose attribution recording was rejected or failed is downgraded to ok: false with a recording_* code while keeping data, so a settled reservation is never mistaken for an unsettled one.
Bounded work
Every call runs inside fixed limits. There is no per-session cap and no option to change these:
| Limit | Value |
|---|---|
| Useful-work deadline | 30 seconds |
| Pages fetched for a paginating tool | 10 |
| Aggregate HTTP response bytes | 1 MiB |
| Encoded request body | 4 MiB |
| Complete tool result | 2 MiB |
| Batch size | 1 to 32 checks |
| Continuation cursor | 4,096 characters |
Hitting a limit returns an ordinary tool error — operation_deadline, page_limit, response_size_limit, result_size_limit — but never loses identities. An oversized result is replaced by the error plus a compact recovery object: for a mutating tool, the operation id, per-item states, reservation and call ids, and the settlement and recording status; for a read, the cursor to resume from. Cancellation or a closed stdin stops future requests and pages but cannot undo a request the API already admitted.
budget_check_batch extends this with a durable journal: supply a caller-retained UUIDv7 operation_id before calling, and if the reply is lost, call again with recover_only: true to read the outcome without admitting anything new. Reservations expire 300 seconds after they were checked; journal entries expire after 24 hours.
Cursor listings retain their first unread cursor on a later-page error. This bounded MCP behavior differs from CLI --all, which emits no partial success. See bounded work and journal recovery, observed media recovery, and service capacity and uncertain writes.
mcp-config
Print the command and argument array a client should use to launch the server. It writes no files, reads no credential, and makes no network call.
solwyn [--project <project_id>] [--api-url <url>] [--json] mcp-config [--executable <path>] [--profile read|guardrails]| Option | Type | Default | Purpose |
|---|---|---|---|
--executable | path | this installation's solwyn | An explicit installed executable. Never a PATH lookup. |
--profile | read | guardrails | read | Profile to bake into the descriptor. |
| Contract | |
|---|---|
| Auth | none; no credential lookup |
| Exit codes | 0 descriptor emitted · 1 executable unavailable · 2 usage |
| JSON schema | solwyn.mcp-config.v1 |
$ solwyn --json --project proj_0123456789abcdef01234567 mcp-config
{"schema":"solwyn.mcp-config.v1","command":"/absolute/path/to/solwyn","args":["--project","proj_0123456789abcdef01234567","--api-url","https://api.solwyn.ai","mcp","--profile","read"]}The descriptor is host-neutral; adapt command and args to your client's configuration format. It never contains a key — supply credentials through the OS keychain or the host's environment block. See Agent integration for ready-to-paste examples and doctor --mcp to verify the result.
Related
- Agent integration — the one rule, the guardrail loop, client wiring, and diagnostics
budget— every option the budget tools mirror, including recovery after a lost replydoctor—--mcplaunches the server in isolation and checks discovery and a read