mcpgraph 0.1.5 → 0.1.7
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 +1 -0
- package/dist/api.d.ts +17 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +33 -4
- package/dist/api.js.map +1 -1
- package/dist/execution/context.d.ts +3 -6
- package/dist/execution/context.d.ts.map +1 -1
- package/dist/execution/context.js +11 -2
- package/dist/execution/context.js.map +1 -1
- package/dist/execution/controller.d.ts +29 -0
- package/dist/execution/controller.d.ts.map +1 -0
- package/dist/execution/controller.js +95 -0
- package/dist/execution/controller.js.map +1 -0
- package/dist/execution/executor.d.ts +8 -1
- package/dist/execution/executor.d.ts.map +1 -1
- package/dist/execution/executor.js +184 -37
- package/dist/execution/executor.js.map +1 -1
- package/dist/execution/nodes/entry-executor.d.ts +1 -1
- package/dist/execution/nodes/entry-executor.d.ts.map +1 -1
- package/dist/execution/nodes/entry-executor.js +3 -2
- package/dist/execution/nodes/entry-executor.js.map +1 -1
- package/dist/execution/nodes/exit-executor.d.ts +1 -1
- package/dist/execution/nodes/exit-executor.d.ts.map +1 -1
- package/dist/execution/nodes/exit-executor.js +3 -2
- package/dist/execution/nodes/exit-executor.js.map +1 -1
- package/dist/execution/nodes/mcp-tool-executor.d.ts +1 -1
- package/dist/execution/nodes/mcp-tool-executor.d.ts.map +1 -1
- package/dist/execution/nodes/mcp-tool-executor.js +3 -2
- package/dist/execution/nodes/mcp-tool-executor.js.map +1 -1
- package/dist/execution/nodes/switch-executor.d.ts +1 -1
- package/dist/execution/nodes/switch-executor.d.ts.map +1 -1
- package/dist/execution/nodes/switch-executor.js +7 -2
- package/dist/execution/nodes/switch-executor.js.map +1 -1
- package/dist/execution/nodes/transform-executor.d.ts +1 -1
- package/dist/execution/nodes/transform-executor.d.ts.map +1 -1
- package/dist/execution/nodes/transform-executor.js +3 -2
- package/dist/execution/nodes/transform-executor.js.map +1 -1
- package/dist/types/execution.d.ts +92 -0
- package/dist/types/execution.d.ts.map +1 -0
- package/dist/types/execution.js +5 -0
- package/dist/types/execution.js.map +1 -0
- package/docs/design.md +220 -0
- package/docs/implementation.md +377 -0
- package/docs/introspection-debugging.md +651 -0
- package/examples/api-usage.ts +49 -1
- package/examples/switch_example.yaml +80 -0
- package/package.json +2 -1
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
# Graph Introspection & Debugging
|
|
2
|
+
|
|
3
|
+
This document explains how to use mcpGraph's introspection and debugging features to build visualizer applications, debuggers, and observability tools.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
mcpGraph provides comprehensive introspection and debugging capabilities that allow you to:
|
|
8
|
+
|
|
9
|
+
- **Observe execution in real-time** using execution hooks
|
|
10
|
+
- **Control execution flow** with pause, resume, and step operations
|
|
11
|
+
- **Set breakpoints** on specific nodes
|
|
12
|
+
- **Collect telemetry** including timing and performance metrics
|
|
13
|
+
- **Inspect execution state** at any point during execution
|
|
14
|
+
- **Access execution history** with detailed timing information
|
|
15
|
+
|
|
16
|
+
All debugging features are **non-intrusive** - they only activate when explicitly enabled, ensuring normal execution remains unaffected.
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { McpGraphApi, type ExecutionHooks } from 'mcpgraph';
|
|
22
|
+
|
|
23
|
+
const api = new McpGraphApi('config.yaml');
|
|
24
|
+
|
|
25
|
+
// Execute with debugging enabled
|
|
26
|
+
const result = await api.executeTool('count_files', {
|
|
27
|
+
directory: './tests/files'
|
|
28
|
+
}, {
|
|
29
|
+
hooks: {
|
|
30
|
+
onNodeStart: async (nodeId, node) => {
|
|
31
|
+
console.log(`Starting node: ${nodeId} (${node.type})`);
|
|
32
|
+
},
|
|
33
|
+
onNodeComplete: async (nodeId, node, input, output, duration) => {
|
|
34
|
+
console.log(`Node ${nodeId} completed in ${duration}ms`);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
enableTelemetry: true
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Access execution history and telemetry
|
|
41
|
+
console.log('History:', result.executionHistory);
|
|
42
|
+
console.log('Telemetry:', result.telemetry);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Execution Hooks
|
|
46
|
+
|
|
47
|
+
Execution hooks allow you to observe and control execution at key points. All hooks are optional and are async functions. If you don't need to perform async operations, you can simply not use `await` - the function will still work correctly.
|
|
48
|
+
|
|
49
|
+
### Available Hooks
|
|
50
|
+
|
|
51
|
+
All hooks are async functions that return Promises:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
interface ExecutionHooks {
|
|
55
|
+
/**
|
|
56
|
+
* Called before a node executes
|
|
57
|
+
* Return false to pause execution (acts as a breakpoint)
|
|
58
|
+
*/
|
|
59
|
+
onNodeStart?: (
|
|
60
|
+
nodeId: string,
|
|
61
|
+
node: NodeDefinition,
|
|
62
|
+
context: ExecutionContext
|
|
63
|
+
) => Promise<boolean>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Called after a node completes successfully
|
|
67
|
+
*/
|
|
68
|
+
onNodeComplete?: (
|
|
69
|
+
nodeId: string,
|
|
70
|
+
node: NodeDefinition,
|
|
71
|
+
input: unknown,
|
|
72
|
+
output: unknown,
|
|
73
|
+
duration: number
|
|
74
|
+
) => Promise<void>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Called when a node encounters an error
|
|
78
|
+
*/
|
|
79
|
+
onNodeError?: (
|
|
80
|
+
nodeId: string,
|
|
81
|
+
node: NodeDefinition,
|
|
82
|
+
error: Error,
|
|
83
|
+
context: ExecutionContext
|
|
84
|
+
) => Promise<void>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Called when execution pauses (breakpoint hit or manual pause)
|
|
88
|
+
*/
|
|
89
|
+
onPause?: (nodeId: string, context: ExecutionContext) => Promise<void>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Called when execution resumes
|
|
93
|
+
*/
|
|
94
|
+
onResume?: () => Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Example: Real-time UI Updates
|
|
99
|
+
|
|
100
|
+
All hooks are async functions. If you don't need async operations, you can simply not use `await`:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const hooks: ExecutionHooks = {
|
|
104
|
+
// Async function - no await needed if you don't have async operations
|
|
105
|
+
onNodeStart: async (nodeId, node, context) => {
|
|
106
|
+
// Update UI synchronously
|
|
107
|
+
updateNodeStatus(nodeId, 'running');
|
|
108
|
+
highlightNode(nodeId);
|
|
109
|
+
return true; // Continue execution
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
// Async function - can use await if needed
|
|
113
|
+
onNodeComplete: async (nodeId, node, input, output, duration) => {
|
|
114
|
+
// Update UI synchronously
|
|
115
|
+
updateNodeResult(nodeId, {
|
|
116
|
+
input,
|
|
117
|
+
output,
|
|
118
|
+
duration,
|
|
119
|
+
status: 'completed'
|
|
120
|
+
});
|
|
121
|
+
updateNodeVisualization(nodeId, output);
|
|
122
|
+
|
|
123
|
+
// Or perform async operations if needed
|
|
124
|
+
// await logToServer(nodeId, output);
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
onNodeError: async (nodeId, node, error, context) => {
|
|
128
|
+
// Error handling
|
|
129
|
+
showError(nodeId, error);
|
|
130
|
+
updateNodeStatus(nodeId, 'error');
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
await api.executeTool('my_tool', {}, { hooks });
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Execution Controller
|
|
138
|
+
|
|
139
|
+
The execution controller provides programmatic control over execution flow. It's available during execution when hooks or breakpoints are enabled.
|
|
140
|
+
|
|
141
|
+
### Getting the Controller
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Start execution with hooks/breakpoints
|
|
145
|
+
const executionPromise = api.executeTool('my_tool', {}, {
|
|
146
|
+
hooks: { /* ... */ },
|
|
147
|
+
breakpoints: ['node_1', 'node_2']
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Get controller (available during execution)
|
|
151
|
+
const controller = api.getController();
|
|
152
|
+
if (controller) {
|
|
153
|
+
// Use controller methods
|
|
154
|
+
controller.pause();
|
|
155
|
+
controller.resume();
|
|
156
|
+
await controller.step();
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Controller Methods
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
interface ExecutionController {
|
|
164
|
+
/**
|
|
165
|
+
* Pause execution at the next node boundary
|
|
166
|
+
* Only valid when status is "running"
|
|
167
|
+
*/
|
|
168
|
+
pause(): void;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Resume execution
|
|
172
|
+
* Only valid when status is "paused"
|
|
173
|
+
*/
|
|
174
|
+
resume(): void;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Step to the next node (step over)
|
|
178
|
+
* Only valid when status is "paused"
|
|
179
|
+
*/
|
|
180
|
+
step(): Promise<void>;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get current execution state
|
|
184
|
+
*/
|
|
185
|
+
getState(): ExecutionState;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Set breakpoints
|
|
189
|
+
*/
|
|
190
|
+
setBreakpoints(nodeIds: string[]): void;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Clear breakpoints
|
|
194
|
+
*/
|
|
195
|
+
clearBreakpoints(): void;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Example: Step-through Debugging
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const hooks: ExecutionHooks = {
|
|
203
|
+
onPause: async (nodeId, context) => {
|
|
204
|
+
console.log(`Paused at node: ${nodeId}`);
|
|
205
|
+
// Show debugger UI
|
|
206
|
+
showDebuggerUI();
|
|
207
|
+
},
|
|
208
|
+
onResume: async () => {
|
|
209
|
+
console.log('Resuming execution');
|
|
210
|
+
hideDebuggerUI();
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Start execution
|
|
215
|
+
const executionPromise = api.executeTool('my_tool', {}, {
|
|
216
|
+
hooks,
|
|
217
|
+
breakpoints: ['entry_node'] // Start paused
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// In your UI event handlers:
|
|
221
|
+
function handleStep() {
|
|
222
|
+
const controller = api.getController();
|
|
223
|
+
if (controller) {
|
|
224
|
+
await controller.step();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function handlePause() {
|
|
229
|
+
const controller = api.getController();
|
|
230
|
+
if (controller) {
|
|
231
|
+
controller.pause();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function handleResume() {
|
|
236
|
+
const controller = api.getController();
|
|
237
|
+
if (controller) {
|
|
238
|
+
controller.resume();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Breakpoints
|
|
244
|
+
|
|
245
|
+
Breakpoints allow you to pause execution at specific nodes. You can set breakpoints in two ways:
|
|
246
|
+
|
|
247
|
+
### 1. Via Execution Options
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
await api.executeTool('my_tool', {}, {
|
|
251
|
+
breakpoints: ['node_1', 'node_2', 'node_3']
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### 2. Via Execution Controller
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
const controller = api.getController();
|
|
259
|
+
if (controller) {
|
|
260
|
+
// Set breakpoints dynamically
|
|
261
|
+
controller.setBreakpoints(['node_1', 'node_2']);
|
|
262
|
+
|
|
263
|
+
// Clear all breakpoints
|
|
264
|
+
controller.clearBreakpoints();
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 3. Via onNodeStart Hook
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
const hooks: ExecutionHooks = {
|
|
272
|
+
onNodeStart: async (nodeId, node, context) => {
|
|
273
|
+
// Conditional breakpoint logic
|
|
274
|
+
if (shouldBreak(nodeId, context)) {
|
|
275
|
+
return false; // Pause execution
|
|
276
|
+
}
|
|
277
|
+
return true; // Continue
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Execution State
|
|
283
|
+
|
|
284
|
+
The execution state provides a snapshot of the current execution status, including:
|
|
285
|
+
|
|
286
|
+
- **Status**: Current execution status (`not_started`, `running`, `paused`, `finished`, `error`)
|
|
287
|
+
- **Current Node**: The node currently executing (or null)
|
|
288
|
+
- **Execution History**: Complete history of all executed nodes
|
|
289
|
+
- **Context**: The current execution context with all data
|
|
290
|
+
|
|
291
|
+
### Getting Execution State
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// During execution
|
|
295
|
+
const state = api.getExecutionState();
|
|
296
|
+
if (state) {
|
|
297
|
+
console.log('Status:', state.status);
|
|
298
|
+
console.log('Current Node:', state.currentNodeId);
|
|
299
|
+
console.log('History:', state.executionHistory);
|
|
300
|
+
console.log('Context:', state.context.getData());
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Or via controller
|
|
304
|
+
const controller = api.getController();
|
|
305
|
+
if (controller) {
|
|
306
|
+
const state = controller.getState();
|
|
307
|
+
// Same state object
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Execution Status
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
type ExecutionStatus =
|
|
315
|
+
| "not_started" // Execution hasn't begun
|
|
316
|
+
| "running" // Execution is actively running
|
|
317
|
+
| "paused" // Execution is paused (can resume/step)
|
|
318
|
+
| "finished" // Execution completed successfully
|
|
319
|
+
| "error"; // Execution failed with an error
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Execution State Interface
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
interface ExecutionState {
|
|
326
|
+
status: ExecutionStatus;
|
|
327
|
+
currentNodeId: string | null;
|
|
328
|
+
executionHistory: NodeExecutionRecord[];
|
|
329
|
+
context: ExecutionContext;
|
|
330
|
+
error?: Error; // Present when status is "error"
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Execution History
|
|
335
|
+
|
|
336
|
+
Execution history provides a complete record of all node executions with detailed timing information.
|
|
337
|
+
|
|
338
|
+
### History Record Structure
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
interface NodeExecutionRecord {
|
|
342
|
+
nodeId: string; // ID of the executed node
|
|
343
|
+
nodeType: string; // Type of node (entry, exit, transform, mcp, switch)
|
|
344
|
+
startTime: number; // Timestamp when node started (milliseconds)
|
|
345
|
+
endTime: number; // Timestamp when node ended (milliseconds)
|
|
346
|
+
duration: number; // Execution duration (milliseconds)
|
|
347
|
+
input: unknown; // Input data for the node
|
|
348
|
+
output: unknown; // Output data from the node
|
|
349
|
+
error?: Error; // Error object if node failed
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Accessing History
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// From execution result
|
|
357
|
+
const result = await api.executeTool('my_tool', {}, {
|
|
358
|
+
enableTelemetry: true
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
for (const record of result.executionHistory || []) {
|
|
362
|
+
console.log(`${record.nodeId}: ${record.duration}ms`);
|
|
363
|
+
if (record.error) {
|
|
364
|
+
console.error(`Error: ${record.error.message}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// From execution state (during execution)
|
|
369
|
+
const state = api.getExecutionState();
|
|
370
|
+
if (state) {
|
|
371
|
+
const history = state.executionHistory;
|
|
372
|
+
// Access history during execution
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Telemetry
|
|
377
|
+
|
|
378
|
+
Telemetry provides aggregated performance metrics and execution statistics.
|
|
379
|
+
|
|
380
|
+
### Telemetry Structure
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
interface ExecutionTelemetry {
|
|
384
|
+
totalDuration: number; // Total execution time (milliseconds)
|
|
385
|
+
nodeDurations: Map<string, number>; // Total duration per node type
|
|
386
|
+
nodeCounts: Map<string, number>; // Execution count per node type
|
|
387
|
+
errorCount: number; // Total number of errors
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Collecting Telemetry
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
const result = await api.executeTool('my_tool', {}, {
|
|
395
|
+
enableTelemetry: true // Enable telemetry collection
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
if (result.telemetry) {
|
|
399
|
+
console.log(`Total duration: ${result.telemetry.totalDuration}ms`);
|
|
400
|
+
console.log(`Errors: ${result.telemetry.errorCount}`);
|
|
401
|
+
|
|
402
|
+
// Node type statistics
|
|
403
|
+
for (const [nodeType, duration] of result.telemetry.nodeDurations) {
|
|
404
|
+
const count = result.telemetry.nodeCounts.get(nodeType) || 0;
|
|
405
|
+
console.log(`${nodeType}: ${count} executions, ${duration}ms total`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Example: Performance Analysis
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
function analyzePerformance(telemetry: ExecutionTelemetry) {
|
|
414
|
+
const avgDurations = new Map<string, number>();
|
|
415
|
+
|
|
416
|
+
for (const [nodeType, totalDuration] of telemetry.nodeDurations) {
|
|
417
|
+
const count = telemetry.nodeCounts.get(nodeType) || 1;
|
|
418
|
+
const avgDuration = totalDuration / count;
|
|
419
|
+
avgDurations.set(nodeType, avgDuration);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Find slowest node types
|
|
423
|
+
const sorted = Array.from(avgDurations.entries())
|
|
424
|
+
.sort((a, b) => b[1] - a[1]);
|
|
425
|
+
|
|
426
|
+
console.log('Slowest node types:');
|
|
427
|
+
for (const [nodeType, avgDuration] of sorted) {
|
|
428
|
+
console.log(` ${nodeType}: ${avgDuration.toFixed(2)}ms average`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
## Complete Example: Visualizer Application
|
|
434
|
+
|
|
435
|
+
Here's a complete example of how to build a visualizer application:
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
import { McpGraphApi, type ExecutionHooks, type ExecutionState } from 'mcpgraph';
|
|
439
|
+
|
|
440
|
+
class GraphVisualizer {
|
|
441
|
+
private api: McpGraphApi;
|
|
442
|
+
private executionPromise: Promise<any> | null = null;
|
|
443
|
+
|
|
444
|
+
constructor(configPath: string) {
|
|
445
|
+
this.api = new McpGraphApi(configPath);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async executeWithVisualization(toolName: string, args: Record<string, unknown>) {
|
|
449
|
+
const hooks: ExecutionHooks = {
|
|
450
|
+
onNodeStart: async (nodeId, node, context) => {
|
|
451
|
+
this.updateNodeStatus(nodeId, 'running');
|
|
452
|
+
this.highlightNode(nodeId);
|
|
453
|
+
return true;
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
onNodeComplete: async (nodeId, node, input, output, duration) => {
|
|
457
|
+
this.updateNodeStatus(nodeId, 'completed');
|
|
458
|
+
this.updateNodeData(nodeId, { input, output, duration });
|
|
459
|
+
this.unhighlightNode(nodeId);
|
|
460
|
+
},
|
|
461
|
+
|
|
462
|
+
onNodeError: async (nodeId, node, error, context) => {
|
|
463
|
+
this.updateNodeStatus(nodeId, 'error');
|
|
464
|
+
this.showError(nodeId, error);
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
onPause: async (nodeId, context) => {
|
|
468
|
+
this.showDebuggerControls();
|
|
469
|
+
this.updateExecutionStatus('paused');
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
onResume: async () => {
|
|
473
|
+
this.hideDebuggerControls();
|
|
474
|
+
this.updateExecutionStatus('running');
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
this.executionPromise = this.api.executeTool(toolName, args, {
|
|
479
|
+
hooks,
|
|
480
|
+
enableTelemetry: true
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const result = await this.executionPromise;
|
|
484
|
+
this.executionPromise = null;
|
|
485
|
+
|
|
486
|
+
// Display results
|
|
487
|
+
this.displayHistory(result.executionHistory || []);
|
|
488
|
+
this.displayTelemetry(result.telemetry);
|
|
489
|
+
|
|
490
|
+
return result;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
pause() {
|
|
494
|
+
const controller = this.api.getController();
|
|
495
|
+
if (controller) {
|
|
496
|
+
controller.pause();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
resume() {
|
|
501
|
+
const controller = this.api.getController();
|
|
502
|
+
if (controller) {
|
|
503
|
+
controller.resume();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async step() {
|
|
508
|
+
const controller = this.api.getController();
|
|
509
|
+
if (controller) {
|
|
510
|
+
await controller.step();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
setBreakpoints(nodeIds: string[]) {
|
|
515
|
+
const controller = this.api.getController();
|
|
516
|
+
if (controller) {
|
|
517
|
+
controller.setBreakpoints(nodeIds);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
getCurrentState(): ExecutionState | null {
|
|
522
|
+
return this.api.getExecutionState();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// UI update methods (implement based on your UI framework)
|
|
526
|
+
private updateNodeStatus(nodeId: string, status: string) { /* ... */ }
|
|
527
|
+
private highlightNode(nodeId: string) { /* ... */ }
|
|
528
|
+
private unhighlightNode(nodeId: string) { /* ... */ }
|
|
529
|
+
private updateNodeData(nodeId: string, data: any) { /* ... */ }
|
|
530
|
+
private showError(nodeId: string, error: Error) { /* ... */ }
|
|
531
|
+
private showDebuggerControls() { /* ... */ }
|
|
532
|
+
private hideDebuggerControls() { /* ... */ }
|
|
533
|
+
private updateExecutionStatus(status: string) { /* ... */ }
|
|
534
|
+
private displayHistory(history: any[]) { /* ... */ }
|
|
535
|
+
private displayTelemetry(telemetry: any) { /* ... */ }
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
## API Reference
|
|
540
|
+
|
|
541
|
+
### McpGraphApi Methods
|
|
542
|
+
|
|
543
|
+
```typescript
|
|
544
|
+
class McpGraphApi {
|
|
545
|
+
/**
|
|
546
|
+
* Execute a tool with optional debugging options
|
|
547
|
+
*/
|
|
548
|
+
async executeTool(
|
|
549
|
+
toolName: string,
|
|
550
|
+
toolArguments: Record<string, unknown>,
|
|
551
|
+
options?: ExecutionOptions
|
|
552
|
+
): Promise<ExecutionResult>;
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Get the execution controller (available during execution)
|
|
556
|
+
*/
|
|
557
|
+
getController(): ExecutionController | null;
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Get the current execution state (if execution is in progress)
|
|
561
|
+
*/
|
|
562
|
+
getExecutionState(): ExecutionState | null;
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Get the graph structure
|
|
566
|
+
*/
|
|
567
|
+
getGraph(): Graph;
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Get the full configuration
|
|
571
|
+
*/
|
|
572
|
+
getConfig(): McpGraphConfig;
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### ExecutionOptions
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
interface ExecutionOptions {
|
|
580
|
+
hooks?: ExecutionHooks; // Execution hooks for observation/control
|
|
581
|
+
breakpoints?: string[]; // Node IDs to pause at
|
|
582
|
+
enableTelemetry?: boolean; // Enable telemetry collection
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### ExecutionResult
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
interface ExecutionResult {
|
|
590
|
+
result: unknown; // The tool's result
|
|
591
|
+
structuredContent?: Record<string, unknown>; // Structured result (for MCP)
|
|
592
|
+
executionHistory?: NodeExecutionRecord[]; // Execution history
|
|
593
|
+
telemetry?: ExecutionTelemetry; // Performance telemetry
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
## Type Exports
|
|
598
|
+
|
|
599
|
+
All types are exported from the main package:
|
|
600
|
+
|
|
601
|
+
```typescript
|
|
602
|
+
import {
|
|
603
|
+
McpGraphApi,
|
|
604
|
+
type ExecutionHooks,
|
|
605
|
+
type ExecutionController,
|
|
606
|
+
type ExecutionState,
|
|
607
|
+
type ExecutionStatus,
|
|
608
|
+
type ExecutionOptions,
|
|
609
|
+
type ExecutionResult,
|
|
610
|
+
type NodeExecutionRecord,
|
|
611
|
+
type ExecutionTelemetry,
|
|
612
|
+
type NodeDefinition,
|
|
613
|
+
type McpGraphConfig
|
|
614
|
+
} from 'mcpgraph';
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
## Best Practices
|
|
618
|
+
|
|
619
|
+
1. **Enable telemetry only when needed**: Telemetry collection adds overhead, so only enable it when you need performance metrics.
|
|
620
|
+
|
|
621
|
+
2. **Use hooks for real-time updates**: Hooks are perfect for updating UI in real-time as execution progresses.
|
|
622
|
+
|
|
623
|
+
3. **All hooks are async**: All hooks are async functions. If you don't need to perform async operations, you can simply not use `await` - the function will still work correctly. This provides consistency and allows you to add async operations later without changing the function signature.
|
|
624
|
+
|
|
625
|
+
4. **Clean up after execution**: The controller is automatically cleaned up after execution completes, but you should check for `null` when accessing it.
|
|
626
|
+
|
|
627
|
+
5. **Check execution state before operations**: Always verify that execution is in the correct state before calling controller methods (e.g., don't call `resume()` when status is `"running"`).
|
|
628
|
+
|
|
629
|
+
6. **Use breakpoints for debugging**: Breakpoints are more efficient than conditional logic in hooks for simple pause-on-node scenarios.
|
|
630
|
+
|
|
631
|
+
## Limitations
|
|
632
|
+
|
|
633
|
+
- **Controller availability**: The controller is only available during execution when hooks or breakpoints are enabled. After execution completes, it's cleaned up.
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
- **State modification**: You cannot modify execution context during paused execution (this may be added in future versions).
|
|
637
|
+
|
|
638
|
+
- **Concurrent executions**: Each `McpGraphApi` instance supports one execution at a time. For concurrent executions, create multiple instances.
|
|
639
|
+
|
|
640
|
+
## Future Enhancements
|
|
641
|
+
|
|
642
|
+
Planned enhancements include:
|
|
643
|
+
|
|
644
|
+
- Conditional breakpoints (break when expression evaluates to true)
|
|
645
|
+
- Watch expressions (monitor specific values during execution)
|
|
646
|
+
- State modification during paused execution
|
|
647
|
+
- Execution replay from history
|
|
648
|
+
- Execution comparison (compare multiple runs)
|
|
649
|
+
- OpenTelemetry integration for distributed tracing
|
|
650
|
+
|
|
651
|
+
|
package/examples/api-usage.ts
CHANGED
|
@@ -38,6 +38,48 @@ async function example() {
|
|
|
38
38
|
await api.close();
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// Example: Using introspection and debugging features
|
|
42
|
+
async function introspectionExample() {
|
|
43
|
+
const api = new McpGraphApi('examples/count_files.yaml');
|
|
44
|
+
|
|
45
|
+
// Execute with hooks and telemetry
|
|
46
|
+
const result = await api.executeTool('count_files', {
|
|
47
|
+
directory: './tests/files',
|
|
48
|
+
}, {
|
|
49
|
+
hooks: {
|
|
50
|
+
onNodeStart: async (nodeId, node) => {
|
|
51
|
+
console.log(`[Hook] Starting node: ${nodeId} (${node.type})`);
|
|
52
|
+
return true; // Continue execution
|
|
53
|
+
},
|
|
54
|
+
onNodeComplete: async (nodeId, node, input, output, duration) => {
|
|
55
|
+
console.log(`[Hook] Node ${nodeId} completed in ${duration}ms`);
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
enableTelemetry: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Access execution history
|
|
62
|
+
if (result.executionHistory) {
|
|
63
|
+
console.log('\nExecution History:');
|
|
64
|
+
for (const record of result.executionHistory) {
|
|
65
|
+
console.log(` ${record.nodeId} (${record.nodeType}): ${record.duration}ms`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Access telemetry
|
|
70
|
+
if (result.telemetry) {
|
|
71
|
+
console.log('\nTelemetry:');
|
|
72
|
+
console.log(` Total duration: ${result.telemetry.totalDuration}ms`);
|
|
73
|
+
console.log(` Errors: ${result.telemetry.errorCount}`);
|
|
74
|
+
for (const [nodeType, duration] of result.telemetry.nodeDurations) {
|
|
75
|
+
const count = result.telemetry.nodeCounts.get(nodeType) || 0;
|
|
76
|
+
console.log(` ${nodeType}: ${count} executions, ${duration}ms total`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await api.close();
|
|
81
|
+
}
|
|
82
|
+
|
|
41
83
|
// Example: Validate config without creating an API instance
|
|
42
84
|
function validateConfigExample() {
|
|
43
85
|
const errors = McpGraphApi.validateConfig('examples/count_files.yaml');
|
|
@@ -64,6 +106,12 @@ function loadAndValidateExample() {
|
|
|
64
106
|
|
|
65
107
|
// Run examples
|
|
66
108
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
67
|
-
|
|
109
|
+
const exampleToRun = process.argv[2] || 'basic';
|
|
110
|
+
|
|
111
|
+
if (exampleToRun === 'introspection') {
|
|
112
|
+
introspectionExample().catch(console.error);
|
|
113
|
+
} else {
|
|
114
|
+
example().catch(console.error);
|
|
115
|
+
}
|
|
68
116
|
}
|
|
69
117
|
|