@mcpjam/inspector 0.3.5 → 0.3.6
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/cli/build/cli.js +52 -16
- package/client/dist/assets/{OAuthCallback-CaYMoLGO.js → OAuthCallback-Bgz6lAmx.js} +2 -1
- package/client/dist/assets/{OAuthDebugCallback-CBFeYzo9.js → OAuthDebugCallback-Gd08QO37.js} +1 -1
- package/client/dist/assets/{index-DQYTYcqe.js → index-Bgrnc5s2.js} +1505 -976
- package/client/dist/assets/{index-DegSReJM.css → index-CWDemo1t.css} +65 -3
- package/client/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/build/database/DatabaseManager.js +108 -0
- package/server/build/database/index.js +8 -0
- package/server/build/database/routes.js +86 -0
- package/server/build/database/types.js +27 -0
- package/server/build/database/utils.js +86 -0
- package/server/build/index.js +109 -131
- package/server/build/shared/MCPProxyService.js +221 -0
- package/server/build/shared/TransportFactory.js +130 -0
- package/server/build/shared/index.js +4 -0
- package/server/build/shared/types.js +1 -0
- package/server/build/shared/utils.js +27 -0
- package/server/build/test-server.js +145 -0
- package/server/build/testing/HealthCheck.js +42 -0
- package/server/build/testing/TestExecutor.js +240 -0
- package/server/build/testing/TestRunner.js +198 -0
- package/server/build/testing/TestServer.js +440 -0
- package/server/build/testing/types.js +1 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// server/src/testing/TestRunner.ts
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
export class TestRunner extends EventEmitter {
|
|
5
|
+
mcpProxyService;
|
|
6
|
+
_database;
|
|
7
|
+
logger;
|
|
8
|
+
activeTests = new Map();
|
|
9
|
+
constructor(mcpProxyService, _database, logger) {
|
|
10
|
+
super();
|
|
11
|
+
this.mcpProxyService = mcpProxyService;
|
|
12
|
+
this._database = _database;
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
}
|
|
15
|
+
async runTest(testCase) {
|
|
16
|
+
const testId = randomUUID();
|
|
17
|
+
const startTime = Date.now();
|
|
18
|
+
this.logger.info(`Starting test: ${testCase.name} (${testId})`);
|
|
19
|
+
try {
|
|
20
|
+
// Create test execution context
|
|
21
|
+
const execution = new TestExecution(testId, testCase, this.logger);
|
|
22
|
+
this.activeTests.set(testId, execution);
|
|
23
|
+
// Create MCP connections
|
|
24
|
+
const connections = await this.createConnections(testCase.serverConfigs);
|
|
25
|
+
execution.setConnections(connections);
|
|
26
|
+
// Execute test
|
|
27
|
+
const toolCalls = await this.executeTest(testCase, connections);
|
|
28
|
+
// Create result
|
|
29
|
+
const result = {
|
|
30
|
+
id: testId,
|
|
31
|
+
testCase,
|
|
32
|
+
toolCalls,
|
|
33
|
+
duration: Date.now() - startTime,
|
|
34
|
+
success: true,
|
|
35
|
+
timestamp: new Date(),
|
|
36
|
+
};
|
|
37
|
+
this.logger.info(`Test completed: ${testCase.name} (${testId})`);
|
|
38
|
+
this.emit("testComplete", result);
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
this.logger.error(`Test failed: ${testCase.name} (${testId}):`, error);
|
|
43
|
+
const result = {
|
|
44
|
+
id: testId,
|
|
45
|
+
testCase,
|
|
46
|
+
toolCalls: [],
|
|
47
|
+
duration: Date.now() - startTime,
|
|
48
|
+
success: false,
|
|
49
|
+
error: error instanceof Error ? error.message : String(error),
|
|
50
|
+
timestamp: new Date(),
|
|
51
|
+
};
|
|
52
|
+
this.emit("testError", result, error);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
this.activeTests.delete(testId);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async runBatch(testCases) {
|
|
60
|
+
this.logger.info(`Starting batch test: ${testCases.length} tests`);
|
|
61
|
+
// Run tests in parallel with controlled concurrency
|
|
62
|
+
const maxConcurrency = 5;
|
|
63
|
+
const results = [];
|
|
64
|
+
for (let i = 0; i < testCases.length; i += maxConcurrency) {
|
|
65
|
+
const batch = testCases.slice(i, i + maxConcurrency);
|
|
66
|
+
const batchResults = await Promise.all(batch.map((testCase) => this.runTest(testCase)));
|
|
67
|
+
results.push(...batchResults);
|
|
68
|
+
}
|
|
69
|
+
this.logger.info(`Batch test completed: ${results.length} results`);
|
|
70
|
+
return results;
|
|
71
|
+
}
|
|
72
|
+
async getResults(_query) {
|
|
73
|
+
// Mock implementation - in real version would query database
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
async getResult(_id) {
|
|
77
|
+
// Mock implementation - in real version would query database
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
getActiveConnections() {
|
|
81
|
+
return this.mcpProxyService.getActiveConnections();
|
|
82
|
+
}
|
|
83
|
+
async closeConnection(id) {
|
|
84
|
+
// Note: MCPProxyService doesn't have closeConnection method
|
|
85
|
+
// Would need to be implemented
|
|
86
|
+
this.logger.info(`Closing connection: ${id}`);
|
|
87
|
+
}
|
|
88
|
+
async close() {
|
|
89
|
+
// Close all active tests
|
|
90
|
+
for (const [_testId, execution] of this.activeTests) {
|
|
91
|
+
await execution.cancel();
|
|
92
|
+
}
|
|
93
|
+
this.activeTests.clear();
|
|
94
|
+
// Close MCP connections
|
|
95
|
+
await this.mcpProxyService.closeAllConnections();
|
|
96
|
+
this.logger.info("Test runner closed");
|
|
97
|
+
}
|
|
98
|
+
async createConnections(serverConfigs) {
|
|
99
|
+
const connections = [];
|
|
100
|
+
for (const config of serverConfigs) {
|
|
101
|
+
try {
|
|
102
|
+
let sessionId;
|
|
103
|
+
if (config.type === "stdio") {
|
|
104
|
+
// For STDIO, create a mock response for SSE
|
|
105
|
+
const mockResponse = {
|
|
106
|
+
writeHead: () => { },
|
|
107
|
+
write: () => { },
|
|
108
|
+
end: () => { },
|
|
109
|
+
on: () => { },
|
|
110
|
+
setHeader: () => { },
|
|
111
|
+
};
|
|
112
|
+
const connection = await this.mcpProxyService.createSSEConnection(config, mockResponse, {});
|
|
113
|
+
sessionId = connection.sessionId;
|
|
114
|
+
}
|
|
115
|
+
else if (config.type === "streamable-http") {
|
|
116
|
+
const connection = await this.mcpProxyService.createStreamableHTTPConnection(config, {});
|
|
117
|
+
sessionId = connection.sessionId;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
throw new Error(`Unsupported server type: ${config.type}`);
|
|
121
|
+
}
|
|
122
|
+
connections.push(sessionId);
|
|
123
|
+
this.logger.info(`Created connection: ${config.name} (${sessionId})`);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
this.logger.error(`Failed to create connection for ${config.name}:`, error);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return connections;
|
|
131
|
+
}
|
|
132
|
+
async executeTest(testCase, connections) {
|
|
133
|
+
const toolCalls = [];
|
|
134
|
+
const _timeout = testCase.timeout || 30000;
|
|
135
|
+
// Simple mock execution for now
|
|
136
|
+
// In real implementation, would use LLM to process prompt and make tool calls
|
|
137
|
+
if (testCase.expectedTools && testCase.expectedTools.length > 0) {
|
|
138
|
+
for (const expectedTool of testCase.expectedTools) {
|
|
139
|
+
const toolCallStartTime = Date.now();
|
|
140
|
+
try {
|
|
141
|
+
// Mock tool call execution
|
|
142
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
143
|
+
const toolCall = {
|
|
144
|
+
toolName: expectedTool,
|
|
145
|
+
serverId: connections[0] || "unknown",
|
|
146
|
+
serverName: "Mock Server",
|
|
147
|
+
parameters: { test: true },
|
|
148
|
+
response: {
|
|
149
|
+
success: true,
|
|
150
|
+
data: `Mock response for ${expectedTool}`,
|
|
151
|
+
},
|
|
152
|
+
executionTimeMs: Date.now() - toolCallStartTime,
|
|
153
|
+
success: true,
|
|
154
|
+
timestamp: new Date(),
|
|
155
|
+
};
|
|
156
|
+
toolCalls.push(toolCall);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
const toolCall = {
|
|
160
|
+
toolName: expectedTool,
|
|
161
|
+
serverId: connections[0] || "unknown",
|
|
162
|
+
serverName: "Mock Server",
|
|
163
|
+
parameters: { test: true },
|
|
164
|
+
response: null,
|
|
165
|
+
executionTimeMs: Date.now() - toolCallStartTime,
|
|
166
|
+
success: false,
|
|
167
|
+
error: error instanceof Error ? error.message : String(error),
|
|
168
|
+
timestamp: new Date(),
|
|
169
|
+
};
|
|
170
|
+
toolCalls.push(toolCall);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return toolCalls;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
class TestExecution {
|
|
178
|
+
id;
|
|
179
|
+
testCase;
|
|
180
|
+
logger;
|
|
181
|
+
_connections = [];
|
|
182
|
+
cancelled = false;
|
|
183
|
+
constructor(id, testCase, logger) {
|
|
184
|
+
this.id = id;
|
|
185
|
+
this.testCase = testCase;
|
|
186
|
+
this.logger = logger;
|
|
187
|
+
}
|
|
188
|
+
setConnections(connections) {
|
|
189
|
+
this._connections = connections;
|
|
190
|
+
}
|
|
191
|
+
async cancel() {
|
|
192
|
+
this.cancelled = true;
|
|
193
|
+
this.logger.info(`Test execution cancelled: ${this.id}`);
|
|
194
|
+
}
|
|
195
|
+
isCancelled() {
|
|
196
|
+
return this.cancelled;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
// server/src/testing/TestServer.ts
|
|
2
|
+
import express from "express";
|
|
3
|
+
import cors from "cors";
|
|
4
|
+
import { HealthCheck } from "./HealthCheck.js";
|
|
5
|
+
import { ConsoleLogger } from "../shared/utils.js";
|
|
6
|
+
export class TestServer {
|
|
7
|
+
app;
|
|
8
|
+
server;
|
|
9
|
+
healthCheck = null;
|
|
10
|
+
logger;
|
|
11
|
+
config;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.logger = new ConsoleLogger();
|
|
15
|
+
this.app = express();
|
|
16
|
+
this.setupMiddleware();
|
|
17
|
+
}
|
|
18
|
+
setupMiddleware() {
|
|
19
|
+
// CORS support
|
|
20
|
+
if (this.config.cors) {
|
|
21
|
+
this.app.use(cors({
|
|
22
|
+
origin: true,
|
|
23
|
+
credentials: true,
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
// Body parsing
|
|
27
|
+
this.app.use(express.json({ limit: "10mb" }));
|
|
28
|
+
this.app.use(express.urlencoded({ extended: true }));
|
|
29
|
+
// Request logging
|
|
30
|
+
this.app.use((req, res, next) => {
|
|
31
|
+
this.logger.info(`${req.method} ${req.path}`, {
|
|
32
|
+
ip: req.ip,
|
|
33
|
+
userAgent: req.get("User-Agent"),
|
|
34
|
+
});
|
|
35
|
+
next();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
setupRoutes() {
|
|
39
|
+
// Health check endpoints
|
|
40
|
+
this.app.get("/api/test/health", (req, res) => {
|
|
41
|
+
if (!this.healthCheck) {
|
|
42
|
+
return res.status(503).json({ error: "Health check not initialized" });
|
|
43
|
+
}
|
|
44
|
+
res.json(this.healthCheck.getStatus());
|
|
45
|
+
});
|
|
46
|
+
this.app.get("/api/test/status", (req, res) => {
|
|
47
|
+
if (!this.healthCheck) {
|
|
48
|
+
return res.status(503).json({ error: "Health check not initialized" });
|
|
49
|
+
}
|
|
50
|
+
res.json(this.healthCheck.getDetailedStatus());
|
|
51
|
+
});
|
|
52
|
+
// Test execution endpoints
|
|
53
|
+
this.app.post("/api/test/run", async (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const { testCase } = req.body;
|
|
56
|
+
if (!testCase ||
|
|
57
|
+
!testCase.id ||
|
|
58
|
+
!testCase.prompt ||
|
|
59
|
+
!testCase.serverConfigs) {
|
|
60
|
+
return res.status(400).json({
|
|
61
|
+
success: false,
|
|
62
|
+
error: "Invalid test case format. Required fields: id, prompt, serverConfigs",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
this.logger.info(`🧪 Starting test execution for: ${testCase.name || testCase.id}`);
|
|
66
|
+
// Mock test execution for now - will be replaced with actual implementation
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
// Simulate test execution
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
70
|
+
const result = {
|
|
71
|
+
id: `result-${Date.now()}`,
|
|
72
|
+
testCase: testCase,
|
|
73
|
+
toolCalls: [],
|
|
74
|
+
duration: Date.now() - startTime,
|
|
75
|
+
success: true,
|
|
76
|
+
timestamp: new Date().toISOString(),
|
|
77
|
+
metadata: {
|
|
78
|
+
executionMode: "single",
|
|
79
|
+
server: "test-server",
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
this.logger.info(`✅ Test execution completed for: ${testCase.name || testCase.id}`);
|
|
83
|
+
res.json({
|
|
84
|
+
success: true,
|
|
85
|
+
result: result,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
this.logger.error("❌ Test execution failed:", error);
|
|
90
|
+
res.status(500).json({
|
|
91
|
+
success: false,
|
|
92
|
+
error: "Test execution failed",
|
|
93
|
+
details: error instanceof Error ? error.message : String(error),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
this.app.post("/api/test/run-batch", async (req, res) => {
|
|
98
|
+
try {
|
|
99
|
+
const { testCases } = req.body;
|
|
100
|
+
if (!Array.isArray(testCases) || testCases.length === 0) {
|
|
101
|
+
return res.status(400).json({
|
|
102
|
+
success: false,
|
|
103
|
+
error: "Invalid request format. Expected array of test cases",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
this.logger.info(`🧪 Starting batch test execution for ${testCases.length} tests`);
|
|
107
|
+
const results = [];
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
// Process each test case
|
|
110
|
+
for (const testCase of testCases) {
|
|
111
|
+
if (!testCase.id || !testCase.prompt || !testCase.serverConfigs) {
|
|
112
|
+
results.push({
|
|
113
|
+
id: `result-${Date.now()}`,
|
|
114
|
+
testCase: testCase,
|
|
115
|
+
toolCalls: [],
|
|
116
|
+
duration: 0,
|
|
117
|
+
success: false,
|
|
118
|
+
error: "Invalid test case format",
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
});
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const testStartTime = Date.now();
|
|
124
|
+
// Simulate test execution
|
|
125
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
126
|
+
results.push({
|
|
127
|
+
id: `result-${Date.now()}`,
|
|
128
|
+
testCase: testCase,
|
|
129
|
+
toolCalls: [],
|
|
130
|
+
duration: Date.now() - testStartTime,
|
|
131
|
+
success: true,
|
|
132
|
+
timestamp: new Date().toISOString(),
|
|
133
|
+
metadata: {
|
|
134
|
+
executionMode: "batch",
|
|
135
|
+
server: "test-server",
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
const totalDuration = Date.now() - startTime;
|
|
140
|
+
const successCount = results.filter((r) => r.success).length;
|
|
141
|
+
this.logger.info(`✅ Batch test execution completed: ${successCount}/${testCases.length} passed`);
|
|
142
|
+
res.json({
|
|
143
|
+
success: true,
|
|
144
|
+
results: results,
|
|
145
|
+
summary: {
|
|
146
|
+
total: testCases.length,
|
|
147
|
+
passed: successCount,
|
|
148
|
+
failed: testCases.length - successCount,
|
|
149
|
+
duration: totalDuration,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
this.logger.error("❌ Batch test execution failed:", error);
|
|
155
|
+
res.status(500).json({
|
|
156
|
+
success: false,
|
|
157
|
+
error: "Batch test execution failed",
|
|
158
|
+
details: error instanceof Error ? error.message : String(error),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
this.app.get("/api/test/results", (req, res) => {
|
|
163
|
+
try {
|
|
164
|
+
const { limit = 50, offset = 0, testCaseId } = req.query;
|
|
165
|
+
// Mock results for now - will be replaced with database queries
|
|
166
|
+
const mockResults = [
|
|
167
|
+
{
|
|
168
|
+
id: "result-1",
|
|
169
|
+
testCase: {
|
|
170
|
+
id: "test-1",
|
|
171
|
+
name: "Sample Test",
|
|
172
|
+
prompt: "Test prompt",
|
|
173
|
+
},
|
|
174
|
+
toolCalls: [],
|
|
175
|
+
duration: 1250,
|
|
176
|
+
success: true,
|
|
177
|
+
timestamp: new Date().toISOString(),
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
res.json({
|
|
181
|
+
success: true,
|
|
182
|
+
results: mockResults,
|
|
183
|
+
pagination: {
|
|
184
|
+
limit: parseInt(limit),
|
|
185
|
+
offset: parseInt(offset),
|
|
186
|
+
total: mockResults.length,
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
this.logger.error("❌ Failed to retrieve test results:", error);
|
|
192
|
+
res.status(500).json({
|
|
193
|
+
success: false,
|
|
194
|
+
error: "Failed to retrieve test results",
|
|
195
|
+
details: error instanceof Error ? error.message : String(error),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
this.app.get("/api/test/results/:id", (req, res) => {
|
|
200
|
+
try {
|
|
201
|
+
const { id } = req.params;
|
|
202
|
+
// Mock result for now - will be replaced with database query
|
|
203
|
+
const mockResult = {
|
|
204
|
+
id: id,
|
|
205
|
+
testCase: {
|
|
206
|
+
id: "test-1",
|
|
207
|
+
name: "Sample Test",
|
|
208
|
+
prompt: "Test prompt",
|
|
209
|
+
},
|
|
210
|
+
toolCalls: [],
|
|
211
|
+
duration: 1250,
|
|
212
|
+
success: true,
|
|
213
|
+
timestamp: new Date().toISOString(),
|
|
214
|
+
metadata: {
|
|
215
|
+
executionMode: "single",
|
|
216
|
+
server: "test-server",
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
res.json({
|
|
220
|
+
success: true,
|
|
221
|
+
result: mockResult,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
this.logger.error("❌ Failed to retrieve test result:", error);
|
|
226
|
+
res.status(500).json({
|
|
227
|
+
success: false,
|
|
228
|
+
error: "Failed to retrieve test result",
|
|
229
|
+
details: error instanceof Error ? error.message : String(error),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
this.app.get("/api/test/connections", (req, res) => {
|
|
234
|
+
try {
|
|
235
|
+
// Mock connection info for now
|
|
236
|
+
const mockConnections = [
|
|
237
|
+
{
|
|
238
|
+
id: "conn-1",
|
|
239
|
+
name: "Test Server",
|
|
240
|
+
type: "stdio",
|
|
241
|
+
status: "connected",
|
|
242
|
+
lastActivity: new Date().toISOString(),
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
res.json({
|
|
246
|
+
success: true,
|
|
247
|
+
connections: mockConnections,
|
|
248
|
+
summary: {
|
|
249
|
+
total: mockConnections.length,
|
|
250
|
+
active: mockConnections.filter((c) => c.status === "connected")
|
|
251
|
+
.length,
|
|
252
|
+
inactive: mockConnections.filter((c) => c.status !== "connected")
|
|
253
|
+
.length,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
this.logger.error("❌ Failed to retrieve connections:", error);
|
|
259
|
+
res.status(500).json({
|
|
260
|
+
success: false,
|
|
261
|
+
error: "Failed to retrieve connections",
|
|
262
|
+
details: error instanceof Error ? error.message : String(error),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
// Test case management endpoints
|
|
267
|
+
this.app.get("/api/test/cases", (req, res) => {
|
|
268
|
+
try {
|
|
269
|
+
const { limit = 50, offset = 0 } = req.query;
|
|
270
|
+
// Mock test cases for now
|
|
271
|
+
const mockTestCases = [
|
|
272
|
+
{
|
|
273
|
+
id: "test-1",
|
|
274
|
+
name: "Sample Test Case",
|
|
275
|
+
prompt: "Test the weather tool functionality",
|
|
276
|
+
expectedTools: ["get_weather"],
|
|
277
|
+
serverConfigs: [{ id: "server-1", name: "Weather Server" }],
|
|
278
|
+
timeout: 30000,
|
|
279
|
+
metadata: { category: "weather" },
|
|
280
|
+
},
|
|
281
|
+
];
|
|
282
|
+
res.json({
|
|
283
|
+
success: true,
|
|
284
|
+
testCases: mockTestCases,
|
|
285
|
+
pagination: {
|
|
286
|
+
limit: parseInt(limit),
|
|
287
|
+
offset: parseInt(offset),
|
|
288
|
+
total: mockTestCases.length,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
this.logger.error("❌ Failed to retrieve test cases:", error);
|
|
294
|
+
res.status(500).json({
|
|
295
|
+
success: false,
|
|
296
|
+
error: "Failed to retrieve test cases",
|
|
297
|
+
details: error instanceof Error ? error.message : String(error),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
this.app.post("/api/test/cases", async (req, res) => {
|
|
302
|
+
try {
|
|
303
|
+
const testCase = req.body;
|
|
304
|
+
if (!testCase.name || !testCase.prompt || !testCase.serverConfigs) {
|
|
305
|
+
return res.status(400).json({
|
|
306
|
+
success: false,
|
|
307
|
+
error: "Invalid test case format. Required fields: name, prompt, serverConfigs",
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
// Generate ID if not provided
|
|
311
|
+
if (!testCase.id) {
|
|
312
|
+
testCase.id = `test-${Date.now()}`;
|
|
313
|
+
}
|
|
314
|
+
this.logger.info(`💾 Creating test case: ${testCase.name}`);
|
|
315
|
+
// Mock save operation
|
|
316
|
+
const savedTestCase = {
|
|
317
|
+
...testCase,
|
|
318
|
+
createdAt: new Date().toISOString(),
|
|
319
|
+
updatedAt: new Date().toISOString(),
|
|
320
|
+
};
|
|
321
|
+
res.status(201).json({
|
|
322
|
+
success: true,
|
|
323
|
+
testCase: savedTestCase,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
this.logger.error("❌ Failed to create test case:", error);
|
|
328
|
+
res.status(500).json({
|
|
329
|
+
success: false,
|
|
330
|
+
error: "Failed to create test case",
|
|
331
|
+
details: error instanceof Error ? error.message : String(error),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
this.app.get("/api/test/cases/:id", (req, res) => {
|
|
336
|
+
try {
|
|
337
|
+
const { id } = req.params;
|
|
338
|
+
// Mock test case
|
|
339
|
+
const mockTestCase = {
|
|
340
|
+
id: id,
|
|
341
|
+
name: "Sample Test Case",
|
|
342
|
+
prompt: "Test the weather tool functionality",
|
|
343
|
+
expectedTools: ["get_weather"],
|
|
344
|
+
serverConfigs: [{ id: "server-1", name: "Weather Server" }],
|
|
345
|
+
timeout: 30000,
|
|
346
|
+
metadata: { category: "weather" },
|
|
347
|
+
createdAt: new Date().toISOString(),
|
|
348
|
+
updatedAt: new Date().toISOString(),
|
|
349
|
+
};
|
|
350
|
+
res.json({
|
|
351
|
+
success: true,
|
|
352
|
+
testCase: mockTestCase,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
this.logger.error("❌ Failed to retrieve test case:", error);
|
|
357
|
+
res.status(500).json({
|
|
358
|
+
success: false,
|
|
359
|
+
error: "Failed to retrieve test case",
|
|
360
|
+
details: error instanceof Error ? error.message : String(error),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
// Statistics endpoint
|
|
365
|
+
this.app.get("/api/test/stats", (_req, res) => {
|
|
366
|
+
try {
|
|
367
|
+
const stats = {
|
|
368
|
+
testCases: {
|
|
369
|
+
total: 1,
|
|
370
|
+
active: 1,
|
|
371
|
+
inactive: 0,
|
|
372
|
+
},
|
|
373
|
+
testResults: {
|
|
374
|
+
total: 1,
|
|
375
|
+
passed: 1,
|
|
376
|
+
failed: 0,
|
|
377
|
+
recentRuns: 1,
|
|
378
|
+
},
|
|
379
|
+
connections: {
|
|
380
|
+
total: 1,
|
|
381
|
+
active: 1,
|
|
382
|
+
inactive: 0,
|
|
383
|
+
},
|
|
384
|
+
performance: {
|
|
385
|
+
averageTestDuration: 1250,
|
|
386
|
+
successRate: 100,
|
|
387
|
+
lastRunTime: new Date().toISOString(),
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
res.json({
|
|
391
|
+
success: true,
|
|
392
|
+
stats: stats,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
this.logger.error("❌ Failed to retrieve statistics:", error);
|
|
397
|
+
res.status(500).json({
|
|
398
|
+
success: false,
|
|
399
|
+
error: "Failed to retrieve statistics",
|
|
400
|
+
details: error instanceof Error ? error.message : String(error),
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
// Error handling
|
|
405
|
+
this.app.use((error, _req, res, _next) => {
|
|
406
|
+
this.logger.error("Unhandled error:", error);
|
|
407
|
+
res.status(500).json({
|
|
408
|
+
success: false,
|
|
409
|
+
error: "Internal server error",
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
async start(mcpProxyService, database) {
|
|
414
|
+
// Initialize components
|
|
415
|
+
this.healthCheck = new HealthCheck(mcpProxyService, database, this.logger);
|
|
416
|
+
// Setup routes
|
|
417
|
+
this.setupRoutes();
|
|
418
|
+
// Start server
|
|
419
|
+
return new Promise((resolve, reject) => {
|
|
420
|
+
this.server = this.app.listen(this.config.port, this.config.host, () => {
|
|
421
|
+
this.logger.info(`🧪 Test server listening on ${this.config.host}:${this.config.port}`);
|
|
422
|
+
resolve();
|
|
423
|
+
});
|
|
424
|
+
this.server.on("error", (error) => {
|
|
425
|
+
this.logger.error("Server error:", error);
|
|
426
|
+
reject(error);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
async stop() {
|
|
431
|
+
if (this.server) {
|
|
432
|
+
await new Promise((resolve) => {
|
|
433
|
+
this.server.close(() => {
|
|
434
|
+
this.logger.info("🧪 Test server stopped");
|
|
435
|
+
resolve();
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|