@esankhan3/code-search-mcp 1.0.5 → 1.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/README.md +114 -54
- package/dist/__tests__/_harness.js +194 -0
- package/dist/__tests__/config-cli.test.js +110 -0
- package/dist/__tests__/config-resolver.test.js +1 -1
- package/dist/__tests__/daemon-rpc-protocol.test.js +95 -0
- package/dist/__tests__/daemon-runtime.test.js +145 -0
- package/dist/__tests__/graph-tools-store.test.js +314 -0
- package/dist/__tests__/graph-tools.test.js +11 -4
- package/dist/__tests__/http-auth.test.js +180 -0
- package/dist/__tests__/http-index.test.js +125 -0
- package/dist/__tests__/metrics-collect.test.js +48 -0
- package/dist/__tests__/observability.test.js +3 -4
- package/dist/__tests__/remote-proxy.test.js +105 -0
- package/dist/__tests__/server-cli-args.test.js +146 -0
- package/dist/__tests__/server-lifecycle.test.js +108 -0
- package/dist/__tests__/tool-inputs.test.js +178 -0
- package/dist/backends/in-process.js +5 -2
- package/dist/backends/index.js +2 -2
- package/dist/cli/index.js +143 -76
- package/dist/core/config.js +106 -35
- package/dist/core/env-config.js +3 -5
- package/dist/core/project-name.js +12 -0
- package/dist/core/version.js +5 -0
- package/dist/daemon/index.js +106 -65
- package/dist/daemon/rpc-server.js +38 -18
- package/dist/index.js +113 -89
- package/dist/middleware/auth.js +34 -66
- package/dist/middleware/rate-limit.js +37 -0
- package/dist/observability/logger.js +10 -2
- package/dist/observability/metrics.js +7 -6
- package/dist/projects/registry.js +13 -15
- package/dist/resources/resources.js +17 -0
- package/dist/server.js +216 -129
- package/dist/tools/graph.js +353 -133
- package/dist/tools/index-tools.js +3 -2
- package/dist/tools/profile.js +14 -8
- package/dist/tools/search.js +61 -51
- package/dist/tools/validate.js +48 -0
- package/dist/transports/http-transport.js +94 -55
- package/dist/transports/remote-proxy.js +58 -97
- package/package.json +1 -1
|
@@ -1,15 +1,32 @@
|
|
|
1
|
-
import { Server } from "@modelcontextprotocol/server";
|
|
1
|
+
import { Server, ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
3
|
+
import { PACKAGE_VERSION } from "../core/version.js";
|
|
4
|
+
import { getComponentLogger } from "../observability/logger.js";
|
|
5
|
+
const log = getComponentLogger("code-search-mcp");
|
|
6
|
+
function parseSseMessages(text) {
|
|
7
|
+
const messages = [];
|
|
8
|
+
for (const event of text.split(/\r?\n\r?\n/)) {
|
|
9
|
+
const data = event.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).replace(/^ /, "")).join("\n");
|
|
10
|
+
if (!data) continue;
|
|
11
|
+
try {
|
|
12
|
+
messages.push(JSON.parse(data));
|
|
13
|
+
} catch {
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return messages;
|
|
17
|
+
}
|
|
3
18
|
class RemoteConnection {
|
|
4
19
|
serverUrl;
|
|
5
20
|
apiKey;
|
|
6
|
-
|
|
21
|
+
nextId = 1;
|
|
7
22
|
constructor(config) {
|
|
8
23
|
this.serverUrl = config.serverUrl.replace(/\/$/, "");
|
|
9
24
|
this.apiKey = config.apiKey;
|
|
10
25
|
}
|
|
11
|
-
/** Send
|
|
12
|
-
|
|
26
|
+
/** Send one request; resolve with its `result`, or throw a ProtocolError
|
|
27
|
+
* carrying the remote's JSON-RPC error / the HTTP failure. */
|
|
28
|
+
async request(method, params = {}) {
|
|
29
|
+
const id = this.nextId++;
|
|
13
30
|
const headers = {
|
|
14
31
|
"Content-Type": "application/json",
|
|
15
32
|
"Accept": "application/json, text/event-stream"
|
|
@@ -17,54 +34,35 @@ class RemoteConnection {
|
|
|
17
34
|
if (this.apiKey) {
|
|
18
35
|
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
19
36
|
}
|
|
20
|
-
|
|
21
|
-
|
|
37
|
+
let response;
|
|
38
|
+
try {
|
|
39
|
+
response = await fetch(`${this.serverUrl}/mcp`, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers,
|
|
42
|
+
body: JSON.stringify({ jsonrpc: "2.0", id, method, params })
|
|
43
|
+
});
|
|
44
|
+
} catch (err) {
|
|
45
|
+
throw new ProtocolError(ProtocolErrorCode.InternalError, `Remote server unreachable: ${err instanceof Error ? err.message : String(err)}`);
|
|
22
46
|
}
|
|
23
|
-
const body = JSON.stringify({
|
|
24
|
-
jsonrpc: "2.0",
|
|
25
|
-
id,
|
|
26
|
-
method,
|
|
27
|
-
params
|
|
28
|
-
});
|
|
29
|
-
const response = await fetch(`${this.serverUrl}/mcp`, {
|
|
30
|
-
method: "POST",
|
|
31
|
-
headers,
|
|
32
|
-
body
|
|
33
|
-
});
|
|
34
47
|
if (!response.ok) {
|
|
35
|
-
if ((response.status === 404 || response.status === 400) && this.sessionId && method !== "initialize" && !_retried) {
|
|
36
|
-
this.sessionId = null;
|
|
37
|
-
await this.initialize();
|
|
38
|
-
return this.request(method, params, id, true);
|
|
39
|
-
}
|
|
40
48
|
const errBody = await response.text().catch(() => "");
|
|
41
|
-
throw new
|
|
42
|
-
}
|
|
43
|
-
const newSessionId = response.headers.get("mcp-session-id");
|
|
44
|
-
if (newSessionId) {
|
|
45
|
-
this.sessionId = newSessionId;
|
|
49
|
+
throw new ProtocolError(ProtocolErrorCode.InternalError, `Remote server error (${response.status}): ${errBody.slice(0, 300)}`);
|
|
46
50
|
}
|
|
47
51
|
const contentType = response.headers.get("content-type") ?? "";
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
return JSON.parse(line.slice(6));
|
|
54
|
-
} catch {
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
throw new Error("No valid JSON in SSE response");
|
|
52
|
+
const messages = contentType.includes("text/event-stream") ? parseSseMessages(await response.text()) : [await response.json()];
|
|
53
|
+
const reply = messages.find((m) => typeof m === "object" && m !== null && m.id === id && ("result" in m || "error" in m));
|
|
54
|
+
if (!reply) {
|
|
55
|
+
throw new ProtocolError(ProtocolErrorCode.InternalError, `Remote server sent no response for ${method} (id ${id})`);
|
|
59
56
|
}
|
|
60
|
-
|
|
57
|
+
if (reply.error) throw new ProtocolError(reply.error.code, reply.error.message, reply.error.data);
|
|
58
|
+
return reply.result;
|
|
61
59
|
}
|
|
62
|
-
/**
|
|
60
|
+
/** MCP handshake — used at startup as a reachability / credentials check. */
|
|
63
61
|
async initialize() {
|
|
64
62
|
return this.request("initialize", {
|
|
65
63
|
protocolVersion: "2024-11-05",
|
|
66
64
|
capabilities: {},
|
|
67
|
-
clientInfo: { name: "code-search-mcp-proxy", version:
|
|
65
|
+
clientInfo: { name: "code-search-mcp-proxy", version: PACKAGE_VERSION }
|
|
68
66
|
});
|
|
69
67
|
}
|
|
70
68
|
/** Check remote server health */
|
|
@@ -78,74 +76,37 @@ async function startRemoteProxy(config) {
|
|
|
78
76
|
const remote = new RemoteConnection(config);
|
|
79
77
|
try {
|
|
80
78
|
const health = await remote.health();
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
log.info(`Connected to remote server at ${config.serverUrl}`);
|
|
80
|
+
log.info(`Remote status: ${health.status}, project: ${health.project ?? "unknown"}, index: ${health.indexReady ? "ready" : "not ready"}`);
|
|
83
81
|
} catch (err) {
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
log.warn(`WARNING: Could not reach remote server at ${config.serverUrl}: ${err.message}`);
|
|
83
|
+
log.warn(`Proxy will start anyway \u2014 requests will fail until server is available.`);
|
|
86
84
|
}
|
|
87
85
|
try {
|
|
88
86
|
await remote.initialize();
|
|
89
|
-
|
|
87
|
+
log.info(`Remote MCP handshake ok`);
|
|
90
88
|
} catch (err) {
|
|
91
|
-
|
|
89
|
+
log.warn(`WARNING: Remote MCP handshake failed: ${err.message}. Requests will fail until this is fixed (check --api-key / CODE_SEARCH_API_KEY).`);
|
|
92
90
|
}
|
|
93
91
|
const server = new Server(
|
|
94
|
-
{ name: "code-search-mcp", version:
|
|
92
|
+
{ name: "code-search-mcp", version: PACKAGE_VERSION },
|
|
95
93
|
{ capabilities: { tools: {}, resources: {} } }
|
|
96
94
|
);
|
|
97
|
-
server.setRequestHandler("tools/list", async () =>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
const response = await remote.request("tools/call", {
|
|
109
|
-
name: request.params.name,
|
|
110
|
-
arguments: request.params.arguments ?? {}
|
|
111
|
-
});
|
|
112
|
-
return response.result ?? {
|
|
113
|
-
content: [{ type: "text", text: "No response from remote server" }],
|
|
114
|
-
isError: true
|
|
115
|
-
};
|
|
116
|
-
} catch (err) {
|
|
117
|
-
return {
|
|
118
|
-
content: [{ type: "text", text: `Remote server error: ${err.message}` }],
|
|
119
|
-
isError: true
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
server.setRequestHandler("resources/list", async () => {
|
|
124
|
-
try {
|
|
125
|
-
const response = await remote.request("resources/list");
|
|
126
|
-
return response.result ?? { resources: [] };
|
|
127
|
-
} catch {
|
|
128
|
-
return { resources: [] };
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
server.setRequestHandler("resources/read", async (request) => {
|
|
132
|
-
try {
|
|
133
|
-
const response = await remote.request("resources/read", {
|
|
134
|
-
uri: request.params.uri
|
|
135
|
-
});
|
|
136
|
-
return response.result ?? {
|
|
137
|
-
contents: [{ uri: request.params.uri, text: "Not available" }]
|
|
138
|
-
};
|
|
139
|
-
} catch (err) {
|
|
140
|
-
return {
|
|
141
|
-
contents: [{ uri: request.params.uri, text: `Error: ${err.message}` }]
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
});
|
|
95
|
+
server.setRequestHandler("tools/list", async () => remote.request("tools/list"));
|
|
96
|
+
server.setRequestHandler("tools/call", async (request) => remote.request("tools/call", {
|
|
97
|
+
name: request.params.name,
|
|
98
|
+
arguments: request.params.arguments ?? {}
|
|
99
|
+
}));
|
|
100
|
+
server.setRequestHandler("resources/list", async () => remote.request("resources/list"));
|
|
101
|
+
server.setRequestHandler("resources/templates/list", async () => remote.request("resources/templates/list"));
|
|
102
|
+
server.setRequestHandler("resources/read", async (request) => remote.request("resources/read", {
|
|
103
|
+
uri: request.params.uri
|
|
104
|
+
}));
|
|
145
105
|
const transport = new StdioServerTransport();
|
|
146
106
|
await server.connect(transport);
|
|
147
|
-
|
|
107
|
+
log.info(`Proxy running (stdio \u2192 ${config.serverUrl})`);
|
|
148
108
|
}
|
|
149
109
|
export {
|
|
110
|
+
parseSseMessages,
|
|
150
111
|
startRemoteProxy
|
|
151
112
|
};
|