-
Notifications
You must be signed in to change notification settings - Fork 68
spec: Add tool registration for Apps, to be called by Host (WebMCP-style!) #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
1dba38b to
55ad06a
Compare
@modelcontextprotocol/ext-apps
@modelcontextprotocol/server-basic-react
@modelcontextprotocol/server-basic-vanillajs
@modelcontextprotocol/server-budget-allocator
@modelcontextprotocol/server-cohort-heatmap
@modelcontextprotocol/server-customer-segmentation
@modelcontextprotocol/server-scenario-modeler
@modelcontextprotocol/server-system-monitor
@modelcontextprotocol/server-threejs
@modelcontextprotocol/server-wiki-explorer
commit: |
|
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 |
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 <[email protected]>
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 <[email protected]>
- 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 <[email protected]>
Avoid double-verb naming pattern for consistency with existing API.
- Add McpUiScreenshotRequest/Result and McpUiClickRequest/Result types - Add onscreenshot and onclick handlers to App class - Add screenshot() and click() methods to AppBridge class - Generate updated Zod schemas
…t-view - Uncomment and fix the navigate-to tool for animated navigation - Add get-current-view tool to query camera position and bounding box - Add flyToBoundingBox function for smooth camera animation - Add setLabel function for displaying location labels
- get-document-info: Get title, current page, total pages, zoom level - go-to-page: Navigate to a specific page - get-page-text: Extract text from a page - search-text: Search for text across the document - set-zoom: Adjust zoom level
…dertoy, wiki-explorer, and threejs budget-allocator: - get-allocations: Get current budget allocations - set-allocation: Set allocation for a category - set-total-budget: Adjust total budget - set-company-stage: Change stage for benchmarks - get-benchmark-comparison: Compare against benchmarks shadertoy: - set-shader-source: Update shader source code - get-shader-info: Get shader source and compilation status - Sends errors via updateModelContext wiki-explorer: - search-article: Search for Wikipedia articles - get-current-article: Get current article info - highlight-node: Highlight a graph node - get-visible-nodes: List visible nodes threejs: - set-scene-source: Update the Three.js scene source code - get-scene-info: Get current scene state and any errors - Sends syntax errors to model via updateModelContext
Wiki Explorer: - Add expand-node tool - the critical missing tool for graph exploration - Claude can now programmatically expand nodes to discover linked articles Server descriptions updated to mention widget tools: - map-server: navigate-to, get-current-view - pdf-server: go-to-page, get-page-text, search-text, set-zoom, get-document-info - budget-allocator: get-allocations, set-allocation, set-total-budget, etc. - shadertoy: set-shader-source, get-shader-info - wiki-explorer: expand-node, search-article, highlight-node, etc. All descriptions now mention 'Use list_widget_tools to discover available actions.'
…etails The server tool descriptions now just mention that widgets are interactive and can be controlled, without teaching the model about list_widget_tools (which is the client's responsibility to teach). Before: 'The widget exposes tools: X, Y, Z. Use list_widget_tools to discover...' After: 'The widget is interactive and exposes tools for X and Y.'
058c4ad to
c8ff8c1
Compare
| 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( |
|
Hey @connor4312, sorry for the slow reply!
On the host side, these tools are more like dynamic methods than functions. IMO the simplest way to support widget-specific tools is to give the model indirect tools such as
For native mobile sdks we'll definitely need to accept death of scrolled-out apps as inevitable.
Different sides of the same problem space: #125 (updateModelContext) is the App lazily pushing information to the model (which it might never pick up if there's no chat turn), while declaring tools from the App allows the model to proactively pull data from it. |
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 interaction:
Changes
App side (
app.ts)registerTool()- Register tools with Zod validationoncalltool/onlisttools- Handle tool requestssendToolListChanged()- Notify on tool updatesenable(),disable(),update(),remove()Host side (
app-bridge.ts)sendCallTool()- Call app toolssendListTools()- List app toolsListToolsResultSchemaCapabilities (
types.ts)tools: { listChanged?: boolean }serverTools: { listChanged?: boolean }(existing)Tests: ✓ 27 passing, 100% coverage
Design
Reuses standard MCP messages:
tools/call,tools/list,notifications/tools/list_changedSimilar 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