@context-engine-bridge/context-engine-mcp-bridge 0.0.2 → 0.0.4

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@context-engine-bridge/context-engine-mcp-bridge",
3
- "version": "0.0.2",
4
- "description": "Context Engine MCP bridge (stdio proxy combining indexer + memory servers)",
3
+ "version": "0.0.4",
4
+ "description": "Context Engine MCP bridge (http/stdio proxy combining indexer + memory servers)",
5
5
  "bin": {
6
6
  "ctxce": "bin/ctxce.js",
7
7
  "ctxce-bridge": "bin/ctxce.js"
package/src/cli.js CHANGED
@@ -3,12 +3,62 @@
3
3
  import process from "node:process";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { runMcpServer } from "./mcpServer.js";
6
+ import { runMcpServer, runHttpMcpServer } from "./mcpServer.js";
7
7
 
8
8
  export async function runCli() {
9
9
  const argv = process.argv.slice(2);
10
10
  const cmd = argv[0];
11
11
 
12
+ if (cmd === "mcp-http-serve") {
13
+ const args = argv.slice(1);
14
+ let workspace = process.cwd();
15
+ let indexerUrl = process.env.CTXCE_INDEXER_URL || "http://localhost:8003/mcp";
16
+ let memoryUrl = process.env.CTXCE_MEMORY_URL || null;
17
+ let port = Number.parseInt(process.env.CTXCE_HTTP_PORT || "30810", 10) || 30810;
18
+
19
+ for (let i = 0; i < args.length; i += 1) {
20
+ const a = args[i];
21
+ if (a === "--workspace" || a === "--path") {
22
+ if (i + 1 < args.length) {
23
+ workspace = args[i + 1];
24
+ i += 1;
25
+ continue;
26
+ }
27
+ }
28
+ if (a === "--indexer-url") {
29
+ if (i + 1 < args.length) {
30
+ indexerUrl = args[i + 1];
31
+ i += 1;
32
+ continue;
33
+ }
34
+ }
35
+ if (a === "--memory-url") {
36
+ if (i + 1 < args.length) {
37
+ memoryUrl = args[i + 1];
38
+ i += 1;
39
+ continue;
40
+ }
41
+ }
42
+ if (a === "--port") {
43
+ if (i + 1 < args.length) {
44
+ const parsed = Number.parseInt(args[i + 1], 10);
45
+ if (!Number.isNaN(parsed) && parsed > 0) {
46
+ port = parsed;
47
+ }
48
+ i += 1;
49
+ continue;
50
+ }
51
+ }
52
+ }
53
+
54
+ // eslint-disable-next-line no-console
55
+ console.error(
56
+ `[ctxce] Starting HTTP MCP bridge: workspace=${workspace}, port=${port}, indexerUrl=${indexerUrl}, memoryUrl=${memoryUrl || "disabled"}`,
57
+ );
58
+ await runHttpMcpServer({ workspace, indexerUrl, memoryUrl, port });
59
+ return;
60
+ }
61
+
12
62
  if (cmd === "mcp-serve") {
13
63
  // Minimal flag parsing for PoC: allow passing workspace/root and indexer URL.
14
64
  // Supported flags:
@@ -59,7 +109,7 @@ export async function runCli() {
59
109
 
60
110
  // eslint-disable-next-line no-console
61
111
  console.error(
62
- `Usage: ${binName} mcp-serve [--workspace <path>] [--indexer-url <url>] [--memory-url <url>]`,
112
+ `Usage: ${binName} mcp-serve [--workspace <path>] [--indexer-url <url>] [--memory-url <url>] | ${binName} mcp-http-serve [--workspace <path>] [--indexer-url <url>] [--memory-url <url>] [--port <port>]`,
63
113
  );
64
114
  process.exit(1);
65
115
  }
package/src/mcpServer.js CHANGED
@@ -94,6 +94,22 @@ function withTimeout(promise, ms, label) {
94
94
  });
95
95
  }
96
96
 
97
+ function getBridgeToolTimeoutMs() {
98
+ try {
99
+ const raw = process.env.CTXCE_TOOL_TIMEOUT_MSEC;
100
+ if (!raw) {
101
+ return 300000;
102
+ }
103
+ const parsed = Number.parseInt(String(raw), 10);
104
+ if (!Number.isFinite(parsed) || parsed <= 0) {
105
+ return 300000;
106
+ }
107
+ return parsed;
108
+ } catch {
109
+ return 300000;
110
+ }
111
+ }
112
+
97
113
  function selectClientForTool(name, indexerClient, memoryClient) {
98
114
  if (!name) {
99
115
  return indexerClient;
@@ -112,13 +128,15 @@ import process from "node:process";
112
128
  import fs from "node:fs";
113
129
  import path from "node:path";
114
130
  import { execSync } from "node:child_process";
131
+ import { createServer } from "node:http";
115
132
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
116
133
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
134
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
117
135
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
118
136
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
119
137
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
120
138
 
121
- export async function runMcpServer(options) {
139
+ async function createBridgeServer(options) {
122
140
  const workspace = options.workspace || process.cwd();
123
141
  const indexerUrl = options.indexerUrl;
124
142
  const memoryUrl = options.memoryUrl;
@@ -164,7 +182,6 @@ export async function runMcpServer(options) {
164
182
  await indexerClient.connect(indexerTransport);
165
183
  } catch (err) {
166
184
  debugLog("[ctxce] Failed to connect MCP HTTP client to indexer: " + String(err));
167
- throw err;
168
185
  }
169
186
 
170
187
  let memoryClient = null;
@@ -299,15 +316,147 @@ export async function runMcpServer(options) {
299
316
  throw new Error(`Tool ${name} not available on any configured MCP server`);
300
317
  }
301
318
 
302
- const result = await targetClient.callTool({
303
- name,
304
- arguments: args,
305
- });
319
+ const timeoutMs = getBridgeToolTimeoutMs();
320
+ const result = await targetClient.callTool(
321
+ {
322
+ name,
323
+ arguments: args,
324
+ },
325
+ undefined,
326
+ { timeout: timeoutMs },
327
+ );
306
328
  return result;
307
329
  });
308
330
 
331
+ return server;
332
+ }
333
+
334
+ export async function runMcpServer(options) {
335
+ const server = await createBridgeServer(options);
309
336
  const transport = new StdioServerTransport();
310
337
  await server.connect(transport);
338
+
339
+ const exitOnStdinClose = process.env.CTXCE_EXIT_ON_STDIN_CLOSE !== "0";
340
+ if (exitOnStdinClose) {
341
+ const handleStdioClosed = () => {
342
+ try {
343
+ debugLog("[ctxce] Stdio transport closed; exiting MCP bridge process.");
344
+ } catch {
345
+ // ignore
346
+ }
347
+ // Allow any in-flight logs to flush, then exit.
348
+ setTimeout(() => {
349
+ process.exit(0);
350
+ }, 10).unref();
351
+ };
352
+
353
+ if (process.stdin && typeof process.stdin.on === "function") {
354
+ process.stdin.on("end", handleStdioClosed);
355
+ process.stdin.on("close", handleStdioClosed);
356
+ process.stdin.on("error", handleStdioClosed);
357
+ }
358
+ }
359
+ }
360
+
361
+ export async function runHttpMcpServer(options) {
362
+ const server = await createBridgeServer(options);
363
+ const port =
364
+ typeof options.port === "number"
365
+ ? options.port
366
+ : Number.parseInt(process.env.CTXCE_HTTP_PORT || "30810", 10) || 30810;
367
+
368
+ const transport = new StreamableHTTPServerTransport({
369
+ sessionIdGenerator: undefined,
370
+ });
371
+
372
+ await server.connect(transport);
373
+
374
+ const httpServer = createServer((req, res) => {
375
+ try {
376
+ if (!req.url || !req.url.startsWith("/mcp")) {
377
+ res.statusCode = 404;
378
+ res.setHeader("Content-Type", "application/json");
379
+ res.end(
380
+ JSON.stringify({
381
+ jsonrpc: "2.0",
382
+ error: { code: -32000, message: "Not found" },
383
+ id: null,
384
+ }),
385
+ );
386
+ return;
387
+ }
388
+
389
+ if (req.method !== "POST") {
390
+ res.statusCode = 405;
391
+ res.setHeader("Content-Type", "application/json");
392
+ res.end(
393
+ JSON.stringify({
394
+ jsonrpc: "2.0",
395
+ error: { code: -32000, message: "Method not allowed" },
396
+ id: null,
397
+ }),
398
+ );
399
+ return;
400
+ }
401
+
402
+ let body = "";
403
+ req.on("data", (chunk) => {
404
+ body += chunk;
405
+ });
406
+ req.on("end", async () => {
407
+ let parsed;
408
+ try {
409
+ parsed = body ? JSON.parse(body) : {};
410
+ } catch (err) {
411
+ debugLog("[ctxce] Failed to parse HTTP MCP request body: " + String(err));
412
+ res.statusCode = 400;
413
+ res.setHeader("Content-Type", "application/json");
414
+ res.end(
415
+ JSON.stringify({
416
+ jsonrpc: "2.0",
417
+ error: { code: -32700, message: "Invalid JSON" },
418
+ id: null,
419
+ }),
420
+ );
421
+ return;
422
+ }
423
+
424
+ try {
425
+ await transport.handleRequest(req, res, parsed);
426
+ } catch (err) {
427
+ debugLog("[ctxce] Error handling HTTP MCP request: " + String(err));
428
+ if (!res.headersSent) {
429
+ res.statusCode = 500;
430
+ res.setHeader("Content-Type", "application/json");
431
+ res.end(
432
+ JSON.stringify({
433
+ jsonrpc: "2.0",
434
+ error: { code: -32603, message: "Internal server error" },
435
+ id: null,
436
+ }),
437
+ );
438
+ }
439
+ }
440
+ });
441
+ } catch (err) {
442
+ debugLog("[ctxce] Unexpected error in HTTP MCP server: " + String(err));
443
+ if (!res.headersSent) {
444
+ res.statusCode = 500;
445
+ res.setHeader("Content-Type", "application/json");
446
+ res.end(
447
+ JSON.stringify({
448
+ jsonrpc: "2.0",
449
+ error: { code: -32603, message: "Internal server error" },
450
+ id: null,
451
+ }),
452
+ );
453
+ }
454
+ }
455
+ });
456
+
457
+ httpServer.listen(port, () => {
458
+ debugLog(`[ctxce] HTTP MCP bridge listening on port ${port}`);
459
+ });
311
460
  }
312
461
 
313
462
  function loadConfig(startDir) {