agentstudio 0.1.0
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/.env +15 -0
- package/README.md +85 -0
- package/dist/bin/agentstudio.d.ts +3 -0
- package/dist/bin/agentstudio.d.ts.map +1 -0
- package/dist/bin/agentstudio.js +141 -0
- package/dist/bin/agentstudio.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +7 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +21 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/routes/agents.d.ts +4 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +804 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/auth.d.ts +4 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +60 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/files.d.ts +4 -0
- package/dist/routes/files.d.ts.map +1 -0
- package/dist/routes/files.js +301 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/mcp.d.ts +4 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +652 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/media.d.ts +5 -0
- package/dist/routes/media.d.ts.map +1 -0
- package/dist/routes/media.js +117 -0
- package/dist/routes/media.js.map +1 -0
- package/dist/routes/slides.d.ts +4 -0
- package/dist/routes/slides.d.ts.map +1 -0
- package/dist/routes/slides.js +146 -0
- package/dist/routes/slides.js.map +1 -0
- package/dist/services/claudeSession.d.ts +83 -0
- package/dist/services/claudeSession.d.ts.map +1 -0
- package/dist/services/claudeSession.js +255 -0
- package/dist/services/claudeSession.js.map +1 -0
- package/dist/services/messageQueue.d.ts +31 -0
- package/dist/services/messageQueue.d.ts.map +1 -0
- package/dist/services/messageQueue.js +67 -0
- package/dist/services/messageQueue.js.map +1 -0
- package/dist/services/sessionManager.d.ts +132 -0
- package/dist/services/sessionManager.d.ts.map +1 -0
- package/dist/services/sessionManager.js +439 -0
- package/dist/services/sessionManager.js.map +1 -0
- package/dist/types/claude-history.d.ts +48 -0
- package/dist/types/claude-history.d.ts.map +1 -0
- package/dist/types/claude-history.js +2 -0
- package/dist/types/claude-history.js.map +1 -0
- package/dist/types/claude-versions.d.ts +31 -0
- package/dist/types/claude-versions.d.ts.map +1 -0
- package/dist/types/claude-versions.js +2 -0
- package/dist/types/claude-versions.js.map +1 -0
- package/dist/types/commands.d.ts +32 -0
- package/dist/types/commands.d.ts.map +1 -0
- package/dist/types/commands.js +2 -0
- package/dist/types/commands.js.map +1 -0
- package/dist/types/index.d.ts +81 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +150 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/subagents.d.ts +88 -0
- package/dist/types/subagents.d.ts.map +1 -0
- package/dist/types/subagents.js +2 -0
- package/dist/types/subagents.js.map +1 -0
- package/dist/utils/agentStorage.d.ts +19 -0
- package/dist/utils/agentStorage.d.ts.map +1 -0
- package/dist/utils/agentStorage.js +110 -0
- package/dist/utils/agentStorage.js.map +1 -0
- package/dist/utils/claudeVersionStorage.d.ts +33 -0
- package/dist/utils/claudeVersionStorage.d.ts.map +1 -0
- package/dist/utils/claudeVersionStorage.js +168 -0
- package/dist/utils/claudeVersionStorage.js.map +1 -0
- package/dist/utils/jwt.d.ts +15 -0
- package/dist/utils/jwt.d.ts.map +1 -0
- package/dist/utils/jwt.js +28 -0
- package/dist/utils/jwt.js.map +1 -0
- package/dist/utils/projectMetadataStorage.d.ts +21 -0
- package/dist/utils/projectMetadataStorage.d.ts.map +1 -0
- package/dist/utils/projectMetadataStorage.js +68 -0
- package/dist/utils/projectMetadataStorage.js.map +1 -0
- package/frontend/dist/index.html +86 -0
- package/package.json +66 -0
- package/src/bin/agentstudio.ts +161 -0
- package/src/index.ts +100 -0
- package/src/middleware/auth.ts +26 -0
- package/src/routes/agents.ts +885 -0
- package/src/routes/auth.ts +73 -0
- package/src/routes/commands.ts.bak +441 -0
- package/src/routes/files.ts +352 -0
- package/src/routes/mcp.ts +751 -0
- package/src/routes/media.ts +140 -0
- package/src/routes/projects.ts.bak +601 -0
- package/src/routes/sessions.ts.bak +809 -0
- package/src/routes/settings.ts.bak +718 -0
- package/src/routes/slides.ts +170 -0
- package/src/routes/subagents.ts.bak +364 -0
- package/src/services/claudeSession.ts +293 -0
- package/src/services/messageQueue.ts +71 -0
- package/src/services/sessionManager.ts +532 -0
- package/src/types/claude-history.ts +50 -0
- package/src/types/claude-versions.ts +33 -0
- package/src/types/commands.ts +35 -0
- package/src/types/index.ts +248 -0
- package/src/types/subagents.ts +106 -0
- package/src/utils/agentStorage.ts +126 -0
- package/src/utils/claudeVersionStorage.ts +199 -0
- package/src/utils/jwt.ts +36 -0
- package/src/utils/projectMetadataStorage.ts +86 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import { spawn, exec } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
|
|
8
|
+
const router: express.Router = express.Router();
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
|
|
11
|
+
// MCP configuration interface
|
|
12
|
+
interface McpServerConfig {
|
|
13
|
+
name: string;
|
|
14
|
+
type: 'stdio' | 'http';
|
|
15
|
+
// For stdio type
|
|
16
|
+
command?: string;
|
|
17
|
+
args?: string[];
|
|
18
|
+
// For http type
|
|
19
|
+
url?: string;
|
|
20
|
+
// Common fields
|
|
21
|
+
timeout?: number;
|
|
22
|
+
autoApprove?: string[];
|
|
23
|
+
status?: 'active' | 'error' | 'validating';
|
|
24
|
+
error?: string;
|
|
25
|
+
tools?: string[];
|
|
26
|
+
lastValidated?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface McpConfigFile {
|
|
30
|
+
mcpServers: Record<string, Omit<McpServerConfig, 'name'>>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Helper function to get MCP config file path
|
|
34
|
+
const getMcpConfigPath = (): string => {
|
|
35
|
+
return path.join(os.homedir(), '.claude-agent', 'mcp-server.json');
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Helper function to ensure config directory exists
|
|
39
|
+
const ensureConfigDirectory = (): void => {
|
|
40
|
+
const configDir = path.dirname(getMcpConfigPath());
|
|
41
|
+
if (!fs.existsSync(configDir)) {
|
|
42
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Helper function to read MCP config
|
|
47
|
+
const readMcpConfig = (): McpConfigFile => {
|
|
48
|
+
const configPath = getMcpConfigPath();
|
|
49
|
+
|
|
50
|
+
if (!fs.existsSync(configPath)) {
|
|
51
|
+
return { mcpServers: {} };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
56
|
+
return JSON.parse(content);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Failed to read MCP config:', error);
|
|
59
|
+
return { mcpServers: {} };
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Helper function to write MCP config
|
|
64
|
+
const writeMcpConfig = (config: McpConfigFile): void => {
|
|
65
|
+
ensureConfigDirectory();
|
|
66
|
+
const configPath = getMcpConfigPath();
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Failed to write MCP config:', error);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Get all MCP configurations
|
|
77
|
+
router.get('/', (req, res) => {
|
|
78
|
+
try {
|
|
79
|
+
const config = readMcpConfig();
|
|
80
|
+
const servers: McpServerConfig[] = Object.entries(config.mcpServers).map(([name, serverConfig]) => ({
|
|
81
|
+
name,
|
|
82
|
+
...serverConfig
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
res.json({ servers });
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Failed to get MCP configs:', error);
|
|
88
|
+
res.status(500).json({ error: 'Failed to retrieve MCP configurations' });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Add or update MCP configuration
|
|
93
|
+
router.post('/', (req, res) => {
|
|
94
|
+
try {
|
|
95
|
+
const { name, type, command, args, url, timeout, autoApprove } = req.body;
|
|
96
|
+
|
|
97
|
+
if (!name || !type) {
|
|
98
|
+
return res.status(400).json({ error: 'Missing required fields: name, type' });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Validate based on type
|
|
102
|
+
if (type === 'stdio') {
|
|
103
|
+
if (!command || !Array.isArray(args)) {
|
|
104
|
+
return res.status(400).json({ error: 'For stdio type: command and args are required' });
|
|
105
|
+
}
|
|
106
|
+
} else if (type === 'http') {
|
|
107
|
+
if (!url) {
|
|
108
|
+
return res.status(400).json({ error: 'For http type: url is required' });
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
return res.status(400).json({ error: 'Invalid type. Must be "stdio" or "http"' });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const config = readMcpConfig();
|
|
115
|
+
|
|
116
|
+
// Create server config without name field
|
|
117
|
+
const serverConfig: Omit<McpServerConfig, 'name'> = {
|
|
118
|
+
type,
|
|
119
|
+
...(command && { command }),
|
|
120
|
+
...(args && { args }),
|
|
121
|
+
...(url && { url }),
|
|
122
|
+
...(timeout && { timeout }),
|
|
123
|
+
...(autoApprove && Array.isArray(autoApprove) && { autoApprove })
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
config.mcpServers[name] = serverConfig;
|
|
127
|
+
writeMcpConfig(config);
|
|
128
|
+
|
|
129
|
+
const responseServer: McpServerConfig = { name, ...serverConfig };
|
|
130
|
+
res.json({ server: responseServer, message: 'MCP configuration saved successfully' });
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('Failed to save MCP config:', error);
|
|
133
|
+
res.status(500).json({ error: 'Failed to save MCP configuration' });
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Update MCP configuration
|
|
138
|
+
router.put('/:name', (req, res) => {
|
|
139
|
+
try {
|
|
140
|
+
const { name } = req.params;
|
|
141
|
+
const { type, command, args, url, timeout, autoApprove } = req.body;
|
|
142
|
+
|
|
143
|
+
if (!type) {
|
|
144
|
+
return res.status(400).json({ error: 'Missing required field: type' });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Validate based on type
|
|
148
|
+
if (type === 'stdio') {
|
|
149
|
+
if (!command || !Array.isArray(args)) {
|
|
150
|
+
return res.status(400).json({ error: 'For stdio type: command and args are required' });
|
|
151
|
+
}
|
|
152
|
+
} else if (type === 'http') {
|
|
153
|
+
if (!url) {
|
|
154
|
+
return res.status(400).json({ error: 'For http type: url is required' });
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
return res.status(400).json({ error: 'Invalid type. Must be "stdio" or "http"' });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const config = readMcpConfig();
|
|
161
|
+
|
|
162
|
+
if (!config.mcpServers[name]) {
|
|
163
|
+
return res.status(404).json({ error: 'MCP configuration not found' });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Update server config
|
|
167
|
+
const serverConfig: Omit<McpServerConfig, 'name'> = {
|
|
168
|
+
type,
|
|
169
|
+
...(command && { command }),
|
|
170
|
+
...(args && { args }),
|
|
171
|
+
...(url && { url }),
|
|
172
|
+
...(timeout && { timeout }),
|
|
173
|
+
...(autoApprove && Array.isArray(autoApprove) && { autoApprove })
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
config.mcpServers[name] = serverConfig;
|
|
177
|
+
writeMcpConfig(config);
|
|
178
|
+
|
|
179
|
+
const responseServer: McpServerConfig = { name, ...serverConfig };
|
|
180
|
+
res.json({ server: responseServer, message: 'MCP configuration updated successfully' });
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error('Failed to update MCP config:', error);
|
|
183
|
+
res.status(500).json({ error: 'Failed to update MCP configuration' });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Delete MCP configuration
|
|
188
|
+
router.delete('/:name', (req, res) => {
|
|
189
|
+
try {
|
|
190
|
+
const { name } = req.params;
|
|
191
|
+
const config = readMcpConfig();
|
|
192
|
+
|
|
193
|
+
if (!config.mcpServers[name]) {
|
|
194
|
+
return res.status(404).json({ error: 'MCP configuration not found' });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
delete config.mcpServers[name];
|
|
198
|
+
writeMcpConfig(config);
|
|
199
|
+
|
|
200
|
+
res.json({ success: true, message: 'MCP configuration deleted successfully' });
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error('Failed to delete MCP config:', error);
|
|
203
|
+
res.status(500).json({ error: 'Failed to delete MCP configuration' });
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Validate MCP server by testing connection and getting tools
|
|
208
|
+
router.post('/:name/validate', async (req, res) => {
|
|
209
|
+
try {
|
|
210
|
+
const { name } = req.params;
|
|
211
|
+
const config = readMcpConfig();
|
|
212
|
+
|
|
213
|
+
if (!config.mcpServers[name]) {
|
|
214
|
+
return res.status(404).json({ error: 'MCP configuration not found' });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const serverConfig = config.mcpServers[name];
|
|
218
|
+
|
|
219
|
+
if (serverConfig.type === 'http') {
|
|
220
|
+
// Validate HTTP MCP server
|
|
221
|
+
await validateHttpMcpServer(name, serverConfig, config, res);
|
|
222
|
+
} else if (serverConfig.type === 'stdio') {
|
|
223
|
+
// Validate stdio MCP server
|
|
224
|
+
await validateStdioMcpServer(name, serverConfig, config, res);
|
|
225
|
+
} else {
|
|
226
|
+
res.status(400).json({ error: 'Invalid MCP server type' });
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error('Failed to validate MCP server:', error);
|
|
230
|
+
res.status(500).json({
|
|
231
|
+
error: 'Failed to validate MCP server',
|
|
232
|
+
details: error instanceof Error ? error.message : String(error)
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
// Get MCP configurations from Claude Code CLI
|
|
240
|
+
router.get('/claude-code', async (req, res) => {
|
|
241
|
+
try {
|
|
242
|
+
// Try to get MCP servers from Claude Code CLI
|
|
243
|
+
const { stdout } = await execAsync('claude mcp list');
|
|
244
|
+
|
|
245
|
+
// Parse the output to extract MCP server information
|
|
246
|
+
const lines = stdout.split('\n').filter(line => line.trim());
|
|
247
|
+
const servers: any[] = [];
|
|
248
|
+
|
|
249
|
+
for (const line of lines) {
|
|
250
|
+
// Look for lines that contain MCP server information
|
|
251
|
+
// Format: "server-name: url (TYPE) - ✓ Connected" or "server-name: url (TYPE) - ✗ Error"
|
|
252
|
+
const match = line.match(/^(.+?):\s+(.+?)\s+\((\w+)\)\s+-\s+(✓|✗)\s+(.+)$/);
|
|
253
|
+
if (match) {
|
|
254
|
+
const [, name, urlOrCommand, type, status, statusText] = match;
|
|
255
|
+
|
|
256
|
+
const server: any = {
|
|
257
|
+
name: name.trim(),
|
|
258
|
+
type: type.toLowerCase() === 'http' ? 'http' : 'stdio',
|
|
259
|
+
status: status === '✓' ? 'active' : 'error'
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
if (server.type === 'http') {
|
|
263
|
+
server.url = urlOrCommand.trim();
|
|
264
|
+
} else {
|
|
265
|
+
// For stdio, we might need to parse command and args
|
|
266
|
+
// This is a simplified approach
|
|
267
|
+
const parts = urlOrCommand.trim().split(' ');
|
|
268
|
+
server.command = parts[0];
|
|
269
|
+
server.args = parts.slice(1);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (status === '✗') {
|
|
273
|
+
server.error = statusText;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
servers.push(server);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
res.json({ servers });
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error('Failed to get Claude Code MCP configurations:', error);
|
|
283
|
+
res.status(500).json({
|
|
284
|
+
error: 'Failed to get Claude Code MCP configurations',
|
|
285
|
+
details: error instanceof Error ? error.message : String(error)
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
export default router;
|
|
291
|
+
|
|
292
|
+
// Validate HTTP MCP server
|
|
293
|
+
async function validateHttpMcpServer(name: string, serverConfig: any, config: any, res: any) {
|
|
294
|
+
try {
|
|
295
|
+
console.log('Validating HTTP MCP server:', serverConfig.url);
|
|
296
|
+
|
|
297
|
+
// Test HTTP connection to MCP server
|
|
298
|
+
const response = await fetch(serverConfig.url, {
|
|
299
|
+
method: 'POST',
|
|
300
|
+
headers: {
|
|
301
|
+
'Content-Type': 'application/json',
|
|
302
|
+
'Accept': 'application/json, text/event-stream',
|
|
303
|
+
},
|
|
304
|
+
body: JSON.stringify({
|
|
305
|
+
jsonrpc: '2.0',
|
|
306
|
+
id: 1,
|
|
307
|
+
method: 'initialize',
|
|
308
|
+
params: {
|
|
309
|
+
protocolVersion: '2024-11-05',
|
|
310
|
+
capabilities: {
|
|
311
|
+
tools: {}
|
|
312
|
+
},
|
|
313
|
+
clientInfo: {
|
|
314
|
+
name: 'claude-code-studio',
|
|
315
|
+
version: '1.0.0'
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (!response.ok) {
|
|
322
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Parse SSE response
|
|
326
|
+
const responseText = await response.text();
|
|
327
|
+
console.log('HTTP MCP initialize response text:', responseText);
|
|
328
|
+
|
|
329
|
+
// Extract JSON from SSE format
|
|
330
|
+
let initResult: any = null;
|
|
331
|
+
const lines = responseText.split('\n');
|
|
332
|
+
for (const line of lines) {
|
|
333
|
+
if (line.startsWith('data: ')) {
|
|
334
|
+
try {
|
|
335
|
+
initResult = JSON.parse(line.substring(6));
|
|
336
|
+
break;
|
|
337
|
+
} catch (e) {
|
|
338
|
+
console.warn('Failed to parse SSE data line:', line);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!initResult) {
|
|
344
|
+
throw new Error('Failed to parse HTTP MCP response');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
console.log('HTTP MCP initialize parsed result:', initResult);
|
|
348
|
+
|
|
349
|
+
// Get tools from HTTP MCP server using proper session management
|
|
350
|
+
let tools: string[] = [];
|
|
351
|
+
console.log('Getting tools from HTTP MCP server...');
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
tools = await getHttpMcpTools(serverConfig.url);
|
|
355
|
+
console.log('Successfully retrieved tools from HTTP MCP:', tools);
|
|
356
|
+
} catch (error) {
|
|
357
|
+
console.warn('Failed to get tools from HTTP MCP server:', error);
|
|
358
|
+
// Don't fail validation just because tools retrieval failed
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Update server config with successful validation
|
|
362
|
+
const currentTime = new Date().toISOString();
|
|
363
|
+
if (config.mcpServers[name]) {
|
|
364
|
+
config.mcpServers[name] = {
|
|
365
|
+
...config.mcpServers[name],
|
|
366
|
+
status: 'active',
|
|
367
|
+
tools,
|
|
368
|
+
lastValidated: currentTime,
|
|
369
|
+
error: undefined
|
|
370
|
+
};
|
|
371
|
+
writeMcpConfig(config);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
res.json({
|
|
375
|
+
success: true,
|
|
376
|
+
tools,
|
|
377
|
+
message: `HTTP MCP server validated successfully. Found ${tools.length} tools.`
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.error('HTTP MCP server validation failed:', error);
|
|
382
|
+
|
|
383
|
+
// Update server config with error status
|
|
384
|
+
const currentTime = new Date().toISOString();
|
|
385
|
+
if (config.mcpServers[name]) {
|
|
386
|
+
config.mcpServers[name] = {
|
|
387
|
+
...config.mcpServers[name],
|
|
388
|
+
status: 'error',
|
|
389
|
+
error: error instanceof Error ? error.message : String(error),
|
|
390
|
+
tools: undefined,
|
|
391
|
+
lastValidated: currentTime
|
|
392
|
+
};
|
|
393
|
+
writeMcpConfig(config);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
res.status(400).json({
|
|
397
|
+
error: 'HTTP MCP server validation failed',
|
|
398
|
+
details: error instanceof Error ? error.message : String(error)
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Validate stdio MCP server
|
|
404
|
+
async function validateStdioMcpServer(name: string, serverConfig: any, config: any, res: any) {
|
|
405
|
+
if (!serverConfig.command || !serverConfig.args) {
|
|
406
|
+
return res.status(400).json({ error: 'Missing command or args for stdio MCP server' });
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Start MCP server process to test connection
|
|
410
|
+
console.log('Starting stdio MCP server:', serverConfig.command, serverConfig.args);
|
|
411
|
+
const child = spawn(serverConfig.command, serverConfig.args, {
|
|
412
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
413
|
+
timeout: serverConfig.timeout || 15000
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
let stdout = '';
|
|
417
|
+
let stderr = '';
|
|
418
|
+
let tools: string[] = [];
|
|
419
|
+
|
|
420
|
+
child.stderr?.on('data', (data) => {
|
|
421
|
+
const errorStr = data.toString();
|
|
422
|
+
console.log('MCP stderr:', errorStr);
|
|
423
|
+
stderr += errorStr;
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
let initializeDone = false;
|
|
427
|
+
|
|
428
|
+
// Send initialize request to MCP server
|
|
429
|
+
const initializeRequest = {
|
|
430
|
+
jsonrpc: '2.0',
|
|
431
|
+
id: 1,
|
|
432
|
+
method: 'initialize',
|
|
433
|
+
params: {
|
|
434
|
+
protocolVersion: '2024-11-05',
|
|
435
|
+
capabilities: {
|
|
436
|
+
tools: {}
|
|
437
|
+
},
|
|
438
|
+
clientInfo: {
|
|
439
|
+
name: 'claude-code-studio',
|
|
440
|
+
version: '1.0.0'
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
child.stdin?.write(JSON.stringify(initializeRequest) + '\n');
|
|
446
|
+
|
|
447
|
+
let buffer = '';
|
|
448
|
+
|
|
449
|
+
// Listen for stdout and parse responses in real-time
|
|
450
|
+
child.stdout?.on('data', (data) => {
|
|
451
|
+
const dataStr = data.toString();
|
|
452
|
+
stdout += dataStr;
|
|
453
|
+
buffer += dataStr;
|
|
454
|
+
|
|
455
|
+
// Try to parse complete JSON messages
|
|
456
|
+
const lines = buffer.split('\n');
|
|
457
|
+
// Keep the last incomplete line in the buffer
|
|
458
|
+
buffer = lines.pop() || '';
|
|
459
|
+
|
|
460
|
+
for (const line of lines) {
|
|
461
|
+
if (!line.trim()) continue;
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
const response = JSON.parse(line);
|
|
465
|
+
console.log('MCP response:', response);
|
|
466
|
+
|
|
467
|
+
// Check if initialization was successful
|
|
468
|
+
if (response.id === 1 && response.result && !initializeDone) {
|
|
469
|
+
console.log('Initialize successful, sending initialized notification');
|
|
470
|
+
initializeDone = true;
|
|
471
|
+
|
|
472
|
+
// Send initialized notification
|
|
473
|
+
const initializedNotification = {
|
|
474
|
+
jsonrpc: '2.0',
|
|
475
|
+
method: 'notifications/initialized'
|
|
476
|
+
};
|
|
477
|
+
child.stdin?.write(JSON.stringify(initializedNotification) + '\n');
|
|
478
|
+
|
|
479
|
+
// Send tools/list request
|
|
480
|
+
setTimeout(() => {
|
|
481
|
+
console.log('Sending tools/list request');
|
|
482
|
+
const toolsRequest = {
|
|
483
|
+
jsonrpc: '2.0',
|
|
484
|
+
id: 2,
|
|
485
|
+
method: 'tools/list',
|
|
486
|
+
params: {}
|
|
487
|
+
};
|
|
488
|
+
child.stdin?.write(JSON.stringify(toolsRequest) + '\n');
|
|
489
|
+
}, 500);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Handle tools/list response
|
|
493
|
+
if (response.id === 2 && response.result) {
|
|
494
|
+
console.log('Tools/list response received:', response.result);
|
|
495
|
+
if (response.result.tools) {
|
|
496
|
+
tools = response.result.tools.map((tool: any) => tool.name);
|
|
497
|
+
console.log('Found tools:', tools);
|
|
498
|
+
}
|
|
499
|
+
// Close stdin after getting tools response (even if empty)
|
|
500
|
+
setTimeout(() => child.stdin?.end(), 100);
|
|
501
|
+
}
|
|
502
|
+
} catch (parseError) {
|
|
503
|
+
// This might be a partial JSON, continue buffering
|
|
504
|
+
console.log('Parse error for line:', line.substring(0, 100) + '...');
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Also try to parse the buffer as a complete JSON in case it's all one response
|
|
509
|
+
if (buffer.trim()) {
|
|
510
|
+
try {
|
|
511
|
+
const response = JSON.parse(buffer);
|
|
512
|
+
console.log('MCP buffered response:', response);
|
|
513
|
+
|
|
514
|
+
// Handle tools/list response from buffer
|
|
515
|
+
if (response.id === 2 && response.result) {
|
|
516
|
+
console.log('Tools/list response received from buffer:', response.result);
|
|
517
|
+
if (response.result.tools) {
|
|
518
|
+
tools = response.result.tools.map((tool: any) => tool.name);
|
|
519
|
+
console.log('Found tools from buffer:', tools);
|
|
520
|
+
}
|
|
521
|
+
// Clear buffer and close stdin
|
|
522
|
+
buffer = '';
|
|
523
|
+
setTimeout(() => child.stdin?.end(), 100);
|
|
524
|
+
}
|
|
525
|
+
} catch (parseError) {
|
|
526
|
+
// Buffer is not complete JSON yet, continue
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
const timeoutId = setTimeout(() => {
|
|
532
|
+
child.kill('SIGTERM');
|
|
533
|
+
}, serverConfig.timeout || 10000);
|
|
534
|
+
|
|
535
|
+
child.on('close', (code) => {
|
|
536
|
+
clearTimeout(timeoutId);
|
|
537
|
+
|
|
538
|
+
try {
|
|
539
|
+
const config = readMcpConfig();
|
|
540
|
+
const currentTime = new Date().toISOString();
|
|
541
|
+
|
|
542
|
+
if (code === 0 || tools.length > 0) {
|
|
543
|
+
// Update server config with successful validation
|
|
544
|
+
if (config.mcpServers[name]) {
|
|
545
|
+
config.mcpServers[name] = {
|
|
546
|
+
...config.mcpServers[name],
|
|
547
|
+
status: 'active',
|
|
548
|
+
tools,
|
|
549
|
+
lastValidated: currentTime,
|
|
550
|
+
error: undefined
|
|
551
|
+
};
|
|
552
|
+
writeMcpConfig(config);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
res.json({
|
|
556
|
+
success: true,
|
|
557
|
+
tools,
|
|
558
|
+
message: `MCP server validated successfully. Found ${tools.length} tools.`
|
|
559
|
+
});
|
|
560
|
+
} else {
|
|
561
|
+
// Update server config with error status
|
|
562
|
+
const errorMessage = `MCP server validation failed with exit code ${code}`;
|
|
563
|
+
if (config.mcpServers[name]) {
|
|
564
|
+
config.mcpServers[name] = {
|
|
565
|
+
...config.mcpServers[name],
|
|
566
|
+
status: 'error',
|
|
567
|
+
error: errorMessage,
|
|
568
|
+
tools: undefined,
|
|
569
|
+
lastValidated: currentTime
|
|
570
|
+
};
|
|
571
|
+
writeMcpConfig(config);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
res.status(400).json({
|
|
575
|
+
error: errorMessage,
|
|
576
|
+
details: stderr || 'No error details available'
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
} catch (configError) {
|
|
580
|
+
console.error('Failed to update config with validation result:', configError);
|
|
581
|
+
|
|
582
|
+
// Still return the validation result even if config update fails
|
|
583
|
+
if (code === 0 || tools.length > 0) {
|
|
584
|
+
res.json({
|
|
585
|
+
success: true,
|
|
586
|
+
tools,
|
|
587
|
+
message: `MCP server validated successfully. Found ${tools.length} tools.`
|
|
588
|
+
});
|
|
589
|
+
} else {
|
|
590
|
+
res.status(400).json({
|
|
591
|
+
error: `MCP server validation failed with exit code ${code}`,
|
|
592
|
+
details: stderr || 'No error details available'
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
child.on('error', (error) => {
|
|
599
|
+
clearTimeout(timeoutId);
|
|
600
|
+
|
|
601
|
+
try {
|
|
602
|
+
const config = readMcpConfig();
|
|
603
|
+
if (config.mcpServers[name]) {
|
|
604
|
+
config.mcpServers[name] = {
|
|
605
|
+
...config.mcpServers[name],
|
|
606
|
+
status: 'error',
|
|
607
|
+
error: `Failed to start MCP server: ${error.message}`,
|
|
608
|
+
tools: undefined,
|
|
609
|
+
lastValidated: new Date().toISOString()
|
|
610
|
+
};
|
|
611
|
+
writeMcpConfig(config);
|
|
612
|
+
}
|
|
613
|
+
} catch (configError) {
|
|
614
|
+
console.error('Failed to update config with error status:', configError);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
res.status(500).json({
|
|
618
|
+
error: 'Failed to start MCP server',
|
|
619
|
+
details: error.message
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Get tools from HTTP MCP server using proper session management
|
|
626
|
+
* HTTP MCP requires maintaining session state between initialize and tools/list calls
|
|
627
|
+
*/
|
|
628
|
+
async function getHttpMcpTools(url: string): Promise<string[]> {
|
|
629
|
+
console.log('Starting HTTP MCP tools discovery for:', url);
|
|
630
|
+
|
|
631
|
+
// Step 1: Initialize the session
|
|
632
|
+
const initResponse = await fetch(url, {
|
|
633
|
+
method: 'POST',
|
|
634
|
+
headers: {
|
|
635
|
+
'Content-Type': 'application/json',
|
|
636
|
+
'Accept': 'application/json, text/event-stream',
|
|
637
|
+
},
|
|
638
|
+
body: JSON.stringify({
|
|
639
|
+
jsonrpc: '2.0',
|
|
640
|
+
id: 1,
|
|
641
|
+
method: 'initialize',
|
|
642
|
+
params: {
|
|
643
|
+
protocolVersion: '2024-11-05',
|
|
644
|
+
capabilities: {
|
|
645
|
+
tools: {}
|
|
646
|
+
},
|
|
647
|
+
clientInfo: {
|
|
648
|
+
name: 'agentstudio-validator',
|
|
649
|
+
version: '1.0.0'
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
})
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
if (!initResponse.ok) {
|
|
656
|
+
throw new Error(`HTTP MCP initialize failed: ${initResponse.status} ${initResponse.statusText}`);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const initResponseText = await initResponse.text();
|
|
660
|
+
console.log('HTTP MCP initialize response:', initResponseText);
|
|
661
|
+
|
|
662
|
+
// Parse initialize response
|
|
663
|
+
let initResult: any = null;
|
|
664
|
+
const initLines = initResponseText.split('\n');
|
|
665
|
+
for (const line of initLines) {
|
|
666
|
+
if (line.startsWith('data: ')) {
|
|
667
|
+
try {
|
|
668
|
+
initResult = JSON.parse(line.substring(6));
|
|
669
|
+
break;
|
|
670
|
+
} catch (e) {
|
|
671
|
+
console.warn('Failed to parse init SSE data line:', line);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (!initResult || !initResult.result) {
|
|
677
|
+
throw new Error('Invalid initialize response from HTTP MCP server');
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Extract session ID from response headers
|
|
681
|
+
const sessionId = initResponse.headers.get('mcp-session-id');
|
|
682
|
+
console.log('HTTP MCP session ID:', sessionId);
|
|
683
|
+
|
|
684
|
+
// Step 2: Get tools list using the session ID
|
|
685
|
+
// Wait a bit to ensure the session is properly established
|
|
686
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
687
|
+
|
|
688
|
+
const toolsHeaders: Record<string, string> = {
|
|
689
|
+
'Content-Type': 'application/json',
|
|
690
|
+
'Accept': 'application/json, text/event-stream',
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
// Add session ID if available
|
|
694
|
+
if (sessionId) {
|
|
695
|
+
toolsHeaders['mcp-session-id'] = sessionId;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Copy any session cookies from the init response
|
|
699
|
+
if (initResponse.headers.get('set-cookie')) {
|
|
700
|
+
toolsHeaders['Cookie'] = initResponse.headers.get('set-cookie')!;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const toolsResponse = await fetch(url, {
|
|
704
|
+
method: 'POST',
|
|
705
|
+
headers: toolsHeaders,
|
|
706
|
+
body: JSON.stringify({
|
|
707
|
+
jsonrpc: '2.0',
|
|
708
|
+
id: 2,
|
|
709
|
+
method: 'tools/list',
|
|
710
|
+
params: {}
|
|
711
|
+
})
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
if (!toolsResponse.ok) {
|
|
715
|
+
throw new Error(`HTTP MCP tools/list failed: ${toolsResponse.status} ${toolsResponse.statusText}`);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const toolsResponseText = await toolsResponse.text();
|
|
719
|
+
console.log('HTTP MCP tools/list response:', toolsResponseText);
|
|
720
|
+
|
|
721
|
+
// Parse tools response
|
|
722
|
+
let toolsResult: any = null;
|
|
723
|
+
const toolsLines = toolsResponseText.split('\n');
|
|
724
|
+
for (const line of toolsLines) {
|
|
725
|
+
if (line.startsWith('data: ')) {
|
|
726
|
+
try {
|
|
727
|
+
toolsResult = JSON.parse(line.substring(6));
|
|
728
|
+
break;
|
|
729
|
+
} catch (e) {
|
|
730
|
+
console.warn('Failed to parse tools SSE data line:', line);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (!toolsResult) {
|
|
736
|
+
throw new Error('Invalid tools/list response from HTTP MCP server');
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (toolsResult.error) {
|
|
740
|
+
throw new Error(`HTTP MCP tools/list error: ${toolsResult.error.message}`);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (!toolsResult.result || !toolsResult.result.tools) {
|
|
744
|
+
console.log('HTTP MCP server returned no tools');
|
|
745
|
+
return [];
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const tools = toolsResult.result.tools.map((tool: any) => tool.name);
|
|
749
|
+
console.log('Extracted tools from HTTP MCP:', tools);
|
|
750
|
+
return tools;
|
|
751
|
+
}
|