@portel/photon-core 1.5.0 → 2.1.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 (163) hide show
  1. package/dist/auto-ui.js +1 -1
  2. package/dist/auto-ui.js.map +1 -1
  3. package/dist/base.d.ts +1 -1
  4. package/dist/base.d.ts.map +1 -1
  5. package/dist/base.js +2 -2
  6. package/dist/base.js.map +1 -1
  7. package/dist/cli-ui-renderer.js +1 -1
  8. package/dist/cli-ui-renderer.js.map +1 -1
  9. package/dist/design-system/index.d.ts +21 -0
  10. package/dist/design-system/index.d.ts.map +1 -0
  11. package/dist/design-system/index.js +27 -0
  12. package/dist/design-system/index.js.map +1 -0
  13. package/dist/design-system/tokens.d.ts +149 -0
  14. package/dist/design-system/tokens.d.ts.map +1 -0
  15. package/dist/design-system/tokens.js +413 -0
  16. package/dist/design-system/tokens.js.map +1 -0
  17. package/dist/design-system/transaction-ui.d.ts +70 -0
  18. package/dist/design-system/transaction-ui.d.ts.map +1 -0
  19. package/dist/design-system/transaction-ui.js +982 -0
  20. package/dist/design-system/transaction-ui.js.map +1 -0
  21. package/dist/generator.d.ts +56 -6
  22. package/dist/generator.d.ts.map +1 -1
  23. package/dist/generator.js.map +1 -1
  24. package/dist/index.d.ts +6 -7
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +46 -56
  27. package/dist/index.js.map +1 -1
  28. package/dist/io.d.ts +103 -2
  29. package/dist/io.d.ts.map +1 -1
  30. package/dist/io.js +37 -1
  31. package/dist/io.js.map +1 -1
  32. package/dist/rendering/components.d.ts +29 -0
  33. package/dist/rendering/components.d.ts.map +1 -0
  34. package/dist/rendering/components.js +773 -0
  35. package/dist/rendering/components.js.map +1 -0
  36. package/dist/rendering/field-analyzer.d.ts +48 -0
  37. package/dist/rendering/field-analyzer.d.ts.map +1 -0
  38. package/dist/rendering/field-analyzer.js +270 -0
  39. package/dist/rendering/field-analyzer.js.map +1 -0
  40. package/dist/rendering/field-renderers.d.ts +64 -0
  41. package/dist/rendering/field-renderers.d.ts.map +1 -0
  42. package/dist/rendering/field-renderers.js +317 -0
  43. package/dist/rendering/field-renderers.js.map +1 -0
  44. package/dist/rendering/index.d.ts +28 -0
  45. package/dist/rendering/index.d.ts.map +1 -0
  46. package/dist/rendering/index.js +60 -0
  47. package/dist/rendering/index.js.map +1 -0
  48. package/dist/rendering/layout-selector.d.ts +48 -0
  49. package/dist/rendering/layout-selector.d.ts.map +1 -0
  50. package/dist/rendering/layout-selector.js +347 -0
  51. package/dist/rendering/layout-selector.js.map +1 -0
  52. package/dist/rendering/template-engine.d.ts +41 -0
  53. package/dist/rendering/template-engine.d.ts.map +1 -0
  54. package/dist/rendering/template-engine.js +236 -0
  55. package/dist/rendering/template-engine.js.map +1 -0
  56. package/dist/schema-extractor.d.ts +30 -0
  57. package/dist/schema-extractor.d.ts.map +1 -1
  58. package/dist/schema-extractor.js +205 -12
  59. package/dist/schema-extractor.js.map +1 -1
  60. package/dist/stateful.js +1 -1
  61. package/dist/stateful.js.map +1 -1
  62. package/dist/types.d.ts +9 -1
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/types.js.map +1 -1
  65. package/dist/ucp/ap2/handlers.d.ts +242 -0
  66. package/dist/ucp/ap2/handlers.d.ts.map +1 -0
  67. package/dist/ucp/ap2/handlers.js +482 -0
  68. package/dist/ucp/ap2/handlers.js.map +1 -0
  69. package/dist/ucp/ap2/mandates.d.ts +95 -0
  70. package/dist/ucp/ap2/mandates.d.ts.map +1 -0
  71. package/dist/ucp/ap2/mandates.js +234 -0
  72. package/dist/ucp/ap2/mandates.js.map +1 -0
  73. package/dist/ucp/ap2/types.d.ts +305 -0
  74. package/dist/ucp/ap2/types.d.ts.map +1 -0
  75. package/dist/ucp/ap2/types.js +8 -0
  76. package/dist/ucp/ap2/types.js.map +1 -0
  77. package/dist/ucp/capabilities/checkout.d.ts +118 -0
  78. package/dist/ucp/capabilities/checkout.d.ts.map +1 -0
  79. package/dist/ucp/capabilities/checkout.js +344 -0
  80. package/dist/ucp/capabilities/checkout.js.map +1 -0
  81. package/dist/ucp/capabilities/identity.d.ts +130 -0
  82. package/dist/ucp/capabilities/identity.d.ts.map +1 -0
  83. package/dist/ucp/capabilities/identity.js +290 -0
  84. package/dist/ucp/capabilities/identity.js.map +1 -0
  85. package/dist/ucp/capabilities/order.d.ts +142 -0
  86. package/dist/ucp/capabilities/order.d.ts.map +1 -0
  87. package/dist/ucp/capabilities/order.js +383 -0
  88. package/dist/ucp/capabilities/order.js.map +1 -0
  89. package/dist/ucp/index.d.ts +18 -0
  90. package/dist/ucp/index.d.ts.map +1 -0
  91. package/dist/ucp/index.js +19 -0
  92. package/dist/ucp/index.js.map +1 -0
  93. package/dist/ucp/manifest.d.ts +62 -0
  94. package/dist/ucp/manifest.d.ts.map +1 -0
  95. package/dist/ucp/manifest.js +180 -0
  96. package/dist/ucp/manifest.js.map +1 -0
  97. package/dist/ucp/types.d.ts +327 -0
  98. package/dist/ucp/types.d.ts.map +1 -0
  99. package/dist/ucp/types.js +8 -0
  100. package/dist/ucp/types.js.map +1 -0
  101. package/package.json +3 -4
  102. package/src/auto-ui.ts +1 -1
  103. package/src/base.ts +2 -2
  104. package/src/cli-ui-renderer.ts +1 -1
  105. package/src/design-system/index.ts +30 -0
  106. package/src/design-system/tokens.ts +451 -0
  107. package/src/design-system/transaction-ui.ts +1038 -0
  108. package/src/generator.ts +58 -2
  109. package/src/index.ts +135 -124
  110. package/src/io.ts +108 -3
  111. package/src/rendering/components.ts +785 -0
  112. package/src/rendering/field-analyzer.ts +299 -0
  113. package/src/rendering/field-renderers.ts +356 -0
  114. package/src/rendering/index.ts +63 -0
  115. package/src/rendering/layout-selector.ts +390 -0
  116. package/src/rendering/template-engine.ts +254 -0
  117. package/src/schema-extractor.ts +225 -12
  118. package/src/stateful.ts +1 -1
  119. package/src/types.ts +10 -1
  120. package/src/ucp/ap2/handlers.ts +779 -0
  121. package/src/ucp/ap2/mandates.ts +354 -0
  122. package/src/ucp/ap2/types.ts +441 -0
  123. package/src/ucp/capabilities/checkout.ts +497 -0
  124. package/src/ucp/capabilities/identity.ts +425 -0
  125. package/src/ucp/capabilities/order.ts +549 -0
  126. package/src/ucp/index.ts +27 -0
  127. package/src/ucp/manifest.ts +257 -0
  128. package/src/ucp/types.ts +454 -0
  129. package/dist/cli-formatter.d.ts +0 -92
  130. package/dist/cli-formatter.d.ts.map +0 -1
  131. package/dist/cli-formatter.js +0 -486
  132. package/dist/cli-formatter.js.map +0 -1
  133. package/dist/context.d.ts +0 -6
  134. package/dist/context.d.ts.map +0 -1
  135. package/dist/context.js +0 -3
  136. package/dist/context.js.map +0 -1
  137. package/dist/elicit.d.ts +0 -93
  138. package/dist/elicit.d.ts.map +0 -1
  139. package/dist/elicit.js +0 -373
  140. package/dist/elicit.js.map +0 -1
  141. package/dist/mcp-client.d.ts +0 -218
  142. package/dist/mcp-client.d.ts.map +0 -1
  143. package/dist/mcp-client.js +0 -424
  144. package/dist/mcp-client.js.map +0 -1
  145. package/dist/mcp-sdk-transport.d.ts +0 -88
  146. package/dist/mcp-sdk-transport.d.ts.map +0 -1
  147. package/dist/mcp-sdk-transport.js +0 -360
  148. package/dist/mcp-sdk-transport.js.map +0 -1
  149. package/dist/photon-config.d.ts +0 -86
  150. package/dist/photon-config.d.ts.map +0 -1
  151. package/dist/photon-config.js +0 -156
  152. package/dist/photon-config.js.map +0 -1
  153. package/dist/progress.d.ts +0 -93
  154. package/dist/progress.d.ts.map +0 -1
  155. package/dist/progress.js +0 -195
  156. package/dist/progress.js.map +0 -1
  157. package/src/cli-formatter.ts +0 -579
  158. package/src/context.ts +0 -7
  159. package/src/elicit.ts +0 -438
  160. package/src/mcp-client.ts +0 -561
  161. package/src/mcp-sdk-transport.ts +0 -449
  162. package/src/photon-config.ts +0 -201
  163. package/src/progress.ts +0 -224
@@ -1,449 +0,0 @@
1
- /**
2
- * MCP SDK Transport for Photon Core
3
- *
4
- * Uses the official @modelcontextprotocol/sdk for connecting to MCP servers.
5
- * Supports multiple transports:
6
- * - stdio: Local processes (command + args)
7
- * - sse: Server-Sent Events over HTTP
8
- * - streamable-http: HTTP streaming
9
- * - websocket: WebSocket connections
10
- *
11
- * Configuration formats:
12
- * 1. stdio (local process):
13
- * { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] }
14
- *
15
- * 2. sse (HTTP SSE):
16
- * { "url": "http://localhost:3000/mcp", "transport": "sse" }
17
- *
18
- * 3. streamable-http:
19
- * { "url": "http://localhost:3000/mcp", "transport": "streamable-http" }
20
- *
21
- * 4. websocket:
22
- * { "url": "ws://localhost:3000/mcp", "transport": "websocket" }
23
- */
24
-
25
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
26
- import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
27
- import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
28
- import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
29
- import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js';
30
- import * as fs from 'fs/promises';
31
- import * as path from 'path';
32
- import * as os from 'os';
33
- import {
34
- MCPClient,
35
- MCPTransport,
36
- MCPClientFactory,
37
- MCPToolInfo,
38
- MCPToolResult,
39
- MCPNotConnectedError,
40
- MCPToolError,
41
- createMCPProxy,
42
- } from './mcp-client.js';
43
-
44
- /**
45
- * MCP Server configuration
46
- * Supports multiple transport types
47
- */
48
- export interface MCPServerConfig {
49
- // For stdio transport (local process)
50
- command?: string;
51
- args?: string[];
52
- cwd?: string;
53
- env?: Record<string, string>;
54
-
55
- // For HTTP/WS transports
56
- url?: string;
57
- transport?: 'stdio' | 'sse' | 'streamable-http' | 'websocket';
58
-
59
- // Authentication (for HTTP transports)
60
- headers?: Record<string, string>;
61
- }
62
-
63
- /**
64
- * Full MCP configuration file format
65
- */
66
- export interface MCPConfig {
67
- mcpServers: Record<string, MCPServerConfig>;
68
- }
69
-
70
- /**
71
- * Manages a single MCP server connection using official SDK
72
- */
73
- class SDKMCPConnection {
74
- private client: Client | null = null;
75
- private transport: any = null;
76
- private tools: MCPToolInfo[] = [];
77
- private initialized = false;
78
-
79
- constructor(
80
- private name: string,
81
- private config: MCPServerConfig,
82
- private verbose: boolean = false
83
- ) {}
84
-
85
- private log(message: string): void {
86
- if (this.verbose) {
87
- console.error(`[MCP:${this.name}] ${message}`);
88
- }
89
- }
90
-
91
- /**
92
- * Create appropriate transport based on config
93
- */
94
- private createTransport(): any {
95
- const transportType = this.config.transport || (this.config.command ? 'stdio' : 'sse');
96
-
97
- switch (transportType) {
98
- case 'stdio': {
99
- if (!this.config.command) {
100
- throw new Error(`stdio transport requires 'command' in config for ${this.name}`);
101
- }
102
- this.log(`Creating stdio transport: ${this.config.command} ${(this.config.args || []).join(' ')}`);
103
- return new StdioClientTransport({
104
- command: this.config.command,
105
- args: this.config.args,
106
- cwd: this.config.cwd,
107
- env: this.config.env,
108
- });
109
- }
110
-
111
- case 'sse': {
112
- if (!this.config.url) {
113
- throw new Error(`sse transport requires 'url' in config for ${this.name}`);
114
- }
115
- this.log(`Creating SSE transport: ${this.config.url}`);
116
- return new SSEClientTransport(new URL(this.config.url), {
117
- requestInit: this.config.headers ? { headers: this.config.headers } : undefined,
118
- });
119
- }
120
-
121
- case 'streamable-http': {
122
- if (!this.config.url) {
123
- throw new Error(`streamable-http transport requires 'url' in config for ${this.name}`);
124
- }
125
- this.log(`Creating streamable HTTP transport: ${this.config.url}`);
126
- return new StreamableHTTPClientTransport(new URL(this.config.url), {
127
- requestInit: this.config.headers ? { headers: this.config.headers } : undefined,
128
- });
129
- }
130
-
131
- case 'websocket': {
132
- if (!this.config.url) {
133
- throw new Error(`websocket transport requires 'url' in config for ${this.name}`);
134
- }
135
- this.log(`Creating WebSocket transport: ${this.config.url}`);
136
- return new WebSocketClientTransport(new URL(this.config.url));
137
- }
138
-
139
- default:
140
- throw new Error(`Unknown transport type: ${transportType}`);
141
- }
142
- }
143
-
144
- /**
145
- * Connect to the MCP server
146
- */
147
- async connect(): Promise<void> {
148
- if (this.client) {
149
- return; // Already connected
150
- }
151
-
152
- this.transport = this.createTransport();
153
- this.client = new Client(
154
- {
155
- name: 'photon-core',
156
- version: '1.0.0',
157
- },
158
- {
159
- capabilities: {
160
- roots: { listChanged: false },
161
- },
162
- }
163
- );
164
-
165
- this.log('Connecting...');
166
- await this.client.connect(this.transport);
167
- this.log('Connected');
168
-
169
- // List available tools
170
- const toolsResult = await this.client.listTools();
171
- this.tools = (toolsResult.tools || []).map((t: any) => ({
172
- name: t.name,
173
- description: t.description,
174
- inputSchema: t.inputSchema,
175
- }));
176
-
177
- this.log(`Loaded ${this.tools.length} tools`);
178
- this.initialized = true;
179
- }
180
-
181
- /**
182
- * Call a tool
183
- */
184
- async callTool(toolName: string, parameters: Record<string, any>): Promise<MCPToolResult> {
185
- if (!this.client || !this.initialized) {
186
- throw new MCPNotConnectedError(this.name);
187
- }
188
-
189
- try {
190
- const result = await this.client.callTool({
191
- name: toolName,
192
- arguments: parameters,
193
- });
194
-
195
- // Convert to MCPToolResult format
196
- if (result?.content && Array.isArray(result.content)) {
197
- return {
198
- content: result.content.map((c: any) => ({
199
- type: c.type || 'text',
200
- text: c.text,
201
- data: c.data,
202
- mimeType: c.mimeType,
203
- })),
204
- isError: result.isError as boolean | undefined,
205
- };
206
- }
207
-
208
- return {
209
- content: [{
210
- type: 'text',
211
- text: typeof result === 'string' ? result : JSON.stringify(result),
212
- }],
213
- isError: false,
214
- };
215
- } catch (error: any) {
216
- throw new MCPToolError(this.name, toolName, error.message);
217
- }
218
- }
219
-
220
- /**
221
- * List available tools
222
- */
223
- listTools(): MCPToolInfo[] {
224
- return this.tools;
225
- }
226
-
227
- /**
228
- * Check if connected
229
- */
230
- isConnected(): boolean {
231
- return this.initialized && this.client !== null;
232
- }
233
-
234
- /**
235
- * Disconnect
236
- */
237
- async disconnect(): Promise<void> {
238
- if (this.client) {
239
- this.log('Disconnecting...');
240
- await this.client.close();
241
- this.client = null;
242
- this.transport = null;
243
- this.initialized = false;
244
- }
245
- }
246
- }
247
-
248
- /**
249
- * SDK-based MCP Transport using official @modelcontextprotocol/sdk
250
- */
251
- export class SDKMCPTransport implements MCPTransport {
252
- private connections: Map<string, SDKMCPConnection> = new Map();
253
-
254
- constructor(
255
- private config: MCPConfig,
256
- private verbose: boolean = false
257
- ) {}
258
-
259
- private log(message: string): void {
260
- if (this.verbose) {
261
- console.error(`[MCPTransport] ${message}`);
262
- }
263
- }
264
-
265
- /**
266
- * Get or create connection to an MCP server
267
- */
268
- private async getConnection(mcpName: string): Promise<SDKMCPConnection> {
269
- let connection = this.connections.get(mcpName);
270
-
271
- if (connection?.isConnected()) {
272
- return connection;
273
- }
274
-
275
- const serverConfig = this.config.mcpServers[mcpName];
276
- if (!serverConfig) {
277
- throw new MCPNotConnectedError(mcpName);
278
- }
279
-
280
- connection = new SDKMCPConnection(mcpName, serverConfig, this.verbose);
281
- await connection.connect();
282
- this.connections.set(mcpName, connection);
283
-
284
- return connection;
285
- }
286
-
287
- async callTool(mcpName: string, toolName: string, parameters: Record<string, any>): Promise<MCPToolResult> {
288
- const connection = await this.getConnection(mcpName);
289
- return connection.callTool(toolName, parameters);
290
- }
291
-
292
- async listTools(mcpName: string): Promise<MCPToolInfo[]> {
293
- const connection = await this.getConnection(mcpName);
294
- return connection.listTools();
295
- }
296
-
297
- async isConnected(mcpName: string): Promise<boolean> {
298
- if (!this.config.mcpServers[mcpName]) {
299
- return false;
300
- }
301
- const connection = this.connections.get(mcpName);
302
- return connection?.isConnected() ?? false;
303
- }
304
-
305
- listServers(): string[] {
306
- return Object.keys(this.config.mcpServers);
307
- }
308
-
309
- async disconnectAll(): Promise<void> {
310
- for (const connection of this.connections.values()) {
311
- await connection.disconnect();
312
- }
313
- this.connections.clear();
314
- }
315
- }
316
-
317
- /**
318
- * SDK-based MCP Client Factory
319
- */
320
- export class SDKMCPClientFactory implements MCPClientFactory {
321
- private transport: SDKMCPTransport;
322
-
323
- constructor(config: MCPConfig, verbose: boolean = false) {
324
- this.transport = new SDKMCPTransport(config, verbose);
325
- }
326
-
327
- create(mcpName: string): MCPClient {
328
- return new MCPClient(mcpName, this.transport);
329
- }
330
-
331
- async listServers(): Promise<string[]> {
332
- return this.transport.listServers();
333
- }
334
-
335
- async disconnect(): Promise<void> {
336
- await this.transport.disconnectAll();
337
- }
338
-
339
- getTransport(): SDKMCPTransport {
340
- return this.transport;
341
- }
342
- }
343
-
344
- /**
345
- * Resolve an MCP source to a runnable configuration
346
- * Handles: GitHub shorthand, npm packages, URLs, local paths
347
- */
348
- export function resolveMCPSource(
349
- name: string,
350
- source: string,
351
- sourceType: 'github' | 'npm' | 'url' | 'local'
352
- ): MCPServerConfig {
353
- switch (sourceType) {
354
- case 'npm': {
355
- // npm:@scope/package or npm:package
356
- const packageName = source.replace(/^npm:/, '');
357
- return {
358
- command: 'npx',
359
- args: ['-y', packageName],
360
- transport: 'stdio',
361
- };
362
- }
363
-
364
- case 'github': {
365
- // GitHub shorthand: owner/repo
366
- // Try to run via npx assuming it's published to npm
367
- return {
368
- command: 'npx',
369
- args: ['-y', `@${source}`],
370
- transport: 'stdio',
371
- };
372
- }
373
-
374
- case 'url': {
375
- // Full URL - determine transport from protocol
376
- if (source.startsWith('ws://') || source.startsWith('wss://')) {
377
- return {
378
- url: source,
379
- transport: 'websocket',
380
- };
381
- }
382
- // Default to SSE for HTTP URLs
383
- return {
384
- url: source,
385
- transport: 'sse',
386
- };
387
- }
388
-
389
- case 'local': {
390
- // Local path - run directly with node
391
- const resolvedPath = source.replace(/^~/, process.env.HOME || '');
392
- return {
393
- command: 'node',
394
- args: [resolvedPath],
395
- transport: 'stdio',
396
- };
397
- }
398
-
399
- default:
400
- throw new Error(`Unknown MCP source type: ${sourceType}`);
401
- }
402
- }
403
-
404
- /**
405
- * Load MCP configuration from standard locations
406
- */
407
- export async function loadMCPConfig(verbose: boolean = false): Promise<MCPConfig> {
408
- const log = verbose ? (msg: string) => console.error(`[MCPConfig] ${msg}`) : () => {};
409
-
410
- const configPaths = [
411
- process.env.PHOTON_MCP_CONFIG,
412
- path.join(process.cwd(), 'photon.mcp.json'),
413
- path.join(os.homedir(), '.config', 'photon', 'mcp.json'),
414
- path.join(os.homedir(), '.photon', 'mcp.json'),
415
- ].filter(Boolean) as string[];
416
-
417
- for (const configPath of configPaths) {
418
- try {
419
- const content = await fs.readFile(configPath, 'utf-8');
420
- const config = JSON.parse(content) as MCPConfig;
421
-
422
- if (config.mcpServers && typeof config.mcpServers === 'object') {
423
- log(`Loaded MCP config from ${configPath}`);
424
- log(`Found ${Object.keys(config.mcpServers).length} MCP servers`);
425
- return config;
426
- }
427
- } catch (error: any) {
428
- if (error.code !== 'ENOENT') {
429
- log(`Failed to load ${configPath}: ${error.message}`);
430
- }
431
- }
432
- }
433
-
434
- log('No MCP config found, MCP access will be unavailable');
435
- return { mcpServers: {} };
436
- }
437
-
438
- /**
439
- * Create an SDK-based MCP client factory from default config
440
- */
441
- export async function createSDKMCPClientFactory(
442
- verbose: boolean = false
443
- ): Promise<SDKMCPClientFactory> {
444
- const config = await loadMCPConfig(verbose);
445
- return new SDKMCPClientFactory(config, verbose);
446
- }
447
-
448
- // Re-export for convenience
449
- export { MCPClient, createMCPProxy };
@@ -1,201 +0,0 @@
1
- /**
2
- * Photon Runtime Configuration
3
- *
4
- * Manages ~/.photon/mcp-servers.json for MCP server configuration.
5
- * Compatible with Claude Desktop's mcpServers format.
6
- */
7
-
8
- import * as fs from 'fs/promises';
9
- import * as path from 'path';
10
- import * as os from 'os';
11
- import type { MCPConfig, MCPServerConfig } from './mcp-sdk-transport.js';
12
-
13
- /**
14
- * Default config directory
15
- */
16
- export const PHOTON_CONFIG_DIR = path.join(os.homedir(), '.photon');
17
-
18
- /**
19
- * Default MCP servers config file
20
- */
21
- export const MCP_SERVERS_CONFIG_FILE = path.join(PHOTON_CONFIG_DIR, 'mcp-servers.json');
22
-
23
- /**
24
- * Photon MCP servers configuration file format
25
- * Compatible with Claude Desktop's mcpServers format
26
- */
27
- export interface PhotonMCPConfig {
28
- mcpServers: Record<string, MCPServerConfig>;
29
- }
30
-
31
- /**
32
- * Load MCP servers configuration from ~/.photon/mcp-servers.json
33
- *
34
- * @param configPath Optional custom config path (defaults to ~/.photon/mcp-servers.json)
35
- * @returns The MCP configuration, or empty config if file doesn't exist
36
- */
37
- export async function loadPhotonMCPConfig(configPath?: string): Promise<PhotonMCPConfig> {
38
- const filePath = configPath || MCP_SERVERS_CONFIG_FILE;
39
-
40
- try {
41
- const content = await fs.readFile(filePath, 'utf-8');
42
- const config = JSON.parse(content) as PhotonMCPConfig;
43
-
44
- // Validate structure
45
- if (!config.mcpServers || typeof config.mcpServers !== 'object') {
46
- console.error(`Invalid config format in ${filePath}: missing mcpServers`);
47
- return { mcpServers: {} };
48
- }
49
-
50
- return config;
51
- } catch (error: any) {
52
- if (error.code === 'ENOENT') {
53
- // File doesn't exist - return empty config
54
- return { mcpServers: {} };
55
- }
56
- console.error(`Failed to load config from ${filePath}: ${error.message}`);
57
- return { mcpServers: {} };
58
- }
59
- }
60
-
61
- /**
62
- * Save MCP servers configuration to ~/.photon/mcp-servers.json
63
- *
64
- * @param config The configuration to save
65
- * @param configPath Optional custom config path
66
- */
67
- export async function savePhotonMCPConfig(
68
- config: PhotonMCPConfig,
69
- configPath?: string
70
- ): Promise<void> {
71
- const filePath = configPath || MCP_SERVERS_CONFIG_FILE;
72
- const dir = path.dirname(filePath);
73
-
74
- // Ensure directory exists
75
- await fs.mkdir(dir, { recursive: true });
76
-
77
- // Write config with pretty formatting
78
- await fs.writeFile(filePath, JSON.stringify(config, null, 2), 'utf-8');
79
- }
80
-
81
- /**
82
- * Check if an MCP server is configured
83
- *
84
- * @param mcpName The MCP server name to check
85
- * @param config Optional pre-loaded config (loads from file if not provided)
86
- */
87
- export async function isMCPConfigured(
88
- mcpName: string,
89
- config?: PhotonMCPConfig
90
- ): Promise<boolean> {
91
- const cfg = config || await loadPhotonMCPConfig();
92
- return mcpName in cfg.mcpServers;
93
- }
94
-
95
- /**
96
- * Get configuration for a specific MCP server
97
- *
98
- * @param mcpName The MCP server name
99
- * @param config Optional pre-loaded config
100
- * @returns The server config or undefined if not found
101
- */
102
- export async function getMCPServerConfig(
103
- mcpName: string,
104
- config?: PhotonMCPConfig
105
- ): Promise<MCPServerConfig | undefined> {
106
- const cfg = config || await loadPhotonMCPConfig();
107
- return cfg.mcpServers[mcpName];
108
- }
109
-
110
- /**
111
- * Add or update an MCP server configuration
112
- *
113
- * @param mcpName The MCP server name
114
- * @param serverConfig The server configuration
115
- * @param configPath Optional custom config path
116
- */
117
- export async function setMCPServerConfig(
118
- mcpName: string,
119
- serverConfig: MCPServerConfig,
120
- configPath?: string
121
- ): Promise<void> {
122
- const config = await loadPhotonMCPConfig(configPath);
123
- config.mcpServers[mcpName] = serverConfig;
124
- await savePhotonMCPConfig(config, configPath);
125
- }
126
-
127
- /**
128
- * Remove an MCP server configuration
129
- *
130
- * @param mcpName The MCP server name to remove
131
- * @param configPath Optional custom config path
132
- */
133
- export async function removeMCPServerConfig(
134
- mcpName: string,
135
- configPath?: string
136
- ): Promise<void> {
137
- const config = await loadPhotonMCPConfig(configPath);
138
- delete config.mcpServers[mcpName];
139
- await savePhotonMCPConfig(config, configPath);
140
- }
141
-
142
- /**
143
- * List all configured MCP servers
144
- *
145
- * @param configPath Optional custom config path
146
- * @returns Array of MCP server names
147
- */
148
- export async function listMCPServers(configPath?: string): Promise<string[]> {
149
- const config = await loadPhotonMCPConfig(configPath);
150
- return Object.keys(config.mcpServers);
151
- }
152
-
153
- /**
154
- * Convert PhotonMCPConfig to MCPConfig (for SDK transport)
155
- */
156
- export function toMCPConfig(config: PhotonMCPConfig): MCPConfig {
157
- return {
158
- mcpServers: config.mcpServers,
159
- };
160
- }
161
-
162
- /**
163
- * Merge environment variables into MCP server config
164
- * Supports ${VAR_NAME} syntax for env var references
165
- *
166
- * @param serverConfig The server config to process
167
- * @returns Config with env vars resolved
168
- */
169
- export function resolveEnvVars(serverConfig: MCPServerConfig): MCPServerConfig {
170
- const resolved = { ...serverConfig };
171
-
172
- // Process env object if present
173
- if (resolved.env) {
174
- const processedEnv: Record<string, string> = {};
175
- for (const [key, value] of Object.entries(resolved.env)) {
176
- processedEnv[key] = resolveEnvValue(value);
177
- }
178
- resolved.env = processedEnv;
179
- }
180
-
181
- // Process args if present
182
- if (resolved.args) {
183
- resolved.args = resolved.args.map(resolveEnvValue);
184
- }
185
-
186
- // Process url if present
187
- if (resolved.url) {
188
- resolved.url = resolveEnvValue(resolved.url);
189
- }
190
-
191
- return resolved;
192
- }
193
-
194
- /**
195
- * Resolve ${VAR_NAME} references in a string value
196
- */
197
- function resolveEnvValue(value: string): string {
198
- return value.replace(/\$\{([^}]+)\}/g, (_, varName) => {
199
- return process.env[varName] || '';
200
- });
201
- }