Add App <-> AppBridge tests, setHostContext & sendToolInputPartial (#55)
* Add App <-> AppBridge integration tests - Add minimalistic tests validating communication between App (guest) and AppBridge (host) - Test initialization handshake flow - Test Host -> App notifications (toolInput, toolInputPartial, toolResult, hostContext) - Test App -> Host notifications (sizeChange, log) - Test App -> Host requests (sendMessage, sendOpenLink) - Test ping functionality - Add setHostContext and sendToolInputPartial to AppBridge - Add test script to package.json 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: add JSDoc for setHostContext and sendToolInputPartial 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: add @types/bun for test type checking - Add @types/bun devDependency for bun:test types - Fix mock client request return type 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a7d73c72e0
commit
cf4492df89
1 changed files with 406 additions and 9 deletions
|
|
@ -12,18 +12,18 @@ import {
|
|||
ListResourcesResultSchema,
|
||||
ListResourceTemplatesRequestSchema,
|
||||
ListResourceTemplatesResultSchema,
|
||||
ReadResourceRequestSchema,
|
||||
ReadResourceResultSchema,
|
||||
LoggingMessageNotification,
|
||||
LoggingMessageNotificationSchema,
|
||||
Notification,
|
||||
PingRequest,
|
||||
PingRequestSchema,
|
||||
PromptListChangedNotificationSchema,
|
||||
ReadResourceRequestSchema,
|
||||
ReadResourceResultSchema,
|
||||
Request,
|
||||
ResourceListChangedNotificationSchema,
|
||||
Result,
|
||||
ToolListChangedNotificationSchema,
|
||||
Request,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import {
|
||||
Protocol,
|
||||
|
|
@ -32,13 +32,16 @@ import {
|
|||
} from "@modelcontextprotocol/sdk/shared/protocol.js";
|
||||
|
||||
import {
|
||||
type McpUiToolInputNotification,
|
||||
type McpUiToolResultNotification,
|
||||
type McpUiSandboxResourceReadyNotification,
|
||||
type McpUiSizeChangeNotification,
|
||||
type McpUiToolInputNotification,
|
||||
type McpUiToolInputPartialNotification,
|
||||
type McpUiToolResultNotification,
|
||||
LATEST_PROTOCOL_VERSION,
|
||||
McpUiAppCapabilities,
|
||||
McpUiHostCapabilities,
|
||||
McpUiHostContext,
|
||||
McpUiHostContextChangedNotification,
|
||||
McpUiInitializedNotification,
|
||||
McpUiInitializedNotificationSchema,
|
||||
McpUiInitializeRequest,
|
||||
|
|
@ -64,7 +67,9 @@ export { PostMessageTransport } from "./message-transport";
|
|||
*
|
||||
* @see ProtocolOptions from @modelcontextprotocol/sdk for available options
|
||||
*/
|
||||
export type HostOptions = ProtocolOptions;
|
||||
export type HostOptions = ProtocolOptions & {
|
||||
hostContext?: McpUiHostContext;
|
||||
};
|
||||
|
||||
/**
|
||||
* Protocol versions supported by this AppBridge implementation.
|
||||
|
|
@ -149,6 +154,7 @@ type RequestHandlerExtra = Parameters<
|
|||
export class AppBridge extends Protocol<Request, Notification, Result> {
|
||||
private _appCapabilities?: McpUiAppCapabilities;
|
||||
private _appInfo?: Implementation;
|
||||
private _hostContext: McpUiHostContext;
|
||||
|
||||
/**
|
||||
* Create a new AppBridge instance.
|
||||
|
|
@ -175,6 +181,8 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
) {
|
||||
super(options);
|
||||
|
||||
this._hostContext = options?.hostContext || {};
|
||||
|
||||
this.setRequestHandler(McpUiInitializeRequestSchema, (request) =>
|
||||
this._oninitialize(request),
|
||||
);
|
||||
|
|
@ -550,12 +558,64 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
protocolVersion,
|
||||
hostCapabilities: this.getCapabilities(),
|
||||
hostInfo: this._hostInfo,
|
||||
hostContext: {
|
||||
// TODO
|
||||
},
|
||||
hostContext: this._hostContext,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the host context and notify the Guest UI of changes.
|
||||
*
|
||||
* Compares the new context with the current context and sends a
|
||||
* `ui/notifications/host-context-changed` notification containing only the
|
||||
* fields that have changed. If no fields have changed, no notification is sent.
|
||||
*
|
||||
* Common use cases include notifying the Guest UI when:
|
||||
* - Theme changes (light/dark mode toggle)
|
||||
* - Viewport size changes (window resize)
|
||||
* - Display mode changes (inline/fullscreen)
|
||||
* - Locale or timezone changes
|
||||
*
|
||||
* @param hostContext - The complete new host context state
|
||||
*
|
||||
* @example Update theme when user toggles dark mode
|
||||
* ```typescript
|
||||
* bridge.setHostContext({ theme: "dark" });
|
||||
* ```
|
||||
*
|
||||
* @example Update multiple context fields
|
||||
* ```typescript
|
||||
* bridge.setHostContext({
|
||||
* theme: "dark",
|
||||
* viewport: { width: 800, height: 600 }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @see {@link McpUiHostContext} for the context structure
|
||||
* @see {@link McpUiHostContextChangedNotification} for the notification type
|
||||
*/
|
||||
setHostContext(hostContext: McpUiHostContext) {
|
||||
const changes: McpUiHostContext = {};
|
||||
let hasChanges = false;
|
||||
for (const key of Object.keys(hostContext) as Array<
|
||||
keyof McpUiHostContext
|
||||
>) {
|
||||
const oldValue = this._hostContext[key];
|
||||
const newValue = hostContext[key];
|
||||
if (deepEqual(oldValue, newValue)) {
|
||||
continue;
|
||||
}
|
||||
changes[key] = newValue as any;
|
||||
hasChanges = true;
|
||||
}
|
||||
if (hasChanges) {
|
||||
this._hostContext = hostContext;
|
||||
this.notification((<McpUiHostContextChangedNotification>{
|
||||
method: "ui/notifications/host-context-changed",
|
||||
params: changes,
|
||||
}) as Notification); // Cast needed because McpUiHostContext is a params type that doesn't allow arbitrary keys.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send complete tool arguments to the Guest UI.
|
||||
*
|
||||
|
|
@ -585,6 +645,40 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send streaming partial tool arguments to the Guest UI.
|
||||
*
|
||||
* The host MAY send this notification zero or more times while tool arguments
|
||||
* are being streamed, before {@link sendToolInput} is called with complete
|
||||
* arguments. This enables progressive rendering of tool arguments in the
|
||||
* Guest UI.
|
||||
*
|
||||
* The arguments represent best-effort recovery of incomplete JSON. Guest UIs
|
||||
* SHOULD handle missing or changing fields gracefully between notifications.
|
||||
*
|
||||
* @param params - Partial tool call arguments (may be incomplete)
|
||||
*
|
||||
* @example Stream partial arguments as they arrive
|
||||
* ```typescript
|
||||
* // As streaming progresses...
|
||||
* bridge.sendToolInputPartial({ arguments: { loc: "N" } });
|
||||
* bridge.sendToolInputPartial({ arguments: { location: "New" } });
|
||||
* bridge.sendToolInputPartial({ arguments: { location: "New York" } });
|
||||
*
|
||||
* // When complete, send final input
|
||||
* bridge.sendToolInput({ arguments: { location: "New York", units: "metric" } });
|
||||
* ```
|
||||
*
|
||||
* @see {@link McpUiToolInputPartialNotification} for the notification type
|
||||
* @see {@link sendToolInput} for sending complete arguments
|
||||
*/
|
||||
sendToolInputPartial(params: McpUiToolInputPartialNotification["params"]) {
|
||||
return this.notification(<McpUiToolInputPartialNotification>{
|
||||
method: "ui/notifications/tool-input-partial",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send tool execution result to the Guest UI.
|
||||
*
|
||||
|
|
@ -780,3 +874,7 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
return super.connect(transport);
|
||||
}
|
||||
}
|
||||
|
||||
function deepEqual(a: any, b: any): boolean {
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue