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.
Files changed (115) hide show
  1. package/.env +15 -0
  2. package/README.md +85 -0
  3. package/dist/bin/agentstudio.d.ts +3 -0
  4. package/dist/bin/agentstudio.d.ts.map +1 -0
  5. package/dist/bin/agentstudio.js +141 -0
  6. package/dist/bin/agentstudio.js.map +1 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +87 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/middleware/auth.d.ts +7 -0
  12. package/dist/middleware/auth.d.ts.map +1 -0
  13. package/dist/middleware/auth.js +21 -0
  14. package/dist/middleware/auth.js.map +1 -0
  15. package/dist/routes/agents.d.ts +4 -0
  16. package/dist/routes/agents.d.ts.map +1 -0
  17. package/dist/routes/agents.js +804 -0
  18. package/dist/routes/agents.js.map +1 -0
  19. package/dist/routes/auth.d.ts +4 -0
  20. package/dist/routes/auth.d.ts.map +1 -0
  21. package/dist/routes/auth.js +60 -0
  22. package/dist/routes/auth.js.map +1 -0
  23. package/dist/routes/files.d.ts +4 -0
  24. package/dist/routes/files.d.ts.map +1 -0
  25. package/dist/routes/files.js +301 -0
  26. package/dist/routes/files.js.map +1 -0
  27. package/dist/routes/mcp.d.ts +4 -0
  28. package/dist/routes/mcp.d.ts.map +1 -0
  29. package/dist/routes/mcp.js +652 -0
  30. package/dist/routes/mcp.js.map +1 -0
  31. package/dist/routes/media.d.ts +5 -0
  32. package/dist/routes/media.d.ts.map +1 -0
  33. package/dist/routes/media.js +117 -0
  34. package/dist/routes/media.js.map +1 -0
  35. package/dist/routes/slides.d.ts +4 -0
  36. package/dist/routes/slides.d.ts.map +1 -0
  37. package/dist/routes/slides.js +146 -0
  38. package/dist/routes/slides.js.map +1 -0
  39. package/dist/services/claudeSession.d.ts +83 -0
  40. package/dist/services/claudeSession.d.ts.map +1 -0
  41. package/dist/services/claudeSession.js +255 -0
  42. package/dist/services/claudeSession.js.map +1 -0
  43. package/dist/services/messageQueue.d.ts +31 -0
  44. package/dist/services/messageQueue.d.ts.map +1 -0
  45. package/dist/services/messageQueue.js +67 -0
  46. package/dist/services/messageQueue.js.map +1 -0
  47. package/dist/services/sessionManager.d.ts +132 -0
  48. package/dist/services/sessionManager.d.ts.map +1 -0
  49. package/dist/services/sessionManager.js +439 -0
  50. package/dist/services/sessionManager.js.map +1 -0
  51. package/dist/types/claude-history.d.ts +48 -0
  52. package/dist/types/claude-history.d.ts.map +1 -0
  53. package/dist/types/claude-history.js +2 -0
  54. package/dist/types/claude-history.js.map +1 -0
  55. package/dist/types/claude-versions.d.ts +31 -0
  56. package/dist/types/claude-versions.d.ts.map +1 -0
  57. package/dist/types/claude-versions.js +2 -0
  58. package/dist/types/claude-versions.js.map +1 -0
  59. package/dist/types/commands.d.ts +32 -0
  60. package/dist/types/commands.d.ts.map +1 -0
  61. package/dist/types/commands.js +2 -0
  62. package/dist/types/commands.js.map +1 -0
  63. package/dist/types/index.d.ts +81 -0
  64. package/dist/types/index.d.ts.map +1 -0
  65. package/dist/types/index.js +150 -0
  66. package/dist/types/index.js.map +1 -0
  67. package/dist/types/subagents.d.ts +88 -0
  68. package/dist/types/subagents.d.ts.map +1 -0
  69. package/dist/types/subagents.js +2 -0
  70. package/dist/types/subagents.js.map +1 -0
  71. package/dist/utils/agentStorage.d.ts +19 -0
  72. package/dist/utils/agentStorage.d.ts.map +1 -0
  73. package/dist/utils/agentStorage.js +110 -0
  74. package/dist/utils/agentStorage.js.map +1 -0
  75. package/dist/utils/claudeVersionStorage.d.ts +33 -0
  76. package/dist/utils/claudeVersionStorage.d.ts.map +1 -0
  77. package/dist/utils/claudeVersionStorage.js +168 -0
  78. package/dist/utils/claudeVersionStorage.js.map +1 -0
  79. package/dist/utils/jwt.d.ts +15 -0
  80. package/dist/utils/jwt.d.ts.map +1 -0
  81. package/dist/utils/jwt.js +28 -0
  82. package/dist/utils/jwt.js.map +1 -0
  83. package/dist/utils/projectMetadataStorage.d.ts +21 -0
  84. package/dist/utils/projectMetadataStorage.d.ts.map +1 -0
  85. package/dist/utils/projectMetadataStorage.js +68 -0
  86. package/dist/utils/projectMetadataStorage.js.map +1 -0
  87. package/frontend/dist/index.html +86 -0
  88. package/package.json +66 -0
  89. package/src/bin/agentstudio.ts +161 -0
  90. package/src/index.ts +100 -0
  91. package/src/middleware/auth.ts +26 -0
  92. package/src/routes/agents.ts +885 -0
  93. package/src/routes/auth.ts +73 -0
  94. package/src/routes/commands.ts.bak +441 -0
  95. package/src/routes/files.ts +352 -0
  96. package/src/routes/mcp.ts +751 -0
  97. package/src/routes/media.ts +140 -0
  98. package/src/routes/projects.ts.bak +601 -0
  99. package/src/routes/sessions.ts.bak +809 -0
  100. package/src/routes/settings.ts.bak +718 -0
  101. package/src/routes/slides.ts +170 -0
  102. package/src/routes/subagents.ts.bak +364 -0
  103. package/src/services/claudeSession.ts +293 -0
  104. package/src/services/messageQueue.ts +71 -0
  105. package/src/services/sessionManager.ts +532 -0
  106. package/src/types/claude-history.ts +50 -0
  107. package/src/types/claude-versions.ts +33 -0
  108. package/src/types/commands.ts +35 -0
  109. package/src/types/index.ts +248 -0
  110. package/src/types/subagents.ts +106 -0
  111. package/src/utils/agentStorage.ts +126 -0
  112. package/src/utils/claudeVersionStorage.ts +199 -0
  113. package/src/utils/jwt.ts +36 -0
  114. package/src/utils/projectMetadataStorage.ts +86 -0
  115. 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
+ }