@portel/photon-core 1.3.0 → 1.4.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/generator.d.ts +236 -2
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +30 -0
- package/dist/generator.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp-client.d.ts +39 -0
- package/dist/mcp-client.d.ts.map +1 -1
- package/dist/mcp-client.js +213 -0
- package/dist/mcp-client.js.map +1 -1
- package/dist/photon-config.d.ts +86 -0
- package/dist/photon-config.d.ts.map +1 -0
- package/dist/photon-config.js +156 -0
- package/dist/photon-config.js.map +1 -0
- package/dist/schema-extractor.d.ts +99 -1
- package/dist/schema-extractor.d.ts.map +1 -1
- package/dist/schema-extractor.js +311 -5
- package/dist/schema-extractor.js.map +1 -1
- package/dist/stateful.d.ts +238 -0
- package/dist/stateful.d.ts.map +1 -0
- package/dist/stateful.js +469 -0
- package/dist/stateful.js.map +1 -0
- package/dist/types.d.ts +260 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -2
- package/src/generator.ts +270 -2
- package/src/index.ts +73 -1
- package/src/mcp-client.ts +254 -0
- package/src/photon-config.ts +201 -0
- package/src/schema-extractor.ts +353 -6
- package/src/stateful.ts +659 -0
- package/src/types.ts +289 -0
package/src/index.ts
CHANGED
|
@@ -83,11 +83,14 @@ export {
|
|
|
83
83
|
MCPError,
|
|
84
84
|
MCPNotConnectedError,
|
|
85
85
|
MCPToolError,
|
|
86
|
+
MCPConfigurationError,
|
|
86
87
|
createMCPProxy,
|
|
87
88
|
type MCPToolInfo,
|
|
88
89
|
type MCPToolResult,
|
|
89
90
|
type MCPTransport,
|
|
90
91
|
type MCPClientFactory,
|
|
92
|
+
type MCPSourceType,
|
|
93
|
+
type MissingMCPInfo,
|
|
91
94
|
} from './mcp-client.js';
|
|
92
95
|
|
|
93
96
|
// MCP SDK Transport - official SDK-based transport implementation
|
|
@@ -107,6 +110,7 @@ export {
|
|
|
107
110
|
// Type guards - check yield direction
|
|
108
111
|
isAskYield,
|
|
109
112
|
isEmitYield,
|
|
113
|
+
isCheckpointYield,
|
|
110
114
|
getAskType,
|
|
111
115
|
getEmitType,
|
|
112
116
|
|
|
@@ -136,6 +140,17 @@ export {
|
|
|
136
140
|
type AskNumber,
|
|
137
141
|
type AskFile,
|
|
138
142
|
type AskDate,
|
|
143
|
+
type AskForm,
|
|
144
|
+
type AskUrl,
|
|
145
|
+
|
|
146
|
+
// Form schema types (for AskForm)
|
|
147
|
+
type FormSchema,
|
|
148
|
+
type FormSchemaProperty,
|
|
149
|
+
type FormSchemaArrayProperty,
|
|
150
|
+
|
|
151
|
+
// MCP elicitation result types
|
|
152
|
+
type ElicitAction,
|
|
153
|
+
type FormElicitResult,
|
|
139
154
|
|
|
140
155
|
// Emit yield types (output to user)
|
|
141
156
|
type EmitYield,
|
|
@@ -146,9 +161,14 @@ export {
|
|
|
146
161
|
type EmitToast,
|
|
147
162
|
type EmitThinking,
|
|
148
163
|
type EmitArtifact,
|
|
164
|
+
type EmitUI,
|
|
149
165
|
|
|
150
|
-
//
|
|
166
|
+
// Checkpoint yield type (for stateful workflows)
|
|
167
|
+
type CheckpointYield,
|
|
168
|
+
|
|
169
|
+
// Combined types
|
|
151
170
|
type PhotonYield,
|
|
171
|
+
type StatefulYield,
|
|
152
172
|
|
|
153
173
|
// Execution config
|
|
154
174
|
type InputProvider,
|
|
@@ -191,3 +211,55 @@ export {
|
|
|
191
211
|
type ElicitHandler,
|
|
192
212
|
type PromptHandler,
|
|
193
213
|
} from './elicit.js';
|
|
214
|
+
|
|
215
|
+
// Photon Runtime Configuration - ~/.photon/mcp-servers.json
|
|
216
|
+
export {
|
|
217
|
+
// Constants
|
|
218
|
+
PHOTON_CONFIG_DIR,
|
|
219
|
+
MCP_SERVERS_CONFIG_FILE,
|
|
220
|
+
// Load/Save
|
|
221
|
+
loadPhotonMCPConfig,
|
|
222
|
+
savePhotonMCPConfig,
|
|
223
|
+
// Query
|
|
224
|
+
isMCPConfigured,
|
|
225
|
+
getMCPServerConfig,
|
|
226
|
+
listMCPServers,
|
|
227
|
+
// Modify
|
|
228
|
+
setMCPServerConfig,
|
|
229
|
+
removeMCPServerConfig,
|
|
230
|
+
// Utilities
|
|
231
|
+
toMCPConfig,
|
|
232
|
+
resolveEnvVars,
|
|
233
|
+
// Types
|
|
234
|
+
type PhotonMCPConfig,
|
|
235
|
+
} from './photon-config.js';
|
|
236
|
+
|
|
237
|
+
// Stateful Workflow Execution - JSONL persistence with checkpoints
|
|
238
|
+
export {
|
|
239
|
+
// Constants
|
|
240
|
+
RUNS_DIR,
|
|
241
|
+
|
|
242
|
+
// State Log - JSONL persistence
|
|
243
|
+
StateLog,
|
|
244
|
+
|
|
245
|
+
// Resume state parsing
|
|
246
|
+
parseResumeState,
|
|
247
|
+
|
|
248
|
+
// Stateful executor
|
|
249
|
+
executeStatefulGenerator,
|
|
250
|
+
generateRunId,
|
|
251
|
+
|
|
252
|
+
// Run management
|
|
253
|
+
listRuns,
|
|
254
|
+
getRunInfo,
|
|
255
|
+
deleteRun,
|
|
256
|
+
cleanupRuns,
|
|
257
|
+
|
|
258
|
+
// Types re-exported from stateful.ts
|
|
259
|
+
type CheckpointYield as StatefulCheckpointYield,
|
|
260
|
+
type StatefulYield as StatefulWorkflowYield,
|
|
261
|
+
isCheckpointYield as isStatefulCheckpointYield,
|
|
262
|
+
type ResumeState,
|
|
263
|
+
type StatefulExecutorConfig,
|
|
264
|
+
type StatefulExecutionResult,
|
|
265
|
+
} from './stateful.js';
|
package/src/mcp-client.ts
CHANGED
|
@@ -282,6 +282,260 @@ export class MCPToolError extends MCPError {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
/**
|
|
286
|
+
* MCP source type - how the MCP was declared
|
|
287
|
+
*/
|
|
288
|
+
export type MCPSourceType = 'npm' | 'github' | 'local' | 'url' | 'unknown';
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Information about a missing MCP dependency
|
|
292
|
+
*/
|
|
293
|
+
export interface MissingMCPInfo {
|
|
294
|
+
name: string;
|
|
295
|
+
source: string;
|
|
296
|
+
sourceType: MCPSourceType;
|
|
297
|
+
declaredIn?: string; // Photon file that declared this dependency
|
|
298
|
+
originalError?: string;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Error thrown when MCP is not configured correctly
|
|
303
|
+
* Provides detailed, actionable guidance for users
|
|
304
|
+
*/
|
|
305
|
+
export class MCPConfigurationError extends MCPError {
|
|
306
|
+
public readonly configPath: string;
|
|
307
|
+
public readonly missingMCPs: MissingMCPInfo[];
|
|
308
|
+
|
|
309
|
+
constructor(missingMCPs: MissingMCPInfo[]) {
|
|
310
|
+
const configPath = `~/.photon/mcp-servers.json`;
|
|
311
|
+
const message = MCPConfigurationError.formatMessage(missingMCPs, configPath);
|
|
312
|
+
super(missingMCPs[0]?.name || 'unknown', message);
|
|
313
|
+
this.name = 'MCPConfigurationError';
|
|
314
|
+
this.configPath = configPath;
|
|
315
|
+
this.missingMCPs = missingMCPs;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Format detailed error message with configuration instructions
|
|
320
|
+
*/
|
|
321
|
+
private static formatMessage(missingMCPs: MissingMCPInfo[], configPath: string): string {
|
|
322
|
+
const lines: string[] = [
|
|
323
|
+
'',
|
|
324
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
|
325
|
+
'❌ MCP Configuration Required',
|
|
326
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
|
327
|
+
'',
|
|
328
|
+
];
|
|
329
|
+
|
|
330
|
+
// List missing MCPs
|
|
331
|
+
lines.push(`The following MCP server${missingMCPs.length > 1 ? 's are' : ' is'} required but not configured:`);
|
|
332
|
+
lines.push('');
|
|
333
|
+
|
|
334
|
+
for (const mcp of missingMCPs) {
|
|
335
|
+
lines.push(` • ${mcp.name}`);
|
|
336
|
+
if (mcp.source) {
|
|
337
|
+
lines.push(` Source: ${mcp.source} (${mcp.sourceType})`);
|
|
338
|
+
}
|
|
339
|
+
if (mcp.declaredIn) {
|
|
340
|
+
lines.push(` Declared in: ${mcp.declaredIn}`);
|
|
341
|
+
}
|
|
342
|
+
if (mcp.originalError) {
|
|
343
|
+
lines.push(` Error: ${mcp.originalError}`);
|
|
344
|
+
}
|
|
345
|
+
lines.push('');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Configuration instructions
|
|
349
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
350
|
+
lines.push('🔧 How to Fix');
|
|
351
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
352
|
+
lines.push('');
|
|
353
|
+
lines.push(`Add the following to ${configPath}:`);
|
|
354
|
+
lines.push('');
|
|
355
|
+
|
|
356
|
+
// Generate example config
|
|
357
|
+
const exampleConfig = MCPConfigurationError.generateExampleConfig(missingMCPs);
|
|
358
|
+
lines.push(exampleConfig);
|
|
359
|
+
lines.push('');
|
|
360
|
+
|
|
361
|
+
// Step-by-step instructions
|
|
362
|
+
lines.push('Steps:');
|
|
363
|
+
lines.push(` 1. Create or edit ${configPath}`);
|
|
364
|
+
lines.push(' 2. Add the configuration above');
|
|
365
|
+
lines.push(' 3. Replace placeholder values with your actual configuration');
|
|
366
|
+
lines.push(' 4. Restart the Photon');
|
|
367
|
+
lines.push('');
|
|
368
|
+
|
|
369
|
+
// Per-source-type guidance
|
|
370
|
+
const uniqueTypes = new Set(missingMCPs.map(m => m.sourceType));
|
|
371
|
+
if (uniqueTypes.size > 0) {
|
|
372
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
373
|
+
lines.push('📖 Configuration Guide');
|
|
374
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
375
|
+
lines.push('');
|
|
376
|
+
|
|
377
|
+
for (const type of uniqueTypes) {
|
|
378
|
+
lines.push(...MCPConfigurationError.getSourceTypeGuide(type));
|
|
379
|
+
lines.push('');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
384
|
+
|
|
385
|
+
return lines.join('\n');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Generate example JSON config for missing MCPs
|
|
390
|
+
*/
|
|
391
|
+
private static generateExampleConfig(missingMCPs: MissingMCPInfo[]): string {
|
|
392
|
+
const servers: Record<string, any> = {};
|
|
393
|
+
|
|
394
|
+
for (const mcp of missingMCPs) {
|
|
395
|
+
servers[mcp.name] = MCPConfigurationError.getExampleServerConfig(mcp);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const config = { mcpServers: servers };
|
|
399
|
+
return JSON.stringify(config, null, 2)
|
|
400
|
+
.split('\n')
|
|
401
|
+
.map(line => ' ' + line)
|
|
402
|
+
.join('\n');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get example server config based on source type
|
|
407
|
+
*/
|
|
408
|
+
private static getExampleServerConfig(mcp: MissingMCPInfo): Record<string, any> {
|
|
409
|
+
switch (mcp.sourceType) {
|
|
410
|
+
case 'npm':
|
|
411
|
+
return {
|
|
412
|
+
command: 'npx',
|
|
413
|
+
args: ['-y', mcp.source],
|
|
414
|
+
env: {
|
|
415
|
+
'// Add required environment variables here': '',
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
case 'github': {
|
|
420
|
+
// Parse github source: owner/repo or owner/repo#branch
|
|
421
|
+
const [repo, branch] = mcp.source.split('#');
|
|
422
|
+
const args = ['-y', `github:${repo}`];
|
|
423
|
+
if (branch) {
|
|
424
|
+
args[1] = `github:${repo}#${branch}`;
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
command: 'npx',
|
|
428
|
+
args,
|
|
429
|
+
env: {
|
|
430
|
+
'// Add required environment variables here': '',
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
case 'url':
|
|
436
|
+
if (mcp.source.startsWith('ws://') || mcp.source.startsWith('wss://')) {
|
|
437
|
+
return {
|
|
438
|
+
url: mcp.source,
|
|
439
|
+
transport: 'websocket',
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
url: mcp.source,
|
|
444
|
+
transport: 'sse',
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
case 'local':
|
|
448
|
+
return {
|
|
449
|
+
command: mcp.source,
|
|
450
|
+
args: [],
|
|
451
|
+
cwd: '// Optional: working directory',
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
default:
|
|
455
|
+
return {
|
|
456
|
+
'// Configure this MCP server': '',
|
|
457
|
+
command: 'npx',
|
|
458
|
+
args: ['-y', '<package-name>'],
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Get source-type specific guidance
|
|
465
|
+
*/
|
|
466
|
+
private static getSourceTypeGuide(type: MCPSourceType): string[] {
|
|
467
|
+
switch (type) {
|
|
468
|
+
case 'npm':
|
|
469
|
+
return [
|
|
470
|
+
'📦 NPM Packages:',
|
|
471
|
+
' MCP servers from npm are run via npx.',
|
|
472
|
+
' Example: @modelcontextprotocol/server-github',
|
|
473
|
+
'',
|
|
474
|
+
' {',
|
|
475
|
+
' "command": "npx",',
|
|
476
|
+
' "args": ["-y", "@modelcontextprotocol/server-github"],',
|
|
477
|
+
' "env": {',
|
|
478
|
+
' "GITHUB_TOKEN": "ghp_your_token_here"',
|
|
479
|
+
' }',
|
|
480
|
+
' }',
|
|
481
|
+
];
|
|
482
|
+
|
|
483
|
+
case 'github':
|
|
484
|
+
return [
|
|
485
|
+
'🐙 GitHub Repositories:',
|
|
486
|
+
' MCP servers from GitHub repos are cloned and run.',
|
|
487
|
+
' Format: owner/repo or owner/repo#branch',
|
|
488
|
+
'',
|
|
489
|
+
' {',
|
|
490
|
+
' "command": "npx",',
|
|
491
|
+
' "args": ["-y", "github:anthropics/mcp-server-github"],',
|
|
492
|
+
' "env": {',
|
|
493
|
+
' "GITHUB_TOKEN": "ghp_your_token_here"',
|
|
494
|
+
' }',
|
|
495
|
+
' }',
|
|
496
|
+
];
|
|
497
|
+
|
|
498
|
+
case 'url':
|
|
499
|
+
return [
|
|
500
|
+
'🌐 Remote URLs:',
|
|
501
|
+
' MCP servers running on remote hosts.',
|
|
502
|
+
'',
|
|
503
|
+
' HTTP/SSE:',
|
|
504
|
+
' {',
|
|
505
|
+
' "url": "https://mcp.example.com/api",',
|
|
506
|
+
' "transport": "sse",',
|
|
507
|
+
' "headers": { "Authorization": "Bearer token" }',
|
|
508
|
+
' }',
|
|
509
|
+
'',
|
|
510
|
+
' WebSocket:',
|
|
511
|
+
' {',
|
|
512
|
+
' "url": "wss://mcp.example.com/ws",',
|
|
513
|
+
' "transport": "websocket"',
|
|
514
|
+
' }',
|
|
515
|
+
];
|
|
516
|
+
|
|
517
|
+
case 'local':
|
|
518
|
+
return [
|
|
519
|
+
'💻 Local Commands:',
|
|
520
|
+
' MCP servers running as local processes.',
|
|
521
|
+
'',
|
|
522
|
+
' {',
|
|
523
|
+
' "command": "/path/to/mcp-server",',
|
|
524
|
+
' "args": ["--port", "3000"],',
|
|
525
|
+
' "cwd": "/working/directory",',
|
|
526
|
+
' "env": { "CONFIG": "value" }',
|
|
527
|
+
' }',
|
|
528
|
+
];
|
|
529
|
+
|
|
530
|
+
default:
|
|
531
|
+
return [
|
|
532
|
+
'⚙️ Custom Configuration:',
|
|
533
|
+
' Configure the MCP server based on its documentation.',
|
|
534
|
+
];
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
285
539
|
/**
|
|
286
540
|
* Create a proxy-based MCP client that allows direct method calls
|
|
287
541
|
*
|
|
@@ -0,0 +1,201 @@
|
|
|
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
|
+
}
|