@portel/photon 1.23.1 → 1.24.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.
Files changed (57) hide show
  1. package/README.md +66 -0
  2. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
  3. package/dist/auto-ui/streamable-http-transport.js +262 -18
  4. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  5. package/dist/beam.bundle.js +58287 -56177
  6. package/dist/beam.bundle.js.map +4 -4
  7. package/dist/capability-negotiator.d.ts +9 -0
  8. package/dist/capability-negotiator.d.ts.map +1 -1
  9. package/dist/capability-negotiator.js +14 -0
  10. package/dist/capability-negotiator.js.map +1 -1
  11. package/dist/cli/commands/claim.d.ts +17 -0
  12. package/dist/cli/commands/claim.d.ts.map +1 -0
  13. package/dist/cli/commands/claim.js +124 -0
  14. package/dist/cli/commands/claim.js.map +1 -0
  15. package/dist/cli/commands/run.d.ts.map +1 -1
  16. package/dist/cli/commands/run.js +2 -0
  17. package/dist/cli/commands/run.js.map +1 -1
  18. package/dist/cli/index.d.ts.map +1 -1
  19. package/dist/cli/index.js +2 -0
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/daemon/claims.d.ts +108 -0
  22. package/dist/daemon/claims.d.ts.map +1 -0
  23. package/dist/daemon/claims.js +245 -0
  24. package/dist/daemon/claims.js.map +1 -0
  25. package/dist/daemon/client.d.ts.map +1 -1
  26. package/dist/daemon/client.js +15 -29
  27. package/dist/daemon/client.js.map +1 -1
  28. package/dist/daemon/cron.d.ts +36 -0
  29. package/dist/daemon/cron.d.ts.map +1 -0
  30. package/dist/daemon/cron.js +216 -0
  31. package/dist/daemon/cron.js.map +1 -0
  32. package/dist/daemon/schedule-loader.d.ts +76 -0
  33. package/dist/daemon/schedule-loader.d.ts.map +1 -0
  34. package/dist/daemon/schedule-loader.js +124 -0
  35. package/dist/daemon/schedule-loader.js.map +1 -0
  36. package/dist/daemon/server.js +76 -226
  37. package/dist/daemon/server.js.map +1 -1
  38. package/dist/deploy/cloudflare.d.ts.map +1 -1
  39. package/dist/deploy/cloudflare.js +68 -3
  40. package/dist/deploy/cloudflare.js.map +1 -1
  41. package/dist/loader.d.ts +22 -1
  42. package/dist/loader.d.ts.map +1 -1
  43. package/dist/loader.js +162 -7
  44. package/dist/loader.js.map +1 -1
  45. package/dist/photon-cli-runner.d.ts.map +1 -1
  46. package/dist/photon-cli-runner.js +17 -0
  47. package/dist/photon-cli-runner.js.map +1 -1
  48. package/dist/server.d.ts +10 -0
  49. package/dist/server.d.ts.map +1 -1
  50. package/dist/server.js +50 -1
  51. package/dist/server.js.map +1 -1
  52. package/dist/shared/memory-sqlite.d.ts +37 -0
  53. package/dist/shared/memory-sqlite.d.ts.map +1 -0
  54. package/dist/shared/memory-sqlite.js +143 -0
  55. package/dist/shared/memory-sqlite.js.map +1 -0
  56. package/package.json +2 -2
  57. package/templates/cloudflare/worker.ts.template +44 -73
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portel/photon",
3
- "version": "1.23.1",
3
+ "version": "1.24.0",
4
4
  "description": "You focus on the business logic. We'll enable the rest. Build MCP servers and CLI tools in a single TypeScript file.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -113,7 +113,7 @@
113
113
  "@modelcontextprotocol/ext-apps": "^1.0.1",
114
114
  "@modelcontextprotocol/sdk": "^1.25.2",
115
115
  "@portel/cli": "^1.1.0",
116
- "@portel/photon-core": "^2.24.0",
116
+ "@portel/photon-core": "^2.25.0",
117
117
  "boxen": "^8.0.1",
118
118
  "chalk": "^5.4.1",
119
119
  "chart.js": "^4.5.1",
@@ -11,13 +11,6 @@ interface Env {
11
11
  [key: string]: string | undefined;
12
12
  }
13
13
 
14
- type SSESession = {
15
- writer: WritableStreamDefaultWriter<Uint8Array>;
16
- encoder: TextEncoder;
17
- };
18
-
19
- const sessions = new Map<string, SSESession>();
20
-
21
14
  // Tool definitions extracted at build time
22
15
  // @ts-ignore - Will be replaced during build
23
16
  const TOOL_DEFINITIONS: any[] = __TOOL_DEFINITIONS__;
@@ -100,66 +93,34 @@ async function handleMCPRequest(request: any, env: Env): Promise<any> {
100
93
  }
101
94
 
102
95
  // SSE stream handler
103
- async function handleSSE(request: Request, env: Env): Promise<Response> {
104
- const sessionId = crypto.randomUUID();
105
- const encoder = new TextEncoder();
106
-
107
- const { readable, writable } = new TransformStream();
108
- const writer = writable.getWriter();
109
-
110
- sessions.set(sessionId, { writer, encoder });
111
-
112
- // Send session ID
113
- await writer.write(encoder.encode(`event: session\ndata: ${JSON.stringify({ sessionId })}\n\n`));
114
-
115
- // Keep-alive
116
- const interval = setInterval(async () => {
117
- try {
118
- await writer.write(encoder.encode(`: keepalive\n\n`));
119
- } catch {
120
- clearInterval(interval);
121
- }
122
- }, 30000);
123
-
124
- // Cleanup on close
125
- request.signal.addEventListener('abort', () => {
126
- clearInterval(interval);
127
- sessions.delete(sessionId);
128
- writer.close().catch(() => {});
129
- });
130
-
131
- return new Response(readable, {
132
- headers: {
133
- 'Content-Type': 'text/event-stream',
134
- 'Cache-Control': 'no-cache',
135
- 'Connection': 'keep-alive',
136
- 'Access-Control-Allow-Origin': '*',
137
- },
138
- });
139
- }
140
-
141
- // Message handler for SSE
142
- async function handleMessage(request: Request, env: Env): Promise<Response> {
143
- const url = new URL(request.url);
144
- const sessionId = url.searchParams.get('sessionId');
145
-
146
- if (!sessionId || !sessions.has(sessionId)) {
147
- return new Response('Invalid session', { status: 400 });
96
+ /**
97
+ * Stateless MCP Streamable HTTP handler.
98
+ *
99
+ * Each POST /mcp is a complete JSON-RPC exchange: body in, result out, no
100
+ * session state carried across requests. This matches the MCP Streamable
101
+ * HTTP spec's single-endpoint pattern and stays within the Cloudflare
102
+ * Workers free tier (no Durable Objects needed for session storage).
103
+ */
104
+ async function handleStreamableMCP(request: Request, env: Env): Promise<Response> {
105
+ let body: unknown;
106
+ try {
107
+ body = await request.json();
108
+ } catch (error: any) {
109
+ return Response.json(
110
+ { jsonrpc: '2.0', id: null, error: { code: -32700, message: `Parse error: ${error?.message ?? String(error)}` } },
111
+ { status: 400, headers: CORS_HEADERS }
112
+ );
148
113
  }
149
-
150
- const session = sessions.get(sessionId)!;
151
- const body = await request.json();
152
- const response = await handleMCPRequest(body, env);
153
-
154
- // Send response via SSE
155
- const data = `event: message\ndata: ${JSON.stringify(response)}\n\n`;
156
- await session.writer.write(session.encoder.encode(data));
157
-
158
- return new Response('OK', {
159
- headers: { 'Access-Control-Allow-Origin': '*' },
160
- });
114
+ const result = await handleMCPRequest(body, env);
115
+ return Response.json(result, { headers: CORS_HEADERS });
161
116
  }
162
117
 
118
+ const CORS_HEADERS = {
119
+ 'Access-Control-Allow-Origin': '*',
120
+ 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
121
+ 'Access-Control-Allow-Headers': 'Content-Type, Mcp-Session-Id',
122
+ };
123
+
163
124
  // Playground HTML
164
125
  function getPlaygroundHTML(): string {
165
126
  return `<!DOCTYPE html>
@@ -324,25 +285,35 @@ export default {
324
285
  if (url.pathname === '/' && request.method === 'GET') {
325
286
  return Response.json({
326
287
  name: PHOTON_NAME,
327
- transport: 'sse',
288
+ transport: 'streamable-http',
328
289
  runtime: 'cloudflare-workers',
329
290
  endpoints: {
330
- sse: '/mcp',
331
- messages: '/mcp/messages',
291
+ mcp: '/mcp',
332
292
  ...(DEV_MODE ? { playground: '/playground' } : {}),
333
293
  },
334
294
  tools: TOOL_DEFINITIONS.length,
335
295
  });
336
296
  }
337
297
 
338
- // SSE endpoint
298
+ // MCP Streamable HTTP: one endpoint, stateless per request.
299
+ if (url.pathname === '/mcp' && request.method === 'POST') {
300
+ return handleStreamableMCP(request, env);
301
+ }
339
302
  if (url.pathname === '/mcp' && request.method === 'GET') {
340
- return handleSSE(request, env);
303
+ // Spec-compliant no-op: clients that open an SSE stream for server
304
+ // notifications get a valid empty stream. We do not push anything
305
+ // server-initiated from a stateless Worker.
306
+ return new Response(': streamable-http\n\n', {
307
+ headers: {
308
+ 'Content-Type': 'text/event-stream',
309
+ 'Cache-Control': 'no-cache',
310
+ ...CORS_HEADERS,
311
+ },
312
+ });
341
313
  }
342
-
343
- // Message endpoint
344
- if (url.pathname === '/mcp/messages' && request.method === 'POST') {
345
- return handleMessage(request, env);
314
+ if (url.pathname === '/mcp' && request.method === 'DELETE') {
315
+ // Session termination: stateless, nothing to clean up.
316
+ return new Response(null, { status: 204, headers: CORS_HEADERS });
346
317
  }
347
318
 
348
319
  // Dev-only endpoints