mcpgraph-ux 0.1.2 â 0.1.4
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 +6 -0
- package/app/api/execution/breakpoints/route.ts +106 -0
- package/app/api/execution/context/route.ts +69 -0
- package/app/api/execution/controller/route.ts +121 -0
- package/app/api/execution/history/route.ts +40 -0
- package/app/api/execution/history-with-indices/route.ts +57 -0
- package/app/api/execution/stream/route.ts +34 -0
- package/app/api/graph/route.ts +99 -33
- package/app/api/tools/[toolName]/route.ts +338 -21
- package/app/api/tools/route.ts +1 -16
- package/app/page.module.css +64 -18
- package/app/page.tsx +60 -26
- package/components/DebugControls.module.css +124 -0
- package/components/DebugControls.tsx +209 -0
- package/components/ExecutionHistory.module.css +371 -0
- package/components/ExecutionHistory.tsx +272 -0
- package/components/GraphVisualization.module.css +11 -0
- package/components/GraphVisualization.tsx +353 -70
- package/components/InputForm.module.css +146 -0
- package/components/InputForm.tsx +282 -0
- package/components/ServerDetails.module.css +118 -0
- package/components/ServerDetails.tsx +116 -0
- package/components/TelemetryDashboard.module.css +177 -0
- package/components/TelemetryDashboard.tsx +154 -0
- package/components/ToolList.module.css +2 -2
- package/components/ToolTester.module.css +54 -110
- package/components/ToolTester.tsx +746 -235
- package/package.json +8 -9
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import styles from './ExecutionHistory.module.css';
|
|
5
|
+
|
|
6
|
+
export interface NodeExecutionRecord {
|
|
7
|
+
nodeId: string;
|
|
8
|
+
nodeType: string;
|
|
9
|
+
startTime: number;
|
|
10
|
+
endTime?: number; // Optional for pending/running nodes
|
|
11
|
+
duration?: number; // Optional for pending/running nodes
|
|
12
|
+
input: unknown;
|
|
13
|
+
output?: unknown; // Optional for pending/running nodes
|
|
14
|
+
error?: Error;
|
|
15
|
+
executionIndex?: number; // For tracking specific node instances
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ExecutionHistoryProps {
|
|
19
|
+
history: NodeExecutionRecord[];
|
|
20
|
+
onNodeClick?: (nodeId: string) => void;
|
|
21
|
+
result?: unknown;
|
|
22
|
+
telemetry?: {
|
|
23
|
+
totalDuration: number;
|
|
24
|
+
nodeCounts: Record<string, number>;
|
|
25
|
+
errorCount: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function ExecutionHistory({ history, onNodeClick, result, telemetry }: ExecutionHistoryProps) {
|
|
30
|
+
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
|
|
31
|
+
|
|
32
|
+
const toggleExpand = (nodeId: string) => {
|
|
33
|
+
const newExpanded = new Set(expandedNodes);
|
|
34
|
+
if (newExpanded.has(nodeId)) {
|
|
35
|
+
newExpanded.delete(nodeId);
|
|
36
|
+
} else {
|
|
37
|
+
newExpanded.add(nodeId);
|
|
38
|
+
}
|
|
39
|
+
setExpandedNodes(newExpanded);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const formatTime = (timestamp: number) => {
|
|
43
|
+
return new Date(timestamp).toLocaleTimeString('en-US', {
|
|
44
|
+
hour12: false,
|
|
45
|
+
hour: '2-digit',
|
|
46
|
+
minute: '2-digit',
|
|
47
|
+
second: '2-digit',
|
|
48
|
+
fractionalSecondDigits: 3,
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const formatDuration = (ms: number) => {
|
|
53
|
+
if (ms < 1) return `${(ms * 1000).toFixed(0)}Ξs`;
|
|
54
|
+
if (ms < 1000) return `${ms.toFixed(2)}ms`;
|
|
55
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const formatJSON = (data: unknown) => {
|
|
59
|
+
try {
|
|
60
|
+
return JSON.stringify(data, null, 2);
|
|
61
|
+
} catch {
|
|
62
|
+
return String(data);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (history.length === 0 && !result) {
|
|
67
|
+
return (
|
|
68
|
+
<div className={styles.container}>
|
|
69
|
+
<div className={styles.empty}>No execution history available</div>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className={styles.container}>
|
|
76
|
+
<div className={styles.list}>
|
|
77
|
+
{history.map((record, index) => {
|
|
78
|
+
const isExpanded = expandedNodes.has(record.nodeId);
|
|
79
|
+
const hasError = !!record.error;
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div
|
|
83
|
+
key={`${record.nodeId}-${index}`}
|
|
84
|
+
className={`${styles.item} ${hasError ? styles.error : ''}`}
|
|
85
|
+
>
|
|
86
|
+
<div
|
|
87
|
+
className={styles.header}
|
|
88
|
+
onClick={() => {
|
|
89
|
+
toggleExpand(record.nodeId);
|
|
90
|
+
onNodeClick?.(record.nodeId);
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
<div className={styles.nodeInfo}>
|
|
94
|
+
<span className={styles.nodeId}>{record.nodeId}</span>
|
|
95
|
+
<span className={styles.nodeType}>{record.nodeType}</span>
|
|
96
|
+
{hasError && <span className={styles.errorBadge}>ERROR</span>}
|
|
97
|
+
</div>
|
|
98
|
+
<div className={styles.timing}>
|
|
99
|
+
{record.duration !== undefined ? (
|
|
100
|
+
<span className={styles.duration}>{formatDuration(record.duration)}</span>
|
|
101
|
+
) : (
|
|
102
|
+
<span className={styles.duration} style={{ fontStyle: 'italic', color: '#666' }}>pending</span>
|
|
103
|
+
)}
|
|
104
|
+
<span className={styles.time}>{formatTime(record.startTime)}</span>
|
|
105
|
+
</div>
|
|
106
|
+
<button className={styles.expandButton}>
|
|
107
|
+
{isExpanded ? 'âž' : 'âķ'}
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{isExpanded && (
|
|
112
|
+
<div className={styles.details}>
|
|
113
|
+
<div className={styles.dataSection}>
|
|
114
|
+
<div className={styles.dataItem}>
|
|
115
|
+
<strong>Input:</strong>
|
|
116
|
+
<pre className={styles.jsonData}>{formatJSON(record.input)}</pre>
|
|
117
|
+
</div>
|
|
118
|
+
{hasError ? (
|
|
119
|
+
<div className={styles.dataItem}>
|
|
120
|
+
<strong>Error:</strong>
|
|
121
|
+
<pre className={styles.errorOutput}>
|
|
122
|
+
{(() => {
|
|
123
|
+
const err = record.error;
|
|
124
|
+
if (!err) return 'Unknown error';
|
|
125
|
+
|
|
126
|
+
// Extract error properties
|
|
127
|
+
const errorCode = 'code' in err && typeof err.code === 'number' ? err.code : null;
|
|
128
|
+
const errorData = 'data' in err ? err.data : null;
|
|
129
|
+
const errorType = 'errorType' in err && typeof err.errorType === 'string' ? err.errorType : 'unknown';
|
|
130
|
+
const stderr = 'stderr' in err && Array.isArray(err.stderr) ? err.stderr : null;
|
|
131
|
+
const result = 'result' in err ? err.result : null;
|
|
132
|
+
|
|
133
|
+
// Clean the message - remove stderr part if stderr is available as separate property
|
|
134
|
+
let errorText = err.message || 'Unknown error';
|
|
135
|
+
if (stderr && stderr.length > 0) {
|
|
136
|
+
// Remove the stderr part that was concatenated into the message
|
|
137
|
+
errorText = errorText.split('\n\nServer stderr output:')[0].trim();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Prepend error code if available
|
|
141
|
+
if (errorCode !== null) {
|
|
142
|
+
errorText = `[Error ${errorCode}] ${errorText}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<>
|
|
147
|
+
<div className={styles.errorTypeBadge}>
|
|
148
|
+
{errorType === 'mcp' && 'ðī MCP Protocol Error'}
|
|
149
|
+
{errorType === 'tool' && 'ðĄ Tool Error'}
|
|
150
|
+
{errorType === 'unknown' && 'â Error'}
|
|
151
|
+
</div>
|
|
152
|
+
{errorText}
|
|
153
|
+
|
|
154
|
+
{/* Show stderr for MCP errors */}
|
|
155
|
+
{stderr && stderr.length > 0 && (
|
|
156
|
+
<div className={styles.errorSection}>
|
|
157
|
+
<strong>Server stderr output:</strong>
|
|
158
|
+
<pre className={styles.stderrOutput}>{stderr.join('\n')}</pre>
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{/* Show error data for MCP errors */}
|
|
163
|
+
{errorData !== null && errorData !== undefined && (
|
|
164
|
+
<div className={styles.errorSection}>
|
|
165
|
+
<strong>Error Details:</strong>
|
|
166
|
+
<pre className={styles.errorDataPre}>{formatJSON(errorData)}</pre>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{/* Show result for tool errors */}
|
|
171
|
+
{result !== null && result !== undefined && (
|
|
172
|
+
<div className={styles.errorSection}>
|
|
173
|
+
<strong>Tool Call Result:</strong>
|
|
174
|
+
<pre className={styles.errorDataPre}>{formatJSON(result)}</pre>
|
|
175
|
+
</div>
|
|
176
|
+
)}
|
|
177
|
+
|
|
178
|
+
{err.stack && (
|
|
179
|
+
<div className={styles.errorSection}>
|
|
180
|
+
<strong>Stack Trace:</strong>
|
|
181
|
+
<pre className={styles.errorDataPre}>{err.stack}</pre>
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
</>
|
|
185
|
+
);
|
|
186
|
+
})()}
|
|
187
|
+
</pre>
|
|
188
|
+
</div>
|
|
189
|
+
) : record.output !== undefined ? (
|
|
190
|
+
<div className={styles.dataItem}>
|
|
191
|
+
<strong>Output:</strong>
|
|
192
|
+
<pre className={styles.jsonData}>{formatJSON(record.output)}</pre>
|
|
193
|
+
</div>
|
|
194
|
+
) : (
|
|
195
|
+
<div className={styles.dataItem}>
|
|
196
|
+
<strong>Output:</strong>
|
|
197
|
+
<div style={{ fontStyle: 'italic', color: '#666' }}>Pending execution</div>
|
|
198
|
+
</div>
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div className={styles.metadata}>
|
|
203
|
+
<div>Start: {formatTime(record.startTime)}</div>
|
|
204
|
+
{record.endTime !== undefined && (
|
|
205
|
+
<div>End: {formatTime(record.endTime)}</div>
|
|
206
|
+
)}
|
|
207
|
+
{record.duration !== undefined && (
|
|
208
|
+
<div>Duration: {formatDuration(record.duration)}</div>
|
|
209
|
+
)}
|
|
210
|
+
{(record.endTime === undefined || record.duration === undefined) && (
|
|
211
|
+
<div style={{ fontStyle: 'italic', color: '#666' }}>Status: Pending</div>
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
})}
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
{/* Result display at the bottom - always expanded and styled to stand out */}
|
|
222
|
+
{result !== null && result !== undefined && (
|
|
223
|
+
<div className={`${styles.resultItem} ${result && typeof result === 'object' && 'error' in result ? styles.errorResultItem : ''}`}>
|
|
224
|
+
<div className={styles.resultHeader}>
|
|
225
|
+
<div className={styles.resultTitle}>
|
|
226
|
+
{result && typeof result === 'object' && 'error' in result ? (
|
|
227
|
+
<>
|
|
228
|
+
<span className={styles.errorIcon}>â</span>
|
|
229
|
+
<strong style={{ color: '#c62828' }}>Execution Error</strong>
|
|
230
|
+
</>
|
|
231
|
+
) : (
|
|
232
|
+
<>
|
|
233
|
+
<span className={styles.resultIcon}>â</span>
|
|
234
|
+
<strong>Final Result</strong>
|
|
235
|
+
</>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
{telemetry && (
|
|
239
|
+
<div className={styles.resultStats}>
|
|
240
|
+
<span className={styles.statItem}>
|
|
241
|
+
<strong>Elapsed:</strong> {formatDuration(telemetry.totalDuration)}
|
|
242
|
+
</span>
|
|
243
|
+
<span className={styles.statItem}>
|
|
244
|
+
<strong>Nodes:</strong> {Object.values(telemetry.nodeCounts).reduce((sum, count) => sum + count, 0)}
|
|
245
|
+
</span>
|
|
246
|
+
{telemetry.errorCount > 0 && (
|
|
247
|
+
<span className={`${styles.statItem} ${styles.errorStat}`}>
|
|
248
|
+
<strong>Errors:</strong> {telemetry.errorCount}
|
|
249
|
+
</span>
|
|
250
|
+
)}
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
<div className={styles.resultContent}>
|
|
255
|
+
{result && typeof result === 'object' && 'error' in result ? (
|
|
256
|
+
<div className={styles.errorResult}>
|
|
257
|
+
<pre className={styles.errorPre}>
|
|
258
|
+
{typeof result.error === 'string'
|
|
259
|
+
? result.error
|
|
260
|
+
: 'Execution failed - see node errors above for details'}
|
|
261
|
+
</pre>
|
|
262
|
+
</div>
|
|
263
|
+
) : (
|
|
264
|
+
<pre className={styles.resultPre}>{formatJSON(result)}</pre>
|
|
265
|
+
)}
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
)}
|
|
269
|
+
</div>
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|