@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.
Files changed (41) hide show
  1. package/README.md +114 -54
  2. package/dist/__tests__/_harness.js +194 -0
  3. package/dist/__tests__/config-cli.test.js +110 -0
  4. package/dist/__tests__/config-resolver.test.js +1 -1
  5. package/dist/__tests__/daemon-rpc-protocol.test.js +95 -0
  6. package/dist/__tests__/daemon-runtime.test.js +145 -0
  7. package/dist/__tests__/graph-tools-store.test.js +314 -0
  8. package/dist/__tests__/graph-tools.test.js +11 -4
  9. package/dist/__tests__/http-auth.test.js +180 -0
  10. package/dist/__tests__/http-index.test.js +125 -0
  11. package/dist/__tests__/metrics-collect.test.js +48 -0
  12. package/dist/__tests__/observability.test.js +3 -4
  13. package/dist/__tests__/remote-proxy.test.js +105 -0
  14. package/dist/__tests__/server-cli-args.test.js +146 -0
  15. package/dist/__tests__/server-lifecycle.test.js +108 -0
  16. package/dist/__tests__/tool-inputs.test.js +178 -0
  17. package/dist/backends/in-process.js +5 -2
  18. package/dist/backends/index.js +2 -2
  19. package/dist/cli/index.js +143 -76
  20. package/dist/core/config.js +106 -35
  21. package/dist/core/env-config.js +3 -5
  22. package/dist/core/project-name.js +12 -0
  23. package/dist/core/version.js +5 -0
  24. package/dist/daemon/index.js +106 -65
  25. package/dist/daemon/rpc-server.js +38 -18
  26. package/dist/index.js +113 -89
  27. package/dist/middleware/auth.js +34 -66
  28. package/dist/middleware/rate-limit.js +37 -0
  29. package/dist/observability/logger.js +10 -2
  30. package/dist/observability/metrics.js +7 -6
  31. package/dist/projects/registry.js +13 -15
  32. package/dist/resources/resources.js +17 -0
  33. package/dist/server.js +216 -129
  34. package/dist/tools/graph.js +353 -133
  35. package/dist/tools/index-tools.js +3 -2
  36. package/dist/tools/profile.js +14 -8
  37. package/dist/tools/search.js +61 -51
  38. package/dist/tools/validate.js +48 -0
  39. package/dist/transports/http-transport.js +94 -55
  40. package/dist/transports/remote-proxy.js +58 -97
  41. 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
- sessionId = null;
21
+ nextId = 1;
7
22
  constructor(config) {
8
23
  this.serverUrl = config.serverUrl.replace(/\/$/, "");
9
24
  this.apiKey = config.apiKey;
10
25
  }
11
- /** Send a JSON-RPC request to the remote MCP server */
12
- async request(method, params = {}, id = 1, _retried = false) {
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
- if (this.sessionId) {
21
- headers["mcp-session-id"] = this.sessionId;
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 Error(`Remote server error (${response.status}): ${errBody.slice(0, 300)}`);
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
- if (contentType.includes("text/event-stream")) {
49
- const text = await response.text();
50
- for (const line of text.split("\n")) {
51
- if (line.startsWith("data: ")) {
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
- return response.json();
57
+ if (reply.error) throw new ProtocolError(reply.error.code, reply.error.message, reply.error.data);
58
+ return reply.result;
61
59
  }
62
- /** Initialize the remote session */
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: "1.0.0" }
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
- console.error(`[code-search-mcp] Connected to remote server at ${config.serverUrl}`);
82
- console.error(`[code-search-mcp] Remote status: ${health.status}, project: ${health.project ?? "unknown"}, index: ${health.indexReady ? "ready" : "not ready"}`);
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
- console.error(`[code-search-mcp] WARNING: Could not reach remote server at ${config.serverUrl}: ${err.message}`);
85
- console.error(`[code-search-mcp] Proxy will start anyway \u2014 requests will fail until server is available.`);
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
- console.error(`[code-search-mcp] Remote session established`);
87
+ log.info(`Remote MCP handshake ok`);
90
88
  } catch (err) {
91
- console.error(`[code-search-mcp] WARNING: Session init failed: ${err.message}. Will retry on first request.`);
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: "1.0.0" },
92
+ { name: "code-search-mcp", version: PACKAGE_VERSION },
95
93
  { capabilities: { tools: {}, resources: {} } }
96
94
  );
97
- server.setRequestHandler("tools/list", async () => {
98
- try {
99
- const response = await remote.request("tools/list");
100
- return response.result ?? { tools: [] };
101
- } catch (err) {
102
- console.error(`[proxy] Failed to list tools: ${err.message}`);
103
- return { tools: [] };
104
- }
105
- });
106
- server.setRequestHandler("tools/call", async (request) => {
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
- console.error(`[code-search-mcp] Proxy running (stdio \u2192 ${config.serverUrl})`);
107
+ log.info(`Proxy running (stdio \u2192 ${config.serverUrl})`);
148
108
  }
149
109
  export {
110
+ parseSseMessages,
150
111
  startRemoteProxy
151
112
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esankhan3/code-search-mcp",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "description": "MCP server for multi-repo code search \u2014 GitHub org or local path, zero config",
5
5
  "type": "module",
6
6
  "license": "MIT",