@portel/photon 1.17.6 → 1.19.0
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/dist/auto-ui/beam/photon-management.d.ts.map +1 -1
- package/dist/auto-ui/beam/photon-management.js +28 -1
- package/dist/auto-ui/beam/photon-management.js.map +1 -1
- package/dist/auto-ui/beam/routes/api-marketplace.d.ts.map +1 -1
- package/dist/auto-ui/beam/routes/api-marketplace.js +10 -5
- package/dist/auto-ui/beam/routes/api-marketplace.js.map +1 -1
- package/dist/auto-ui/beam.d.ts.map +1 -1
- package/dist/auto-ui/beam.js +14 -4
- package/dist/auto-ui/beam.js.map +1 -1
- package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
- package/dist/auto-ui/streamable-http-transport.js +259 -88
- package/dist/auto-ui/streamable-http-transport.js.map +1 -1
- package/dist/auto-ui/types.d.ts +2 -0
- package/dist/auto-ui/types.d.ts.map +1 -1
- package/dist/auto-ui/types.js +5 -0
- package/dist/auto-ui/types.js.map +1 -1
- package/dist/beam-form.bundle.js +5 -3
- package/dist/beam-form.bundle.js.map +2 -2
- package/dist/beam.bundle.js +912 -59
- package/dist/beam.bundle.js.map +3 -3
- package/dist/claude-code-plugin.js +1 -1
- package/dist/cli/commands/beam.d.ts.map +1 -1
- package/dist/cli/commands/beam.js +8 -2
- package/dist/cli/commands/beam.js.map +1 -1
- package/dist/cli/commands/changelog.d.ts +9 -0
- package/dist/cli/commands/changelog.d.ts.map +1 -0
- package/dist/cli/commands/changelog.js +133 -0
- package/dist/cli/commands/changelog.js.map +1 -0
- package/dist/cli/commands/maker.d.ts.map +1 -1
- package/dist/cli/commands/maker.js +23 -2
- package/dist/cli/commands/maker.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +53 -0
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/package.d.ts.map +1 -1
- package/dist/cli/commands/package.js +18 -2
- package/dist/cli/commands/package.js.map +1 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +1 -0
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/update.d.ts +3 -2
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +50 -43
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +16 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli-alias.js +1 -1
- package/dist/cli-alias.js.map +1 -1
- package/dist/context-store.d.ts +23 -33
- package/dist/context-store.d.ts.map +1 -1
- package/dist/context-store.js +147 -97
- package/dist/context-store.js.map +1 -1
- package/dist/context.d.ts +15 -10
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +37 -13
- package/dist/context.js.map +1 -1
- package/dist/daemon/server.js +4 -2
- package/dist/daemon/server.js.map +1 -1
- package/dist/data-migration.d.ts +27 -0
- package/dist/data-migration.d.ts.map +1 -0
- package/dist/data-migration.js +307 -0
- package/dist/data-migration.js.map +1 -0
- package/dist/editor-support/docblock-tag-catalog.d.ts.map +1 -1
- package/dist/editor-support/docblock-tag-catalog.js +6 -0
- package/dist/editor-support/docblock-tag-catalog.js.map +1 -1
- package/dist/loader.d.ts +10 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +106 -20
- package/dist/loader.js.map +1 -1
- package/dist/marketplace-manager.d.ts.map +1 -1
- package/dist/marketplace-manager.js +25 -5
- package/dist/marketplace-manager.js.map +1 -1
- package/dist/photon-cli-runner.d.ts.map +1 -1
- package/dist/photon-cli-runner.js +56 -19
- package/dist/photon-cli-runner.js.map +1 -1
- package/dist/photon-doc-extractor.d.ts +1 -0
- package/dist/photon-doc-extractor.d.ts.map +1 -1
- package/dist/photon-doc-extractor.js +6 -0
- package/dist/photon-doc-extractor.js.map +1 -1
- package/dist/readme-syncer.d.ts.map +1 -1
- package/dist/readme-syncer.js +6 -1
- package/dist/readme-syncer.js.map +1 -1
- package/dist/server.d.ts +40 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +298 -29
- package/dist/server.js.map +1 -1
- package/dist/shared/audit.js +4 -4
- package/dist/shared/audit.js.map +1 -1
- package/dist/tasks/executor.d.ts +47 -0
- package/dist/tasks/executor.d.ts.map +1 -0
- package/dist/tasks/executor.js +180 -0
- package/dist/tasks/executor.js.map +1 -0
- package/dist/tasks/store.d.ts +13 -6
- package/dist/tasks/store.d.ts.map +1 -1
- package/dist/tasks/store.js +56 -11
- package/dist/tasks/store.js.map +1 -1
- package/dist/tasks/types.d.ts +23 -2
- package/dist/tasks/types.d.ts.map +1 -1
- package/dist/tasks/types.js +23 -3
- package/dist/tasks/types.js.map +1 -1
- package/dist/version-notify.d.ts +27 -0
- package/dist/version-notify.d.ts.map +1 -0
- package/dist/version-notify.js +142 -0
- package/dist/version-notify.js.map +1 -0
- package/package.json +5 -4
- package/dist/auto-ui/bridge/openai-shim.d.ts +0 -20
- package/dist/auto-ui/bridge/openai-shim.d.ts.map +0 -1
- package/dist/auto-ui/bridge/openai-shim.js +0 -231
- package/dist/auto-ui/bridge/openai-shim.js.map +0 -1
- package/dist/auto-ui/bridge/photon-app.d.ts +0 -162
- package/dist/auto-ui/bridge/photon-app.d.ts.map +0 -1
- package/dist/auto-ui/bridge/photon-app.js +0 -460
- package/dist/auto-ui/bridge/photon-app.js.map +0 -1
- package/dist/auto-ui/daemon-tools.d.ts +0 -45
- package/dist/auto-ui/daemon-tools.d.ts.map +0 -1
- package/dist/auto-ui/daemon-tools.js +0 -581
- package/dist/auto-ui/daemon-tools.js.map +0 -1
- package/dist/auto-ui/design-system/index.d.ts +0 -21
- package/dist/auto-ui/design-system/index.d.ts.map +0 -1
- package/dist/auto-ui/design-system/index.js +0 -27
- package/dist/auto-ui/design-system/index.js.map +0 -1
- package/dist/auto-ui/design-system/transaction-ui.d.ts +0 -70
- package/dist/auto-ui/design-system/transaction-ui.d.ts.map +0 -1
- package/dist/auto-ui/design-system/transaction-ui.js +0 -982
- package/dist/auto-ui/design-system/transaction-ui.js.map +0 -1
- package/dist/auto-ui/playground-server.d.ts +0 -7
- package/dist/auto-ui/playground-server.d.ts.map +0 -1
- package/dist/auto-ui/playground-server.js +0 -840
- package/dist/auto-ui/playground-server.js.map +0 -1
- package/dist/auto-ui/rendering/components.d.ts +0 -29
- package/dist/auto-ui/rendering/components.d.ts.map +0 -1
- package/dist/auto-ui/rendering/components.js +0 -1341
- package/dist/auto-ui/rendering/components.js.map +0 -1
- package/dist/auto-ui/rendering/field-analyzer.d.ts +0 -104
- package/dist/auto-ui/rendering/field-analyzer.d.ts.map +0 -1
- package/dist/auto-ui/rendering/field-analyzer.js +0 -447
- package/dist/auto-ui/rendering/field-analyzer.js.map +0 -1
- package/dist/auto-ui/rendering/field-renderers.d.ts +0 -64
- package/dist/auto-ui/rendering/field-renderers.d.ts.map +0 -1
- package/dist/auto-ui/rendering/field-renderers.js +0 -317
- package/dist/auto-ui/rendering/field-renderers.js.map +0 -1
- package/dist/auto-ui/rendering/index.d.ts +0 -28
- package/dist/auto-ui/rendering/index.d.ts.map +0 -1
- package/dist/auto-ui/rendering/index.js +0 -60
- package/dist/auto-ui/rendering/index.js.map +0 -1
- package/dist/auto-ui/rendering/layout-selector.d.ts +0 -60
- package/dist/auto-ui/rendering/layout-selector.d.ts.map +0 -1
- package/dist/auto-ui/rendering/layout-selector.js +0 -476
- package/dist/auto-ui/rendering/layout-selector.js.map +0 -1
- package/dist/markdown-utils.d.ts +0 -8
- package/dist/markdown-utils.d.ts.map +0 -1
- package/dist/markdown-utils.js +0 -64
- package/dist/markdown-utils.js.map +0 -1
- package/dist/mcp-client.d.ts +0 -9
- package/dist/mcp-client.d.ts.map +0 -1
- package/dist/mcp-client.js +0 -11
- package/dist/mcp-client.js.map +0 -1
- package/dist/mcp-elicitation.d.ts +0 -32
- package/dist/mcp-elicitation.d.ts.map +0 -1
- package/dist/mcp-elicitation.js +0 -26
- package/dist/mcp-elicitation.js.map +0 -1
- package/dist/photons/builder-compass.photon.d.ts +0 -167
- package/dist/photons/builder-compass.photon.d.ts.map +0 -1
- package/dist/photons/builder-compass.photon.js +0 -816
- package/dist/photons/builder-compass.photon.js.map +0 -1
- package/dist/photons/builder-compass.photon.ts +0 -1129
- package/dist/photons/docs/ui/docs.html +0 -441
- package/dist/photons/docs.photon.d.ts +0 -237
- package/dist/photons/docs.photon.d.ts.map +0 -1
- package/dist/photons/docs.photon.js +0 -483
- package/dist/photons/docs.photon.js.map +0 -1
- package/dist/photons/docs.photon.ts +0 -536
- package/dist/photons/slides.photon.d.ts +0 -212
- package/dist/photons/slides.photon.d.ts.map +0 -1
- package/dist/photons/slides.photon.js +0 -355
- package/dist/photons/slides.photon.js.map +0 -1
- package/dist/photons/slides.photon.ts +0 -370
- package/dist/photons/spreadsheet/ui/spreadsheet.html +0 -779
- package/dist/photons/spreadsheet.photon.d.ts +0 -554
- package/dist/photons/spreadsheet.photon.d.ts.map +0 -1
- package/dist/photons/spreadsheet.photon.js +0 -1050
- package/dist/photons/spreadsheet.photon.js.map +0 -1
- package/dist/photons/spreadsheet.photon.ts +0 -1239
- package/dist/photons/ui/builder-compass.html +0 -1199
- package/dist/photons/ui/builder-compass.photon.html +0 -380
- package/dist/security-scanner.d.ts +0 -52
- package/dist/security-scanner.d.ts.map +0 -1
- package/dist/security-scanner.js +0 -181
- package/dist/security-scanner.js.map +0 -1
- package/dist/shared/performance.d.ts +0 -65
- package/dist/shared/performance.d.ts.map +0 -1
- package/dist/shared/performance.js +0 -136
- package/dist/shared/performance.js.map +0 -1
package/dist/server.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
8
8
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
9
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
10
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
10
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, GetTaskRequestSchema, ListTasksRequestSchema, CancelTaskRequestSchema, GetTaskPayloadRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
11
11
|
import { readFileSync } from 'node:fs';
|
|
12
12
|
import { readText } from './shared/io.js';
|
|
13
13
|
import * as path from 'node:path';
|
|
@@ -26,6 +26,9 @@ import { isGlobalDaemonRunning, startGlobalDaemon } from './daemon/manager.js';
|
|
|
26
26
|
import { PhotonDocExtractor } from './photon-doc-extractor.js';
|
|
27
27
|
import { isLocalRequest, readBody, setSecurityHeaders } from './shared/security.js';
|
|
28
28
|
import { audit } from './shared/audit.js';
|
|
29
|
+
import { createTask, getTask, updateTask, listTasks, registerController, getController, unregisterController, } from './tasks/store.js';
|
|
30
|
+
import { toWireFormat, relatedTaskMeta, TERMINAL_STATES } from './tasks/types.js';
|
|
31
|
+
import { runTaskExecution, resolveTaskInput, waitForTerminalOrInput } from './tasks/executor.js';
|
|
29
32
|
export class HotReloadDisabledError extends Error {
|
|
30
33
|
constructor(message) {
|
|
31
34
|
super(message);
|
|
@@ -102,7 +105,8 @@ class BeamCompatTransport {
|
|
|
102
105
|
}
|
|
103
106
|
// Otherwise push to SSE stream if connected
|
|
104
107
|
if (this.sseResponse && !this.sseResponse.writableEnded) {
|
|
105
|
-
|
|
108
|
+
const id = message.id ?? crypto.randomUUID();
|
|
109
|
+
this.sseResponse.write(`event: message\nid: ${id}\ndata: ${JSON.stringify(message)}\n\n`);
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
async handleHTTP(req, res, url) {
|
|
@@ -201,7 +205,10 @@ class BeamCompatTransport {
|
|
|
201
205
|
// Notifications have no id — fire-and-forget
|
|
202
206
|
if (parsed.id === undefined) {
|
|
203
207
|
this.onmessage?.(parsed, { sessionId: this.sessionId });
|
|
204
|
-
res.writeHead(202
|
|
208
|
+
res.writeHead(202, {
|
|
209
|
+
'Access-Control-Allow-Origin': '*',
|
|
210
|
+
'Mcp-Session-Id': this.sessionId,
|
|
211
|
+
});
|
|
205
212
|
res.end();
|
|
206
213
|
return;
|
|
207
214
|
}
|
|
@@ -221,11 +228,11 @@ class BeamCompatTransport {
|
|
|
221
228
|
}
|
|
222
229
|
// DELETE — session termination (spec compliance)
|
|
223
230
|
if (req.method === 'DELETE') {
|
|
224
|
-
res.writeHead(200);
|
|
231
|
+
res.writeHead(200, { 'Access-Control-Allow-Origin': '*' });
|
|
225
232
|
res.end();
|
|
226
233
|
return;
|
|
227
234
|
}
|
|
228
|
-
res.writeHead(405);
|
|
235
|
+
res.writeHead(405, { 'Access-Control-Allow-Origin': '*' });
|
|
229
236
|
res.end('Method not allowed');
|
|
230
237
|
}
|
|
231
238
|
}
|
|
@@ -306,10 +313,14 @@ export class PhotonServer {
|
|
|
306
313
|
baseLoggerOptions.scope = this.devMode ? 'dev' : 'runtime';
|
|
307
314
|
}
|
|
308
315
|
this.logger = createLogger(baseLoggerOptions);
|
|
309
|
-
|
|
316
|
+
const loaderVerbose = (baseLoggerOptions.level ?? 'info') !== 'warn' &&
|
|
317
|
+
(baseLoggerOptions.level ?? 'info') !== 'error';
|
|
318
|
+
this.loader = new PhotonLoader(loaderVerbose, this.logger.child({ component: 'photon-loader', scope: 'loader' }), options.workingDir);
|
|
310
319
|
// Create MCP server instance
|
|
320
|
+
// When channelMode is set, declare claude/channel capability and use photon name as server name
|
|
321
|
+
const serverName = options.channelMode && options.channelName ? options.channelName : 'photon-mcp';
|
|
311
322
|
this.server = new Server({
|
|
312
|
-
name:
|
|
323
|
+
name: serverName,
|
|
313
324
|
version: PHOTON_VERSION,
|
|
314
325
|
}, {
|
|
315
326
|
capabilities: {
|
|
@@ -323,9 +334,32 @@ export class PhotonServer {
|
|
|
323
334
|
listChanged: true, // We support hot reload notifications
|
|
324
335
|
},
|
|
325
336
|
logging: {}, // Required for notifications/message (used by render, log, etc.)
|
|
337
|
+
tasks: {
|
|
338
|
+
list: {},
|
|
339
|
+
cancel: {},
|
|
340
|
+
requests: {
|
|
341
|
+
tools: { call: {} },
|
|
342
|
+
},
|
|
343
|
+
},
|
|
326
344
|
// Note: Server doesn't declare elicitation capability - that's a client capability
|
|
327
345
|
// The server uses elicitInput() when the client has elicitation support
|
|
346
|
+
//
|
|
347
|
+
// Channel capabilities — declare only the protocols specified by @channel tag.
|
|
348
|
+
// e.g. @channel claude → { 'claude/channel': {}, 'claude/channel/permission': {} }
|
|
349
|
+
...(options.channelMode && options.channelTargets?.length
|
|
350
|
+
? {
|
|
351
|
+
experimental: Object.fromEntries(options.channelTargets.flatMap((t) => [
|
|
352
|
+
[`${t}/channel`, {}],
|
|
353
|
+
[`${t}/channel/permission`, {}],
|
|
354
|
+
])),
|
|
355
|
+
}
|
|
356
|
+
: {}),
|
|
328
357
|
},
|
|
358
|
+
...(options.channelMode && options.channelInstructions
|
|
359
|
+
? {
|
|
360
|
+
instructions: options.channelInstructions,
|
|
361
|
+
}
|
|
362
|
+
: {}),
|
|
329
363
|
});
|
|
330
364
|
// Set up protocol handlers
|
|
331
365
|
this.setupHandlers();
|
|
@@ -456,6 +490,62 @@ export class PhotonServer {
|
|
|
456
490
|
return true;
|
|
457
491
|
return false;
|
|
458
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* Get the channel notification method for the connected client.
|
|
495
|
+
* Each client uses its own notification namespace under the MCP experimental extension point.
|
|
496
|
+
* Returns undefined if the client is unknown or doesn't support channels.
|
|
497
|
+
*/
|
|
498
|
+
/**
|
|
499
|
+
* Get the notification methods for all declared channel targets.
|
|
500
|
+
* Each target (e.g. 'claude') maps to its notification method.
|
|
501
|
+
*/
|
|
502
|
+
getChannelNotificationMethods() {
|
|
503
|
+
const targets = this.options.channelTargets || [];
|
|
504
|
+
// Each target's notification method follows the pattern: notifications/{target}/channel
|
|
505
|
+
return targets.map((t) => `notifications/${t}/channel`);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Handle permission request from the client (e.g. Claude Code asking "Allow tool X?").
|
|
509
|
+
* Forwards to the photon instance via channel._dispatchPermission().
|
|
510
|
+
*/
|
|
511
|
+
handlePermissionRequest(params) {
|
|
512
|
+
if (!params?.request_id || !params?.tool_name)
|
|
513
|
+
return;
|
|
514
|
+
const request = {
|
|
515
|
+
request_id: params.request_id,
|
|
516
|
+
tool_name: params.tool_name,
|
|
517
|
+
description: params.description || '',
|
|
518
|
+
input_preview: params.input_preview || '',
|
|
519
|
+
};
|
|
520
|
+
this.log('info', `Permission request: ${request.tool_name} (${request.request_id})`);
|
|
521
|
+
// Dispatch to the photon instance's permission handler
|
|
522
|
+
const instance = this.mcp;
|
|
523
|
+
if (instance?.channel?._dispatchPermission) {
|
|
524
|
+
instance.channel._dispatchPermission(request);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Send a permission response back to the client.
|
|
529
|
+
* Called by the photon instance (via this.channel.respond) when the user approves/denies.
|
|
530
|
+
*/
|
|
531
|
+
respondToPermission(response) {
|
|
532
|
+
const targets = this.options.channelTargets || [];
|
|
533
|
+
for (const target of targets) {
|
|
534
|
+
const notification = {
|
|
535
|
+
method: `notifications/${target}/channel/permission`,
|
|
536
|
+
params: {
|
|
537
|
+
request_id: response.request_id,
|
|
538
|
+
behavior: response.behavior,
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
this.server.notification(notification).catch((e) => {
|
|
542
|
+
this.log('debug', 'Permission response failed', { error: getErrorMessage(e) });
|
|
543
|
+
});
|
|
544
|
+
for (const session of Array.from(this.sseSessions.values())) {
|
|
545
|
+
session.server.notification(notification).catch(() => { });
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
459
549
|
/**
|
|
460
550
|
* Log client identity and capabilities for debugging tier detection
|
|
461
551
|
*/
|
|
@@ -1340,6 +1430,27 @@ export class PhotonServer {
|
|
|
1340
1430
|
};
|
|
1341
1431
|
}
|
|
1342
1432
|
}
|
|
1433
|
+
// ── Task mode: when params contain task field, run async ──
|
|
1434
|
+
const taskField = request.params?.task;
|
|
1435
|
+
if (taskField && this.mcp) {
|
|
1436
|
+
const { name: toolName, arguments: args } = request.params;
|
|
1437
|
+
const ttl = typeof taskField.ttl === 'number' ? taskField.ttl : undefined;
|
|
1438
|
+
const task = createTask(this.mcp.name, toolName, args, ttl);
|
|
1439
|
+
const controller = new AbortController();
|
|
1440
|
+
registerController(task.id, controller);
|
|
1441
|
+
const executeFn = async (taskInputProvider, outputHandler) => {
|
|
1442
|
+
return this.loader.executeTool(this.mcp, toolName, args || {}, {
|
|
1443
|
+
outputHandler,
|
|
1444
|
+
inputProvider: taskInputProvider,
|
|
1445
|
+
});
|
|
1446
|
+
};
|
|
1447
|
+
runTaskExecution(task.id, executeFn, {
|
|
1448
|
+
signal: controller.signal,
|
|
1449
|
+
});
|
|
1450
|
+
return {
|
|
1451
|
+
content: [{ type: 'text', text: JSON.stringify({ task: toWireFormat(task) }, null, 2) }],
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1343
1454
|
try {
|
|
1344
1455
|
return await this.handleCallTool(ctx, request);
|
|
1345
1456
|
}
|
|
@@ -1386,6 +1497,129 @@ export class PhotonServer {
|
|
|
1386
1497
|
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
1387
1498
|
return this.handleReadResource(request);
|
|
1388
1499
|
});
|
|
1500
|
+
// ── MCP Tasks handlers (2025-11-25 spec) ──
|
|
1501
|
+
this.server.setRequestHandler(GetTaskRequestSchema, async (request) => {
|
|
1502
|
+
const { taskId } = request.params;
|
|
1503
|
+
const task = getTask(taskId);
|
|
1504
|
+
if (!task)
|
|
1505
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
1506
|
+
return toWireFormat(task);
|
|
1507
|
+
});
|
|
1508
|
+
this.server.setRequestHandler(ListTasksRequestSchema, async (request) => {
|
|
1509
|
+
const allTasks = listTasks();
|
|
1510
|
+
const cursor = request.params?.cursor;
|
|
1511
|
+
const offset = cursor ? parseInt(cursor, 10) || 0 : 0;
|
|
1512
|
+
const pageSize = 50;
|
|
1513
|
+
const page = allTasks.slice(offset, offset + pageSize);
|
|
1514
|
+
const nextCursor = offset + pageSize < allTasks.length ? String(offset + pageSize) : undefined;
|
|
1515
|
+
return {
|
|
1516
|
+
tasks: page.map(toWireFormat),
|
|
1517
|
+
...(nextCursor && { nextCursor }),
|
|
1518
|
+
};
|
|
1519
|
+
});
|
|
1520
|
+
this.server.setRequestHandler(CancelTaskRequestSchema, async (request) => {
|
|
1521
|
+
const { taskId } = request.params;
|
|
1522
|
+
const task = getTask(taskId);
|
|
1523
|
+
if (!task)
|
|
1524
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
1525
|
+
if (TERMINAL_STATES.includes(task.state)) {
|
|
1526
|
+
throw new Error(`Cannot cancel task in terminal state: ${task.state}`);
|
|
1527
|
+
}
|
|
1528
|
+
const controller = getController(taskId);
|
|
1529
|
+
if (controller)
|
|
1530
|
+
controller.abort();
|
|
1531
|
+
const updated = updateTask(taskId, {
|
|
1532
|
+
state: 'cancelled',
|
|
1533
|
+
statusMessage: 'The task was cancelled by request.',
|
|
1534
|
+
});
|
|
1535
|
+
unregisterController(taskId);
|
|
1536
|
+
return toWireFormat(updated);
|
|
1537
|
+
});
|
|
1538
|
+
this.server.setRequestHandler(GetTaskPayloadRequestSchema, async (request) => {
|
|
1539
|
+
const { taskId } = request.params;
|
|
1540
|
+
const task = getTask(taskId);
|
|
1541
|
+
if (!task)
|
|
1542
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
1543
|
+
// Helper to format terminal task result
|
|
1544
|
+
const formatResult = (t) => {
|
|
1545
|
+
if (t.state === 'failed') {
|
|
1546
|
+
return {
|
|
1547
|
+
content: [{ type: 'text', text: t.error || 'Task failed' }],
|
|
1548
|
+
isError: true,
|
|
1549
|
+
_meta: relatedTaskMeta(taskId),
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
if (t.state === 'cancelled') {
|
|
1553
|
+
return {
|
|
1554
|
+
content: [{ type: 'text', text: 'Task was cancelled.' }],
|
|
1555
|
+
isError: false,
|
|
1556
|
+
_meta: relatedTaskMeta(taskId),
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
// Completed
|
|
1560
|
+
if (t.result && typeof t.result === 'object' && 'content' in t.result) {
|
|
1561
|
+
return { ...t.result, _meta: relatedTaskMeta(taskId) };
|
|
1562
|
+
}
|
|
1563
|
+
const text = typeof t.result === 'string' ? t.result : JSON.stringify(t.result ?? null);
|
|
1564
|
+
return {
|
|
1565
|
+
content: [{ type: 'text', text }],
|
|
1566
|
+
isError: false,
|
|
1567
|
+
_meta: relatedTaskMeta(taskId),
|
|
1568
|
+
};
|
|
1569
|
+
};
|
|
1570
|
+
// Already terminal — return immediately
|
|
1571
|
+
if (TERMINAL_STATES.includes(task.state)) {
|
|
1572
|
+
return formatResult(task);
|
|
1573
|
+
}
|
|
1574
|
+
// If input_required, try to get input via elicitation
|
|
1575
|
+
if (task.state === 'input_required' && task.input) {
|
|
1576
|
+
const inputProvider = this.createMCPInputProvider(this.server);
|
|
1577
|
+
try {
|
|
1578
|
+
const value = await inputProvider(task.input);
|
|
1579
|
+
resolveTaskInput(taskId, value);
|
|
1580
|
+
}
|
|
1581
|
+
catch {
|
|
1582
|
+
resolveTaskInput(taskId, null);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
// Block until terminal (max 5 min per call)
|
|
1586
|
+
try {
|
|
1587
|
+
const abortController = new AbortController();
|
|
1588
|
+
const timeout = setTimeout(() => abortController.abort(), 300000);
|
|
1589
|
+
try {
|
|
1590
|
+
while (true) {
|
|
1591
|
+
const current = await waitForTerminalOrInput(taskId, abortController.signal);
|
|
1592
|
+
if (TERMINAL_STATES.includes(current.state)) {
|
|
1593
|
+
return formatResult(current);
|
|
1594
|
+
}
|
|
1595
|
+
if (current.state === 'input_required' && current.input) {
|
|
1596
|
+
const inputProvider = this.createMCPInputProvider(this.server);
|
|
1597
|
+
try {
|
|
1598
|
+
const value = await inputProvider(current.input);
|
|
1599
|
+
resolveTaskInput(taskId, value);
|
|
1600
|
+
}
|
|
1601
|
+
catch {
|
|
1602
|
+
resolveTaskInput(taskId, null);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
finally {
|
|
1608
|
+
clearTimeout(timeout);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
catch {
|
|
1612
|
+
const current = getTask(taskId);
|
|
1613
|
+
if (current && TERMINAL_STATES.includes(current.state)) {
|
|
1614
|
+
return formatResult(current);
|
|
1615
|
+
}
|
|
1616
|
+
return {
|
|
1617
|
+
content: [{ type: 'text', text: `Task ${taskId} is still running.` }],
|
|
1618
|
+
isError: false,
|
|
1619
|
+
_meta: relatedTaskMeta(taskId),
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1389
1623
|
}
|
|
1390
1624
|
/**
|
|
1391
1625
|
* Format tool result as text
|
|
@@ -1733,6 +1967,8 @@ export class PhotonServer {
|
|
|
1733
1967
|
const metadata = await extractor.extractFullMetadata();
|
|
1734
1968
|
const isStateful = metadata.stateful;
|
|
1735
1969
|
// Start daemon for stateful photons (enables cross-client communication)
|
|
1970
|
+
// Channel mode also uses the daemon — the singleton instance holds the
|
|
1971
|
+
// bot connection, and multiple MCP clients subscribe to its events
|
|
1736
1972
|
if (isStateful) {
|
|
1737
1973
|
const photonName = metadata.name;
|
|
1738
1974
|
this.daemonName = photonName; // Store for subscription
|
|
@@ -1767,7 +2003,9 @@ export class PhotonServer {
|
|
|
1767
2003
|
this.mcp = await this.loader.loadFile(this.options.filePath);
|
|
1768
2004
|
}
|
|
1769
2005
|
}
|
|
1770
|
-
// Subscribe to daemon channels for cross-process notifications
|
|
2006
|
+
// Subscribe to daemon channels for cross-process notifications.
|
|
2007
|
+
// In channel mode, handleChannelMessage intercepts 'channel-push' events
|
|
2008
|
+
// and translates them to notifications/claude/channel for the connected client.
|
|
1771
2009
|
await this.subscribeToChannels();
|
|
1772
2010
|
// Start with the appropriate transport
|
|
1773
2011
|
const transport = this.options.transport || 'stdio';
|
|
@@ -1828,6 +2066,40 @@ export class PhotonServer {
|
|
|
1828
2066
|
if (process.env.PHOTON_DEBUG_EVENTS === '1') {
|
|
1829
2067
|
console.error(`[PHOTON-SERVER] Received daemon message on ${String(msg.channel)}: event=${String(msg.event)}`);
|
|
1830
2068
|
}
|
|
2069
|
+
// Channel permission responses — photon called this.channel.respond()
|
|
2070
|
+
if (this.options.channelMode && String(msg.channel).endsWith(':channel-permission-response')) {
|
|
2071
|
+
const data = msg.data;
|
|
2072
|
+
if (data?.request_id && data?.behavior) {
|
|
2073
|
+
this.respondToPermission({
|
|
2074
|
+
request_id: data.request_id,
|
|
2075
|
+
behavior: data.behavior,
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
// Channel events — translate to client-specific channel notifications.
|
|
2081
|
+
// Each declared target (e.g. 'claude') gets its own notification method.
|
|
2082
|
+
if (this.options.channelMode && String(msg.channel).endsWith(':channel-push')) {
|
|
2083
|
+
const pushData = msg.data;
|
|
2084
|
+
const methods = this.getChannelNotificationMethods();
|
|
2085
|
+
if (methods.length === 0)
|
|
2086
|
+
return;
|
|
2087
|
+
const content = typeof pushData?.content === 'string' ? pushData.content : '';
|
|
2088
|
+
const meta = pushData?.meta || {};
|
|
2089
|
+
try {
|
|
2090
|
+
for (const method of methods) {
|
|
2091
|
+
const notification = { method, params: { content, meta } };
|
|
2092
|
+
await this.server.notification(notification);
|
|
2093
|
+
for (const session of Array.from(this.sseSessions.values())) {
|
|
2094
|
+
await session.server.notification(notification);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
catch (e) {
|
|
2099
|
+
this.log('debug', 'Channel notification failed', { error: getErrorMessage(e) });
|
|
2100
|
+
}
|
|
2101
|
+
return; // Don't also forward as a regular photon event
|
|
2102
|
+
}
|
|
1831
2103
|
// Use STANDARD notification with embedded photon data
|
|
1832
2104
|
// Claude Desktop will forward this (it's a standard notification)
|
|
1833
2105
|
// Our bridge extracts _photon and routes to the appropriate event handler
|
|
@@ -1877,9 +2149,15 @@ export class PhotonServer {
|
|
|
1877
2149
|
interceptTransportForRawCapabilities(transport, targetServer) {
|
|
1878
2150
|
const origOnMessage = transport.onmessage;
|
|
1879
2151
|
transport.onmessage = (message, extra) => {
|
|
1880
|
-
// Capture raw capabilities from initialize request
|
|
1881
|
-
if (message?.method === 'initialize' && message?.params
|
|
1882
|
-
|
|
2152
|
+
// Capture raw capabilities and client name from initialize request
|
|
2153
|
+
if (message?.method === 'initialize' && message?.params) {
|
|
2154
|
+
if (message.params.capabilities) {
|
|
2155
|
+
this.rawClientCapabilities.set(targetServer, message.params.capabilities);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
// Intercept channel permission requests from the client
|
|
2159
|
+
if (this.options.channelMode && message?.method?.endsWith('/channel/permission_request')) {
|
|
2160
|
+
this.handlePermissionRequest(message.params);
|
|
1883
2161
|
}
|
|
1884
2162
|
origOnMessage?.(message, extra);
|
|
1885
2163
|
};
|
|
@@ -1898,15 +2176,16 @@ export class PhotonServer {
|
|
|
1898
2176
|
*/
|
|
1899
2177
|
async startSSE() {
|
|
1900
2178
|
const port = this.options.port || 3000;
|
|
1901
|
-
const useStreamableHTTP = !!this.options.embeddedAssets;
|
|
1902
2179
|
const ssePath = '/mcp';
|
|
1903
2180
|
const messagesPath = '/mcp/messages';
|
|
1904
|
-
//
|
|
1905
|
-
//
|
|
1906
|
-
//
|
|
1907
|
-
//
|
|
2181
|
+
// Always use Streamable HTTP transport for SSE mode.
|
|
2182
|
+
// The legacy SSE transport (endpoint event + /mcp/messages?sessionId=) is deprecated
|
|
2183
|
+
// in the MCP spec and not supported by modern clients (e.g. llama.cpp).
|
|
2184
|
+
// Streamable HTTP uses the standard protocol:
|
|
2185
|
+
// POST /mcp — JSON-RPC request → JSON response
|
|
2186
|
+
// GET /mcp — SSE stream for server-to-client notifications
|
|
1908
2187
|
let beamTransport = null;
|
|
1909
|
-
|
|
2188
|
+
{
|
|
1910
2189
|
const photonName = this.mcp?.name || 'photon';
|
|
1911
2190
|
beamTransport = new BeamCompatTransport(photonName, {
|
|
1912
2191
|
description: this.mcp?.description,
|
|
@@ -1978,7 +2257,7 @@ export class PhotonServer {
|
|
|
1978
2257
|
res.writeHead(204, {
|
|
1979
2258
|
'Access-Control-Allow-Origin': '*',
|
|
1980
2259
|
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
|
|
1981
|
-
'Access-Control-Allow-Headers': 'Content-Type, Accept, Mcp-Session-Id',
|
|
2260
|
+
'Access-Control-Allow-Headers': 'Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version',
|
|
1982
2261
|
'Access-Control-Expose-Headers': 'Mcp-Session-Id',
|
|
1983
2262
|
});
|
|
1984
2263
|
res.end();
|
|
@@ -2345,17 +2624,7 @@ export class PhotonServer {
|
|
|
2345
2624
|
});
|
|
2346
2625
|
await new Promise((resolve) => {
|
|
2347
2626
|
this.httpServer.listen(port, () => {
|
|
2348
|
-
|
|
2349
|
-
transport: 'sse',
|
|
2350
|
-
port,
|
|
2351
|
-
devMode: this.devMode,
|
|
2352
|
-
});
|
|
2353
|
-
this.log('debug', 'SSE endpoints ready', {
|
|
2354
|
-
baseUrl: `http://localhost:${port}`,
|
|
2355
|
-
ssePath,
|
|
2356
|
-
messagesPath,
|
|
2357
|
-
playground: this.devMode ? `http://localhost:${port}/playground` : undefined,
|
|
2358
|
-
});
|
|
2627
|
+
process.stdout.write(`⚡ ${this.mcp.name} → http://localhost:${port}${ssePath}\n`);
|
|
2359
2628
|
resolve();
|
|
2360
2629
|
});
|
|
2361
2630
|
});
|