Skip to content

Conversation

@ochafik
Copy link
Collaborator

@ochafik ochafik commented Dec 12, 2025

Summary

Key change: AppBridge's client constructor parameter is now optional (Client | null). When a client is provided, connect() automatically forwards MCP requests/notifications between the Guest UI and the server. When null is passed, you must register handlers manually using the new setters.

This enables hosts like mcp-ui to use AppBridge without a direct MCP client reference, registering custom handlers instead.

Changes

  • Make AppBridge's client constructor parameter optional (Client | null) - hosts can now create an AppBridge without an MCP client
  • Add setter-based handlers for Guest UI → Host requests:
    • oncalltool - handles tools/call requests
    • onlistresources - handles resources/list requests
    • onlistresourcetemplates - handles resources/templates/list requests
    • onreadresource - handles resources/read requests
    • onlistprompts - handles prompts/list requests
  • Add send methods for Host → Guest UI notifications:
    • sendToolListChanged() - forwards tool list changes
    • sendResourceListChanged() - forwards resource list changes
    • sendPromptListChanged() - forwards prompt list changes
  • Refactor connect() to use the new setters with inlined callbacks when a client is provided
  • Remove unused forwardRequest/forwardNotification helper methods
  • Add comprehensive tests for the null client case

Type Safety Improvements

  • New explicit protocol types: Created AppRequest, AppNotification, AppResult type unions that document exactly what types are valid in the MCP Apps protocol
  • Shared types: Both App and AppBridge now extend Protocol<AppRequest, AppNotification, AppResult> instead of using the generic SDK types
  • No more type casts: All notification/request calls now use as const on method literals, eliminating the need for as Notification casts
  • Forward compatibility: Added index signature to McpUiHostContext to allow unknown properties

Documentation & Test Improvements

  • TypeDoc: Fixed callback parameter docs to use proper @param callback.params / @param callback.extra syntax
  • Tests: Factored out test value objects and added missing assertions for better coverage and readability

Usage

With MCP client (automatic forwarding):

const bridge = new AppBridge(mcpClient, hostInfo, capabilities);
await bridge.connect(transport);
// Requests are automatically forwarded to the MCP server

Without MCP client (manual handlers):

const bridge = new AppBridge(null, hostInfo, capabilities);

bridge.oncalltool = async (params, extra) => {
  // Custom tool call handling
  return { content: [{ type: "text", text: "result" }] };
};

await bridge.connect(transport);

Test plan

  • Build passes
  • All existing tests pass
  • New tests for manual handler registration pass (9 new tests)
  • TypeScript strict mode passes with new type unions

🤖 Generated with Claude Code

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 12, 2025

Open in StackBlitz

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

commit: 08540f2

@ochafik ochafik force-pushed the ochafik/app-bridge-setters branch from 71bb196 to 47258b8 Compare December 12, 2025 17:13
@ochafik ochafik marked this pull request as ready for review December 12, 2025 17:36
@ochafik ochafik changed the title Add setter-based handlers for MCP request/notification forwarding app-bridge: make Client optional (for forwarding), allow custom handling Dec 12, 2025
jonathanhefner
jonathanhefner previously approved these changes Dec 12, 2025
Copy link
Member

@jonathanhefner jonathanhefner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I think this looks good. 👍

Comment on lines 588 to 591
expect(receivedRequests).toHaveLength(1);
expect(result.resources).toEqual([
{ uri: "test://resource", name: "Test" },
]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing an assertion on receivedRequests[0] here, but that's probably fine.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

Comment on lines 635 to 638
expect(receivedRequests).toHaveLength(1);
expect(result.resourceTemplates).toEqual([
{ uriTemplate: "test://{id}", name: "Test Template" },
]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing an assertion on receivedRequests[0] here, but that's probably fine.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

Comment on lines 656 to 657
expect(receivedRequests).toHaveLength(1);
expect(result.prompts).toEqual([{ name: "test-prompt" }]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

Comment on lines 537 to 541
* @param callback - Handler that receives tool call params and returns a result
* - params.name - Name of the tool to call
* - params.arguments - Tool arguments
* - extra - Request metadata (abort signal, session info)
* - Returns: Promise<CallToolResult> with tool execution result
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like this to be better formatted, but I'm not sure of the proper TypeDoc syntax for it. Not a blocker — I can look into it later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! Used proper TypeDoc syntax with @param callback.params and @param callback.extra.

Comment on lines 601 to 604
* @param callback - Handler that receives list params and returns resources
* - params - Request params (may include cursor for pagination)
* - extra - Request metadata (abort signal, session info)
* - Returns: Promise<ListResourcesResult> with available resources
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise for this @param callback. (Not a blocker.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment on lines 641 to 644
* @param callback - Handler that receives list params and returns templates
* - params - Request params (may include cursor for pagination)
* - extra - Request metadata (abort signal, session info)
* - Returns: Promise<ListResourceTemplatesResult> with available templates
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto. (Not a blocker.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@ochafik ochafik changed the title app-bridge: make Client optional (for forwarding), allow custom handling api: optional Client in AppBridge (allow custom forwarding), better protocol types (AppRequest, AppNotification, AppResult) Dec 13, 2025
ochafik and others added 8 commits December 13, 2025 11:48
- Add oncalltool, onlistresources, onlistresourcetemplates, onreadresource, onlistprompts setters for Guest UI → Host requests
- Add sendToolListChanged, sendResourceListChanged, sendPromptListChanged methods for Host → Guest UI notifications
- Refactor connect() to use new setters with inlined callbacks
- Update documentation to clarify optional client parameter behavior
- Remove unused forwardRequest/forwardNotification helper methods
- Add comprehensive tests for AppBridge without MCP client (manual handlers)

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

Co-Authored-By: Claude <[email protected]>
…tion or class'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Address PR review feedback:
- Factor out test value objects (toolCall, resultContent, etc.) for clearer tests
- Add missing assertions on receivedRequests[0] for list handlers
- Improve test readability by defining expected values at the top of each test

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

Co-Authored-By: Claude <[email protected]>
…methods

- Use proper TypeDoc syntax with `@param callback.params` and `@param callback.extra`
- Add {@link ResultType} references for return types
- Replace `as Notification` with `as const` on method literals where possible
  (sendResourceListChanged, sendPromptListChanged)
- Simplify sendHostContextChange cast syntax

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

Co-Authored-By: Claude <[email protected]>
- Add AppBridgeRequest, AppBridgeNotification, AppBridgeResult type unions
  that explicitly list all valid types for the protocol
- Replace generic SDK Request/Notification/Result with our specific types
- Remove all type casts by using `as const` on method literals
- Add index signature to McpUiHostContext for forward compatibility

This improves type safety and documents exactly what the AppBridge supports.

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

Co-Authored-By: Claude <[email protected]>
@ochafik ochafik force-pushed the ochafik/app-bridge-setters branch from 9a7461f to 16fb2e4 Compare December 13, 2025 11:49
Move protocol type unions from app-bridge.ts to types.ts where they
belong with other type definitions.

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

Co-Authored-By: Claude <[email protected]>
@ochafik ochafik force-pushed the ochafik/app-bridge-setters branch from 021dab8 to 4653156 Compare December 13, 2025 11:53
@ochafik ochafik merged commit 5434455 into main Dec 13, 2025
8 of 9 checks passed
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