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
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ A Next.js application that provides a visual interface for the [mcpGraph](https:
|
|
|
6
6
|
- **List tools**: See all available MCP tools defined in your graph configuration
|
|
7
7
|
- **Test tools**: Execute tools with custom parameters and view results from the exit node
|
|
8
8
|
|
|
9
|
+

|
|
10
|
+
|
|
9
11
|
## Features
|
|
10
12
|
|
|
11
13
|
- 🎨 **Graph Visualization**: Interactive graph visualization using React Flow, showing all nodes and their connections
|
|
@@ -104,3 +106,7 @@ This application follows the design recommendations from the mcpGraph project:
|
|
|
104
106
|
## License
|
|
105
107
|
|
|
106
108
|
MIT
|
|
109
|
+
|
|
110
|
+
## TODO
|
|
111
|
+
|
|
112
|
+
Would be nice to be able to inspect nodes in graph
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getController } from '@/lib/executionController';
|
|
3
|
+
|
|
4
|
+
// Force dynamic rendering
|
|
5
|
+
export const dynamic = 'force-dynamic';
|
|
6
|
+
|
|
7
|
+
// Store breakpoints by executionId (fallback if controller not available)
|
|
8
|
+
const breakpointsStore = new Map<string, string[]>();
|
|
9
|
+
|
|
10
|
+
export async function GET(request: NextRequest) {
|
|
11
|
+
try {
|
|
12
|
+
const executionId = request.nextUrl.searchParams.get('executionId');
|
|
13
|
+
|
|
14
|
+
if (!executionId) {
|
|
15
|
+
return NextResponse.json(
|
|
16
|
+
{ error: 'Missing executionId parameter' },
|
|
17
|
+
{ status: 400 }
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const controller = getController(executionId);
|
|
22
|
+
if (controller) {
|
|
23
|
+
// Get breakpoints from controller if available
|
|
24
|
+
const state = controller.getState();
|
|
25
|
+
// Note: mcpGraph doesn't expose breakpoints directly, so we use our store
|
|
26
|
+
const breakpoints = breakpointsStore.get(executionId) || [];
|
|
27
|
+
return NextResponse.json({ breakpoints });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Fallback to store
|
|
31
|
+
const breakpoints = breakpointsStore.get(executionId) || [];
|
|
32
|
+
return NextResponse.json({ breakpoints });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Error getting breakpoints:', error);
|
|
35
|
+
return NextResponse.json(
|
|
36
|
+
{ error: error instanceof Error ? error.message : 'Unknown error' },
|
|
37
|
+
{ status: 500 }
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function POST(request: NextRequest) {
|
|
43
|
+
try {
|
|
44
|
+
const body = await request.json();
|
|
45
|
+
const { executionId, breakpoints } = body;
|
|
46
|
+
|
|
47
|
+
if (!executionId) {
|
|
48
|
+
return NextResponse.json(
|
|
49
|
+
{ error: 'Missing executionId' },
|
|
50
|
+
{ status: 400 }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!Array.isArray(breakpoints)) {
|
|
55
|
+
return NextResponse.json(
|
|
56
|
+
{ error: 'breakpoints must be an array' },
|
|
57
|
+
{ status: 400 }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const controller = getController(executionId);
|
|
62
|
+
if (controller) {
|
|
63
|
+
controller.setBreakpoints(breakpoints);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Store breakpoints
|
|
67
|
+
breakpointsStore.set(executionId, breakpoints);
|
|
68
|
+
|
|
69
|
+
return NextResponse.json({ success: true, breakpoints });
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Error setting breakpoints:', error);
|
|
72
|
+
return NextResponse.json(
|
|
73
|
+
{ error: error instanceof Error ? error.message : 'Unknown error' },
|
|
74
|
+
{ status: 500 }
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function DELETE(request: NextRequest) {
|
|
80
|
+
try {
|
|
81
|
+
const executionId = request.nextUrl.searchParams.get('executionId');
|
|
82
|
+
|
|
83
|
+
if (!executionId) {
|
|
84
|
+
return NextResponse.json(
|
|
85
|
+
{ error: 'Missing executionId parameter' },
|
|
86
|
+
{ status: 400 }
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const controller = getController(executionId);
|
|
91
|
+
if (controller) {
|
|
92
|
+
controller.clearBreakpoints();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
breakpointsStore.delete(executionId);
|
|
96
|
+
|
|
97
|
+
return NextResponse.json({ success: true });
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('Error clearing breakpoints:', error);
|
|
100
|
+
return NextResponse.json(
|
|
101
|
+
{ error: error instanceof Error ? error.message : 'Unknown error' },
|
|
102
|
+
{ status: 500 }
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getController } from '@/lib/executionController';
|
|
3
|
+
import { getApi } from '@/lib/mcpGraphApi';
|
|
4
|
+
|
|
5
|
+
// Force dynamic rendering
|
|
6
|
+
export const dynamic = 'force-dynamic';
|
|
7
|
+
|
|
8
|
+
export async function GET(request: NextRequest) {
|
|
9
|
+
try {
|
|
10
|
+
const executionId = request.nextUrl.searchParams.get('executionId');
|
|
11
|
+
const nodeId = request.nextUrl.searchParams.get('nodeId');
|
|
12
|
+
const sequenceIdParam = request.nextUrl.searchParams.get('sequenceId');
|
|
13
|
+
|
|
14
|
+
if (!executionId) {
|
|
15
|
+
return NextResponse.json(
|
|
16
|
+
{ error: 'Missing executionId parameter' },
|
|
17
|
+
{ status: 400 }
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!nodeId || !sequenceIdParam) {
|
|
22
|
+
return NextResponse.json(
|
|
23
|
+
{ error: 'Missing nodeId or sequenceId parameter' },
|
|
24
|
+
{ status: 400 }
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const sequenceId = parseInt(sequenceIdParam, 10);
|
|
29
|
+
if (isNaN(sequenceId)) {
|
|
30
|
+
return NextResponse.json(
|
|
31
|
+
{ error: 'Invalid sequenceId parameter' },
|
|
32
|
+
{ status: 400 }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Try to get controller first (for active executions)
|
|
37
|
+
const controller = getController(executionId);
|
|
38
|
+
let context: Record<string, unknown> | null = null;
|
|
39
|
+
|
|
40
|
+
if (controller) {
|
|
41
|
+
// Use controller's context directly (works for active executions)
|
|
42
|
+
try {
|
|
43
|
+
const state = controller.getState();
|
|
44
|
+
context = state.context.getContextForExecution(sequenceId);
|
|
45
|
+
console.log(`[API] Got context from controller for executionIndex=${sequenceId}, nodeId=${nodeId}:`, context ? 'present' : 'null');
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(`[API] Error getting context from controller:`, error);
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
// Fallback to API method (may not work after execution completes)
|
|
51
|
+
const api = getApi();
|
|
52
|
+
if (api) {
|
|
53
|
+
context = api.getContextForExecution(sequenceId);
|
|
54
|
+
console.log(`[API] Got context from API for executionIndex=${sequenceId}, nodeId=${nodeId}:`, context ? 'present' : 'null');
|
|
55
|
+
} else {
|
|
56
|
+
console.error(`[API] No controller or API available`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return NextResponse.json({ context: context || null });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Error getting node input context:', error);
|
|
63
|
+
return NextResponse.json(
|
|
64
|
+
{ error: error instanceof Error ? error.message : 'Unknown error' },
|
|
65
|
+
{ status: 500 }
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getController, unregisterController, getAllExecutionIds } from '@/lib/executionController';
|
|
3
|
+
import { sendExecutionEvent, closeExecutionStream } from '@/lib/executionStreamServer';
|
|
4
|
+
import { getApi } from '@/lib/mcpGraphApi';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export async function POST(request: NextRequest) {
|
|
8
|
+
try {
|
|
9
|
+
const body = await request.json();
|
|
10
|
+
const { executionId, action } = body;
|
|
11
|
+
|
|
12
|
+
if (!executionId) {
|
|
13
|
+
return NextResponse.json(
|
|
14
|
+
{ error: 'Missing executionId' },
|
|
15
|
+
{ status: 400 }
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!action || !['pause', 'resume', 'step', 'stop'].includes(action)) {
|
|
20
|
+
return NextResponse.json(
|
|
21
|
+
{ error: 'Invalid action. Must be one of: pause, resume, step, stop' },
|
|
22
|
+
{ status: 400 }
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(`[Controller API] Looking for controller with executionId: ${executionId}`);
|
|
27
|
+
// Force getApi to be called to ensure module is loaded
|
|
28
|
+
const api = getApi();
|
|
29
|
+
console.log(`[Controller API] API instance: ${api ? 'present' : 'null'}`);
|
|
30
|
+
const controller = getController(executionId);
|
|
31
|
+
if (!controller) {
|
|
32
|
+
console.log(`[Controller API] Controller not found for executionId: ${executionId}`);
|
|
33
|
+
// Log all registered executionIds for debugging
|
|
34
|
+
const allIds = getAllExecutionIds();
|
|
35
|
+
console.log(`[Controller API] Currently registered executionIds: ${allIds.length > 0 ? allIds.join(', ') : 'none'}`);
|
|
36
|
+
return NextResponse.json(
|
|
37
|
+
{ error: 'Execution not found or not active' },
|
|
38
|
+
{ status: 404 }
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
console.log(`[Controller API] Found controller for executionId: ${executionId}, status: ${controller.getState().status}`);
|
|
42
|
+
|
|
43
|
+
const state = controller.getState();
|
|
44
|
+
|
|
45
|
+
switch (action) {
|
|
46
|
+
case 'pause':
|
|
47
|
+
if (state.status !== 'running') {
|
|
48
|
+
return NextResponse.json(
|
|
49
|
+
{ error: `Cannot pause: execution status is ${state.status}` },
|
|
50
|
+
{ status: 400 }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
controller.pause();
|
|
54
|
+
// Don't send stateUpdate here - onPause hook will send it
|
|
55
|
+
return NextResponse.json({ success: true, status: 'paused' });
|
|
56
|
+
|
|
57
|
+
case 'resume':
|
|
58
|
+
if (state.status !== 'paused') {
|
|
59
|
+
return NextResponse.json(
|
|
60
|
+
{ error: `Cannot resume: execution status is ${state.status}` },
|
|
61
|
+
{ status: 400 }
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
controller.resume();
|
|
65
|
+
// Don't send stateUpdate here - the onResume hook already sends it
|
|
66
|
+
return NextResponse.json({ success: true, status: 'running' });
|
|
67
|
+
|
|
68
|
+
case 'step':
|
|
69
|
+
if (state.status !== 'paused') {
|
|
70
|
+
return NextResponse.json(
|
|
71
|
+
{ error: `Cannot step: execution status is ${state.status}` },
|
|
72
|
+
{ status: 400 }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
await controller.step();
|
|
76
|
+
const newState = controller.getState();
|
|
77
|
+
// Don't send stateUpdate here - the onPause hook already sends the correct stateUpdate
|
|
78
|
+
// when step completes and pauses at the next node
|
|
79
|
+
return NextResponse.json({
|
|
80
|
+
success: true,
|
|
81
|
+
status: newState.status,
|
|
82
|
+
currentNodeId: newState.currentNodeId,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
case 'stop':
|
|
86
|
+
if (state.status !== 'running' && state.status !== 'paused') {
|
|
87
|
+
return NextResponse.json(
|
|
88
|
+
{ error: `Cannot stop: execution status is ${state.status}` },
|
|
89
|
+
{ status: 400 }
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Call stop() - this sets status to "stopped" and will cause execution to throw "Execution was stopped"
|
|
94
|
+
controller.stop();
|
|
95
|
+
|
|
96
|
+
// Send stopped event
|
|
97
|
+
sendExecutionEvent(executionId, 'executionStopped', {
|
|
98
|
+
timestamp: Date.now(),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Clean up controller and stream
|
|
102
|
+
unregisterController(executionId);
|
|
103
|
+
closeExecutionStream(executionId);
|
|
104
|
+
|
|
105
|
+
return NextResponse.json({ success: true, status: 'stopped' });
|
|
106
|
+
|
|
107
|
+
default:
|
|
108
|
+
return NextResponse.json(
|
|
109
|
+
{ error: 'Invalid action' },
|
|
110
|
+
{ status: 400 }
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error('Error in controller action:', error);
|
|
115
|
+
return NextResponse.json(
|
|
116
|
+
{ error: error instanceof Error ? error.message : 'Unknown error' },
|
|
117
|
+
{ status: 500 }
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getController } from '@/lib/executionController';
|
|
3
|
+
import { getApi } from '@/lib/mcpGraphApi';
|
|
4
|
+
|
|
5
|
+
// Force dynamic rendering
|
|
6
|
+
export const dynamic = 'force-dynamic';
|
|
7
|
+
|
|
8
|
+
export async function GET(request: NextRequest) {
|
|
9
|
+
try {
|
|
10
|
+
const executionId = request.nextUrl.searchParams.get('executionId');
|
|
11
|
+
|
|
12
|
+
if (!executionId) {
|
|
13
|
+
return NextResponse.json(
|
|
14
|
+
{ error: 'Missing executionId parameter' },
|
|
15
|
+
{ status: 400 }
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const controller = getController(executionId);
|
|
20
|
+
if (!controller) {
|
|
21
|
+
return NextResponse.json(
|
|
22
|
+
{ error: 'Execution not found or not active' },
|
|
23
|
+
{ status: 404 }
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Get execution history from the controller's state
|
|
28
|
+
const state = controller.getState();
|
|
29
|
+
const history = state.executionHistory || [];
|
|
30
|
+
|
|
31
|
+
return NextResponse.json({ history });
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('Error getting execution history:', error);
|
|
34
|
+
return NextResponse.json(
|
|
35
|
+
{ error: error instanceof Error ? error.message : 'Unknown error' },
|
|
36
|
+
{ status: 500 }
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getController } from '@/lib/executionController';
|
|
3
|
+
import { getApi } from '@/lib/mcpGraphApi';
|
|
4
|
+
import type { NodeExecutionRecord } from 'mcpgraph';
|
|
5
|
+
|
|
6
|
+
// Force dynamic rendering
|
|
7
|
+
export const dynamic = 'force-dynamic';
|
|
8
|
+
|
|
9
|
+
export async function GET(request: NextRequest) {
|
|
10
|
+
try {
|
|
11
|
+
const executionId = request.nextUrl.searchParams.get('executionId');
|
|
12
|
+
|
|
13
|
+
if (!executionId) {
|
|
14
|
+
return NextResponse.json(
|
|
15
|
+
{ error: 'Missing executionId parameter' },
|
|
16
|
+
{ status: 400 }
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const controller = getController(executionId);
|
|
21
|
+
if (!controller) {
|
|
22
|
+
return NextResponse.json(
|
|
23
|
+
{ error: 'Execution not found or not active' },
|
|
24
|
+
{ status: 404 }
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const api = getApi();
|
|
29
|
+
if (!api) {
|
|
30
|
+
return NextResponse.json(
|
|
31
|
+
{ error: 'API instance not available' },
|
|
32
|
+
{ status: 500 }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Get execution history by iterating through execution records
|
|
37
|
+
// We'll get records until we hit null
|
|
38
|
+
const history: NodeExecutionRecord[] = [];
|
|
39
|
+
let index = 0;
|
|
40
|
+
|
|
41
|
+
while (true) {
|
|
42
|
+
const record = api.getExecutionByIndex(index);
|
|
43
|
+
if (!record) break;
|
|
44
|
+
history.push(record);
|
|
45
|
+
index++;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return NextResponse.json({ history });
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('Error getting execution history with indices:', error);
|
|
51
|
+
return NextResponse.json(
|
|
52
|
+
{ error: error instanceof Error ? error.message : 'Unknown error' },
|
|
53
|
+
{ status: 500 }
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { registerExecutionStream, unregisterExecutionStream } from '@/lib/executionStreamServer';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export async function GET(request: NextRequest) {
|
|
6
|
+
const executionId = request.nextUrl.searchParams.get('executionId');
|
|
7
|
+
|
|
8
|
+
if (!executionId) {
|
|
9
|
+
return new Response('Missing executionId parameter', { status: 400 });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const stream = new ReadableStream({
|
|
13
|
+
start(controller) {
|
|
14
|
+
// Register stream for this execution
|
|
15
|
+
registerExecutionStream(executionId, controller);
|
|
16
|
+
|
|
17
|
+
// Note: The actual execution will be started via POST /api/tools/[toolName]
|
|
18
|
+
// This stream will receive events from those hooks
|
|
19
|
+
},
|
|
20
|
+
cancel() {
|
|
21
|
+
// Clean up when client disconnects
|
|
22
|
+
unregisterExecutionStream(executionId);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return new Response(stream, {
|
|
27
|
+
headers: {
|
|
28
|
+
'Content-Type': 'text/event-stream',
|
|
29
|
+
'Cache-Control': 'no-cache',
|
|
30
|
+
'Connection': 'keep-alive',
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
package/app/api/graph/route.ts
CHANGED
|
@@ -1,31 +1,51 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
|
-
import {
|
|
2
|
+
import { type NodeDefinition } from 'mcpgraph';
|
|
3
|
+
import { getApi } from '@/lib/mcpGraphApi';
|
|
3
4
|
|
|
4
5
|
// Force dynamic rendering - this route requires runtime config
|
|
5
6
|
export const dynamic = 'force-dynamic';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
function getApi(): McpGraphApi {
|
|
10
|
-
const configPath = process.env.MCPGRAPH_CONFIG_PATH;
|
|
11
|
-
if (!configPath) {
|
|
12
|
-
throw new Error('MCPGRAPH_CONFIG_PATH environment variable is not set');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (!apiInstance) {
|
|
16
|
-
apiInstance = new McpGraphApi(configPath);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return apiInstance;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function GET() {
|
|
8
|
+
export async function GET(request: Request) {
|
|
23
9
|
try {
|
|
24
10
|
const api = getApi();
|
|
25
11
|
const config = api.getConfig();
|
|
26
12
|
|
|
13
|
+
// Get toolName from query parameter
|
|
14
|
+
const url = new URL(request.url);
|
|
15
|
+
const toolName = url.searchParams.get('toolName');
|
|
16
|
+
|
|
17
|
+
if (!toolName) {
|
|
18
|
+
return NextResponse.json(
|
|
19
|
+
{ error: 'toolName query parameter is required' },
|
|
20
|
+
{ status: 400 }
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Get tool definition from config.tools (which is ToolDefinition[], not ToolInfo)
|
|
25
|
+
// getTool() returns ToolInfo which doesn't have nodes - we need ToolDefinition from config
|
|
26
|
+
const tool = config.tools.find(t => t.name === toolName);
|
|
27
|
+
|
|
28
|
+
if (!tool) {
|
|
29
|
+
const toolNames = config.tools.map(t => t.name);
|
|
30
|
+
return NextResponse.json(
|
|
31
|
+
{ error: `Tool '${toolName}' not found. Available tools: ${toolNames.join(', ')}` },
|
|
32
|
+
{ status: 404 }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!tool.nodes || tool.nodes.length === 0) {
|
|
37
|
+
return NextResponse.json(
|
|
38
|
+
{ error: `Tool '${toolName}' has no nodes defined` },
|
|
39
|
+
{ status: 404 }
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const allNodes: NodeDefinition[] = tool.nodes;
|
|
44
|
+
console.log(`[graph/route] Found ${allNodes.length} nodes for tool '${toolName}'`);
|
|
45
|
+
console.log(`[graph/route] Node IDs:`, allNodes.map(n => n.id));
|
|
46
|
+
|
|
27
47
|
// Transform nodes into React Flow format
|
|
28
|
-
const nodes =
|
|
48
|
+
const nodes = allNodes.map((node: NodeDefinition) => {
|
|
29
49
|
const baseNode = {
|
|
30
50
|
id: node.id,
|
|
31
51
|
type: node.type,
|
|
@@ -37,39 +57,47 @@ export async function GET() {
|
|
|
37
57
|
position: { x: 0, y: 0 }, // Will be calculated by layout algorithm
|
|
38
58
|
};
|
|
39
59
|
|
|
40
|
-
// Add specific data based on node type
|
|
41
|
-
if (node.type === 'entry'
|
|
60
|
+
// Add specific data based on node type using type guards
|
|
61
|
+
if (node.type === 'entry' && 'tool' in node) {
|
|
62
|
+
return {
|
|
63
|
+
...baseNode,
|
|
64
|
+
data: {
|
|
65
|
+
...baseNode.data,
|
|
66
|
+
tool: node.tool,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
} else if (node.type === 'exit' && 'tool' in node) {
|
|
42
70
|
return {
|
|
43
71
|
...baseNode,
|
|
44
72
|
data: {
|
|
45
73
|
...baseNode.data,
|
|
46
|
-
tool:
|
|
74
|
+
tool: node.tool,
|
|
47
75
|
},
|
|
48
76
|
};
|
|
49
|
-
} else if (node.type === 'mcp') {
|
|
77
|
+
} else if (node.type === 'mcp' && 'server' in node && 'tool' in node && 'args' in node) {
|
|
50
78
|
return {
|
|
51
79
|
...baseNode,
|
|
52
80
|
data: {
|
|
53
81
|
...baseNode.data,
|
|
54
|
-
server:
|
|
55
|
-
tool:
|
|
56
|
-
args:
|
|
82
|
+
server: node.server,
|
|
83
|
+
tool: node.tool,
|
|
84
|
+
args: node.args,
|
|
57
85
|
},
|
|
58
86
|
};
|
|
59
|
-
} else if (node.type === 'transform') {
|
|
87
|
+
} else if (node.type === 'transform' && 'transform' in node) {
|
|
60
88
|
return {
|
|
61
89
|
...baseNode,
|
|
62
90
|
data: {
|
|
63
91
|
...baseNode.data,
|
|
64
|
-
transform:
|
|
92
|
+
transform: node.transform,
|
|
65
93
|
},
|
|
66
94
|
};
|
|
67
|
-
} else if (node.type === 'switch') {
|
|
95
|
+
} else if (node.type === 'switch' && 'conditions' in node) {
|
|
68
96
|
return {
|
|
69
97
|
...baseNode,
|
|
70
98
|
data: {
|
|
71
99
|
...baseNode.data,
|
|
72
|
-
conditions:
|
|
100
|
+
conditions: node.conditions,
|
|
73
101
|
},
|
|
74
102
|
};
|
|
75
103
|
}
|
|
@@ -80,7 +108,7 @@ export async function GET() {
|
|
|
80
108
|
// Create edges from node.next and switch conditions
|
|
81
109
|
const edges: Array<{ id: string; source: string; target: string; label?: string }> = [];
|
|
82
110
|
|
|
83
|
-
|
|
111
|
+
allNodes.forEach((node: NodeDefinition) => {
|
|
84
112
|
if ('next' in node && node.next) {
|
|
85
113
|
edges.push({
|
|
86
114
|
id: `${node.id}-${node.next}`,
|
|
@@ -90,8 +118,7 @@ export async function GET() {
|
|
|
90
118
|
}
|
|
91
119
|
|
|
92
120
|
if (node.type === 'switch' && 'conditions' in node) {
|
|
93
|
-
|
|
94
|
-
switchNode.conditions.forEach((condition: any, index: number) => {
|
|
121
|
+
node.conditions.forEach((condition, index: number) => {
|
|
95
122
|
edges.push({
|
|
96
123
|
id: `${node.id}-${condition.target}-${index}`,
|
|
97
124
|
source: node.id,
|
|
@@ -102,7 +129,46 @@ export async function GET() {
|
|
|
102
129
|
}
|
|
103
130
|
});
|
|
104
131
|
|
|
105
|
-
|
|
132
|
+
console.log(`[graph/route] Returning ${nodes.length} nodes and ${edges.length} edges`);
|
|
133
|
+
|
|
134
|
+
return NextResponse.json({
|
|
135
|
+
nodes,
|
|
136
|
+
edges,
|
|
137
|
+
tools: config.tools,
|
|
138
|
+
config: {
|
|
139
|
+
name: config.server.name,
|
|
140
|
+
version: config.server.version,
|
|
141
|
+
servers: Object.entries(config.mcpServers || {}).map(([name, server]) => {
|
|
142
|
+
const details: {
|
|
143
|
+
name: string;
|
|
144
|
+
type: string;
|
|
145
|
+
command?: string;
|
|
146
|
+
args?: string[];
|
|
147
|
+
cwd?: string;
|
|
148
|
+
url?: string;
|
|
149
|
+
headers?: Record<string, string>;
|
|
150
|
+
} = {
|
|
151
|
+
name,
|
|
152
|
+
type: server.type || 'stdio',
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
if (server.type === 'stdio' || !server.type) {
|
|
156
|
+
details.command = server.command;
|
|
157
|
+
details.args = server.args || [];
|
|
158
|
+
if (server.cwd) {
|
|
159
|
+
details.cwd = server.cwd;
|
|
160
|
+
}
|
|
161
|
+
} else if (server.type === 'sse' || server.type === 'streamableHttp') {
|
|
162
|
+
details.url = server.url;
|
|
163
|
+
if (server.headers) {
|
|
164
|
+
details.headers = server.headers;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return details;
|
|
169
|
+
}),
|
|
170
|
+
},
|
|
171
|
+
});
|
|
106
172
|
} catch (error) {
|
|
107
173
|
console.error('Error getting graph:', error);
|
|
108
174
|
return NextResponse.json(
|