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.
Files changed (64) hide show
  1. package/out/language/generated/ast.d.ts +14 -5
  2. package/out/language/generated/ast.d.ts.map +1 -1
  3. package/out/language/generated/ast.js +17 -4
  4. package/out/language/generated/ast.js.map +1 -1
  5. package/out/language/generated/grammar.d.ts.map +1 -1
  6. package/out/language/generated/grammar.js +248 -213
  7. package/out/language/generated/grammar.js.map +1 -1
  8. package/out/language/main.cjs +262 -217
  9. package/out/language/main.cjs.map +2 -2
  10. package/out/language/syntax.d.ts +6 -2
  11. package/out/language/syntax.d.ts.map +1 -1
  12. package/out/language/syntax.js +19 -6
  13. package/out/language/syntax.js.map +1 -1
  14. package/out/runtime/agents/common.d.ts +1 -1
  15. package/out/runtime/agents/common.d.ts.map +1 -1
  16. package/out/runtime/agents/common.js +6 -2
  17. package/out/runtime/agents/common.js.map +1 -1
  18. package/out/runtime/defs.d.ts +9 -7
  19. package/out/runtime/defs.d.ts.map +1 -1
  20. package/out/runtime/defs.js +11 -7
  21. package/out/runtime/defs.js.map +1 -1
  22. package/out/runtime/exec-graph.d.ts.map +1 -1
  23. package/out/runtime/exec-graph.js +32 -2
  24. package/out/runtime/exec-graph.js.map +1 -1
  25. package/out/runtime/interpreter.d.ts +4 -1
  26. package/out/runtime/interpreter.d.ts.map +1 -1
  27. package/out/runtime/interpreter.js +54 -8
  28. package/out/runtime/interpreter.js.map +1 -1
  29. package/out/runtime/mcpclient.d.ts +46 -0
  30. package/out/runtime/mcpclient.d.ts.map +1 -0
  31. package/out/runtime/mcpclient.js +457 -0
  32. package/out/runtime/mcpclient.js.map +1 -0
  33. package/out/runtime/module.d.ts +8 -1
  34. package/out/runtime/module.d.ts.map +1 -1
  35. package/out/runtime/module.js +66 -6
  36. package/out/runtime/module.js.map +1 -1
  37. package/out/runtime/modules/ai.d.ts.map +1 -1
  38. package/out/runtime/modules/ai.js +6 -20
  39. package/out/runtime/modules/ai.js.map +1 -1
  40. package/out/runtime/modules/core.d.ts.map +1 -1
  41. package/out/runtime/modules/core.js +6 -1
  42. package/out/runtime/modules/core.js.map +1 -1
  43. package/out/runtime/modules/mcp.d.ts +6 -0
  44. package/out/runtime/modules/mcp.d.ts.map +1 -0
  45. package/out/runtime/modules/mcp.js +43 -0
  46. package/out/runtime/modules/mcp.js.map +1 -0
  47. package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
  48. package/out/runtime/resolvers/sqldb/database.js +3 -2
  49. package/out/runtime/resolvers/sqldb/database.js.map +1 -1
  50. package/package.json +3 -2
  51. package/src/language/agentlang.langium +5 -3
  52. package/src/language/generated/ast.ts +32 -9
  53. package/src/language/generated/grammar.ts +248 -213
  54. package/src/language/syntax.ts +24 -7
  55. package/src/runtime/agents/common.ts +6 -2
  56. package/src/runtime/defs.ts +5 -0
  57. package/src/runtime/exec-graph.ts +36 -1
  58. package/src/runtime/interpreter.ts +68 -7
  59. package/src/runtime/mcpclient.ts +542 -0
  60. package/src/runtime/module.ts +70 -7
  61. package/src/runtime/modules/ai.ts +6 -22
  62. package/src/runtime/modules/core.ts +5 -1
  63. package/src/runtime/modules/mcp.ts +45 -0
  64. 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
+ }
@@ -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: string[];
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;