Repository navigation
Description
Description
TL;DR
OpenCode mangles a primitive number emitted by the model into a string (or runs it through decimal.js) before forwarding it to an MCP server, then every conformant MCP server rejects it with a type-mismatch error. This is not the model emitting a string — the model emits id: 3, and OpenCode is the one that converts it. The same tool calls succeed in Cursor and Claude Desktop.
This is a recurrence / generalization of the DecimalError family (#6010, #6161), which were closed as "fixed by upgrading" but are still happening on 0.15.23.
Why this is not a duplicate of the closed "not a bug" issues
The most similar prior reports were closed with the rationale that the model was emitting strings:
- Interaction with Chrome Dev Tools MCP Server causes Error #3593 — closed: "this isn't a bug, it is just a tool call validation error... the model failing to call a tool"
- Tools with array/object parameters fail when LLM sends stringified JSON #7512 — closed as NOT_PLANNED: "LLMs sometimes serialize typed parameters as strings"
- agent cant use read tool SchemaError(Expected number | undefined, got "2480" at ["offset"]). #26870 — auto-closed stale
Our case is different and verifiable at the wire level: the model emits a number, and OpenCode turns it into a string before it reaches the server. See the wire-level proof below — feeding the same model output as a raw JSON number vs. a string to any conformant MCP server shows only the number form is accepted, yet OpenCode sends the string form.
Error observed
echo_id %!s(float64=3) Error: [DecimalError] Invalid argument: [object Object]
The %!s(float64=3) fragment is Go fmt verb-mismatch output (a %s applied to a float64) embedded into the error message text — i.e. the value was a number (float64) at some point in OpenCode's pipeline. The [DecimalError] Invalid argument: [object Object] is a decimal.js error thrown inside OpenCode's JS/TS client when new Decimal(...) is handed a boxed/object value instead of a primitive.
Expected vs actual
- Expected:
tools/callcarries{"name":"echo_id","arguments":{"id":3}}over stdio, withidas a JSON number primitive. Server receivestypeof arg === "number". - Actual: OpenCode either (a) runs the number through
Decimaland aborts with[DecimalError] Invalid argument: [object Object], or (b) serializes it as a JSON string ({"id":"3"}), which MCP servers reject with a type mismatch, e.g.cannot unmarshal string into Go struct field .id of type int64.
Wire-level proof that OpenCode (not the model, not the server) is at fault
The bug is entirely in OpenCode's argument-serialization layer. Feeding the two candidate payloads directly to any conformant MCP server over stdio shows that only the number form is accepted — so when OpenCode sends the string form, OpenCode is the source of the breakage:
# number form — what the MODEL emits and what Cursor/Claude Desktop forward:
{"name":"echo_id","arguments":{"id":3}} # => server: ok, typeof id === "number"
# string form — what OpenCode ends up sending:
{"name":"echo_id","arguments":{"id":"3"}} # => server: error, cannot unmarshal string into numeric fieldThe model is not in this loop; the divergence happens inside OpenCode between receiving the model's tool call and writing to the MCP stdio transport.
Plugins
Any local stdio MCP server exposing a tool with a "type": "number" parameter (see Steps to reproduce for a minimal 20-line example).
OpenCode version
0.15.23
Steps to reproduce
- Save this minimal MCP server (Node.js, stdio, NDJSON) as
server.js. It asserts on the server side thatidarrives as a JSON number, not a string:
const readline = require("readline");
const rl = readline.createInterface({ input: process.stdin });
const send = (o) => process.stdout.write(JSON.stringify(o) + "\n");
rl.on("line", (line) => {
const msg = JSON.parse(line);
if (msg.method === "initialize") {
send({ jsonrpc: "2.0", id: msg.id, result: {
protocolVersion: "2024-11-05",
capabilities: { tools: {} },
serverInfo: { name: "repro", version: "1.0.0" },
}});
} else if (msg.method === "tools/list") {
send({ jsonrpc: "2.0", id: msg.id, result: { tools: [{
name: "echo_id",
description: "Echoes back the numeric id it receives",
inputSchema: {
type: "object",
properties: { id: { type: "number", description: "A numeric id" } },
required: ["id"],
},
}]});
} else if (msg.method === "tools/call") {
const arg = msg.params.arguments.id;
const ok = typeof arg === "number";
send({ jsonrpc: "2.0", id: msg.id, result: { content: [
{ type: "text", text: `received id=${arg} (typeof=${typeof arg}) ok=${ok}` },
]}});
}
});- Register it in
opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"repro": { "type": "local", "command": ["node", "server.js"], "enabled": true }
}
}-
Start OpenCode and ask the agent to call the tool with a number, e.g. "call echo_id with id 3".
-
Observe the failure:
echo_id %!s(float64=3) Error: [DecimalError] Invalid argument: [object Object]. If you instead hit the string-coercion path, the server's response will readtypeof=string ok=false. -
(Control) Run the same
echo_idtool from Cursor or Claude Desktop with the same model — it succeeds, confirming the model emits a number and only OpenCode mangles it.
Screenshot and/or share link
n/a — the wire-level proof in Description and the minimal server in Steps to reproduce fully demonstrate the bug without a session.
Operating System
Linux 6.1.0-51-amd64 (the wire-level behavior is OS-independent)
Terminal
xterm-256color
Activity
github-actions commentedon Jul 28, 2026
This issue might be a duplicate of existing issues. Please check:
tool.schema.number()argument crashes with "Cannot read properties of undefined (reading 'split')" #38956: Similar root cause —number-typed tool arguments are mishandled in OpenCode's argument-serialization layer (plugintool.schema.number()path crashes with a different error, but the same numeric-type handling area is involved)mrashad10 commentedon Jul 28, 2026
Thanks for the automated triage. After reviewing both issues in detail, I believe #39334 and #38956 are related but not duplicates — they are separate bugs in different components:
DecimalError/ type mismatch on serverCannot read properties of undefined (reading 'split')#39334 concerns the MCP transport layer incorrectly serializing numeric values when communicating with MCP servers (wire-level proof included in the description). #38956 concerns a crash in the plugin tool registration path when
tool.schema.number()is used. They may both stem from a shared numeric-handling utility in OpenCode's internals, but they manifest in different components with different symptoms.Suggest keeping both open as separate issues (possibly linking them as related) rather than closing one as a duplicate.
github-actions commentedon Sep 27, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.