mcp-app-studio 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -0
- package/bin/mcp-app-studio.js +5 -0
- package/dist/bridge-BOSEqpaS.d.ts +66 -0
- package/dist/chunk-4LAH4JH6.js +47 -0
- package/dist/chunk-KRCGOYZ5.js +16 -0
- package/dist/chunk-L2RRNF7V.js +140 -0
- package/dist/chunk-QO43ZGJI.js +174 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +579 -0
- package/dist/core/index.d.ts +445 -0
- package/dist/core/index.js +16 -0
- package/dist/index.d.ts +508 -0
- package/dist/index.js +358 -0
- package/dist/platforms/chatgpt/index.d.ts +158 -0
- package/dist/platforms/chatgpt/index.js +149 -0
- package/dist/platforms/mcp/index.d.ts +37 -0
- package/dist/platforms/mcp/index.js +159 -0
- package/package.json +124 -0
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# MCP App Studio
|
|
2
|
+
|
|
3
|
+
**Build interactive apps for AI assistants (ChatGPT, Claude, MCP hosts).**
|
|
4
|
+
|
|
5
|
+
Create widgets that work across multiple platforms with a single codebase. The SDK auto-detects whether you're running in ChatGPT, Claude Desktop, or another MCP-compatible host.
|
|
6
|
+
|
|
7
|
+
## What You Get
|
|
8
|
+
|
|
9
|
+
- **Local workbench** — Preview widgets without deploying
|
|
10
|
+
- **Universal SDK** — Single API works on ChatGPT and MCP hosts
|
|
11
|
+
- **Platform detection** — Auto-adapts to the host environment
|
|
12
|
+
- **One-command export** — Generate production bundle + MCP server
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx mcp-app-studio my-app
|
|
18
|
+
cd my-app
|
|
19
|
+
npm install
|
|
20
|
+
npm run dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Open http://localhost:3000 — you're in the workbench.
|
|
24
|
+
|
|
25
|
+
## Universal SDK
|
|
26
|
+
|
|
27
|
+
The SDK provides React hooks that work identically across platforms:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import {
|
|
31
|
+
UniversalProvider,
|
|
32
|
+
usePlatform,
|
|
33
|
+
useToolInput,
|
|
34
|
+
useCallTool,
|
|
35
|
+
useTheme,
|
|
36
|
+
useFeature
|
|
37
|
+
} from "mcp-app-studio";
|
|
38
|
+
|
|
39
|
+
function MyWidget() {
|
|
40
|
+
const platform = usePlatform(); // "chatgpt" | "mcp" | "unknown"
|
|
41
|
+
const input = useToolInput<{ query: string }>();
|
|
42
|
+
const callTool = useCallTool();
|
|
43
|
+
const theme = useTheme();
|
|
44
|
+
|
|
45
|
+
// Platform-specific features
|
|
46
|
+
const hasWidgetState = useFeature('widgetState'); // ChatGPT only
|
|
47
|
+
const hasModelContext = useFeature('modelContext'); // MCP only
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className={theme === 'dark' ? 'bg-gray-900' : 'bg-white'}>
|
|
51
|
+
{/* Your widget */}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Wrap your app
|
|
57
|
+
function App() {
|
|
58
|
+
return (
|
|
59
|
+
<UniversalProvider>
|
|
60
|
+
<MyWidget />
|
|
61
|
+
</UniversalProvider>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Platform Capabilities
|
|
67
|
+
|
|
68
|
+
| Feature | ChatGPT | MCP |
|
|
69
|
+
|---------|---------|-----|
|
|
70
|
+
| `callTool` | ✅ | ✅ |
|
|
71
|
+
| `openLink` | ✅ | ✅ |
|
|
72
|
+
| `sendMessage` | ✅ | ✅ |
|
|
73
|
+
| `widgetState` (persistence) | ✅ | ❌ |
|
|
74
|
+
| `modelContext` (dynamic context) | ❌ | ✅ |
|
|
75
|
+
| `fileUpload` / `fileDownload` | ✅ | ❌ |
|
|
76
|
+
| `partialToolInput` (streaming) | ❌ | ✅ |
|
|
77
|
+
|
|
78
|
+
## Workflow
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
1. DEVELOP npm run dev Edit widgets, test with mock tools
|
|
82
|
+
2. EXPORT npm run export Generate widget bundle + manifest
|
|
83
|
+
3. DEPLOY Your choice Vercel, Netlify, any static host
|
|
84
|
+
4. REGISTER Platform dashboard Connect your app
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Generated Project
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
my-app/
|
|
91
|
+
├── app/ Next.js app
|
|
92
|
+
├── components/
|
|
93
|
+
│ └── examples/ Example widgets
|
|
94
|
+
├── lib/
|
|
95
|
+
│ ├── workbench/ Dev environment + React hooks
|
|
96
|
+
│ └── export/ Production bundler
|
|
97
|
+
└── server/ MCP server (if selected)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Export Output
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm run export
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Generates:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
export/
|
|
110
|
+
├── widget/
|
|
111
|
+
│ └── index.html Self-contained widget (deploy to static host)
|
|
112
|
+
├── manifest.json App manifest
|
|
113
|
+
└── README.md Deployment instructions
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Debugging
|
|
117
|
+
|
|
118
|
+
Enable debug mode to troubleshoot platform detection:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { enableDebugMode, detectPlatformDetailed } from "mcp-app-studio";
|
|
122
|
+
|
|
123
|
+
// In browser console or before app init
|
|
124
|
+
enableDebugMode();
|
|
125
|
+
|
|
126
|
+
// Get detailed detection info
|
|
127
|
+
const result = detectPlatformDetailed();
|
|
128
|
+
console.log('Platform:', result.platform);
|
|
129
|
+
console.log('Detected by:', result.detectedBy);
|
|
130
|
+
console.log('Checks:', result.checks);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## MCP Server
|
|
134
|
+
|
|
135
|
+
If you selected "Include MCP server" during setup:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
cd server
|
|
139
|
+
npm install
|
|
140
|
+
npm run dev # http://localhost:3001/mcp
|
|
141
|
+
npm run inspect # Test with MCP Inspector
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Learn More
|
|
145
|
+
|
|
146
|
+
- [MCP Apps Specification](https://modelcontextprotocol.io/specification/)
|
|
147
|
+
- [ChatGPT Apps SDK](https://developers.openai.com/apps-sdk/)
|
|
148
|
+
- [assistant-ui](https://www.assistant-ui.com/)
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { McpUiHostCapabilities, App } from '@modelcontextprotocol/ext-apps';
|
|
2
|
+
import { DisplayMode, ExtendedBridge, HostCapabilities, HostContext, ToolInputCallback, ToolInputPartialCallback, ToolResultCallback, ToolCancelledCallback, HostContextChangedCallback, TeardownCallback, ToolResult, ChatMessage, ContentBlock } from './core/index.js';
|
|
3
|
+
|
|
4
|
+
interface AppCapabilities {
|
|
5
|
+
tools?: {
|
|
6
|
+
listChanged?: boolean;
|
|
7
|
+
};
|
|
8
|
+
resources?: {
|
|
9
|
+
listChanged?: boolean;
|
|
10
|
+
};
|
|
11
|
+
prompts?: {
|
|
12
|
+
listChanged?: boolean;
|
|
13
|
+
};
|
|
14
|
+
displayMode?: {
|
|
15
|
+
supported: DisplayMode[];
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
interface MCPBridgeOptions {
|
|
19
|
+
autoResize?: boolean;
|
|
20
|
+
}
|
|
21
|
+
type CallToolHandler = (name: string, args: Record<string, unknown>, extra: unknown) => Promise<ToolResult>;
|
|
22
|
+
type ListToolsHandler = (cursor?: string) => Promise<string[]>;
|
|
23
|
+
declare class MCPBridge implements ExtendedBridge {
|
|
24
|
+
readonly platform: "mcp";
|
|
25
|
+
readonly capabilities: HostCapabilities;
|
|
26
|
+
private app;
|
|
27
|
+
private toolInputCallbacks;
|
|
28
|
+
private toolInputPartialCallbacks;
|
|
29
|
+
private toolResultCallbacks;
|
|
30
|
+
private toolCancelledCallbacks;
|
|
31
|
+
private contextCallbacks;
|
|
32
|
+
private teardownCallbacks;
|
|
33
|
+
constructor(appInfo?: {
|
|
34
|
+
name: string;
|
|
35
|
+
version: string;
|
|
36
|
+
}, appCapabilities?: AppCapabilities, options?: MCPBridgeOptions);
|
|
37
|
+
connect(): Promise<void>;
|
|
38
|
+
getHostContext(): HostContext | null;
|
|
39
|
+
private mapHostContext;
|
|
40
|
+
onToolInput(callback: ToolInputCallback): () => void;
|
|
41
|
+
onToolInputPartial(callback: ToolInputPartialCallback): () => void;
|
|
42
|
+
onToolResult(callback: ToolResultCallback): () => void;
|
|
43
|
+
onToolCancelled(callback: ToolCancelledCallback): () => void;
|
|
44
|
+
onHostContextChanged(callback: HostContextChangedCallback): () => void;
|
|
45
|
+
onTeardown(callback: TeardownCallback): () => void;
|
|
46
|
+
callTool(name: string, args: Record<string, unknown>): Promise<ToolResult>;
|
|
47
|
+
openLink(url: string): Promise<void>;
|
|
48
|
+
requestDisplayMode(mode: DisplayMode): Promise<DisplayMode>;
|
|
49
|
+
sendSizeChanged(size: {
|
|
50
|
+
width?: number;
|
|
51
|
+
height?: number;
|
|
52
|
+
}): void;
|
|
53
|
+
sendMessage(message: ChatMessage): Promise<void>;
|
|
54
|
+
updateModelContext(ctx: {
|
|
55
|
+
content?: ContentBlock[];
|
|
56
|
+
structuredContent?: Record<string, unknown>;
|
|
57
|
+
}): Promise<void>;
|
|
58
|
+
sendLog(level: "debug" | "info" | "notice" | "warning" | "error" | "critical" | "alert" | "emergency", data: string, logger?: string): void;
|
|
59
|
+
setCallToolHandler(handler: CallToolHandler): void;
|
|
60
|
+
setListToolsHandler(handler: ListToolsHandler): void;
|
|
61
|
+
getHostCapabilities(): McpUiHostCapabilities | undefined;
|
|
62
|
+
setupSizeChangedNotifications(): () => void;
|
|
63
|
+
getApp(): App;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { type AppCapabilities as A, MCPBridge as M, type MCPBridgeOptions as a };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/core/capabilities.ts
|
|
2
|
+
var CHATGPT_CAPABILITIES = {
|
|
3
|
+
platform: "chatgpt",
|
|
4
|
+
callTool: true,
|
|
5
|
+
openLink: true,
|
|
6
|
+
displayModes: ["pip", "inline", "fullscreen"],
|
|
7
|
+
sizeReporting: true,
|
|
8
|
+
closeWidget: true,
|
|
9
|
+
sendMessage: true,
|
|
10
|
+
modal: true,
|
|
11
|
+
fileUpload: true,
|
|
12
|
+
fileDownload: true,
|
|
13
|
+
widgetState: true,
|
|
14
|
+
modelContext: false,
|
|
15
|
+
logging: false,
|
|
16
|
+
partialToolInput: false,
|
|
17
|
+
toolCancellation: false,
|
|
18
|
+
teardown: false
|
|
19
|
+
};
|
|
20
|
+
var MCP_CAPABILITIES = {
|
|
21
|
+
platform: "mcp",
|
|
22
|
+
callTool: true,
|
|
23
|
+
openLink: true,
|
|
24
|
+
displayModes: ["inline", "fullscreen", "pip"],
|
|
25
|
+
sizeReporting: true,
|
|
26
|
+
closeWidget: false,
|
|
27
|
+
sendMessage: true,
|
|
28
|
+
modal: false,
|
|
29
|
+
fileUpload: false,
|
|
30
|
+
fileDownload: false,
|
|
31
|
+
widgetState: false,
|
|
32
|
+
modelContext: true,
|
|
33
|
+
logging: true,
|
|
34
|
+
partialToolInput: true,
|
|
35
|
+
toolCancellation: true,
|
|
36
|
+
teardown: true
|
|
37
|
+
};
|
|
38
|
+
function hasFeature(capabilities, feature) {
|
|
39
|
+
if (!capabilities) return false;
|
|
40
|
+
return capabilities[feature] === true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
CHATGPT_CAPABILITIES,
|
|
45
|
+
MCP_CAPABILITIES,
|
|
46
|
+
hasFeature
|
|
47
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/core/types.ts
|
|
2
|
+
function textBlock(text, annotations) {
|
|
3
|
+
const block = { type: "text", text };
|
|
4
|
+
if (annotations) block.annotations = annotations;
|
|
5
|
+
return block;
|
|
6
|
+
}
|
|
7
|
+
function imageBlock(data, mimeType, annotations) {
|
|
8
|
+
const block = { type: "image", data, mimeType };
|
|
9
|
+
if (annotations) block.annotations = annotations;
|
|
10
|
+
return block;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
textBlock,
|
|
15
|
+
imageBlock
|
|
16
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHATGPT_CAPABILITIES
|
|
3
|
+
} from "./chunk-4LAH4JH6.js";
|
|
4
|
+
|
|
5
|
+
// src/platforms/chatgpt/bridge.ts
|
|
6
|
+
var ChatGPTBridge = class {
|
|
7
|
+
platform = "chatgpt";
|
|
8
|
+
capabilities = CHATGPT_CAPABILITIES;
|
|
9
|
+
toolInputCallbacks = /* @__PURE__ */ new Set();
|
|
10
|
+
toolResultCallbacks = /* @__PURE__ */ new Set();
|
|
11
|
+
contextCallbacks = /* @__PURE__ */ new Set();
|
|
12
|
+
lastContext = null;
|
|
13
|
+
connected = false;
|
|
14
|
+
get openai() {
|
|
15
|
+
if (!window.openai) {
|
|
16
|
+
throw new Error("ChatGPT bridge not available");
|
|
17
|
+
}
|
|
18
|
+
return window.openai;
|
|
19
|
+
}
|
|
20
|
+
async connect() {
|
|
21
|
+
if (!window.openai) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"ChatGPT bridge not available. Is this running inside ChatGPT?"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
window.addEventListener("openai:set_globals", this.handleGlobalsChange);
|
|
27
|
+
this.lastContext = this.buildHostContext();
|
|
28
|
+
if (this.openai.toolInput) {
|
|
29
|
+
this.toolInputCallbacks.forEach((cb) => cb(this.openai.toolInput));
|
|
30
|
+
}
|
|
31
|
+
if (this.openai.toolOutput) {
|
|
32
|
+
this.toolResultCallbacks.forEach(
|
|
33
|
+
(cb) => cb({
|
|
34
|
+
structuredContent: this.openai.toolOutput
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
this.connected = true;
|
|
39
|
+
}
|
|
40
|
+
buildHostContext() {
|
|
41
|
+
const g = this.openai;
|
|
42
|
+
return {
|
|
43
|
+
theme: g.theme,
|
|
44
|
+
locale: g.locale,
|
|
45
|
+
displayMode: g.displayMode,
|
|
46
|
+
availableDisplayModes: ["pip", "inline", "fullscreen"],
|
|
47
|
+
containerDimensions: { maxHeight: g.maxHeight },
|
|
48
|
+
platform: this.mapDeviceType(g.userAgent?.device?.type),
|
|
49
|
+
deviceCapabilities: g.userAgent?.capabilities,
|
|
50
|
+
safeAreaInsets: g.safeArea?.insets,
|
|
51
|
+
userAgent: "ChatGPT"
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
mapDeviceType(type) {
|
|
55
|
+
if (type === "mobile" || type === "tablet") return "mobile";
|
|
56
|
+
return "web";
|
|
57
|
+
}
|
|
58
|
+
handleGlobalsChange = () => {
|
|
59
|
+
const newContext = this.buildHostContext();
|
|
60
|
+
if (JSON.stringify(newContext) !== JSON.stringify(this.lastContext)) {
|
|
61
|
+
this.lastContext = newContext;
|
|
62
|
+
this.contextCallbacks.forEach((cb) => cb(newContext));
|
|
63
|
+
}
|
|
64
|
+
if (this.openai.toolInput) {
|
|
65
|
+
this.toolInputCallbacks.forEach((cb) => cb(this.openai.toolInput));
|
|
66
|
+
}
|
|
67
|
+
if (this.openai.toolOutput) {
|
|
68
|
+
const result = {
|
|
69
|
+
structuredContent: this.openai.toolOutput
|
|
70
|
+
};
|
|
71
|
+
if (this.openai.toolResponseMetadata) {
|
|
72
|
+
result._meta = this.openai.toolResponseMetadata;
|
|
73
|
+
}
|
|
74
|
+
this.toolResultCallbacks.forEach((cb) => cb(result));
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
getHostContext() {
|
|
78
|
+
return this.lastContext;
|
|
79
|
+
}
|
|
80
|
+
onToolInput(callback) {
|
|
81
|
+
this.toolInputCallbacks.add(callback);
|
|
82
|
+
if (this.connected && this.openai.toolInput) {
|
|
83
|
+
callback(this.openai.toolInput);
|
|
84
|
+
}
|
|
85
|
+
return () => this.toolInputCallbacks.delete(callback);
|
|
86
|
+
}
|
|
87
|
+
onToolResult(callback) {
|
|
88
|
+
this.toolResultCallbacks.add(callback);
|
|
89
|
+
if (this.connected && this.openai.toolOutput) {
|
|
90
|
+
callback({ structuredContent: this.openai.toolOutput });
|
|
91
|
+
}
|
|
92
|
+
return () => this.toolResultCallbacks.delete(callback);
|
|
93
|
+
}
|
|
94
|
+
onHostContextChanged(callback) {
|
|
95
|
+
this.contextCallbacks.add(callback);
|
|
96
|
+
return () => this.contextCallbacks.delete(callback);
|
|
97
|
+
}
|
|
98
|
+
async callTool(name, args) {
|
|
99
|
+
const result = await this.openai.callTool(name, args);
|
|
100
|
+
return { structuredContent: result };
|
|
101
|
+
}
|
|
102
|
+
async openLink(url) {
|
|
103
|
+
this.openai.openExternal({ href: url });
|
|
104
|
+
}
|
|
105
|
+
async requestDisplayMode(mode) {
|
|
106
|
+
const result = await this.openai.requestDisplayMode({ mode });
|
|
107
|
+
return result.mode;
|
|
108
|
+
}
|
|
109
|
+
sendSizeChanged(size) {
|
|
110
|
+
if (size.height != null) {
|
|
111
|
+
this.openai.notifyIntrinsicHeight(size.height);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async sendMessage(message) {
|
|
115
|
+
const text = message.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
|
|
116
|
+
await this.openai.sendFollowUpMessage({ prompt: text });
|
|
117
|
+
}
|
|
118
|
+
setWidgetState(state) {
|
|
119
|
+
this.openai.setWidgetState(state);
|
|
120
|
+
}
|
|
121
|
+
getWidgetState() {
|
|
122
|
+
return this.openai.widgetState;
|
|
123
|
+
}
|
|
124
|
+
async uploadFile(file) {
|
|
125
|
+
return this.openai.uploadFile(file);
|
|
126
|
+
}
|
|
127
|
+
async getFileDownloadUrl(fileId) {
|
|
128
|
+
return this.openai.getFileDownloadUrl({ fileId });
|
|
129
|
+
}
|
|
130
|
+
requestClose() {
|
|
131
|
+
this.openai.requestClose();
|
|
132
|
+
}
|
|
133
|
+
async requestModal(options) {
|
|
134
|
+
await this.openai.requestModal(options);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export {
|
|
139
|
+
ChatGPTBridge
|
|
140
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MCP_CAPABILITIES
|
|
3
|
+
} from "./chunk-4LAH4JH6.js";
|
|
4
|
+
|
|
5
|
+
// src/platforms/mcp/bridge.ts
|
|
6
|
+
import { App } from "@modelcontextprotocol/ext-apps";
|
|
7
|
+
var MCPBridge = class {
|
|
8
|
+
platform = "mcp";
|
|
9
|
+
capabilities = MCP_CAPABILITIES;
|
|
10
|
+
app;
|
|
11
|
+
toolInputCallbacks = /* @__PURE__ */ new Set();
|
|
12
|
+
toolInputPartialCallbacks = /* @__PURE__ */ new Set();
|
|
13
|
+
toolResultCallbacks = /* @__PURE__ */ new Set();
|
|
14
|
+
toolCancelledCallbacks = /* @__PURE__ */ new Set();
|
|
15
|
+
contextCallbacks = /* @__PURE__ */ new Set();
|
|
16
|
+
teardownCallbacks = /* @__PURE__ */ new Set();
|
|
17
|
+
constructor(appInfo, appCapabilities, options) {
|
|
18
|
+
const autoResize = options?.autoResize ?? true;
|
|
19
|
+
this.app = new App(
|
|
20
|
+
appInfo ?? { name: "MCP App", version: "1.0.0" },
|
|
21
|
+
appCapabilities ?? {},
|
|
22
|
+
{ autoResize }
|
|
23
|
+
);
|
|
24
|
+
this.app.ontoolinput = (params) => {
|
|
25
|
+
this.toolInputCallbacks.forEach(
|
|
26
|
+
(cb) => cb(params.arguments)
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
this.app.ontoolinputpartial = (params) => {
|
|
30
|
+
this.toolInputPartialCallbacks.forEach(
|
|
31
|
+
(cb) => cb(params.arguments)
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
this.app.ontoolresult = (params) => {
|
|
35
|
+
const result = {
|
|
36
|
+
content: params.content,
|
|
37
|
+
structuredContent: params.structuredContent
|
|
38
|
+
};
|
|
39
|
+
if (params.isError !== void 0) {
|
|
40
|
+
result.isError = params.isError;
|
|
41
|
+
}
|
|
42
|
+
if (params._meta) {
|
|
43
|
+
result._meta = params._meta;
|
|
44
|
+
}
|
|
45
|
+
this.toolResultCallbacks.forEach((cb) => cb(result));
|
|
46
|
+
};
|
|
47
|
+
this.app.ontoolcancelled = (params) => {
|
|
48
|
+
this.toolCancelledCallbacks.forEach((cb) => cb(params.reason));
|
|
49
|
+
};
|
|
50
|
+
this.app.onhostcontextchanged = (params) => {
|
|
51
|
+
const ctx = this.mapHostContext(params);
|
|
52
|
+
this.contextCallbacks.forEach((cb) => cb(ctx));
|
|
53
|
+
};
|
|
54
|
+
this.app.onteardown = async () => {
|
|
55
|
+
for (const cb of this.teardownCallbacks) {
|
|
56
|
+
await cb();
|
|
57
|
+
}
|
|
58
|
+
return {};
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async connect() {
|
|
62
|
+
await this.app.connect();
|
|
63
|
+
}
|
|
64
|
+
getHostContext() {
|
|
65
|
+
const ctx = this.app.getHostContext();
|
|
66
|
+
return ctx ? this.mapHostContext(ctx) : null;
|
|
67
|
+
}
|
|
68
|
+
mapHostContext(ctx) {
|
|
69
|
+
return ctx;
|
|
70
|
+
}
|
|
71
|
+
onToolInput(callback) {
|
|
72
|
+
this.toolInputCallbacks.add(callback);
|
|
73
|
+
return () => this.toolInputCallbacks.delete(callback);
|
|
74
|
+
}
|
|
75
|
+
onToolInputPartial(callback) {
|
|
76
|
+
this.toolInputPartialCallbacks.add(callback);
|
|
77
|
+
return () => this.toolInputPartialCallbacks.delete(callback);
|
|
78
|
+
}
|
|
79
|
+
onToolResult(callback) {
|
|
80
|
+
this.toolResultCallbacks.add(callback);
|
|
81
|
+
return () => this.toolResultCallbacks.delete(callback);
|
|
82
|
+
}
|
|
83
|
+
onToolCancelled(callback) {
|
|
84
|
+
this.toolCancelledCallbacks.add(callback);
|
|
85
|
+
return () => this.toolCancelledCallbacks.delete(callback);
|
|
86
|
+
}
|
|
87
|
+
onHostContextChanged(callback) {
|
|
88
|
+
this.contextCallbacks.add(callback);
|
|
89
|
+
return () => this.contextCallbacks.delete(callback);
|
|
90
|
+
}
|
|
91
|
+
onTeardown(callback) {
|
|
92
|
+
this.teardownCallbacks.add(callback);
|
|
93
|
+
return () => this.teardownCallbacks.delete(callback);
|
|
94
|
+
}
|
|
95
|
+
async callTool(name, args) {
|
|
96
|
+
const result = await this.app.callServerTool({ name, arguments: args });
|
|
97
|
+
const toolResult = {
|
|
98
|
+
content: result.content,
|
|
99
|
+
structuredContent: result.structuredContent
|
|
100
|
+
};
|
|
101
|
+
if (result.isError !== void 0) {
|
|
102
|
+
toolResult.isError = result.isError;
|
|
103
|
+
}
|
|
104
|
+
return toolResult;
|
|
105
|
+
}
|
|
106
|
+
async openLink(url) {
|
|
107
|
+
await this.app.openLink({ url });
|
|
108
|
+
}
|
|
109
|
+
async requestDisplayMode(mode) {
|
|
110
|
+
const result = await this.app.requestDisplayMode({ mode });
|
|
111
|
+
return result.mode;
|
|
112
|
+
}
|
|
113
|
+
sendSizeChanged(size) {
|
|
114
|
+
this.app.sendSizeChanged(size);
|
|
115
|
+
}
|
|
116
|
+
async sendMessage(message) {
|
|
117
|
+
await this.app.sendMessage({
|
|
118
|
+
role: message.role,
|
|
119
|
+
content: message.content.map((c) => {
|
|
120
|
+
if (c.type === "text") {
|
|
121
|
+
return { type: "text", text: c.text };
|
|
122
|
+
}
|
|
123
|
+
return c;
|
|
124
|
+
})
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async updateModelContext(ctx) {
|
|
128
|
+
await this.app.updateModelContext(ctx);
|
|
129
|
+
}
|
|
130
|
+
sendLog(level, data, logger) {
|
|
131
|
+
this.app.sendLog({ level, data, logger });
|
|
132
|
+
}
|
|
133
|
+
setCallToolHandler(handler) {
|
|
134
|
+
this.app.oncalltool = async (params, extra) => {
|
|
135
|
+
const result = await handler(params.name, params.arguments ?? {}, extra);
|
|
136
|
+
const content = result.content?.map((c) => {
|
|
137
|
+
if (c.type === "text") {
|
|
138
|
+
return { type: "text", text: c.text };
|
|
139
|
+
}
|
|
140
|
+
if (c.type === "image" || c.type === "audio") {
|
|
141
|
+
return { type: c.type, data: c.data, mimeType: c.mimeType };
|
|
142
|
+
}
|
|
143
|
+
return c;
|
|
144
|
+
}) ?? [];
|
|
145
|
+
const response = { content };
|
|
146
|
+
if (result.structuredContent !== void 0) {
|
|
147
|
+
response.structuredContent = result.structuredContent;
|
|
148
|
+
}
|
|
149
|
+
if (result.isError !== void 0) {
|
|
150
|
+
response.isError = result.isError;
|
|
151
|
+
}
|
|
152
|
+
return response;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
setListToolsHandler(handler) {
|
|
156
|
+
this.app.onlisttools = async (params) => {
|
|
157
|
+
const tools = await handler(params?.cursor);
|
|
158
|
+
return { tools };
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
getHostCapabilities() {
|
|
162
|
+
return this.app.getHostCapabilities();
|
|
163
|
+
}
|
|
164
|
+
setupSizeChangedNotifications() {
|
|
165
|
+
return this.app.setupSizeChangedNotifications();
|
|
166
|
+
}
|
|
167
|
+
getApp() {
|
|
168
|
+
return this.app;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export {
|
|
173
|
+
MCPBridge
|
|
174
|
+
};
|