@leeoohoo/ui-apps-devkit 0.1.8 → 0.1.9
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/package.json +1 -1
- package/src/sandbox/server.js +60 -2
package/package.json
CHANGED
package/src/sandbox/server.js
CHANGED
|
@@ -8,6 +8,8 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
|
8
8
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
9
9
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
10
10
|
import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js';
|
|
11
|
+
import { LoggingMessageNotificationSchema, NotificationSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
12
|
+
import * as z from 'zod/v4';
|
|
11
13
|
|
|
12
14
|
import { copyDir, ensureDir, isDirectory, isFile } from '../lib/fs.js';
|
|
13
15
|
import { loadPluginManifest, pickAppFromManifest } from '../lib/plugin.js';
|
|
@@ -273,6 +275,34 @@ function formatMcpToolResult(serverName, toolName, result) {
|
|
|
273
275
|
return `${header}\n${segments.join('\n\n')}`;
|
|
274
276
|
}
|
|
275
277
|
|
|
278
|
+
const MCP_STREAM_NOTIFICATION_METHODS = [
|
|
279
|
+
'codex_app.window_run.stream',
|
|
280
|
+
'codex_app.window_run.done',
|
|
281
|
+
'codex_app.window_run.completed',
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
const buildLooseNotificationSchema = (method) =>
|
|
285
|
+
NotificationSchema.extend({
|
|
286
|
+
method: z.literal(method),
|
|
287
|
+
params: z.unknown().optional(),
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
function registerMcpNotificationHandlers(client, { serverName, onNotification } = {}) {
|
|
291
|
+
if (!client || typeof client.setNotificationHandler !== 'function') return;
|
|
292
|
+
if (typeof onNotification !== 'function') return;
|
|
293
|
+
const emit = (notification) => {
|
|
294
|
+
try {
|
|
295
|
+
onNotification({ serverName, ...notification });
|
|
296
|
+
} catch {
|
|
297
|
+
// ignore notification relay errors
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
client.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => emit(notification));
|
|
301
|
+
MCP_STREAM_NOTIFICATION_METHODS.forEach((method) => {
|
|
302
|
+
client.setNotificationHandler(buildLooseNotificationSchema(method), (notification) => emit(notification));
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
276
306
|
async function listAllMcpTools(client) {
|
|
277
307
|
const collected = [];
|
|
278
308
|
let cursor = null;
|
|
@@ -290,15 +320,17 @@ async function listAllMcpTools(client) {
|
|
|
290
320
|
return collected;
|
|
291
321
|
}
|
|
292
322
|
|
|
293
|
-
async function connectMcpServer(entry) {
|
|
323
|
+
async function connectMcpServer(entry, options = {}) {
|
|
294
324
|
if (!entry || typeof entry !== 'object') return null;
|
|
295
325
|
const serverName = normalizeText(entry.name) || 'mcp_server';
|
|
326
|
+
const onNotification = typeof options?.onNotification === 'function' ? options.onNotification : null;
|
|
296
327
|
const env = { ...process.env };
|
|
297
328
|
if (!env.MODEL_CLI_SESSION_ROOT) env.MODEL_CLI_SESSION_ROOT = process.cwd();
|
|
298
329
|
if (!env.MODEL_CLI_WORKSPACE_ROOT) env.MODEL_CLI_WORKSPACE_ROOT = process.cwd();
|
|
299
330
|
|
|
300
331
|
if (entry.command) {
|
|
301
332
|
const client = new Client({ name: 'sandbox', version: '0.1.0' });
|
|
333
|
+
registerMcpNotificationHandlers(client, { serverName, onNotification });
|
|
302
334
|
const transport = new StdioClientTransport({
|
|
303
335
|
command: entry.command,
|
|
304
336
|
args: Array.isArray(entry.args) ? entry.args : [],
|
|
@@ -318,6 +350,7 @@ async function connectMcpServer(entry) {
|
|
|
318
350
|
if (parsed.protocol === 'ws:' || parsed.protocol === 'wss:') {
|
|
319
351
|
const client = new Client({ name: 'sandbox', version: '0.1.0' });
|
|
320
352
|
const transport = new WebSocketClientTransport(parsed);
|
|
353
|
+
registerMcpNotificationHandlers(client, { serverName, onNotification });
|
|
321
354
|
await client.connect(transport);
|
|
322
355
|
const tools = await listAllMcpTools(client);
|
|
323
356
|
return { serverName, client, transport, tools };
|
|
@@ -327,6 +360,7 @@ async function connectMcpServer(entry) {
|
|
|
327
360
|
try {
|
|
328
361
|
const client = new Client({ name: 'sandbox', version: '0.1.0' });
|
|
329
362
|
const transport = new StreamableHTTPClientTransport(parsed);
|
|
363
|
+
registerMcpNotificationHandlers(client, { serverName, onNotification });
|
|
330
364
|
await client.connect(transport);
|
|
331
365
|
const tools = await listAllMcpTools(client);
|
|
332
366
|
return { serverName, client, transport, tools };
|
|
@@ -336,6 +370,7 @@ async function connectMcpServer(entry) {
|
|
|
336
370
|
try {
|
|
337
371
|
const client = new Client({ name: 'sandbox', version: '0.1.0' });
|
|
338
372
|
const transport = new SSEClientTransport(parsed);
|
|
373
|
+
registerMcpNotificationHandlers(client, { serverName, onNotification });
|
|
339
374
|
await client.connect(transport);
|
|
340
375
|
const tools = await listAllMcpTools(client);
|
|
341
376
|
return { serverName, client, transport, tools };
|
|
@@ -1918,6 +1953,19 @@ const scheduleReload = (() => {
|
|
|
1918
1953
|
try {
|
|
1919
1954
|
const es = new EventSource('/events');
|
|
1920
1955
|
es.addEventListener('reload', () => scheduleReload());
|
|
1956
|
+
es.addEventListener('mcp-notification', (event) => {
|
|
1957
|
+
if (!event?.data) return;
|
|
1958
|
+
let payload = null;
|
|
1959
|
+
try {
|
|
1960
|
+
payload = JSON.parse(event.data);
|
|
1961
|
+
} catch {
|
|
1962
|
+
payload = { raw: event.data };
|
|
1963
|
+
}
|
|
1964
|
+
if (!payload) return;
|
|
1965
|
+
const server = payload?.serverName ? String(payload.serverName) : 'mcp';
|
|
1966
|
+
const method = payload?.method ? String(payload.method) : 'notification';
|
|
1967
|
+
appendMcpOutput(`${server} ${method}`, payload?.params?.text || payload);
|
|
1968
|
+
});
|
|
1921
1969
|
} catch {
|
|
1922
1970
|
// ignore
|
|
1923
1971
|
}
|
|
@@ -1970,6 +2018,7 @@ export async function startSandboxServer({ pluginDir, port = 4399, appId = '' })
|
|
|
1970
2018
|
let mcpRuntime = null;
|
|
1971
2019
|
let mcpRuntimePromise = null;
|
|
1972
2020
|
let sandboxCallMeta = null;
|
|
2021
|
+
let relayMcpNotification = null;
|
|
1973
2022
|
|
|
1974
2023
|
const resetMcpRuntime = async () => {
|
|
1975
2024
|
const runtime = mcpRuntime;
|
|
@@ -1996,7 +2045,9 @@ export async function startSandboxServer({ pluginDir, port = 4399, appId = '' })
|
|
|
1996
2045
|
if (mcpRuntime) return mcpRuntime;
|
|
1997
2046
|
if (!mcpRuntimePromise) {
|
|
1998
2047
|
mcpRuntimePromise = (async () => {
|
|
1999
|
-
const handle = await connectMcpServer(appMcpEntry
|
|
2048
|
+
const handle = await connectMcpServer(appMcpEntry, {
|
|
2049
|
+
onNotification: relayMcpNotification,
|
|
2050
|
+
});
|
|
2000
2051
|
if (!handle) return null;
|
|
2001
2052
|
const toolEntries = Array.isArray(handle.tools)
|
|
2002
2053
|
? handle.tools.map((tool) => {
|
|
@@ -2203,6 +2254,13 @@ export async function startSandboxServer({ pluginDir, port = 4399, appId = '' })
|
|
|
2203
2254
|
sseWrite(res, event, data);
|
|
2204
2255
|
}
|
|
2205
2256
|
};
|
|
2257
|
+
relayMcpNotification = (notification) => {
|
|
2258
|
+
if (!notification) return;
|
|
2259
|
+
sseBroadcast('mcp-notification', {
|
|
2260
|
+
...notification,
|
|
2261
|
+
receivedAt: new Date().toISOString(),
|
|
2262
|
+
});
|
|
2263
|
+
};
|
|
2206
2264
|
|
|
2207
2265
|
let changeSeq = 0;
|
|
2208
2266
|
const stopWatch = startRecursiveWatcher(pluginDir, ({ eventType, filePath }) => {
|