deepdebug-local-agent 0.3.1
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/.dockerignore +24 -0
- package/.idea/deepdebug-local-agent.iml +12 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/Dockerfile +46 -0
- package/cloudbuild.yaml +42 -0
- package/index.js +42 -0
- package/mcp-server.js +533 -0
- package/package.json +22 -0
- package/src/ai-engine.js +861 -0
- package/src/analyzers/config-analyzer.js +446 -0
- package/src/analyzers/controller-analyzer.js +429 -0
- package/src/analyzers/dto-analyzer.js +455 -0
- package/src/detectors/build-tool-detector.js +0 -0
- package/src/detectors/framework-detector.js +91 -0
- package/src/detectors/language-detector.js +89 -0
- package/src/detectors/multi-project-detector.js +191 -0
- package/src/detectors/service-detector.js +244 -0
- package/src/detectors.js +30 -0
- package/src/exec-utils.js +215 -0
- package/src/fs-utils.js +34 -0
- package/src/git/base-git-provider.js +384 -0
- package/src/git/git-provider-registry.js +110 -0
- package/src/git/github-provider.js +502 -0
- package/src/mcp-http-server.js +313 -0
- package/src/patch/patch-engine.js +339 -0
- package/src/patch-manager.js +816 -0
- package/src/patch.js +607 -0
- package/src/patch_bkp.js +154 -0
- package/src/ports.js +69 -0
- package/src/routes/workspace.route.js +528 -0
- package/src/runtimes/base-runtime.js +290 -0
- package/src/runtimes/java/gradle-runtime.js +378 -0
- package/src/runtimes/java/java-integrations.js +339 -0
- package/src/runtimes/java/maven-runtime.js +418 -0
- package/src/runtimes/node/node-integrations.js +247 -0
- package/src/runtimes/node/npm-runtime.js +466 -0
- package/src/runtimes/node/yarn-runtime.js +354 -0
- package/src/runtimes/runtime-registry.js +256 -0
- package/src/server-local.js +576 -0
- package/src/server.js +4565 -0
- package/src/utils/environment-diagnostics.js +666 -0
- package/src/utils/exec-utils.js +264 -0
- package/src/utils/fs-utils.js +218 -0
- package/src/workspace/detect-port.js +176 -0
- package/src/workspace/file-reader.js +54 -0
- package/src/workspace/git-client.js +0 -0
- package/src/workspace/process-manager.js +619 -0
- package/src/workspace/scanner.js +72 -0
- package/src/workspace-manager.js +172 -0
package/mcp-server.js
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
4
|
+
import {
|
|
5
|
+
CallToolRequestSchema,
|
|
6
|
+
ListToolsRequestSchema,
|
|
7
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { spawn } from "child_process";
|
|
10
|
+
import { exists, listRecursive, readFile, writeFile, stat } from "./fs-utils.js";
|
|
11
|
+
import { detectProject, readText } from "./detectors.js";
|
|
12
|
+
import { applyUnifiedDiff } from "./patch.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* DeepDebug MCP Server
|
|
16
|
+
*
|
|
17
|
+
* Expõe as capacidades do Local Agent via Model Context Protocol.
|
|
18
|
+
* Permite que o Claude AI aceda ao código real dos workspaces
|
|
19
|
+
* antes de gerar patches.
|
|
20
|
+
*
|
|
21
|
+
* Tools:
|
|
22
|
+
* 1. read_file — Lê ficheiro do workspace
|
|
23
|
+
* 2. write_file — Escreve ficheiro no workspace
|
|
24
|
+
* 3. list_directory — Lista ficheiros/dirs
|
|
25
|
+
* 4. search_code — Grep no workspace
|
|
26
|
+
* 5. execute_command — Corre comando (compile, test)
|
|
27
|
+
* 6. get_project_info — Language, framework, deps
|
|
28
|
+
* 7. apply_patch — Aplica unified diff
|
|
29
|
+
* 8. get_workspace_status — Status de todos os workspaces
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** @type {import('./workspace-manager.js').WorkspaceManager} */
|
|
33
|
+
let workspaceManager = null;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Inicializa o MCP Server com referência ao WorkspaceManager.
|
|
37
|
+
* @param {import('./workspace-manager.js').WorkspaceManager} wsManager
|
|
38
|
+
*/
|
|
39
|
+
export function createMCPServer(wsManager) {
|
|
40
|
+
workspaceManager = wsManager;
|
|
41
|
+
|
|
42
|
+
const server = new Server(
|
|
43
|
+
{
|
|
44
|
+
name: "deepdebug-local-agent",
|
|
45
|
+
version: "1.0.0",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
capabilities: {
|
|
49
|
+
tools: {},
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// ============================================
|
|
55
|
+
// LIST TOOLS
|
|
56
|
+
// ============================================
|
|
57
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
58
|
+
tools: [
|
|
59
|
+
{
|
|
60
|
+
name: "read_file",
|
|
61
|
+
description: "Read the contents of a file from a workspace. Returns the full file content as text. Use this to understand existing code before generating patches.",
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: "object",
|
|
64
|
+
properties: {
|
|
65
|
+
workspaceId: { type: "string", description: "Workspace ID (optional, uses default if not provided)" },
|
|
66
|
+
path: { type: "string", description: "Relative file path (e.g. 'src/main/java/com/example/UserService.java')" },
|
|
67
|
+
},
|
|
68
|
+
required: ["path"],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "write_file",
|
|
73
|
+
description: "Write content to a file in a workspace. Creates the file if it doesn't exist. Use with caution.",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
type: "object",
|
|
76
|
+
properties: {
|
|
77
|
+
workspaceId: { type: "string", description: "Workspace ID" },
|
|
78
|
+
path: { type: "string", description: "Relative file path" },
|
|
79
|
+
content: { type: "string", description: "File content to write" },
|
|
80
|
+
},
|
|
81
|
+
required: ["path", "content"],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "list_directory",
|
|
86
|
+
description: "List files and directories in a workspace path. Returns file tree with types (file/dir). Useful to understand project structure.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
workspaceId: { type: "string", description: "Workspace ID" },
|
|
91
|
+
path: { type: "string", description: "Relative directory path (empty or '.' for root)" },
|
|
92
|
+
maxFiles: { type: "number", description: "Max files to return (default 500)", default: 500 },
|
|
93
|
+
},
|
|
94
|
+
required: [],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "search_code",
|
|
99
|
+
description: "Search for text patterns in workspace files using grep. Returns matching lines with file paths and line numbers. Use to find related code, usages, and dependencies.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
workspaceId: { type: "string", description: "Workspace ID" },
|
|
104
|
+
query: { type: "string", description: "Search pattern (text or regex)" },
|
|
105
|
+
filePattern: { type: "string", description: "File glob pattern (e.g. '*.java', '*.ts')", default: "*" },
|
|
106
|
+
maxResults: { type: "number", description: "Max results (default 50)", default: 50 },
|
|
107
|
+
},
|
|
108
|
+
required: ["query"],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "execute_command",
|
|
113
|
+
description: "Execute a shell command in the workspace directory. Use for compilation, testing, and validation. Returns stdout, stderr, and exit code.",
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
workspaceId: { type: "string", description: "Workspace ID" },
|
|
118
|
+
command: { type: "string", description: "Command to execute (e.g. 'mvn compile', 'npm test')" },
|
|
119
|
+
timeoutMs: { type: "number", description: "Timeout in ms (default 120000)", default: 120000 },
|
|
120
|
+
},
|
|
121
|
+
required: ["command"],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "get_project_info",
|
|
126
|
+
description: "Get project metadata: programming language, framework, build tool, and detected port. Use to understand the tech stack before generating fixes.",
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
workspaceId: { type: "string", description: "Workspace ID" },
|
|
131
|
+
},
|
|
132
|
+
required: [],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "apply_patch",
|
|
137
|
+
description: "Apply a unified diff patch to a file in the workspace. The diff must be in standard unified diff format. Returns the patched file path and size.",
|
|
138
|
+
inputSchema: {
|
|
139
|
+
type: "object",
|
|
140
|
+
properties: {
|
|
141
|
+
workspaceId: { type: "string", description: "Workspace ID" },
|
|
142
|
+
diff: { type: "string", description: "Unified diff content" },
|
|
143
|
+
},
|
|
144
|
+
required: ["diff"],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "get_workspace_status",
|
|
149
|
+
description: "Get status of all open workspaces, or a specific one. Returns workspace IDs, roots, project info, and last access times.",
|
|
150
|
+
inputSchema: {
|
|
151
|
+
type: "object",
|
|
152
|
+
properties: {
|
|
153
|
+
workspaceId: { type: "string", description: "Specific workspace ID (optional, returns all if not provided)" },
|
|
154
|
+
},
|
|
155
|
+
required: [],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
// ============================================
|
|
162
|
+
// CALL TOOL — Route to handler
|
|
163
|
+
// ============================================
|
|
164
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
165
|
+
const { name, arguments: args } = request.params;
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
switch (name) {
|
|
169
|
+
case "read_file":
|
|
170
|
+
return await handleReadFile(args);
|
|
171
|
+
case "write_file":
|
|
172
|
+
return await handleWriteFile(args);
|
|
173
|
+
case "list_directory":
|
|
174
|
+
return await handleListDirectory(args);
|
|
175
|
+
case "search_code":
|
|
176
|
+
return await handleSearchCode(args);
|
|
177
|
+
case "execute_command":
|
|
178
|
+
return await handleExecuteCommand(args);
|
|
179
|
+
case "get_project_info":
|
|
180
|
+
return await handleGetProjectInfo(args);
|
|
181
|
+
case "apply_patch":
|
|
182
|
+
return await handleApplyPatch(args);
|
|
183
|
+
case "get_workspace_status":
|
|
184
|
+
return await handleGetWorkspaceStatus(args);
|
|
185
|
+
default:
|
|
186
|
+
return errorResponse(`Unknown tool: ${name}`);
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
console.error(`❌ MCP tool error [${name}]:`, err.message);
|
|
190
|
+
return errorResponse(err.message);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return server;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ============================================
|
|
198
|
+
// TOOL HANDLERS
|
|
199
|
+
// ============================================
|
|
200
|
+
|
|
201
|
+
async function handleReadFile(args) {
|
|
202
|
+
const root = workspaceManager.resolveRoot(args.workspaceId);
|
|
203
|
+
const filePath = args.path;
|
|
204
|
+
|
|
205
|
+
// Security: prevent path traversal
|
|
206
|
+
const full = path.resolve(root, filePath);
|
|
207
|
+
if (!full.startsWith(root)) {
|
|
208
|
+
return errorResponse("Path traversal not allowed");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!(await exists(full))) {
|
|
212
|
+
return errorResponse(`File not found: ${filePath}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const fileStats = await stat(full);
|
|
216
|
+
// Limit: 1MB
|
|
217
|
+
if (fileStats.size > 1024 * 1024) {
|
|
218
|
+
return errorResponse(`File too large: ${(fileStats.size / 1024).toFixed(0)}KB (max 1MB)`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const content = await readFile(full, "utf8");
|
|
222
|
+
|
|
223
|
+
return textResponse(
|
|
224
|
+
`File: ${filePath}\nSize: ${fileStats.size} bytes\nLines: ${content.split("\n").length}\n\n${content}`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function handleWriteFile(args) {
|
|
229
|
+
const root = workspaceManager.resolveRoot(args.workspaceId);
|
|
230
|
+
const filePath = args.path;
|
|
231
|
+
const content = args.content;
|
|
232
|
+
|
|
233
|
+
const full = path.resolve(root, filePath);
|
|
234
|
+
if (!full.startsWith(root)) {
|
|
235
|
+
return errorResponse("Path traversal not allowed");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Ensure directory exists
|
|
239
|
+
const dir = path.dirname(full);
|
|
240
|
+
const fs = await import("fs");
|
|
241
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
242
|
+
|
|
243
|
+
await writeFile(full, content, "utf8");
|
|
244
|
+
|
|
245
|
+
return textResponse(`Written: ${filePath} (${Buffer.byteLength(content, "utf8")} bytes)`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function handleListDirectory(args) {
|
|
249
|
+
const root = workspaceManager.resolveRoot(args.workspaceId);
|
|
250
|
+
const dirPath = args.path || ".";
|
|
251
|
+
const maxFiles = args.maxFiles || 500;
|
|
252
|
+
|
|
253
|
+
const full = path.resolve(root, dirPath);
|
|
254
|
+
if (!full.startsWith(root)) {
|
|
255
|
+
return errorResponse("Path traversal not allowed");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const entries = await listRecursive(full, { maxFiles });
|
|
259
|
+
|
|
260
|
+
// Format as tree-like output
|
|
261
|
+
const lines = entries.map(e =>
|
|
262
|
+
`${e.type === "dir" ? "📁" : "📄"} ${e.path}`
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return textResponse(
|
|
266
|
+
`Directory: ${dirPath}\nEntries: ${entries.length}${entries.length >= maxFiles ? " (truncated)" : ""}\n\n${lines.join("\n")}`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function handleSearchCode(args) {
|
|
271
|
+
const root = workspaceManager.resolveRoot(args.workspaceId);
|
|
272
|
+
const query = args.query;
|
|
273
|
+
const filePattern = args.filePattern || "*";
|
|
274
|
+
const maxResults = args.maxResults || 50;
|
|
275
|
+
|
|
276
|
+
return new Promise((resolve) => {
|
|
277
|
+
// Use grep with recursive search, excluding common dirs
|
|
278
|
+
const grepArgs = [
|
|
279
|
+
"-rn", // recursive + line numbers
|
|
280
|
+
"--include", filePattern, // file pattern
|
|
281
|
+
"-m", String(maxResults), // max matches per file
|
|
282
|
+
"--exclude-dir=node_modules",
|
|
283
|
+
"--exclude-dir=.git",
|
|
284
|
+
"--exclude-dir=target",
|
|
285
|
+
"--exclude-dir=build",
|
|
286
|
+
"--exclude-dir=dist",
|
|
287
|
+
"--exclude-dir=.idea",
|
|
288
|
+
"--exclude-dir=__pycache__",
|
|
289
|
+
query,
|
|
290
|
+
"."
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
const child = spawn("grep", grepArgs, { cwd: root, shell: false });
|
|
294
|
+
let stdout = "";
|
|
295
|
+
let stderr = "";
|
|
296
|
+
|
|
297
|
+
child.stdout.on("data", (d) => stdout += d.toString());
|
|
298
|
+
child.stderr.on("data", (d) => stderr += d.toString());
|
|
299
|
+
|
|
300
|
+
const timer = setTimeout(() => {
|
|
301
|
+
try { child.kill("SIGKILL"); } catch {}
|
|
302
|
+
}, 10000);
|
|
303
|
+
|
|
304
|
+
child.on("close", (code) => {
|
|
305
|
+
clearTimeout(timer);
|
|
306
|
+
|
|
307
|
+
if (code > 1) {
|
|
308
|
+
resolve(errorResponse(`Search failed: ${stderr.trim()}`));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
313
|
+
const truncated = lines.length >= maxResults;
|
|
314
|
+
|
|
315
|
+
resolve(textResponse(
|
|
316
|
+
`Search: "${query}" (pattern: ${filePattern})\nResults: ${lines.length}${truncated ? " (truncated)" : ""}\n\n${lines.join("\n")}`
|
|
317
|
+
));
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function handleExecuteCommand(args) {
|
|
323
|
+
const root = workspaceManager.resolveRoot(args.workspaceId);
|
|
324
|
+
const command = args.command;
|
|
325
|
+
const timeoutMs = args.timeoutMs || 120000;
|
|
326
|
+
|
|
327
|
+
// Security: block dangerous commands
|
|
328
|
+
const blocked = ["rm -rf /", "rm -rf ~", "mkfs", "dd if=", "> /dev/"];
|
|
329
|
+
for (const b of blocked) {
|
|
330
|
+
if (command.includes(b)) {
|
|
331
|
+
return errorResponse(`Blocked command: ${b}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return new Promise((resolve) => {
|
|
336
|
+
const child = spawn("sh", ["-c", command], { cwd: root });
|
|
337
|
+
let stdout = "";
|
|
338
|
+
let stderr = "";
|
|
339
|
+
|
|
340
|
+
child.stdout.on("data", (d) => stdout += d.toString());
|
|
341
|
+
child.stderr.on("data", (d) => stderr += d.toString());
|
|
342
|
+
|
|
343
|
+
const timer = setTimeout(() => {
|
|
344
|
+
try { child.kill("SIGKILL"); } catch {}
|
|
345
|
+
resolve(textResponse(
|
|
346
|
+
`Command: ${command}\nStatus: TIMEOUT (${timeoutMs}ms)\n\nstdout:\n${stdout}\n\nstderr:\n${stderr}`
|
|
347
|
+
));
|
|
348
|
+
}, timeoutMs);
|
|
349
|
+
|
|
350
|
+
child.on("close", (code) => {
|
|
351
|
+
clearTimeout(timer);
|
|
352
|
+
resolve(textResponse(
|
|
353
|
+
`Command: ${command}\nExit code: ${code}\n\nstdout:\n${stdout}\n\nstderr:\n${stderr}`
|
|
354
|
+
));
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function handleGetProjectInfo(args) {
|
|
360
|
+
const root = workspaceManager.resolveRoot(args.workspaceId);
|
|
361
|
+
const projectInfo = await detectProject(root);
|
|
362
|
+
|
|
363
|
+
// Try to read key config files for more context
|
|
364
|
+
let dependencies = "";
|
|
365
|
+
try {
|
|
366
|
+
if (projectInfo.language === "java" && projectInfo.buildTool === "maven") {
|
|
367
|
+
const pom = await readFile(path.join(root, "pom.xml"), "utf8");
|
|
368
|
+
// Extract just the dependencies section (trimmed)
|
|
369
|
+
const depsMatch = pom.match(/<dependencies>([\s\S]*?)<\/dependencies>/);
|
|
370
|
+
dependencies = depsMatch ? depsMatch[1].trim() : "No dependencies section found";
|
|
371
|
+
} else if (projectInfo.language === "node") {
|
|
372
|
+
const pkg = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"));
|
|
373
|
+
dependencies = JSON.stringify({
|
|
374
|
+
dependencies: pkg.dependencies || {},
|
|
375
|
+
devDependencies: pkg.devDependencies || {}
|
|
376
|
+
}, null, 2);
|
|
377
|
+
} else if (projectInfo.language === "python") {
|
|
378
|
+
try {
|
|
379
|
+
dependencies = await readFile(path.join(root, "requirements.txt"), "utf8");
|
|
380
|
+
} catch {
|
|
381
|
+
try {
|
|
382
|
+
dependencies = await readFile(path.join(root, "pyproject.toml"), "utf8");
|
|
383
|
+
} catch {
|
|
384
|
+
dependencies = "No dependency file found";
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
} catch {
|
|
389
|
+
dependencies = "Could not read dependencies";
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return textResponse(
|
|
393
|
+
`Project Info:\n` +
|
|
394
|
+
` Language: ${projectInfo.language}\n` +
|
|
395
|
+
` Build Tool: ${projectInfo.buildTool}\n` +
|
|
396
|
+
` Marker File: ${projectInfo.marker}\n` +
|
|
397
|
+
` Root: ${root}\n\n` +
|
|
398
|
+
`Dependencies:\n${dependencies}`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function handleApplyPatch(args) {
|
|
403
|
+
const root = workspaceManager.resolveRoot(args.workspaceId);
|
|
404
|
+
const diff = args.diff;
|
|
405
|
+
|
|
406
|
+
const result = await applyUnifiedDiff(root, diff);
|
|
407
|
+
|
|
408
|
+
return textResponse(
|
|
409
|
+
`Patch applied successfully!\n` +
|
|
410
|
+
` Target: ${result.target}\n` +
|
|
411
|
+
` Size: ${result.bytes} bytes`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function handleGetWorkspaceStatus(args) {
|
|
416
|
+
if (args.workspaceId) {
|
|
417
|
+
const ws = workspaceManager.get(args.workspaceId);
|
|
418
|
+
if (!ws) return errorResponse(`Workspace not found: ${args.workspaceId}`);
|
|
419
|
+
return textResponse(JSON.stringify(ws, null, 2));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const all = workspaceManager.list();
|
|
423
|
+
return textResponse(
|
|
424
|
+
`Open workspaces: ${all.length}\nDefault: ${workspaceManager.defaultWorkspaceId || "none"}\n\n` +
|
|
425
|
+
JSON.stringify(all, null, 2)
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ============================================
|
|
430
|
+
// HELPERS
|
|
431
|
+
// ============================================
|
|
432
|
+
|
|
433
|
+
function textResponse(text) {
|
|
434
|
+
return {
|
|
435
|
+
content: [{ type: "text", text }],
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function errorResponse(message) {
|
|
440
|
+
return {
|
|
441
|
+
content: [{ type: "text", text: `ERROR: ${message}` }],
|
|
442
|
+
isError: true,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ============================================
|
|
447
|
+
// HTTP/SSE TRANSPORT — Para o Gateway chamar
|
|
448
|
+
// ============================================
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Inicia o MCP Server com transporte SSE numa porta HTTP.
|
|
452
|
+
* O Gateway liga-se via HTTP POST com SSE.
|
|
453
|
+
*/
|
|
454
|
+
export async function startMCPHttpServer(wsManager, port = 5056) {
|
|
455
|
+
const express = (await import("express")).default;
|
|
456
|
+
const app = express();
|
|
457
|
+
|
|
458
|
+
const server = createMCPServer(wsManager);
|
|
459
|
+
|
|
460
|
+
// Map para guardar transportes activos
|
|
461
|
+
const transports = new Map();
|
|
462
|
+
|
|
463
|
+
// SSE endpoint — o client liga-se aqui
|
|
464
|
+
app.get("/sse", (req, res) => {
|
|
465
|
+
const sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
466
|
+
console.log(`🔌 MCP SSE connection: ${sessionId}`);
|
|
467
|
+
|
|
468
|
+
const transport = new SSEServerTransport(`/messages/${sessionId}`, res);
|
|
469
|
+
transports.set(sessionId, transport);
|
|
470
|
+
|
|
471
|
+
server.connect(transport).catch(err => {
|
|
472
|
+
console.error(`❌ MCP transport error: ${err.message}`);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
req.on("close", () => {
|
|
476
|
+
console.log(`🔌 MCP SSE disconnected: ${sessionId}`);
|
|
477
|
+
transports.delete(sessionId);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Message endpoint — o client envia mensagens aqui
|
|
482
|
+
app.post("/messages/:sessionId", express.json(), (req, res) => {
|
|
483
|
+
const { sessionId } = req.params;
|
|
484
|
+
const transport = transports.get(sessionId);
|
|
485
|
+
if (!transport) {
|
|
486
|
+
return res.status(404).json({ error: "Session not found" });
|
|
487
|
+
}
|
|
488
|
+
transport.handlePostMessage(req, res);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// Health check
|
|
492
|
+
app.get("/health", (_req, res) => {
|
|
493
|
+
res.json({
|
|
494
|
+
status: "ok",
|
|
495
|
+
protocol: "mcp",
|
|
496
|
+
workspaces: wsManager.count,
|
|
497
|
+
defaultWorkspace: wsManager.defaultWorkspaceId
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Simple REST wrapper — para o Gateway chamar tools directamente via HTTP
|
|
502
|
+
// Isto é mais simples que MCP SSE para integração Java
|
|
503
|
+
app.post("/tool/:toolName", express.json(), async (req, res) => {
|
|
504
|
+
const { toolName } = req.params;
|
|
505
|
+
const args = req.body || {};
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
const result = await server.callTool({ name: toolName, arguments: args });
|
|
509
|
+
res.json(result);
|
|
510
|
+
} catch (err) {
|
|
511
|
+
res.status(500).json({ error: err.message });
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// List available tools
|
|
516
|
+
app.get("/tools", async (_req, res) => {
|
|
517
|
+
try {
|
|
518
|
+
const result = await server.listTools();
|
|
519
|
+
res.json(result);
|
|
520
|
+
} catch (err) {
|
|
521
|
+
res.status(500).json({ error: err.message });
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
app.listen(port, () => {
|
|
526
|
+
console.log(`🧠 DeepDebug MCP Server listening on http://localhost:${port}`);
|
|
527
|
+
console.log(` SSE: http://localhost:${port}/sse`);
|
|
528
|
+
console.log(` REST: http://localhost:${port}/tool/{toolName}`);
|
|
529
|
+
console.log(` Tools: http://localhost:${port}/tools`);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
return { server, app };
|
|
533
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deepdebug-local-agent",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/server.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node src/server.js",
|
|
8
|
+
"dev": "NODE_ENV=development node src/server.js",
|
|
9
|
+
"mcp": "node src/mcp-server.js"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
13
|
+
"body-parser": "^1.20.3",
|
|
14
|
+
"cors": "^2.8.5",
|
|
15
|
+
"express": "^4.19.2",
|
|
16
|
+
"js-yaml": "^4.1.0",
|
|
17
|
+
"pidusage": "^3.0.2",
|
|
18
|
+
"properties-reader": "^2.3.0",
|
|
19
|
+
"strip-ansi": "^7.1.0",
|
|
20
|
+
"unidiff": "^1.0.2"
|
|
21
|
+
}
|
|
22
|
+
}
|