@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leeoohoo/ui-apps-devkit",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "ChatOS UI Apps DevKit (CLI + templates + sandbox) for building installable ChatOS UI Apps plugins.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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 }) => {