Skip to content

Conversation

@ochafik
Copy link
Collaborator

@ochafik ochafik commented Dec 3, 2025

Summary

This PR enables Apps to register their own tools that agents can call, making apps introspectable and accessible to the model without DOM parsing.

Apps expose semantic interfaces (state queries, operations) via standard MCP tools. The agent discovers capabilities via tools/list, queries state, and drives interactions.

This is a different model from other approaches where apps keep the model informed through side channels (e.g., OAI Apps SDK sending widget state changes to the model, MCP-UI adding tool call results to chat history). Instead, the agent actively queries app state and executes operations through tools.

Example:

// Agent can query structured state
app.registerTool("get_board_state", {
  outputSchema: z.object({
    board: z.array(z.enum(['X', 'O', null])),
    currentPlayer: z.enum(['X', 'O']),
    winner: z.enum(['X', 'O', 'draw', null]).nullable()
  })
}, async () => ({
  structuredContent: { board, currentPlayer, winner: checkWinner(board) }
}));

// Agent can execute actions
app.registerTool("make_move", {
  inputSchema: z.object({ position: z.number().int().min(0).max(8) })
}, async ({ position }) => {
  board[position] = currentPlayer;
  currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
  return { structuredContent: { board, winner: checkWinner(board) } };
});

Agent interaction:

// Discover → Query → Act
const { tools } = await bridge.sendListTools({});
const state = await bridge.sendCallTool({ name: "get_board_state" });
await bridge.sendCallTool({ name: "make_move", arguments: { position: 4 } });

Changes

App side (app.ts)

  • registerTool() - Register tools with Zod validation
  • oncalltool / onlisttools - Handle tool requests
  • sendToolListChanged() - Notify on tool updates
  • Tools support enable(), disable(), update(), remove()

Host side (app-bridge.ts)

  • sendCallTool() - Call app tools
  • sendListTools() - List app tools
  • Fix: Use correct ListToolsResultSchema

Capabilities (types.ts)

  • Apps: tools: { listChanged?: boolean }
  • Hosts: serverTools: { listChanged?: boolean } (existing)

Tests: ✓ 27 passing, 100% coverage

Design

Reuses standard MCP messages: tools/call, tools/list, notifications/tools/list_changed

Similar to WebMCP but without turning the App (embedded page) into an MCP server - apps register tools within the App/Host architecture.

Lifecycle: App tools exist only while app loaded (ephemeral, sandboxed)

Separation: Server tools (persistent, trusted) vs App tools (ephemeral, sandboxed)

Breaking Changes

None. Purely additive.

Related

Implements the gist of #35 (WebMCP-style tool registration) while preserving the App/Host architecture.

🤖 Generated with Claude Code

@ochafik ochafik changed the title feat: Add tool registration and bidirectional tool support feat: Make Apps bidirectional with tool registration support Dec 3, 2025
@ochafik ochafik changed the title feat: Make Apps bidirectional with tool registration support feat: Add tool registration for Apps Dec 3, 2025
@ochafik ochafik marked this pull request as draft December 3, 2025 00:18
@ochafik ochafik changed the title feat: Add tool registration for Apps feat: Add tool registration for Apps (WebMCP-style!) Dec 3, 2025
@ochafik ochafik changed the title feat: Add tool registration for Apps (WebMCP-style!) spec: Add tool registration for Apps, to be called by Host (WebMCP-style!) Dec 3, 2025
@ochafik ochafik marked this pull request as ready for review December 3, 2025 00:53
ochafik and others added 9 commits January 9, 2026 16:05
This PR adds comprehensive tool support for MCP Apps, enabling apps
to register their own tools and handle tool calls from the host.

- Add `registerTool()` method for registering tools with input/output schemas
- Add `oncalltool` setter for handling tool call requests from host
- Add `onlisttools` setter for handling tool list requests from host
- Add `sendToolListChanged()` for notifying host of tool updates
- Registered tools support enable/disable/update/remove operations

- Add `sendCallTool()` method for calling tools on the app
- Add `sendListTools()` method for listing available app tools
- Fix: Use correct ListToolsResultSchema (was ListToolsRequestSchema)

- Add comprehensive tests for tool registration lifecycle
- Add tests for input/output schema validation
- Add tests for bidirectional tool call communication
- Add tests for tool list change notifications
- All 27 tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implement automatic `oncalltool` and `onlisttools` handlers that are
initialized when apps register tools. This removes the need for manual
handler setup and ensures tools work seamlessly out of the box.

- Add automatic `oncalltool` handler that routes calls to registered tools
- Add automatic `onlisttools` handler that returns full Tool objects with JSON schemas
- Convert Zod schemas to MCP-compliant JSON Schema using `zod-to-json-schema`
- Add 27 comprehensive tests covering automatic handlers and tool lifecycle
- Test coverage includes error handling, schema validation, and multi-app isolation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Always return inputSchema as object (never undefined)
- Keep filter for enabled tools only in list
- Update test to match behavior (only enabled tools in list)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@ochafik ochafik force-pushed the feat/app-tool-registration branch from 1dba38b to 55ad06a Compare January 9, 2026 16:55
const tool2 = app.registerTool("tool2", {}, async (_args: any) => ({
content: [],
}));
const tool3 = app.registerTool("tool3", {}, async (_args: any) => ({
const appCapabilities = { tools: { listChanged: true } };
app = new App(testAppInfo, appCapabilities, { autoResize: false });

const tool1 = app.registerTool(
@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 9, 2026

Open in StackBlitz

@modelcontextprotocol/ext-apps

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/ext-apps@72

@modelcontextprotocol/server-basic-react

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-basic-react@72

@modelcontextprotocol/server-basic-vanillajs

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-basic-vanillajs@72

@modelcontextprotocol/server-budget-allocator

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-budget-allocator@72

@modelcontextprotocol/server-cohort-heatmap

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-cohort-heatmap@72

@modelcontextprotocol/server-customer-segmentation

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-customer-segmentation@72

@modelcontextprotocol/server-scenario-modeler

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-scenario-modeler@72

@modelcontextprotocol/server-system-monitor

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-system-monitor@72

@modelcontextprotocol/server-threejs

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-threejs@72

@modelcontextprotocol/server-wiki-explorer

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-wiki-explorer@72

commit: 058c4ad

Avoid double-verb naming pattern for consistency with existing API.
@connor4312
Copy link

connor4312 commented Jan 9, 2026

How should it would if I'm in a chat and have multiple MCP apps currently visible, and they register the same tool? Does the host send tool calls to most recent one? (If so is there a way to signal to the older app that it can no longer receive tool calls?)

I think I'm also not clear on the lifetime of apps. Currently in the spec there is no specific bounds on the lifetime of the MCP apps. For VS Code, we implement virtualization and so an app is not loaded if the user has it scrolled out of the viewport. But now we need to actively keep app apps alive for some amount of time (forever?) in case a model decides to later call them. For long chat sessions this could get quite expensive.

I think #125 is a simpler approach that should cover the bases here without introducing lifetime concerns

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants