Forty MCP tools is 22 KB per model call. A free endpoint said no.

The Autoplans desktop app and CLI run a coding agent on top of the OpenCode runtime. When they shipped, each session attached the account's MCP server directly, which meant every tool the server exposes was sent to the model on every call. On the OpenCode Zen free endpoints, model opencode/big-pickle, which the apps default to, that stopped working: the endpoint answered 503 with the message Endpoint is unavailable. This is what the number is, what it weighs, what we changed, and how to tell this failure apart from a server-side one.
Counting the tools
The docs and code comments lag by one or two in a few places; every number here is from the code at the time of writing. The HTTP transport in packages/mcp-server/src/transport/http.ts answers tools/list from a literal array of 28 tool definitions (projects, tasks, subtasks, comments, assignment, business plan, branding, and the four code_agent_* helpers) and spreads in TELEMETRY_TOOL_DEFINITIONS from tools/telemetry-tools.ts, which holds 12 more. Forty tools, as of desktop app 1.1.6 and CLI 0.2.2 in August 2026. Every tool with its arguments is listed in MCP tools.
The hosted endpoint is https://autoplans.dev/api/v1/mcp. A tools/list request and the top of its answer look like this:
curl -s https://autoplans.dev/api/v1/mcp \
-H 'content-type: application/json' \
-H "authorization: Bearer $AUTOPLANS_API_KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "list_projects",
"description": "List all projects for the authenticated user",
"inputSchema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "Filter by status (planning, in_progress, completed, archived)",
"enum": ["planning", "in_progress", "completed", "archived"]
},
"limit": {
"type": "number",
"description": "Maximum results to return (default: 50)",
"default": 50
}
},
"required": []
}
}
]
}
}
The real answer has 39 more entries in the array. Each entry is a name, a description, and a JSON Schema for the arguments. An MCP client fetches this list once per session and then includes the definitions in every request it makes to the model. The list is the fixed cost of every turn.
What forty tools weigh
We measured the fixed cost rather than guessing at it. This works against any MCP server that speaks HTTP, and prints one line per tool with its serialised size, smallest first:
curl -s "$MCP_URL" \
-H "authorization: Bearer $KEY" \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
| jq -r '.result.tools[] | "\(.name)\t\(tojson | length)"' \
| sort -t$'\t' -k2 -n
The totals below come from the same measurement run on the source arrays in http.ts and telemetry-tools.ts, so they do not depend on a key or a live server. They are JSON.stringify byte lengths:
- All 40 tools: 22,171 bytes. The full
tools/listresponse, envelope included, is 22,215 bytes. - The 28 non-telemetry tools: 14,255 bytes. The 12 telemetry tools: 7,917 bytes.
- Mean per tool: 554 bytes. The largest is
update_brandingat 1,854 bytes, thenupdate_business_planat 1,729. The smallest,delete_task, is 189.
Those are bytes of JSON, not tokens. The runtime reformats tool definitions into whatever shape the provider expects, and each provider tokenises differently, so we are not quoting a token figure we did not measure.
The 503 itself did not say "payload too large". The message we saw was Endpoint is unavailable, nothing more specific. We did not bisect the tool count. What we saw was this: with the mcp block in the runtime config, sessions on opencode/big-pickle answered 503 on the first turn, launch after launch; with the plugin and no mcp block, the same model completed sessions. That looked nothing like throttling, and the comment in runtimeConfig() records it as the cause. Free endpoints do throttle and sometimes refuse anyway; the desktop app's model router counts a recent failure as twenty seconds of latency when it picks the model for the next session.
What we changed
The apps no longer attach the MCP server to the runtime. They had always loaded the opencode-autoplans plugin as well; the fix was to drop the mcp block and bundle the plugin with the app rather than pull it from npm at runtime. The plugin's source is packages/opencode-plugin/src/index.ts, the same file that is published to npm. The desktop app and the CLI each carry a byte-identical copy under agent-rules/plugin/index.ts and load it by file URL, so a fix to the plugin reaches users with the app update rather than through a second npm install at runtime.
The plugin defines thirteen tools as of desktop app 1.1.6 (the docs said eleven for a while; they are corrected now):
autoplans_my_tasks autoplans_project
autoplans_project_tasks autoplans_list_projects
autoplans_create_task autoplans_update_task_status
autoplans_comment autoplans_branding
autoplans_update_branding autoplans_create_branding
autoplans_business_plan autoplans_update_business_plan
autoplans_create_business_plan
The two newest, autoplans_create_branding and autoplans_create_business_plan, were added later so the brand and plan agents could save to a project that had no record yet.
Every plugin tool is a thin call to the same server. packages/opencode-plugin/src/client.ts posts a tools/call to /api/v1/mcp with the bearer key and parses the { success, data } body out of the first text content block. So the server still serves all 40 tools; the model just never sees them. The plugin also reshapes results for an agent: autoplans_project_tasks returns one line per task, - [status] (priority) title (task id ...), rather than the raw record.
Measured the same way, converting each tool's zod arguments with z.toJSONSchema(z.object(args)) and keeping the $schema key, the 13 tools come to 5,254 bytes, a mean of 404 bytes each. Drop $schema and it is 4,513. Either way, roughly a quarter of the server's payload.
The fallback
The desktop app keeps the MCP server as a fallback, because a session with forty tools beats a session with none. runtimeConfig() in packages/agent-desktop/src/main.js first checks that the bundled plugin is loadable, meaning its entry point and its own node_modules/@opencode-ai/plugin and node_modules/zod exist beside it, since a file-URL plugin resolves imports by walking up from its directory and an installed app has no dependency tree above it. If that check passes, the config gets plugin: [file-url]. If it fails and a credential is stored, the config gets an mcp block instead:
{
"mcp": {
"autoplans": {
"type": "remote",
"url": "https://autoplans.dev/api/v1/mcp",
"enabled": true,
"headers": { "Authorization": "Bearer apk_live_..." }
}
}
}
After the runtime starts, verifyAutoplansTools() probes it for tool ids beginning autoplans_ and for the status of the autoplans MCP connection. If neither is present it shows a warning dialog, because the alternative was an agent that quietly kept a local checklist and reported task updates that never reached the server. Session → Connection status… reports "Task tools: 13 loaded via the bundled plugin" when the plugin loaded. On the MCP fallback the dialog currently reports "Task tools: NONE" even though the startup check accepts a connected server, a gap we still have to close.
The CLI does not fall back. packages/agent-cli/src/config.ts sets the plugin and deliberately leaves the mcp block out; if the tools are missing there, the fix is to reinstall the package (see CLI). Only the desktop app falls back.
The runtime merges any opencode.json it finds in the user's config directory. A developer's personal file brought its own MCP servers into every Autoplans session, and its own mcp block replaced the one the app injects, which is how the fallback server went missing on one machine. The app now points XDG_CONFIG_HOME at its own agent-config directory under userData, XDG_DATA_HOME at agent-data beside it, and passes its config through OPENCODE_CONFIG_CONTENT, so a personal opencode.json is no longer merged in.
If your own MCP client sees the same thing
First establish which side is failing. The Autoplans endpoint is a proxy in front of the MCP server process, and each layer has a distinct answer:
| Status | Body | Meaning |
|---|---|---|
401 | UNAUTHORIZED | The key is missing or wrong. |
403 | FEATURE_NOT_AVAILABLE | The account's tier does not include the hosted MCP server. |
429 | RATE_LIMITED, with Retry-After | Per-IP or per-key limiter in the MCP server. |
503 | SERVICE_UNAVAILABLE, "MCP server is not running" | The proxy could not reach the MCP process. |
200 | JSON-RPC error | The request was understood and refused; the message says why. |
Run the curl above. If it returns a result with a tools array, the server is fine, and a 503 that appears only once a model is in the loop is coming from the inference provider. Its body will be the provider's, not one of the shapes in the table.
Then reduce what the model sees. tools/list takes no filter, so the reduction happens on the client side: a plugin or wrapper that exposes a handful of task-shaped tools and forwards each to tools/call, which is exactly what opencode-autoplans does, and which you can use directly in your own OpenCode setup (OpenCode). Pick the tools an agent uses while working, not the ones a person uses while browsing. The plugin does not expose the telemetry tools at all, which alone removes a third of the bytes.
Whether you need to do this depends on the endpoint. The Claude Code plugin attaches the whole server through its .mcp.json; the reduced set exists for the first-party apps because they default to opencode/big-pickle and the desktop app's router prefers free endpoints. If you pin a paid model, the full server may be the right answer, since it exposes everything.
Tags
Ready to Transform Your Development Workflow?
Join developers who are building faster with AI-powered project management.
Get Started Free