@portel/photon 1.33.1 → 1.33.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 CHANGED
@@ -10,13 +10,15 @@
10
10
  [![Node](https://img.shields.io/badge/node-%3E%3D20-43853d.svg)](https://nodejs.org)
11
11
  [![MCP](https://img.shields.io/badge/MCP-compatible-7c3aed.svg)](https://modelcontextprotocol.io)
12
12
 
13
- ### Define intent once. Deliver everywhere.
13
+ ### Build MCP apps from TypeScript methods.
14
14
 
15
- Photon turns a single TypeScript file into:
15
+ Define the capability once. Photon turns it into the interfaces people now
16
+ expect around AI tools:
16
17
 
17
- - **MCP server** for AI agents
18
- - **CLI tool** for automation
19
- - **Web interface** for humans
18
+ - **MCP server** for Claude, ChatGPT, Cursor, and agents
19
+ - **Embedded app UI** for chat clients that support MCP app resources
20
+ - **CLI tool** for scripts, demos, and automation
21
+ - **Beam web interface** for humans
20
22
 
21
23
  Photon is free and open source software released under the [MIT license](./LICENSE).
22
24
 
@@ -24,6 +26,36 @@ Photon is free and open source software released under the [MIT license](./LICEN
24
26
 
25
27
  ---
26
28
 
29
+ ## From one method to every surface
30
+
31
+ The weather example is intentionally small: one TypeScript method, a few
32
+ docblock tags, and one `@ui` HTML asset. Photon turns that into a CLI command,
33
+ Beam UI, MCP tool, and embedded app surface for MCP app-capable chat clients.
34
+ Claude Desktop can run it from a local stdio MCP command; ChatGPT developer
35
+ mode can connect to the same Photon over a public HTTPS `/mcp` endpoint.
36
+
37
+ <div align="center">
38
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/showcase/weather/photon-surface-map.svg" alt="Infographic showing one Photon method becoming CLI, Beam, MCP, Claude Desktop, ChatGPT, and other agent surfaces" width="100%">
39
+ </div>
40
+
41
+ <div align="center">
42
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/showcase/weather/chat-clients-photon-weather-showcase.gif" alt="Animated Photon weather showcase with matched ChatGPT and Claude Desktop MCP app demos" width="100%">
43
+ </div>
44
+
45
+ <div align="center">
46
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/showcase/weather/weather-showcase-hero.png" alt="Photon weather showcase: one method rendered across Beam, CLI, and chat app surfaces" width="100%">
47
+ </div>
48
+
49
+ [Follow the step-by-step tutorial](./docs/tutorials/from-method-to-chat-app.md)
50
+ or open the runnable example in
51
+ [`examples/weather-showcase`](./examples/weather-showcase).
52
+ Real-client proof is included for
53
+ [Claude Desktop](https://raw.githubusercontent.com/portel-dev/photon/main/assets/showcase/weather/claude-weather-real.png)
54
+ and
55
+ [ChatGPT developer mode](https://raw.githubusercontent.com/portel-dev/photon/main/assets/showcase/weather/chatgpt-photon-weather-local-dot.png).
56
+
57
+ ---
58
+
27
59
  ## One definition. Multiple interfaces.
28
60
 
29
61
  <div align="center">
@@ -575,6 +607,7 @@ Uses Bun's compiler under the hood. The binary bundles the photon, its `@depende
575
607
  | Guide | |
576
608
  |---|---|
577
609
  | [Getting Started](./docs/getting-started.md) | Install, build, and run your first photon in 5 minutes |
610
+ | [From Method to Chat App](./docs/tutorials/from-method-to-chat-app.md) | Weather showcase: CLI, Beam, MCP, and embedded app UI from one method |
578
611
  | [Core Concepts](./docs/concepts.md) | The 6 ideas behind Photon |
579
612
  | [Tag Reference](./docs/reference/DOCBLOCK-TAGS.md) | Public reference for every docblock tag Photon understands |
580
613
  | [Output Formats](./docs/formats.md) | Visual gallery of every `@format` type |
@@ -236,9 +236,12 @@
236
236
  // Handle JSON-RPC tools/call from bridge and form components
237
237
  if (msg.jsonrpc === '2.0' && msg.method === 'tools/call' && msg.id != null) {
238
238
  var rawName = (msg.params && msg.params.name) || '';
239
- // Don't double-prefix: invoke-form sends fully-qualified names (photon/method),
239
+ // Don't double-prefix: invoke-form sends fully-qualified names (photon.method),
240
240
  // bridge sends unqualified names (just method). Check for prefix before adding.
241
- var toolName = rawName.indexOf('/') !== -1 ? rawName : photonName + '/' + rawName;
241
+ var toolName =
242
+ rawName.indexOf('.') !== -1 || rawName.indexOf('/') !== -1
243
+ ? rawName
244
+ : photonName + '.' + rawName;
242
245
  var args = (msg.params && msg.params.arguments) || {};
243
246
 
244
247
  var doCall = function() {
@@ -1 +1 @@
1
- {"version":3,"file":"streamable-http-transport.d.ts","sourceRoot":"","sources":["../../src/auto-ui/streamable-http-transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"streamable-http-transport.d.ts","sourceRoot":"","sources":["../../src/auto-ui/streamable-http-transport.ts"],"names":[],"mappings":"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"}
@@ -148,6 +148,33 @@ function decodeJWTCaller(authHeader) {
148
148
  return undefined;
149
149
  }
150
150
  }
151
+ function isOpenAIAppSession(session) {
152
+ const name = session.clientInfo?.name?.toLowerCase();
153
+ return name === 'chatgpt' || !!name?.includes('openai');
154
+ }
155
+ function namespacedToolName(serverName, methodName) {
156
+ return `${serverName}.${methodName}`;
157
+ }
158
+ function toolNameForSession(session, photonName, methodName) {
159
+ return isOpenAIAppSession(session) ? methodName : namespacedToolName(photonName, methodName);
160
+ }
161
+ function splitNamespacedToolName(name) {
162
+ const dotIndex = name.indexOf('.');
163
+ if (dotIndex !== -1) {
164
+ return {
165
+ serverName: name.slice(0, dotIndex),
166
+ methodName: name.slice(dotIndex + 1),
167
+ };
168
+ }
169
+ const slashIndex = name.indexOf('/');
170
+ if (slashIndex !== -1) {
171
+ return {
172
+ serverName: name.slice(0, slashIndex),
173
+ methodName: name.slice(slashIndex + 1),
174
+ };
175
+ }
176
+ return null;
177
+ }
151
178
  // ════════════════════════════════════════════════════════════════════════════════
152
179
  // SESSION MANAGEMENT
153
180
  // ════════════════════════════════════════════════════════════════════════════════
@@ -1150,7 +1177,7 @@ const handlers = {
1150
1177
  : undefined;
1151
1178
  const meta = buildToolMCPMeta(method, { uiResourceUri });
1152
1179
  tools.push({
1153
- name: `${photon.name}/${method.name}`,
1180
+ name: toolNameForSession(session, photon.name, method.name),
1154
1181
  description: method.description || `Execute ${method.name}`,
1155
1182
  inputSchema: method.params || { type: 'object', properties: {} },
1156
1183
  'x-photon-id': photon.id, // Unique ID (hash of path) for subscriptions
@@ -1186,7 +1213,7 @@ const handlers = {
1186
1213
  if (!photon.configured || !photon.stateful)
1187
1214
  continue;
1188
1215
  tools.push({
1189
- name: `${photon.name}/_use`,
1216
+ name: namespacedToolName(photon.name, '_use'),
1190
1217
  description: `Switch to a named instance of ${photon.name}. Omit name to select interactively.`,
1191
1218
  inputSchema: {
1192
1219
  type: 'object',
@@ -1201,21 +1228,21 @@ const handlers = {
1201
1228
  'x-photon-internal': true,
1202
1229
  });
1203
1230
  tools.push({
1204
- name: `${photon.name}/_instances`,
1231
+ name: namespacedToolName(photon.name, '_instances'),
1205
1232
  description: `List all available instances of ${photon.name}.`,
1206
1233
  inputSchema: { type: 'object', properties: {} },
1207
1234
  'x-photon-id': photon.id,
1208
1235
  'x-photon-internal': true,
1209
1236
  });
1210
1237
  tools.push({
1211
- name: `${photon.name}/_undo`,
1238
+ name: namespacedToolName(photon.name, '_undo'),
1212
1239
  description: `Undo the last state mutation on ${photon.name}. Reverts the most recent change.`,
1213
1240
  inputSchema: { type: 'object', properties: {} },
1214
1241
  'x-photon-id': photon.id,
1215
1242
  'x-photon-internal': true,
1216
1243
  });
1217
1244
  tools.push({
1218
- name: `${photon.name}/_redo`,
1245
+ name: namespacedToolName(photon.name, '_redo'),
1219
1246
  description: `Redo the last undone mutation on ${photon.name}. Re-applies a previously undone change.`,
1220
1247
  inputSchema: { type: 'object', properties: {} },
1221
1248
  'x-photon-id': photon.id,
@@ -1230,7 +1257,7 @@ const handlers = {
1230
1257
  for (const method of mcp.methods) {
1231
1258
  const meta = buildToolMCPMeta(method, { uiResourceUri: method.linkedUi });
1232
1259
  tools.push({
1233
- name: `${mcp.name}/${method.name}`,
1260
+ name: namespacedToolName(mcp.name, method.name),
1234
1261
  description: method.description || `Execute ${method.name}`,
1235
1262
  inputSchema: method.params || { type: 'object', properties: {} },
1236
1263
  'x-external-mcp': true, // Marker for frontend to identify external MCPs
@@ -1542,9 +1569,31 @@ const handlers = {
1542
1569
  if (name === 'beam/studio-parse') {
1543
1570
  return handleBeamStudioParse(req, args || {});
1544
1571
  }
1545
- // Parse tool name: server-name/method-name or namespace:server-name/method-name
1546
- const slashIndex = name.indexOf('/');
1547
- if (slashIndex === -1) {
1572
+ // Parse tool name: server-name.method-name.
1573
+ // ChatGPT/OpenAI app sessions receive slashless names in tools/list because
1574
+ // their connector layer treats slash-qualified names as app resource paths.
1575
+ let serverName;
1576
+ let methodName;
1577
+ const namespacedName = splitNamespacedToolName(name);
1578
+ if (!namespacedName && isOpenAIAppSession(session)) {
1579
+ const matches = ctx.photons.filter((photon) => photon.configured && photon.methods?.some((method) => method.name === name));
1580
+ if (matches.length === 1) {
1581
+ const photon = matches[0];
1582
+ serverName = photon.name;
1583
+ methodName = name;
1584
+ }
1585
+ else {
1586
+ return {
1587
+ jsonrpc: '2.0',
1588
+ id: req.id,
1589
+ result: {
1590
+ content: [{ type: 'text', text: `Invalid tool name: ${name}` }],
1591
+ isError: true,
1592
+ },
1593
+ };
1594
+ }
1595
+ }
1596
+ else if (!namespacedName) {
1548
1597
  return {
1549
1598
  jsonrpc: '2.0',
1550
1599
  id: req.id,
@@ -1554,8 +1603,10 @@ const handlers = {
1554
1603
  },
1555
1604
  };
1556
1605
  }
1557
- const serverName = name.slice(0, slashIndex);
1558
- const methodName = name.slice(slashIndex + 1);
1606
+ else {
1607
+ serverName = namespacedName.serverName;
1608
+ methodName = namespacedName.methodName;
1609
+ }
1559
1610
  // Per-photon auth check: if this photon requires auth but caller is anonymous, reject
1560
1611
  const targetPhoton = ctx.photons.find((p) => p.name === serverName);
1561
1612
  // Claim-code scope enforcement: filtering tools/list alone is not a