@portel/photon-core 1.2.0 → 1.3.1
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 +81 -0
- package/dist/base.d.ts +67 -0
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +92 -0
- package/dist/base.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-client.d.ts +179 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +211 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/mcp-sdk-transport.d.ts +88 -0
- package/dist/mcp-sdk-transport.d.ts.map +1 -0
- package/dist/mcp-sdk-transport.js +360 -0
- package/dist/mcp-sdk-transport.js.map +1 -0
- package/dist/schema-extractor.d.ts +26 -1
- package/dist/schema-extractor.d.ts.map +1 -1
- package/dist/schema-extractor.js +60 -0
- package/dist/schema-extractor.js.map +1 -1
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +5 -2
- package/src/base.ts +103 -0
- package/src/index.ts +24 -0
- package/src/mcp-client.ts +307 -0
- package/src/mcp-sdk-transport.ts +449 -0
- package/src/schema-extractor.ts +71 -1
- package/src/types.ts +34 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Protocol Client for Photons
|
|
3
|
+
*
|
|
4
|
+
* Enables Photons to call external MCPs via the MCP protocol.
|
|
5
|
+
* This is runtime-agnostic - the actual transport is provided by the runtime (NCP, Lumina, etc.)
|
|
6
|
+
*
|
|
7
|
+
* Usage in Photon:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* export default class MyPhoton extends PhotonMCP {
|
|
10
|
+
* async doSomething() {
|
|
11
|
+
* const github = this.mcp('github');
|
|
12
|
+
* const issues = await github.call('list_issues', { repo: 'foo/bar' });
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* MCP Client - Protocol wrapper for calling external MCPs
|
|
19
|
+
*
|
|
20
|
+
* Provides a clean async interface for Photons to call MCP tools.
|
|
21
|
+
* The actual protocol communication is handled by the transport layer.
|
|
22
|
+
*/
|
|
23
|
+
export class MCPClient {
|
|
24
|
+
mcpName;
|
|
25
|
+
transport;
|
|
26
|
+
toolsCache = null;
|
|
27
|
+
constructor(mcpName, transport) {
|
|
28
|
+
this.mcpName = mcpName;
|
|
29
|
+
this.transport = transport;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get the MCP server name
|
|
33
|
+
*/
|
|
34
|
+
get name() {
|
|
35
|
+
return this.mcpName;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Call a tool on this MCP server
|
|
39
|
+
*
|
|
40
|
+
* @param toolName The tool to call
|
|
41
|
+
* @param parameters Tool parameters
|
|
42
|
+
* @returns Tool result (parsed from MCP response)
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const github = this.mcp('github');
|
|
47
|
+
* const issues = await github.call('list_issues', { repo: 'owner/repo', state: 'open' });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
async call(toolName, parameters = {}) {
|
|
51
|
+
// Check connection first
|
|
52
|
+
const connected = await this.transport.isConnected(this.mcpName);
|
|
53
|
+
if (!connected) {
|
|
54
|
+
throw new MCPNotConnectedError(this.mcpName);
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const result = await this.transport.callTool(this.mcpName, toolName, parameters);
|
|
58
|
+
if (result.isError) {
|
|
59
|
+
const errorText = result.content.find(c => c.type === 'text')?.text || 'Unknown error';
|
|
60
|
+
throw new MCPToolError(this.mcpName, toolName, errorText);
|
|
61
|
+
}
|
|
62
|
+
// Extract and parse the result
|
|
63
|
+
return this.parseResult(result);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof MCPError) {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
throw new MCPToolError(this.mcpName, toolName, error instanceof Error ? error.message : String(error));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* List all available tools on this MCP server
|
|
74
|
+
*
|
|
75
|
+
* @returns Array of tool information
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const github = this.mcp('github');
|
|
80
|
+
* const tools = await github.list();
|
|
81
|
+
* // [{ name: 'list_issues', description: '...' }, ...]
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
async list() {
|
|
85
|
+
if (this.toolsCache) {
|
|
86
|
+
return this.toolsCache;
|
|
87
|
+
}
|
|
88
|
+
const connected = await this.transport.isConnected(this.mcpName);
|
|
89
|
+
if (!connected) {
|
|
90
|
+
throw new MCPNotConnectedError(this.mcpName);
|
|
91
|
+
}
|
|
92
|
+
this.toolsCache = await this.transport.listTools(this.mcpName);
|
|
93
|
+
return this.toolsCache;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Find tools matching a query
|
|
97
|
+
*
|
|
98
|
+
* @param query Search query (matches name or description)
|
|
99
|
+
* @returns Matching tools
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const github = this.mcp('github');
|
|
104
|
+
* const issueTools = await github.find('issue');
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
async find(query) {
|
|
108
|
+
const tools = await this.list();
|
|
109
|
+
const lowerQuery = query.toLowerCase();
|
|
110
|
+
return tools.filter(t => t.name.toLowerCase().includes(lowerQuery) ||
|
|
111
|
+
t.description?.toLowerCase().includes(lowerQuery));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if this MCP server is connected
|
|
115
|
+
*/
|
|
116
|
+
async isConnected() {
|
|
117
|
+
return this.transport.isConnected(this.mcpName);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Clear the tools cache (useful after reconnection)
|
|
121
|
+
*/
|
|
122
|
+
clearCache() {
|
|
123
|
+
this.toolsCache = null;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Parse MCP tool result into a usable value
|
|
127
|
+
*/
|
|
128
|
+
parseResult(result) {
|
|
129
|
+
if (!result.content || result.content.length === 0) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
// Single text result - try to parse as JSON
|
|
133
|
+
if (result.content.length === 1 && result.content[0].type === 'text') {
|
|
134
|
+
const text = result.content[0].text || '';
|
|
135
|
+
try {
|
|
136
|
+
return JSON.parse(text);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return text;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Multiple results or non-text - return as-is
|
|
143
|
+
return result.content.map(c => {
|
|
144
|
+
if (c.type === 'text') {
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(c.text || '');
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return c.text;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return c;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Base class for MCP-related errors
|
|
158
|
+
*/
|
|
159
|
+
export class MCPError extends Error {
|
|
160
|
+
mcpName;
|
|
161
|
+
constructor(mcpName, message) {
|
|
162
|
+
super(message);
|
|
163
|
+
this.mcpName = mcpName;
|
|
164
|
+
this.name = 'MCPError';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Error thrown when MCP server is not connected
|
|
169
|
+
*/
|
|
170
|
+
export class MCPNotConnectedError extends MCPError {
|
|
171
|
+
constructor(mcpName) {
|
|
172
|
+
super(mcpName, `MCP server '${mcpName}' is not connected`);
|
|
173
|
+
this.name = 'MCPNotConnectedError';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Error thrown when MCP tool call fails
|
|
178
|
+
*/
|
|
179
|
+
export class MCPToolError extends MCPError {
|
|
180
|
+
toolName;
|
|
181
|
+
details;
|
|
182
|
+
constructor(mcpName, toolName, details) {
|
|
183
|
+
super(mcpName, `MCP tool '${mcpName}:${toolName}' failed: ${details}`);
|
|
184
|
+
this.toolName = toolName;
|
|
185
|
+
this.details = details;
|
|
186
|
+
this.name = 'MCPToolError';
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Create a proxy-based MCP client that allows direct method calls
|
|
191
|
+
*
|
|
192
|
+
* This enables a more fluent API:
|
|
193
|
+
* ```typescript
|
|
194
|
+
* const github = this.mcp('github');
|
|
195
|
+
* // Instead of: await github.call('list_issues', { repo: 'foo/bar' })
|
|
196
|
+
* // You can do: await github.list_issues({ repo: 'foo/bar' })
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export function createMCPProxy(client) {
|
|
200
|
+
return new Proxy(client, {
|
|
201
|
+
get(target, prop) {
|
|
202
|
+
// Return existing methods
|
|
203
|
+
if (prop in target) {
|
|
204
|
+
return target[prop];
|
|
205
|
+
}
|
|
206
|
+
// Return a function that calls the tool
|
|
207
|
+
return (params = {}) => target.call(prop, params);
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=mcp-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-client.js","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA2EH;;;;;GAKG;AACH,MAAM,OAAO,SAAS;IAIV;IACA;IAJF,UAAU,GAAyB,IAAI,CAAC;IAEhD,YACU,OAAe,EACf,SAAuB;QADvB,YAAO,GAAP,OAAO,CAAQ;QACf,cAAS,GAAT,SAAS,CAAc;IAC9B,CAAC;IAEJ;;OAEG;IACH,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,IAAI,CAAC,QAAgB,EAAE,aAAkC,EAAE;QAC/D,yBAAyB;QACzB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/C,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;YAEjF,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,IAAI,IAAI,eAAe,CAAC;gBACvF,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;YAC5D,CAAC;YAED,+BAA+B;YAC/B,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;gBAC9B,MAAM,KAAK,CAAC;YACd,CAAC;YACD,MAAM,IAAI,YAAY,CACpB,IAAI,CAAC,OAAO,EACZ,QAAQ,EACR,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACvD,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,OAAO,IAAI,CAAC,UAAU,CAAC;QACzB,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/C,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/D,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,IAAI,CAAC,KAAa;QACtB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAChC,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC,MAAM,CACjB,CAAC,CAAC,EAAE,CACF,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC;YACzC,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CACpD,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW;QACf,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IACzB,CAAC;IAED;;OAEG;IACK,WAAW,CAAC,MAAqB;QACvC,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,4CAA4C;QAC5C,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACrE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;YAC1C,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAED,8CAA8C;QAC9C,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YAC5B,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACtB,IAAI,CAAC;oBACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;gBAClC,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,CAAC,CAAC,IAAI,CAAC;gBAChB,CAAC;YACH,CAAC;YACD,OAAO,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IAEf;IADlB,YACkB,OAAe,EAC/B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,YAAO,GAAP,OAAO,CAAQ;QAI/B,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,oBAAqB,SAAQ,QAAQ;IAChD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,eAAe,OAAO,oBAAoB,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,QAAQ;IAGtB;IACA;IAHlB,YACE,OAAe,EACC,QAAgB,EAChB,OAAe;QAE/B,KAAK,CAAC,OAAO,EAAE,aAAa,OAAO,IAAI,QAAQ,aAAa,OAAO,EAAE,CAAC,CAAC;QAHvD,aAAQ,GAAR,QAAQ,CAAQ;QAChB,YAAO,GAAP,OAAO,CAAQ;QAG/B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,MAAiB;IAC9C,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE;QACvB,GAAG,CAAC,MAAM,EAAE,IAAY;YACtB,0BAA0B;YAC1B,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;gBACnB,OAAQ,MAAc,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC;YAED,wCAAwC;YACxC,OAAO,CAAC,SAA8B,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACzE,CAAC;KACF,CAA+D,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
import { MCPClient, MCPTransport, MCPClientFactory, MCPToolInfo, MCPToolResult, createMCPProxy } from './mcp-client.js';
|
|
25
|
+
/**
|
|
26
|
+
* MCP Server configuration
|
|
27
|
+
* Supports multiple transport types
|
|
28
|
+
*/
|
|
29
|
+
export interface MCPServerConfig {
|
|
30
|
+
command?: string;
|
|
31
|
+
args?: string[];
|
|
32
|
+
cwd?: string;
|
|
33
|
+
env?: Record<string, string>;
|
|
34
|
+
url?: string;
|
|
35
|
+
transport?: 'stdio' | 'sse' | 'streamable-http' | 'websocket';
|
|
36
|
+
headers?: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Full MCP configuration file format
|
|
40
|
+
*/
|
|
41
|
+
export interface MCPConfig {
|
|
42
|
+
mcpServers: Record<string, MCPServerConfig>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* SDK-based MCP Transport using official @modelcontextprotocol/sdk
|
|
46
|
+
*/
|
|
47
|
+
export declare class SDKMCPTransport implements MCPTransport {
|
|
48
|
+
private config;
|
|
49
|
+
private verbose;
|
|
50
|
+
private connections;
|
|
51
|
+
constructor(config: MCPConfig, verbose?: boolean);
|
|
52
|
+
private log;
|
|
53
|
+
/**
|
|
54
|
+
* Get or create connection to an MCP server
|
|
55
|
+
*/
|
|
56
|
+
private getConnection;
|
|
57
|
+
callTool(mcpName: string, toolName: string, parameters: Record<string, any>): Promise<MCPToolResult>;
|
|
58
|
+
listTools(mcpName: string): Promise<MCPToolInfo[]>;
|
|
59
|
+
isConnected(mcpName: string): Promise<boolean>;
|
|
60
|
+
listServers(): string[];
|
|
61
|
+
disconnectAll(): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* SDK-based MCP Client Factory
|
|
65
|
+
*/
|
|
66
|
+
export declare class SDKMCPClientFactory implements MCPClientFactory {
|
|
67
|
+
private transport;
|
|
68
|
+
constructor(config: MCPConfig, verbose?: boolean);
|
|
69
|
+
create(mcpName: string): MCPClient;
|
|
70
|
+
listServers(): Promise<string[]>;
|
|
71
|
+
disconnect(): Promise<void>;
|
|
72
|
+
getTransport(): SDKMCPTransport;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Resolve an MCP source to a runnable configuration
|
|
76
|
+
* Handles: GitHub shorthand, npm packages, URLs, local paths
|
|
77
|
+
*/
|
|
78
|
+
export declare function resolveMCPSource(name: string, source: string, sourceType: 'github' | 'npm' | 'url' | 'local'): MCPServerConfig;
|
|
79
|
+
/**
|
|
80
|
+
* Load MCP configuration from standard locations
|
|
81
|
+
*/
|
|
82
|
+
export declare function loadMCPConfig(verbose?: boolean): Promise<MCPConfig>;
|
|
83
|
+
/**
|
|
84
|
+
* Create an SDK-based MCP client factory from default config
|
|
85
|
+
*/
|
|
86
|
+
export declare function createSDKMCPClientFactory(verbose?: boolean): Promise<SDKMCPClientFactory>;
|
|
87
|
+
export { MCPClient, createMCPProxy };
|
|
88
|
+
//# sourceMappingURL=mcp-sdk-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-sdk-transport.d.ts","sourceRoot":"","sources":["../src/mcp-sdk-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAUH,OAAO,EACL,SAAS,EACT,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,aAAa,EAGb,cAAc,EACf,MAAM,iBAAiB,CAAC;AAEzB;;;GAGG;AACH,MAAM,WAAW,eAAe;IAE9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAG7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,iBAAiB,GAAG,WAAW,CAAC;IAG9D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAC7C;AAoLD;;GAEG;AACH,qBAAa,eAAgB,YAAW,YAAY;IAIhD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,OAAO;IAJjB,OAAO,CAAC,WAAW,CAA4C;gBAGrD,MAAM,EAAE,SAAS,EACjB,OAAO,GAAE,OAAe;IAGlC,OAAO,CAAC,GAAG;IAMX;;OAEG;YACW,aAAa;IAmBrB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IAKpG,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAKlD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQpD,WAAW,IAAI,MAAM,EAAE;IAIjB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAMrC;AAED;;GAEG;AACH,qBAAa,mBAAoB,YAAW,gBAAgB;IAC1D,OAAO,CAAC,SAAS,CAAkB;gBAEvB,MAAM,EAAE,SAAS,EAAE,OAAO,GAAE,OAAe;IAIvD,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS;IAI5B,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIhC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,YAAY,IAAI,eAAe;CAGhC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,GAC7C,eAAe,CAkDjB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,OAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CA6BhF;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,GAAE,OAAe,GACvB,OAAO,CAAC,mBAAmB,CAAC,CAG9B;AAGD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,360 @@
|
|
|
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
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
25
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
26
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
27
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
28
|
+
import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js';
|
|
29
|
+
import * as fs from 'fs/promises';
|
|
30
|
+
import * as path from 'path';
|
|
31
|
+
import * as os from 'os';
|
|
32
|
+
import { MCPClient, MCPNotConnectedError, MCPToolError, createMCPProxy, } from './mcp-client.js';
|
|
33
|
+
/**
|
|
34
|
+
* Manages a single MCP server connection using official SDK
|
|
35
|
+
*/
|
|
36
|
+
class SDKMCPConnection {
|
|
37
|
+
name;
|
|
38
|
+
config;
|
|
39
|
+
verbose;
|
|
40
|
+
client = null;
|
|
41
|
+
transport = null;
|
|
42
|
+
tools = [];
|
|
43
|
+
initialized = false;
|
|
44
|
+
constructor(name, config, verbose = false) {
|
|
45
|
+
this.name = name;
|
|
46
|
+
this.config = config;
|
|
47
|
+
this.verbose = verbose;
|
|
48
|
+
}
|
|
49
|
+
log(message) {
|
|
50
|
+
if (this.verbose) {
|
|
51
|
+
console.error(`[MCP:${this.name}] ${message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Create appropriate transport based on config
|
|
56
|
+
*/
|
|
57
|
+
createTransport() {
|
|
58
|
+
const transportType = this.config.transport || (this.config.command ? 'stdio' : 'sse');
|
|
59
|
+
switch (transportType) {
|
|
60
|
+
case 'stdio': {
|
|
61
|
+
if (!this.config.command) {
|
|
62
|
+
throw new Error(`stdio transport requires 'command' in config for ${this.name}`);
|
|
63
|
+
}
|
|
64
|
+
this.log(`Creating stdio transport: ${this.config.command} ${(this.config.args || []).join(' ')}`);
|
|
65
|
+
return new StdioClientTransport({
|
|
66
|
+
command: this.config.command,
|
|
67
|
+
args: this.config.args,
|
|
68
|
+
cwd: this.config.cwd,
|
|
69
|
+
env: this.config.env,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
case 'sse': {
|
|
73
|
+
if (!this.config.url) {
|
|
74
|
+
throw new Error(`sse transport requires 'url' in config for ${this.name}`);
|
|
75
|
+
}
|
|
76
|
+
this.log(`Creating SSE transport: ${this.config.url}`);
|
|
77
|
+
return new SSEClientTransport(new URL(this.config.url), {
|
|
78
|
+
requestInit: this.config.headers ? { headers: this.config.headers } : undefined,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
case 'streamable-http': {
|
|
82
|
+
if (!this.config.url) {
|
|
83
|
+
throw new Error(`streamable-http transport requires 'url' in config for ${this.name}`);
|
|
84
|
+
}
|
|
85
|
+
this.log(`Creating streamable HTTP transport: ${this.config.url}`);
|
|
86
|
+
return new StreamableHTTPClientTransport(new URL(this.config.url), {
|
|
87
|
+
requestInit: this.config.headers ? { headers: this.config.headers } : undefined,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
case 'websocket': {
|
|
91
|
+
if (!this.config.url) {
|
|
92
|
+
throw new Error(`websocket transport requires 'url' in config for ${this.name}`);
|
|
93
|
+
}
|
|
94
|
+
this.log(`Creating WebSocket transport: ${this.config.url}`);
|
|
95
|
+
return new WebSocketClientTransport(new URL(this.config.url));
|
|
96
|
+
}
|
|
97
|
+
default:
|
|
98
|
+
throw new Error(`Unknown transport type: ${transportType}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Connect to the MCP server
|
|
103
|
+
*/
|
|
104
|
+
async connect() {
|
|
105
|
+
if (this.client) {
|
|
106
|
+
return; // Already connected
|
|
107
|
+
}
|
|
108
|
+
this.transport = this.createTransport();
|
|
109
|
+
this.client = new Client({
|
|
110
|
+
name: 'photon-core',
|
|
111
|
+
version: '1.0.0',
|
|
112
|
+
}, {
|
|
113
|
+
capabilities: {
|
|
114
|
+
roots: { listChanged: false },
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
this.log('Connecting...');
|
|
118
|
+
await this.client.connect(this.transport);
|
|
119
|
+
this.log('Connected');
|
|
120
|
+
// List available tools
|
|
121
|
+
const toolsResult = await this.client.listTools();
|
|
122
|
+
this.tools = (toolsResult.tools || []).map((t) => ({
|
|
123
|
+
name: t.name,
|
|
124
|
+
description: t.description,
|
|
125
|
+
inputSchema: t.inputSchema,
|
|
126
|
+
}));
|
|
127
|
+
this.log(`Loaded ${this.tools.length} tools`);
|
|
128
|
+
this.initialized = true;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Call a tool
|
|
132
|
+
*/
|
|
133
|
+
async callTool(toolName, parameters) {
|
|
134
|
+
if (!this.client || !this.initialized) {
|
|
135
|
+
throw new MCPNotConnectedError(this.name);
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const result = await this.client.callTool({
|
|
139
|
+
name: toolName,
|
|
140
|
+
arguments: parameters,
|
|
141
|
+
});
|
|
142
|
+
// Convert to MCPToolResult format
|
|
143
|
+
if (result?.content && Array.isArray(result.content)) {
|
|
144
|
+
return {
|
|
145
|
+
content: result.content.map((c) => ({
|
|
146
|
+
type: c.type || 'text',
|
|
147
|
+
text: c.text,
|
|
148
|
+
data: c.data,
|
|
149
|
+
mimeType: c.mimeType,
|
|
150
|
+
})),
|
|
151
|
+
isError: result.isError,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
content: [{
|
|
156
|
+
type: 'text',
|
|
157
|
+
text: typeof result === 'string' ? result : JSON.stringify(result),
|
|
158
|
+
}],
|
|
159
|
+
isError: false,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
throw new MCPToolError(this.name, toolName, error.message);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* List available tools
|
|
168
|
+
*/
|
|
169
|
+
listTools() {
|
|
170
|
+
return this.tools;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Check if connected
|
|
174
|
+
*/
|
|
175
|
+
isConnected() {
|
|
176
|
+
return this.initialized && this.client !== null;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Disconnect
|
|
180
|
+
*/
|
|
181
|
+
async disconnect() {
|
|
182
|
+
if (this.client) {
|
|
183
|
+
this.log('Disconnecting...');
|
|
184
|
+
await this.client.close();
|
|
185
|
+
this.client = null;
|
|
186
|
+
this.transport = null;
|
|
187
|
+
this.initialized = false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* SDK-based MCP Transport using official @modelcontextprotocol/sdk
|
|
193
|
+
*/
|
|
194
|
+
export class SDKMCPTransport {
|
|
195
|
+
config;
|
|
196
|
+
verbose;
|
|
197
|
+
connections = new Map();
|
|
198
|
+
constructor(config, verbose = false) {
|
|
199
|
+
this.config = config;
|
|
200
|
+
this.verbose = verbose;
|
|
201
|
+
}
|
|
202
|
+
log(message) {
|
|
203
|
+
if (this.verbose) {
|
|
204
|
+
console.error(`[MCPTransport] ${message}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get or create connection to an MCP server
|
|
209
|
+
*/
|
|
210
|
+
async getConnection(mcpName) {
|
|
211
|
+
let connection = this.connections.get(mcpName);
|
|
212
|
+
if (connection?.isConnected()) {
|
|
213
|
+
return connection;
|
|
214
|
+
}
|
|
215
|
+
const serverConfig = this.config.mcpServers[mcpName];
|
|
216
|
+
if (!serverConfig) {
|
|
217
|
+
throw new MCPNotConnectedError(mcpName);
|
|
218
|
+
}
|
|
219
|
+
connection = new SDKMCPConnection(mcpName, serverConfig, this.verbose);
|
|
220
|
+
await connection.connect();
|
|
221
|
+
this.connections.set(mcpName, connection);
|
|
222
|
+
return connection;
|
|
223
|
+
}
|
|
224
|
+
async callTool(mcpName, toolName, parameters) {
|
|
225
|
+
const connection = await this.getConnection(mcpName);
|
|
226
|
+
return connection.callTool(toolName, parameters);
|
|
227
|
+
}
|
|
228
|
+
async listTools(mcpName) {
|
|
229
|
+
const connection = await this.getConnection(mcpName);
|
|
230
|
+
return connection.listTools();
|
|
231
|
+
}
|
|
232
|
+
async isConnected(mcpName) {
|
|
233
|
+
if (!this.config.mcpServers[mcpName]) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
const connection = this.connections.get(mcpName);
|
|
237
|
+
return connection?.isConnected() ?? false;
|
|
238
|
+
}
|
|
239
|
+
listServers() {
|
|
240
|
+
return Object.keys(this.config.mcpServers);
|
|
241
|
+
}
|
|
242
|
+
async disconnectAll() {
|
|
243
|
+
for (const connection of this.connections.values()) {
|
|
244
|
+
await connection.disconnect();
|
|
245
|
+
}
|
|
246
|
+
this.connections.clear();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* SDK-based MCP Client Factory
|
|
251
|
+
*/
|
|
252
|
+
export class SDKMCPClientFactory {
|
|
253
|
+
transport;
|
|
254
|
+
constructor(config, verbose = false) {
|
|
255
|
+
this.transport = new SDKMCPTransport(config, verbose);
|
|
256
|
+
}
|
|
257
|
+
create(mcpName) {
|
|
258
|
+
return new MCPClient(mcpName, this.transport);
|
|
259
|
+
}
|
|
260
|
+
async listServers() {
|
|
261
|
+
return this.transport.listServers();
|
|
262
|
+
}
|
|
263
|
+
async disconnect() {
|
|
264
|
+
await this.transport.disconnectAll();
|
|
265
|
+
}
|
|
266
|
+
getTransport() {
|
|
267
|
+
return this.transport;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Resolve an MCP source to a runnable configuration
|
|
272
|
+
* Handles: GitHub shorthand, npm packages, URLs, local paths
|
|
273
|
+
*/
|
|
274
|
+
export function resolveMCPSource(name, source, sourceType) {
|
|
275
|
+
switch (sourceType) {
|
|
276
|
+
case 'npm': {
|
|
277
|
+
// npm:@scope/package or npm:package
|
|
278
|
+
const packageName = source.replace(/^npm:/, '');
|
|
279
|
+
return {
|
|
280
|
+
command: 'npx',
|
|
281
|
+
args: ['-y', packageName],
|
|
282
|
+
transport: 'stdio',
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
case 'github': {
|
|
286
|
+
// GitHub shorthand: owner/repo
|
|
287
|
+
// Try to run via npx assuming it's published to npm
|
|
288
|
+
return {
|
|
289
|
+
command: 'npx',
|
|
290
|
+
args: ['-y', `@${source}`],
|
|
291
|
+
transport: 'stdio',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
case 'url': {
|
|
295
|
+
// Full URL - determine transport from protocol
|
|
296
|
+
if (source.startsWith('ws://') || source.startsWith('wss://')) {
|
|
297
|
+
return {
|
|
298
|
+
url: source,
|
|
299
|
+
transport: 'websocket',
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
// Default to SSE for HTTP URLs
|
|
303
|
+
return {
|
|
304
|
+
url: source,
|
|
305
|
+
transport: 'sse',
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
case 'local': {
|
|
309
|
+
// Local path - run directly with node
|
|
310
|
+
const resolvedPath = source.replace(/^~/, process.env.HOME || '');
|
|
311
|
+
return {
|
|
312
|
+
command: 'node',
|
|
313
|
+
args: [resolvedPath],
|
|
314
|
+
transport: 'stdio',
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
default:
|
|
318
|
+
throw new Error(`Unknown MCP source type: ${sourceType}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Load MCP configuration from standard locations
|
|
323
|
+
*/
|
|
324
|
+
export async function loadMCPConfig(verbose = false) {
|
|
325
|
+
const log = verbose ? (msg) => console.error(`[MCPConfig] ${msg}`) : () => { };
|
|
326
|
+
const configPaths = [
|
|
327
|
+
process.env.PHOTON_MCP_CONFIG,
|
|
328
|
+
path.join(process.cwd(), 'photon.mcp.json'),
|
|
329
|
+
path.join(os.homedir(), '.config', 'photon', 'mcp.json'),
|
|
330
|
+
path.join(os.homedir(), '.photon', 'mcp.json'),
|
|
331
|
+
].filter(Boolean);
|
|
332
|
+
for (const configPath of configPaths) {
|
|
333
|
+
try {
|
|
334
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
335
|
+
const config = JSON.parse(content);
|
|
336
|
+
if (config.mcpServers && typeof config.mcpServers === 'object') {
|
|
337
|
+
log(`Loaded MCP config from ${configPath}`);
|
|
338
|
+
log(`Found ${Object.keys(config.mcpServers).length} MCP servers`);
|
|
339
|
+
return config;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
if (error.code !== 'ENOENT') {
|
|
344
|
+
log(`Failed to load ${configPath}: ${error.message}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
log('No MCP config found, MCP access will be unavailable');
|
|
349
|
+
return { mcpServers: {} };
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Create an SDK-based MCP client factory from default config
|
|
353
|
+
*/
|
|
354
|
+
export async function createSDKMCPClientFactory(verbose = false) {
|
|
355
|
+
const config = await loadMCPConfig(verbose);
|
|
356
|
+
return new SDKMCPClientFactory(config, verbose);
|
|
357
|
+
}
|
|
358
|
+
// Re-export for convenience
|
|
359
|
+
export { MCPClient, createMCPProxy };
|
|
360
|
+
//# sourceMappingURL=mcp-sdk-transport.js.map
|