Skip to content

mcp: local tool calls return result: null, response text goes to Logs instead #48910

Closed as not planned

Description

@XXLxhPLMM

Description

Summary
MCP local server tools return result: null in the execute runtime; the actual response text is emitted to Logs instead of being available as the tool result. All 42 tools from the same MCP server exhibit the identical behavior.
Environment

  • opencode version: 2.0.3
  • OS: Windows_NT 10.0.26200 (MINGW64_NT-10.0-26200, win32 x64)
  • Terminal: xterm-256color (MINGW64 / Git Bash)
  • Shell: /usr/bin/bash (Git Bash), COMSPEC: C:\WINDOWS\system32\cmd.exe
  • Install/channel: npm global, latest
  • Active plugins: none (all disabled in config)
  • MCP server: @cocaxcode/[email protected] (local, via npx)
  • MCP SDK (server side): @modelcontextprotocol/[email protected]
    Reproduction
  1. Configure a local MCP server in opencode.jsonc:
    "api-testing": {
    "type": "local",
    "command": ["npx", "-y", "@cocaxcode/api-testing-mcp@latest"],
    "enabled": true
    }
  2. Start opencode, open a session.
  3. In a tools code block, call any MCP tool, e.g.:
    const result = await tools["api-testing"].env_list();
    console.log(result); // null
  4. Observe the output:
    result: null
    Logs: [ { "name": "local_dev", ... } ]
    Expected Behavior
    result should contain the parsed tool response (the array of environments), matching what the MCP server returns:
    { "content": [{ "type": "text", "text": "[{ "name": "local_dev", ... }]" }] }
    The content[0].text value should be parsed and returned as the tool result, not logged separately.
    Actual Behavior
    Every MCP tool call returns result: null. The actual response text appears under a Logs: heading in the output. This is consistent across all 42 tools from the api-testing MCP server.
    Sample output:
    null

Logs:
[
{
"name": "local_dev",
"active": true,
"default": true,
"group": "rh-front",
"variableCount": 2,
"spec": "rh-remote-v3"
}
]
The MCP server's response format is correct per the MCP protocol — the issue is on the OpenCode client side, which fails to pass content[0].text through as the tool return value.
Additional Context

  • The MCP server logs show mcp connected server=api-testing tools=42 — connection and tool registration succeed.
  • No errors in ~/.local/share/opencode/log/opencode.log related to MCP.
  • All 42 tools (env_list, api_spec_list, api_import, collection_list, etc.) exhibit the same result: null behavior.
  • Tool validation errors (wrong argument names/types) DO return proper error messages — only successful calls produce null.
  • The MCP server uses @modelcontextprotocol/[email protected]; OpenCode's bundled MCP client version is unknown but should be compatible.
  • Config: opencode.jsonc with "type": "local" MCP, shell "sh", "lsp": false.

Plugins

No response

OpenCode version

v2.0.3

Steps to reproduce

No response

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Activity

  1. VishvakR commented on Sep 14, 2026

    @VishvakR

    had a look and i don't think this is a bug, the snippet just doesn't return anything. execute uses the program's return value as the result, and when there's no return it takes the last top-level expression, which here is console.log(result). that's undefined, and toData in packages/codemode/src/data.ts turns an undefined execution result into null. so you get null and then whatever was logged.

    the array under Logs: is result itself, so the MCP call did come back fine (JSON text from schemaless tools gets parsed into an object since #48357, which is in 2.0.3). ending the block with return result should give you the array as the result.

    didn't run it against your server, went off the v2.0.3 source. if you've got a call that still comes back null with an explicit return i'd like to see it, otherwise this is probably ok to close.

  2. Hotragn commented on Sep 14, 2026

    @Hotragn

    I think the tool call is actually working here, and the null is coming from the script rather than from MCP.

    In packages/opencode/src/tool/code-mode.ts, the tool output is built from the script's return value, and console.log output is appended separately under Logs::

    const withLogs = (text: string) =>
      text.length > 0 ? `${text}\n\nLogs:\n${logs.join("\n")}` : `Logs:\n${logs.join("\n")}`

    Your script is:

    const result = await tools["api-testing"].env_list();
    console.log(result);

    That returns nothing, so the script's value is undefined, normalized to null on the way out of the sandbox (copyOut(..., undefinedAsNull) in packages/codemode/src/tool-runtime.ts) and serialized as null. Meanwhile the array under Logs: is exactly what console.log(result) printed — which means result did hold the parsed response, not null.

    Ending the script with the value should give you what you expect:

    const result = await tools["api-testing"].env_list();
    return result;

    For what it's worth, projectMcpResult in the same file does already handle the case you describe — it collects text blocks and returns text.join("\n"), and prefers structuredContent when present.

    If you do return the value and still get null, then something else is going on and it's worth digging further — in that case the content array your server sends for one specific call would help.

  3. neriousy commented on Oct 20, 2026

    @neriousy
    Member

    End the script with return result; instead of console.log(result); to return the MCP response; console output appears separately under Logs. Closing this as an answered question; if an explicit return still produces null, please open a new issue with that reproduction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions