agentlang 0.9.0 → 0.9.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/out/language/generated/ast.d.ts +14 -5
- package/out/language/generated/ast.d.ts.map +1 -1
- package/out/language/generated/ast.js +17 -4
- package/out/language/generated/ast.js.map +1 -1
- package/out/language/generated/grammar.d.ts.map +1 -1
- package/out/language/generated/grammar.js +248 -213
- package/out/language/generated/grammar.js.map +1 -1
- package/out/language/main.cjs +262 -217
- package/out/language/main.cjs.map +2 -2
- package/out/language/syntax.d.ts +6 -2
- package/out/language/syntax.d.ts.map +1 -1
- package/out/language/syntax.js +19 -6
- package/out/language/syntax.js.map +1 -1
- package/out/runtime/agents/common.d.ts +1 -1
- package/out/runtime/agents/common.d.ts.map +1 -1
- package/out/runtime/agents/common.js +6 -2
- package/out/runtime/agents/common.js.map +1 -1
- package/out/runtime/defs.d.ts +9 -7
- package/out/runtime/defs.d.ts.map +1 -1
- package/out/runtime/defs.js +11 -7
- package/out/runtime/defs.js.map +1 -1
- package/out/runtime/exec-graph.d.ts.map +1 -1
- package/out/runtime/exec-graph.js +32 -2
- package/out/runtime/exec-graph.js.map +1 -1
- package/out/runtime/interpreter.d.ts +4 -1
- package/out/runtime/interpreter.d.ts.map +1 -1
- package/out/runtime/interpreter.js +54 -8
- package/out/runtime/interpreter.js.map +1 -1
- package/out/runtime/mcpclient.d.ts +46 -0
- package/out/runtime/mcpclient.d.ts.map +1 -0
- package/out/runtime/mcpclient.js +457 -0
- package/out/runtime/mcpclient.js.map +1 -0
- package/out/runtime/module.d.ts +8 -1
- package/out/runtime/module.d.ts.map +1 -1
- package/out/runtime/module.js +66 -6
- package/out/runtime/module.js.map +1 -1
- package/out/runtime/modules/ai.d.ts.map +1 -1
- package/out/runtime/modules/ai.js +6 -20
- package/out/runtime/modules/ai.js.map +1 -1
- package/out/runtime/modules/core.d.ts.map +1 -1
- package/out/runtime/modules/core.js +6 -1
- package/out/runtime/modules/core.js.map +1 -1
- package/out/runtime/modules/mcp.d.ts +6 -0
- package/out/runtime/modules/mcp.d.ts.map +1 -0
- package/out/runtime/modules/mcp.js +43 -0
- package/out/runtime/modules/mcp.js.map +1 -0
- package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
- package/out/runtime/resolvers/sqldb/database.js +3 -2
- package/out/runtime/resolvers/sqldb/database.js.map +1 -1
- package/package.json +3 -2
- package/src/language/agentlang.langium +5 -3
- package/src/language/generated/ast.ts +32 -9
- package/src/language/generated/grammar.ts +248 -213
- package/src/language/syntax.ts +24 -7
- package/src/runtime/agents/common.ts +6 -2
- package/src/runtime/defs.ts +5 -0
- package/src/runtime/exec-graph.ts +36 -1
- package/src/runtime/interpreter.ts +68 -7
- package/src/runtime/mcpclient.ts +542 -0
- package/src/runtime/module.ts +70 -7
- package/src/runtime/modules/ai.ts +6 -22
- package/src/runtime/modules/core.ts +5 -1
- package/src/runtime/modules/mcp.ts +45 -0
- package/src/runtime/resolvers/sqldb/database.ts +3 -2
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
// Ref: https://github.com/modelcontextprotocol/typescript-sdk/tree/v1.x
|
|
2
|
+
import { Client } from '@modelcontextprotocol/sdk/client';
|
|
3
|
+
import {
|
|
4
|
+
ClientCredentialsProvider,
|
|
5
|
+
PrivateKeyJwtProvider,
|
|
6
|
+
} from '@modelcontextprotocol/sdk/client/auth-extensions.js';
|
|
7
|
+
import {
|
|
8
|
+
StreamableHTTPClientTransport,
|
|
9
|
+
StreamableHTTPClientTransportOptions,
|
|
10
|
+
} from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
11
|
+
import {
|
|
12
|
+
ListToolsRequest,
|
|
13
|
+
ListToolsResultSchema,
|
|
14
|
+
CallToolRequest,
|
|
15
|
+
CallToolResultSchema,
|
|
16
|
+
ListPromptsRequest,
|
|
17
|
+
ListPromptsResultSchema,
|
|
18
|
+
GetPromptRequest,
|
|
19
|
+
GetPromptResultSchema,
|
|
20
|
+
ListResourcesRequest,
|
|
21
|
+
ListResourcesResultSchema,
|
|
22
|
+
LoggingMessageNotificationSchema,
|
|
23
|
+
ResourceListChangedNotificationSchema,
|
|
24
|
+
ElicitRequestSchema,
|
|
25
|
+
ReadResourceRequest,
|
|
26
|
+
ReadResourceResultSchema,
|
|
27
|
+
RELATED_TASK_META_KEY,
|
|
28
|
+
ErrorCode,
|
|
29
|
+
McpError,
|
|
30
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
31
|
+
import { getDisplayName } from '@modelcontextprotocol/sdk/shared/metadataUtils.js';
|
|
32
|
+
import { logger } from './logger.js';
|
|
33
|
+
import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
34
|
+
import { addMcpEvent, addModule, Instance, isModule } from './module.js';
|
|
35
|
+
|
|
36
|
+
export function createProvider(
|
|
37
|
+
clientId: string,
|
|
38
|
+
clientSecret?: string,
|
|
39
|
+
privateKeyPem?: string
|
|
40
|
+
): OAuthClientProvider {
|
|
41
|
+
if (privateKeyPem) {
|
|
42
|
+
const algorithm = process.env.MCP_CLIENT_ALGORITHM || 'RS256';
|
|
43
|
+
return new PrivateKeyJwtProvider({
|
|
44
|
+
clientId,
|
|
45
|
+
privateKey: privateKeyPem,
|
|
46
|
+
algorithm,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (clientSecret) {
|
|
50
|
+
return new ClientCredentialsProvider({
|
|
51
|
+
clientId,
|
|
52
|
+
clientSecret,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
throw Error(`Either clientSecret or privateKeyPem is required for client: ${clientId}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type McpAuthInfo = {
|
|
59
|
+
provider?: OAuthClientProvider;
|
|
60
|
+
bearerToken?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type McpTool = {
|
|
64
|
+
id: string;
|
|
65
|
+
name: string;
|
|
66
|
+
description: string;
|
|
67
|
+
inputSchema: any;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export class McpClient {
|
|
71
|
+
// Track received notifications for debugging resumability
|
|
72
|
+
private notificationCount = 0;
|
|
73
|
+
|
|
74
|
+
private name: string;
|
|
75
|
+
private version: string = '1.0.0';
|
|
76
|
+
private client: Client | undefined;
|
|
77
|
+
private transport: StreamableHTTPClientTransport | undefined;
|
|
78
|
+
private serverUrl: string; // e.g 'https://mcp.deepwiki.com/mcp'
|
|
79
|
+
private notificationsToolLastEventId: string | undefined;
|
|
80
|
+
private sessionId: string | undefined = undefined;
|
|
81
|
+
|
|
82
|
+
constructor(name: string, serverUrl: string) {
|
|
83
|
+
this.name = name;
|
|
84
|
+
this.serverUrl = serverUrl;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public setVersion(v: string): McpClient {
|
|
88
|
+
this.version = v;
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public async connect(authInfo?: McpAuthInfo): Promise<void> {
|
|
93
|
+
if (this.client) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
logger.info(`Connecting to ${this.serverUrl}...`);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Create a new client with form elicitation capability
|
|
100
|
+
this.client = new Client(
|
|
101
|
+
{
|
|
102
|
+
name: this.name,
|
|
103
|
+
version: this.version,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
capabilities: {
|
|
107
|
+
elicitation: {
|
|
108
|
+
form: {},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
this.client.onerror = error => {
|
|
114
|
+
throw new Error(`MCP Client ${this.name} error: ${error}`);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Set up elicitation request handler with proper validation
|
|
118
|
+
this.client.setRequestHandler(ElicitRequestSchema, async request => {
|
|
119
|
+
if (request.params.mode !== 'form') {
|
|
120
|
+
throw new McpError(
|
|
121
|
+
ErrorCode.InvalidParams,
|
|
122
|
+
`Unsupported elicitation mode: ${request.params.mode}`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
const log = `MCP Client ${this.name} - Elicitation (form) Request Received:
|
|
126
|
+
Message: ${request.params.message}
|
|
127
|
+
Related Task: ${request.params._meta?.[RELATED_TASK_META_KEY]?.taskId}
|
|
128
|
+
Requested Schema:
|
|
129
|
+
${JSON.stringify(request.params.requestedSchema, null, 2)}
|
|
130
|
+
Cancelling the request in non-interactive mode`;
|
|
131
|
+
logger.debug(log);
|
|
132
|
+
return { action: 'cancel' };
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const transArgs: StreamableHTTPClientTransportOptions = {
|
|
136
|
+
sessionId: this.sessionId,
|
|
137
|
+
};
|
|
138
|
+
if (authInfo?.provider) {
|
|
139
|
+
transArgs.authProvider = authInfo.provider;
|
|
140
|
+
} else if (authInfo?.bearerToken) {
|
|
141
|
+
const customHeaders = {
|
|
142
|
+
Authorization: `Bearer ${authInfo.bearerToken}`,
|
|
143
|
+
};
|
|
144
|
+
transArgs.requestInit = {
|
|
145
|
+
headers: customHeaders,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.transport = new StreamableHTTPClientTransport(new URL(this.serverUrl), transArgs);
|
|
150
|
+
|
|
151
|
+
// Set up notification handlers
|
|
152
|
+
this.client.setNotificationHandler(LoggingMessageNotificationSchema, notification => {
|
|
153
|
+
this.notificationCount++;
|
|
154
|
+
logger.debug(
|
|
155
|
+
`MCP Client ${this.name} - Notification ${this.notificationCount}: ${notification.params.level} - ${notification.params.data}`
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async _ => {
|
|
160
|
+
logger.debug(`MCP Client ${this.name} - Resource list changed notification received!`);
|
|
161
|
+
try {
|
|
162
|
+
if (!this.client) {
|
|
163
|
+
logger.warn(`MCP Client ${this.name} disconnected, cannot fetch resources`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const resourcesResult = await this.client.request(
|
|
167
|
+
{
|
|
168
|
+
method: 'resources/list',
|
|
169
|
+
params: {},
|
|
170
|
+
},
|
|
171
|
+
ListResourcesResultSchema
|
|
172
|
+
);
|
|
173
|
+
logger.debug(
|
|
174
|
+
`MCP Client ${this.name} - Available resources count: ${resourcesResult.resources.length}`
|
|
175
|
+
);
|
|
176
|
+
} catch {
|
|
177
|
+
logger.warn(
|
|
178
|
+
`MCP Client ${this.name} - Failed to list resources after change notification`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Connect the client
|
|
184
|
+
await this.client.connect(this.transport);
|
|
185
|
+
this.sessionId = this.transport.sessionId;
|
|
186
|
+
logger.debug(
|
|
187
|
+
`MCP Client ${this.name} - Transport created with session ID: ${this.sessionId}`
|
|
188
|
+
);
|
|
189
|
+
logger.debug(`MCP Client ${this.name} - Connected to MCP server ${this.serverUrl}`);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
this.client = undefined;
|
|
192
|
+
this.transport = undefined;
|
|
193
|
+
throw new Error(`MCP Client ${this.name} - Failed to connect: ${error}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public async disconnect(): Promise<boolean> {
|
|
198
|
+
if (this.client === undefined || this.transport === undefined) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
await this.transport.close();
|
|
204
|
+
logger.debug(`MCP Client ${this.name} - Disconnected from MCP server`);
|
|
205
|
+
this.client = undefined;
|
|
206
|
+
this.transport = undefined;
|
|
207
|
+
return true;
|
|
208
|
+
} catch (error) {
|
|
209
|
+
logger.warn(`MCP Client ${this.name} - Error disconnecting: ${error}`);
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public async terminateSession(): Promise<boolean> {
|
|
215
|
+
if (this.client === undefined || this.transport === undefined) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
console.debug(
|
|
221
|
+
`MCP Client ${this.name} - Terminating session with ID: ${this.transport.sessionId}`
|
|
222
|
+
);
|
|
223
|
+
await this.transport.terminateSession();
|
|
224
|
+
// Check if sessionId was cleared after termination
|
|
225
|
+
if (!this.transport.sessionId) {
|
|
226
|
+
this.sessionId = undefined;
|
|
227
|
+
|
|
228
|
+
// Also close the transport and clear client objects
|
|
229
|
+
await this.disconnect();
|
|
230
|
+
return true;
|
|
231
|
+
} else {
|
|
232
|
+
logger.warn(
|
|
233
|
+
`MCP Client ${this.name} - Server responded with 405 Method Not Allowed (session termination not supported`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
console.warn(`MCP Client ${this.name} - Error terminating session: ${error}`);
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
public async reconnect(): Promise<void> {
|
|
243
|
+
if (this.client) {
|
|
244
|
+
await this.disconnect();
|
|
245
|
+
}
|
|
246
|
+
await this.connect();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public async listTools(): Promise<McpTool[]> {
|
|
250
|
+
if (!this.client) {
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const toolsRequest: ListToolsRequest = {
|
|
256
|
+
method: 'tools/list',
|
|
257
|
+
params: {},
|
|
258
|
+
};
|
|
259
|
+
const toolsResult = await this.client.request(toolsRequest, ListToolsResultSchema);
|
|
260
|
+
|
|
261
|
+
if (toolsResult.tools.length === 0) {
|
|
262
|
+
return [];
|
|
263
|
+
} else {
|
|
264
|
+
const result = new Array<McpTool>();
|
|
265
|
+
for (const tool of toolsResult.tools) {
|
|
266
|
+
result.push({
|
|
267
|
+
id: tool.name,
|
|
268
|
+
name: getDisplayName(tool),
|
|
269
|
+
description: tool.description || '',
|
|
270
|
+
inputSchema: tool.inputSchema,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
} catch (error) {
|
|
276
|
+
throw new Error(`Tools not supported by this server ${this.serverUrl} - ${error}`);
|
|
277
|
+
}
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
public async callTool(name: string, args: Record<string, unknown>): Promise<any> {
|
|
282
|
+
if (!this.client) {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const request: CallToolRequest = {
|
|
288
|
+
method: 'tools/call',
|
|
289
|
+
params: {
|
|
290
|
+
name,
|
|
291
|
+
arguments: args,
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
return await this.client.request(request, CallToolResultSchema);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
throw new Error(`MCP Client ${name} - Error calling tool ${name}: ${error}`);
|
|
297
|
+
}
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
public async callGreetTool(name: string): Promise<any> {
|
|
302
|
+
return await this.callTool('greet', { name });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
public async callMultiGreetTool(name: string): Promise<any> {
|
|
306
|
+
return await this.callTool('multi-greet', { name });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
public async callCollectInfoTool(infoType: string): Promise<any> {
|
|
310
|
+
return await this.callTool('collect-user-info', { infoType });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
public async startNotifications(interval: number, count: number): Promise<any> {
|
|
314
|
+
return await this.callTool('start-notification-stream', { interval, count });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
public async runNotificationsToolWithResumability(interval: number, count: number): Promise<any> {
|
|
318
|
+
if (!this.client) {
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const request: CallToolRequest = {
|
|
324
|
+
method: 'tools/call',
|
|
325
|
+
params: {
|
|
326
|
+
name: 'start-notification-stream',
|
|
327
|
+
arguments: { interval, count },
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const onLastEventIdUpdate = (event: string) => {
|
|
332
|
+
this.notificationsToolLastEventId = event;
|
|
333
|
+
console.log(`Updated resumption token: ${event}`);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const result = await this.client.request(request, CallToolResultSchema, {
|
|
337
|
+
resumptionToken: this.notificationsToolLastEventId,
|
|
338
|
+
onresumptiontoken: onLastEventIdUpdate,
|
|
339
|
+
});
|
|
340
|
+
return result;
|
|
341
|
+
} catch (error) {
|
|
342
|
+
throw new Error(`MCP Client ${this.name} - Error starting notification stream: ${error}`);
|
|
343
|
+
}
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
public async listPrompts(): Promise<any> {
|
|
348
|
+
if (!this.client) {
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const promptsRequest: ListPromptsRequest = {
|
|
354
|
+
method: 'prompts/list',
|
|
355
|
+
params: {},
|
|
356
|
+
};
|
|
357
|
+
return await this.client.request(promptsRequest, ListPromptsResultSchema);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
throw new Error(`MCP Client ${this.name} - Prompts not supported by this server (${error})`);
|
|
360
|
+
}
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
public async getPrompt(name: string, args: Record<string, unknown>): Promise<any> {
|
|
365
|
+
if (!this.client) {
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
const promptRequest: GetPromptRequest = {
|
|
371
|
+
method: 'prompts/get',
|
|
372
|
+
params: {
|
|
373
|
+
name,
|
|
374
|
+
arguments: args as Record<string, string>,
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
return await this.client.request(promptRequest, GetPromptResultSchema);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
throw new Error(`MCP Client ${this.name} - Error getting prompt ${name}: ${error}`);
|
|
381
|
+
}
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
public async listResources(): Promise<any> {
|
|
386
|
+
if (!this.client) {
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
const resourcesRequest: ListResourcesRequest = {
|
|
392
|
+
method: 'resources/list',
|
|
393
|
+
params: {},
|
|
394
|
+
};
|
|
395
|
+
return await this.client.request(resourcesRequest, ListResourcesResultSchema);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
`MCP Client ${this.name} - Resources not supported by this server (${error})`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
public async readResource(uri: string): Promise<any> {
|
|
405
|
+
if (!this.client) {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
const request: ReadResourceRequest = {
|
|
411
|
+
method: 'resources/read',
|
|
412
|
+
params: { uri },
|
|
413
|
+
};
|
|
414
|
+
return await this.client.request(request, ReadResourceResultSchema);
|
|
415
|
+
} catch (error) {
|
|
416
|
+
throw new Error(`MCP Client ${this.name} - Error reading resource ${uri}: ${error}`);
|
|
417
|
+
}
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
public async callToolTask(name: string, args: Record<string, unknown>): Promise<any> {
|
|
422
|
+
if (!this.client) {
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Use task-based execution - call now, fetch later
|
|
427
|
+
// Using the experimental tasks API - WARNING: may change without notice
|
|
428
|
+
try {
|
|
429
|
+
// Call the tool with task metadata using streaming API
|
|
430
|
+
const stream = this.client.experimental.tasks.callToolStream(
|
|
431
|
+
{
|
|
432
|
+
name,
|
|
433
|
+
arguments: args,
|
|
434
|
+
},
|
|
435
|
+
CallToolResultSchema,
|
|
436
|
+
{
|
|
437
|
+
task: {
|
|
438
|
+
ttl: 60000, // Keep results for 60 seconds
|
|
439
|
+
},
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
for await (const message of stream) {
|
|
443
|
+
switch (message.type) {
|
|
444
|
+
case 'taskCreated':
|
|
445
|
+
logger.info(
|
|
446
|
+
`MCP Client ${this.name} - Task created successfully with ID: ${message.task.taskId}`
|
|
447
|
+
);
|
|
448
|
+
break;
|
|
449
|
+
case 'taskStatus':
|
|
450
|
+
logger.info(`MCP Client ${this.name} - Task status: ${message.task.status}`);
|
|
451
|
+
break;
|
|
452
|
+
case 'result':
|
|
453
|
+
return message.result.content;
|
|
454
|
+
case 'error':
|
|
455
|
+
throw message.error;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
} catch (error) {
|
|
459
|
+
throw new Error(`MCP Client ${this.name} - Error with task-based execution: ${error}`);
|
|
460
|
+
}
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
public async cleanup(): Promise<boolean> {
|
|
465
|
+
if (this.client && this.transport) {
|
|
466
|
+
try {
|
|
467
|
+
// First try to terminate the session gracefully
|
|
468
|
+
if (this.transport.sessionId) {
|
|
469
|
+
try {
|
|
470
|
+
await this.transport.terminateSession();
|
|
471
|
+
} catch (error) {
|
|
472
|
+
logger.warn(`MCP Client ${this.name} - Error terminating session: ${error}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Then close the transport
|
|
477
|
+
await this.transport.close();
|
|
478
|
+
return true;
|
|
479
|
+
} catch (error) {
|
|
480
|
+
logger.warn(`MCP Client ${this.name} - Error closing transport:, ${error}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const ConnectedClients = new Map<string, McpClient>();
|
|
488
|
+
|
|
489
|
+
export async function listClientTools(clientInst: Instance): Promise<any> {
|
|
490
|
+
if (clientInst) {
|
|
491
|
+
const n = clientInst.lookup('name');
|
|
492
|
+
let mcpClient: McpClient | undefined = ConnectedClients.get(n);
|
|
493
|
+
if (mcpClient === undefined) {
|
|
494
|
+
mcpClient = new McpClient(n, clientInst.lookup('serverUrl')).setVersion(
|
|
495
|
+
clientInst.lookup('version')
|
|
496
|
+
);
|
|
497
|
+
let authInfo: McpAuthInfo | undefined;
|
|
498
|
+
const clientId = clientInst.lookup('clientId');
|
|
499
|
+
const clientSecret = clientInst.lookup('clientSecret');
|
|
500
|
+
if (clientId && clientSecret) {
|
|
501
|
+
authInfo = { provider: createProvider(clientId, clientSecret) };
|
|
502
|
+
} else {
|
|
503
|
+
const bearerToken = clientInst.lookup('bearerToken');
|
|
504
|
+
authInfo = { bearerToken };
|
|
505
|
+
}
|
|
506
|
+
await mcpClient.connect(authInfo);
|
|
507
|
+
ConnectedClients.set(n, mcpClient);
|
|
508
|
+
}
|
|
509
|
+
const tools: any[] = await mcpClient.listTools();
|
|
510
|
+
if (tools && tools.length > 0) {
|
|
511
|
+
const moduleName = `${n}.mcp`;
|
|
512
|
+
if (!isModule(moduleName)) {
|
|
513
|
+
addModule(moduleName);
|
|
514
|
+
}
|
|
515
|
+
tools.forEach((tool: any) => {
|
|
516
|
+
addMcpEvent(tool.id, moduleName);
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
return tools;
|
|
520
|
+
}
|
|
521
|
+
return [];
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export function mcpClientNameFromToolEvent(toolEventInst: Instance): string {
|
|
525
|
+
const parts = toolEventInst.moduleName.split('.');
|
|
526
|
+
return parts[0];
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export async function callMcpTool(clientInst: Instance, toolEventInst: Instance): Promise<any> {
|
|
530
|
+
if (clientInst) {
|
|
531
|
+
const n = clientInst.lookup('name');
|
|
532
|
+
const mcpClient: McpClient | undefined = ConnectedClients.get(n);
|
|
533
|
+
if (mcpClient !== undefined) {
|
|
534
|
+
const args = toolEventInst.attributesAsObject();
|
|
535
|
+
return await mcpClient.callTool(toolEventInst.name, args as Record<string, unknown>);
|
|
536
|
+
} else {
|
|
537
|
+
return { error: `MCP client ${n} is not connected to server` };
|
|
538
|
+
}
|
|
539
|
+
} else {
|
|
540
|
+
return { error: 'MCP client not found' };
|
|
541
|
+
}
|
|
542
|
+
}
|
package/src/runtime/module.ts
CHANGED
|
@@ -132,6 +132,7 @@ function normalizePropertyNames(props: Map<string, any>) {
|
|
|
132
132
|
|
|
133
133
|
const SystemAttributeProperty: string = 'system-attribute';
|
|
134
134
|
const SystemDefinedEvent = 'system-event';
|
|
135
|
+
const McpToolEvent = 'mcp-tool';
|
|
135
136
|
|
|
136
137
|
function asSystemAttribute(attrSpec: AttributeSpec): AttributeSpec {
|
|
137
138
|
const props: Map<string, any> = attrSpec.properties ? attrSpec.properties : new Map();
|
|
@@ -1261,6 +1262,10 @@ export class Event extends Record {
|
|
|
1261
1262
|
isSystemDefined(): boolean {
|
|
1262
1263
|
return this.meta?.get(SystemDefinedEvent) === 'true';
|
|
1263
1264
|
}
|
|
1265
|
+
|
|
1266
|
+
isMcpTool(): boolean {
|
|
1267
|
+
return this.meta?.get(McpToolEvent) === 'true';
|
|
1268
|
+
}
|
|
1264
1269
|
}
|
|
1265
1270
|
|
|
1266
1271
|
enum RelType {
|
|
@@ -1661,13 +1666,32 @@ export class Workflow extends ModuleEntry {
|
|
|
1661
1666
|
}
|
|
1662
1667
|
}
|
|
1663
1668
|
|
|
1669
|
+
type FlowGraphEdge = {
|
|
1670
|
+
rep: string;
|
|
1671
|
+
label: string;
|
|
1672
|
+
};
|
|
1673
|
+
|
|
1664
1674
|
export type FlowGraphNode = {
|
|
1665
1675
|
label: string;
|
|
1666
1676
|
type: 'action' | 'condition';
|
|
1667
1677
|
on?: string[] | undefined;
|
|
1668
|
-
next:
|
|
1678
|
+
next: FlowGraphEdge[];
|
|
1669
1679
|
};
|
|
1670
1680
|
|
|
1681
|
+
function asFlowGraphEdge(stmt: Statement | undefined): FlowGraphEdge {
|
|
1682
|
+
if (stmt === undefined) {
|
|
1683
|
+
return {
|
|
1684
|
+
rep: '',
|
|
1685
|
+
label: '',
|
|
1686
|
+
};
|
|
1687
|
+
} else {
|
|
1688
|
+
return {
|
|
1689
|
+
rep: stmt.$cstNode?.text || '',
|
|
1690
|
+
label: statementLabel(stmt),
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1671
1695
|
function splitFlowSteps(flow: FlowDefinition): string[] {
|
|
1672
1696
|
const steps = new Array<string>();
|
|
1673
1697
|
flow.body?.entries.forEach((fe: FlowEntry) => {
|
|
@@ -1719,7 +1743,7 @@ export class Flow extends ModuleEntry {
|
|
|
1719
1743
|
});
|
|
1720
1744
|
if (orig) {
|
|
1721
1745
|
const nxs = orig.next;
|
|
1722
|
-
nxs?.push(fp.next);
|
|
1746
|
+
nxs?.push(asFlowGraphEdge(fp.next));
|
|
1723
1747
|
const conds = orig.on;
|
|
1724
1748
|
conds?.push(fp.condition);
|
|
1725
1749
|
} else {
|
|
@@ -1727,14 +1751,14 @@ export class Flow extends ModuleEntry {
|
|
|
1727
1751
|
label: fp.first,
|
|
1728
1752
|
type: 'condition',
|
|
1729
1753
|
on: [fp.condition],
|
|
1730
|
-
next: [fp.next],
|
|
1754
|
+
next: [asFlowGraphEdge(fp.next)],
|
|
1731
1755
|
});
|
|
1732
1756
|
}
|
|
1733
1757
|
} else {
|
|
1734
1758
|
result.push({
|
|
1735
1759
|
label: fp.first,
|
|
1736
1760
|
type: 'action',
|
|
1737
|
-
next: [fp.next],
|
|
1761
|
+
next: [asFlowGraphEdge(fp.next)],
|
|
1738
1762
|
});
|
|
1739
1763
|
}
|
|
1740
1764
|
}
|
|
@@ -1822,6 +1846,29 @@ export class GlossaryEntry extends ModuleEntry {
|
|
|
1822
1846
|
}
|
|
1823
1847
|
}
|
|
1824
1848
|
|
|
1849
|
+
function statementLabel(stmt: Statement | undefined): string {
|
|
1850
|
+
if (stmt === undefined) return '';
|
|
1851
|
+
let lbl: string | undefined = undefined;
|
|
1852
|
+
if (isLiteral(stmt.pattern.expr)) {
|
|
1853
|
+
lbl = stmt.pattern.expr.id || stmt.pattern.expr.ref || stmt.pattern.expr.str;
|
|
1854
|
+
}
|
|
1855
|
+
if (lbl !== undefined) return lbl;
|
|
1856
|
+
if (stmt.hints.length > 0) {
|
|
1857
|
+
for (let i = 0; i < stmt.hints.length; ++i) {
|
|
1858
|
+
const rh = stmt.hints[i];
|
|
1859
|
+
if (rh.aliasSpec?.alias) {
|
|
1860
|
+
lbl = rh.aliasSpec.alias;
|
|
1861
|
+
break;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
if (lbl !== undefined) return lbl;
|
|
1866
|
+
if (stmt.pattern.crudMap) {
|
|
1867
|
+
return stmt.pattern.crudMap.name;
|
|
1868
|
+
}
|
|
1869
|
+
throw new Error(`Failed to extract label from flow step - ${stmt.$cstNode?.text}`);
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1825
1872
|
export function flowGraphNext(
|
|
1826
1873
|
graph: FlowGraphNode[],
|
|
1827
1874
|
currentNode?: FlowGraphNode,
|
|
@@ -1841,15 +1888,15 @@ export function flowGraphNext(
|
|
|
1841
1888
|
if (c !== undefined) {
|
|
1842
1889
|
const next = node.next[c];
|
|
1843
1890
|
const r = graph.find((n: FlowGraphNode) => {
|
|
1844
|
-
return n.label == next;
|
|
1891
|
+
return n.label == next.label;
|
|
1845
1892
|
});
|
|
1846
|
-
return r || { label: next, type: 'action', next: [] };
|
|
1893
|
+
return r || { label: next.label, type: 'action', next: [] };
|
|
1847
1894
|
} else {
|
|
1848
1895
|
return undefined;
|
|
1849
1896
|
}
|
|
1850
1897
|
} else {
|
|
1851
1898
|
return graph.find((n: FlowGraphNode) => {
|
|
1852
|
-
return n.label == node.next[0];
|
|
1899
|
+
return n.label == node.next[0].label;
|
|
1853
1900
|
});
|
|
1854
1901
|
}
|
|
1855
1902
|
}
|
|
@@ -2955,6 +3002,14 @@ export function addEvent(
|
|
|
2955
3002
|
return event;
|
|
2956
3003
|
}
|
|
2957
3004
|
|
|
3005
|
+
export function addMcpEvent(name: string, moduleName: string): Event {
|
|
3006
|
+
const event = addEvent(name, moduleName);
|
|
3007
|
+
event.addMeta(SystemDefinedEvent, 'true');
|
|
3008
|
+
event.addMeta(McpToolEvent, 'true');
|
|
3009
|
+
event.setPublic(true);
|
|
3010
|
+
return event;
|
|
3011
|
+
}
|
|
3012
|
+
|
|
2958
3013
|
export function addRecord(
|
|
2959
3014
|
name: string,
|
|
2960
3015
|
moduleName = activeModule,
|
|
@@ -4071,6 +4126,14 @@ export function isBetweenRelationship(relName: string, moduleName: string): bool
|
|
|
4071
4126
|
return mod.isBetweenRelationship(fr.entryName);
|
|
4072
4127
|
}
|
|
4073
4128
|
|
|
4129
|
+
export function isOneToOneBetweenRelationship(relName: string, moduleName: string): boolean {
|
|
4130
|
+
if (isBetweenRelationship(relName, moduleName)) {
|
|
4131
|
+
const rel = getRelationship(relName, moduleName);
|
|
4132
|
+
return rel.isOneToOne();
|
|
4133
|
+
}
|
|
4134
|
+
return false;
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4074
4137
|
export function isContainsRelationship(relName: string, moduleName: string): boolean {
|
|
4075
4138
|
const fr: FetchModuleByEntryNameResult = fetchModuleByEntryName(relName, moduleName);
|
|
4076
4139
|
const mod: Module = fr.module;
|