Skip to content

McpAdapter: add callTool() method that returns raw CallToolResult #128

@bokelley

Description

@bokelley

Problem

McpAdapter.request<T>() unwraps the MCP CallToolResult and returns only the parsed data payload (from structuredContent or content[0].text). This makes it impossible for consumers to:

  1. Access _meta.ui.resourceUri — needed to discover MCP App HTML resources for interactive rendering
  2. Get the full CallToolResult — needed by @mcp-ui/client's AppRenderer component (the toolResult prop)
  3. Access the content[] array — which may contain multiple content blocks (text, image, resource)

Since mcpClient and transport are private, there's no workaround without modifying the SDK.

Impact

Any app that wants to render MCP App UIs from Scope3 tool responses (like Claude Desktop and ChatGPT do) cannot use McpAdapter today. This affects our own product (Goldie/Bayes) and will affect any third-party consumer building MCP Apps-compatible hosts.

Proposed Solution

Add a callTool() method to McpAdapter that returns the raw CallToolResult:

async callTool(
  method: HttpMethod,
  path: string,
  body?: unknown,
  options?: RequestOptions
): Promise<CallToolResult> {
  if (!this.connected) await this.connect();
  const fullPath = this.buildPath(path, options);
  const args = { method, path: fullPath, ...(body ? { body } : {}) };
  return this.mcpClient.callTool({ name: 'api_call', arguments: args });
}

Also add readResource() for fetching MCP App HTML:

async readResource(uri: string): Promise<ReadResourceResult> {
  if (!this.connected) await this.connect();
  return this.mcpClient.readResource({ uri });
}

Extract the path-building logic from request() into a private buildPath() method so both request() and callTool() share it.

Add these as optional methods on BaseAdapter (non-breaking — RestAdapter doesn't implement them):

export interface BaseAdapter {
  // ... existing ...
  callTool?(method: HttpMethod, path: string, body?: unknown, options?: RequestOptions): Promise<CallToolResult>;
  readResource?(uri: string): Promise<ReadResourceResult>;
}

Backwards Compatibility

  • request<T>() behavior is unchanged
  • New methods are additive
  • BaseAdapter additions are optional (no breaking change for RestAdapter)

Metadata

Metadata

Assignees

No one assigned

    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