@context-engine-bridge/context-engine-mcp-bridge 0.0.1 → 0.0.3

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.
@@ -0,0 +1,20 @@
1
+ {
2
+ "mcpServers": {
3
+ "context-engine": {
4
+ "command": "node",
5
+ "args": [
6
+ "C:/Users/Admin/Documents/GitHub/Context-Engine/ctx-mcp-bridge/bin/ctxce.js",
7
+ "mcp-serve",
8
+ "--indexer-url",
9
+ "http://192.168.100.249:30806/mcp",
10
+ "--memory-url",
11
+ "http://192.168.100.249:30804/mcp",
12
+ "--workspace",
13
+ "C:/Users/Admin/Documents/GitHub/Pirate Survivors"
14
+ ],
15
+ "env": {
16
+ "CTXCE_DEBUG_LOG": "C:/Users/Admin/ctxce-mcp.log"
17
+ }
18
+ }
19
+ }
20
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@context-engine-bridge/context-engine-mcp-bridge",
3
- "version": "0.0.1",
4
- "description": "Context Engine MCP bridge (stdio proxy combining indexer + memory servers)",
3
+ "version": "0.0.3",
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/publish.sh ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Simple helper to login (if needed) and publish the package.
5
+ # Usage:
6
+ # ./publish.sh # publishes current version
7
+ # ./publish.sh 0.0.2 # bumps version to 0.0.2 then publishes
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ cd "$SCRIPT_DIR"
11
+
12
+ PACKAGE_NAME="@context-engine-bridge/context-engine-mcp-bridge"
13
+
14
+ echo "[publish] Verifying npm authentication..."
15
+ if ! npm whoami >/dev/null 2>&1; then
16
+ echo "[publish] Not logged in; running npm login"
17
+ npm login
18
+ else
19
+ echo "[publish] Already authenticated as $(npm whoami)"
20
+ fi
21
+
22
+ if [[ $# -gt 0 ]]; then
23
+ VERSION="$1"
24
+ echo "[publish] Bumping version to $VERSION"
25
+ npm version "$VERSION" --no-git-tag-version
26
+ fi
27
+
28
+ echo "[publish] Packing $PACKAGE_NAME for verification..."
29
+ npm pack >/dev/null
30
+
31
+ echo "[publish] Publishing $PACKAGE_NAME..."
32
+ npm publish --access public
33
+
34
+ echo "[publish] Done!"
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
@@ -1,3 +1,15 @@
1
+ function debugLog(message) {
2
+ try {
3
+ const text = typeof message === "string" ? message : String(message);
4
+ console.error(text);
5
+ const dest = process.env.CTXCE_DEBUG_LOG;
6
+ if (dest) {
7
+ fs.appendFileSync(dest, `${new Date().toISOString()} ${text}\n`, "utf8");
8
+ }
9
+ } catch {
10
+ }
11
+ }
12
+
1
13
  async function sendSessionDefaults(client, payload, label) {
2
14
  if (!client) {
3
15
  return;
@@ -36,15 +48,52 @@ async function listMemoryTools(client) {
36
48
  return [];
37
49
  }
38
50
  try {
39
- const remote = await client.listTools();
51
+ const remote = await withTimeout(
52
+ client.listTools(),
53
+ 5000,
54
+ "memory tools/list",
55
+ );
40
56
  return Array.isArray(remote?.tools) ? remote.tools.slice() : [];
41
57
  } catch (err) {
42
- // eslint-disable-next-line no-console
43
- console.error("[ctxce] Error calling memory tools/list:", err);
58
+ debugLog("[ctxce] Error calling memory tools/list: " + String(err));
44
59
  return [];
45
60
  }
46
61
  }
47
62
 
63
+ function withTimeout(promise, ms, label) {
64
+ return new Promise((resolve, reject) => {
65
+ let settled = false;
66
+ const timer = setTimeout(() => {
67
+ if (settled) {
68
+ return;
69
+ }
70
+ settled = true;
71
+ const errorMessage =
72
+ label != null
73
+ ? `[ctxce] Timeout after ${ms}ms in ${label}`
74
+ : `[ctxce] Timeout after ${ms}ms`;
75
+ reject(new Error(errorMessage));
76
+ }, ms);
77
+ promise
78
+ .then((value) => {
79
+ if (settled) {
80
+ return;
81
+ }
82
+ settled = true;
83
+ clearTimeout(timer);
84
+ resolve(value);
85
+ })
86
+ .catch((err) => {
87
+ if (settled) {
88
+ return;
89
+ }
90
+ settled = true;
91
+ clearTimeout(timer);
92
+ reject(err);
93
+ });
94
+ });
95
+ }
96
+
48
97
  function selectClientForTool(name, indexerClient, memoryClient) {
49
98
  if (!name) {
50
99
  return indexerClient;
@@ -63,13 +112,15 @@ import process from "node:process";
63
112
  import fs from "node:fs";
64
113
  import path from "node:path";
65
114
  import { execSync } from "node:child_process";
115
+ import { createServer } from "node:http";
66
116
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
67
117
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
118
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
68
119
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
69
120
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
70
121
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
71
122
 
72
- export async function runMcpServer(options) {
123
+ async function createBridgeServer(options) {
73
124
  const workspace = options.workspace || process.cwd();
74
125
  const indexerUrl = options.indexerUrl;
75
126
  const memoryUrl = options.memoryUrl;
@@ -84,8 +135,7 @@ export async function runMcpServer(options) {
84
135
  const defaultUnder =
85
136
  config && typeof config.default_under === "string" ? config.default_under : null;
86
137
 
87
- // eslint-disable-next-line no-console
88
- console.error(
138
+ debugLog(
89
139
  `[ctxce] MCP low-level stdio bridge starting: workspace=${workspace}, indexerUrl=${indexerUrl}`,
90
140
  );
91
141
 
@@ -115,9 +165,7 @@ export async function runMcpServer(options) {
115
165
  try {
116
166
  await indexerClient.connect(indexerTransport);
117
167
  } catch (err) {
118
- // eslint-disable-next-line no-console
119
- console.error("[ctxce] Failed to connect MCP HTTP client to indexer:", err);
120
- throw err;
168
+ debugLog("[ctxce] Failed to connect MCP HTTP client to indexer: " + String(err));
121
169
  }
122
170
 
123
171
  let memoryClient = null;
@@ -138,11 +186,9 @@ export async function runMcpServer(options) {
138
186
  },
139
187
  );
140
188
  await memoryClient.connect(memoryTransport);
141
- // eslint-disable-next-line no-console
142
- console.error("[ctxce] Connected memory MCP client:", memoryUrl);
189
+ debugLog(`[ctxce] Connected memory MCP client: ${memoryUrl}`);
143
190
  } catch (err) {
144
- // eslint-disable-next-line no-console
145
- console.error("[ctxce] Failed to connect memory MCP client:", err);
191
+ debugLog("[ctxce] Failed to connect memory MCP client: " + String(err));
146
192
  memoryClient = null;
147
193
  }
148
194
  }
@@ -186,45 +232,47 @@ export async function runMcpServer(options) {
186
232
  },
187
233
  );
188
234
 
189
- // tools/list → fetch tools from remote indexer and append local ping tool
235
+ // tools/list → fetch tools from remote indexer
190
236
  server.setRequestHandler(ListToolsRequestSchema, async () => {
191
237
  let remote;
192
238
  try {
193
- remote = await indexerClient.listTools();
239
+ debugLog("[ctxce] tools/list: fetching tools from indexer");
240
+ remote = await withTimeout(
241
+ indexerClient.listTools(),
242
+ 10000,
243
+ "indexer tools/list",
244
+ );
194
245
  } catch (err) {
195
- // eslint-disable-next-line no-console
196
- console.error("[ctxce] Error calling remote tools/list:", err);
197
- return { tools: [buildPingTool()] };
246
+ debugLog("[ctxce] Error calling remote tools/list: " + String(err));
247
+ const memoryToolsFallback = await listMemoryTools(memoryClient);
248
+ const toolsFallback = dedupeTools([...memoryToolsFallback]);
249
+ return { tools: toolsFallback };
198
250
  }
199
251
 
200
- // eslint-disable-next-line no-console
201
- console.error("[ctxce] tools/list remote result:", JSON.stringify(remote));
252
+ try {
253
+ const toolNames =
254
+ remote && Array.isArray(remote.tools)
255
+ ? remote.tools.map((t) => (t && typeof t.name === "string" ? t.name : "<unnamed>"))
256
+ : [];
257
+ debugLog("[ctxce] tools/list remote result tools: " + JSON.stringify(toolNames));
258
+ } catch (err) {
259
+ debugLog("[ctxce] tools/list remote result: <unserializable> " + String(err));
260
+ }
202
261
 
203
262
  const indexerTools = Array.isArray(remote?.tools) ? remote.tools.slice() : [];
204
263
  const memoryTools = await listMemoryTools(memoryClient);
205
- const tools = dedupeTools([...indexerTools, ...memoryTools, buildPingTool()]);
264
+ const tools = dedupeTools([...indexerTools, ...memoryTools]);
265
+ debugLog(`[ctxce] tools/list: returning ${tools.length} tools`);
206
266
  return { tools };
207
267
  });
208
268
 
209
- // tools/call → handle ping locally, everything else is proxied to indexer
269
+ // tools/call → proxied to indexer or memory server
210
270
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
211
271
  const params = request.params || {};
212
272
  const name = params.name;
213
273
  let args = params.arguments;
214
274
 
215
- if (name === "ping") {
216
- const branch = detectGitBranch(workspace);
217
- const text = args && typeof args.text === "string" ? args.text : "pong";
218
- const suffix = branch ? ` (branch=${branch})` : "";
219
- return {
220
- content: [
221
- {
222
- type: "text",
223
- text: `${text}${suffix}`,
224
- },
225
- ],
226
- };
227
- }
275
+ debugLog(`[ctxce] tools/call: ${name || "<no-name>"}`);
228
276
 
229
277
  // Attach session id so the target server can apply per-session defaults.
230
278
  if (sessionId && (args === undefined || args === null || typeof args === "object")) {
@@ -241,8 +289,7 @@ export async function runMcpServer(options) {
241
289
  try {
242
290
  await memoryClient.callTool({ name, arguments: args });
243
291
  } catch (err) {
244
- // eslint-disable-next-line no-console
245
- console.error("[ctxce] Memory set_session_defaults failed:", err);
292
+ debugLog("[ctxce] Memory set_session_defaults failed: " + String(err));
246
293
  }
247
294
  }
248
295
  return indexerResult;
@@ -260,10 +307,116 @@ export async function runMcpServer(options) {
260
307
  return result;
261
308
  });
262
309
 
310
+ return server;
311
+ }
312
+
313
+ export async function runMcpServer(options) {
314
+ const server = await createBridgeServer(options);
263
315
  const transport = new StdioServerTransport();
264
316
  await server.connect(transport);
265
317
  }
266
318
 
319
+ export async function runHttpMcpServer(options) {
320
+ const server = await createBridgeServer(options);
321
+ const port =
322
+ typeof options.port === "number"
323
+ ? options.port
324
+ : Number.parseInt(process.env.CTXCE_HTTP_PORT || "30810", 10) || 30810;
325
+
326
+ const transport = new StreamableHTTPServerTransport({
327
+ sessionIdGenerator: undefined,
328
+ });
329
+
330
+ await server.connect(transport);
331
+
332
+ const httpServer = createServer((req, res) => {
333
+ try {
334
+ if (!req.url || !req.url.startsWith("/mcp")) {
335
+ res.statusCode = 404;
336
+ res.setHeader("Content-Type", "application/json");
337
+ res.end(
338
+ JSON.stringify({
339
+ jsonrpc: "2.0",
340
+ error: { code: -32000, message: "Not found" },
341
+ id: null,
342
+ }),
343
+ );
344
+ return;
345
+ }
346
+
347
+ if (req.method !== "POST") {
348
+ res.statusCode = 405;
349
+ res.setHeader("Content-Type", "application/json");
350
+ res.end(
351
+ JSON.stringify({
352
+ jsonrpc: "2.0",
353
+ error: { code: -32000, message: "Method not allowed" },
354
+ id: null,
355
+ }),
356
+ );
357
+ return;
358
+ }
359
+
360
+ let body = "";
361
+ req.on("data", (chunk) => {
362
+ body += chunk;
363
+ });
364
+ req.on("end", async () => {
365
+ let parsed;
366
+ try {
367
+ parsed = body ? JSON.parse(body) : {};
368
+ } catch (err) {
369
+ debugLog("[ctxce] Failed to parse HTTP MCP request body: " + String(err));
370
+ res.statusCode = 400;
371
+ res.setHeader("Content-Type", "application/json");
372
+ res.end(
373
+ JSON.stringify({
374
+ jsonrpc: "2.0",
375
+ error: { code: -32700, message: "Invalid JSON" },
376
+ id: null,
377
+ }),
378
+ );
379
+ return;
380
+ }
381
+
382
+ try {
383
+ await transport.handleRequest(req, res, parsed);
384
+ } catch (err) {
385
+ debugLog("[ctxce] Error handling HTTP MCP request: " + String(err));
386
+ if (!res.headersSent) {
387
+ res.statusCode = 500;
388
+ res.setHeader("Content-Type", "application/json");
389
+ res.end(
390
+ JSON.stringify({
391
+ jsonrpc: "2.0",
392
+ error: { code: -32603, message: "Internal server error" },
393
+ id: null,
394
+ }),
395
+ );
396
+ }
397
+ }
398
+ });
399
+ } catch (err) {
400
+ debugLog("[ctxce] Unexpected error in HTTP MCP server: " + String(err));
401
+ if (!res.headersSent) {
402
+ res.statusCode = 500;
403
+ res.setHeader("Content-Type", "application/json");
404
+ res.end(
405
+ JSON.stringify({
406
+ jsonrpc: "2.0",
407
+ error: { code: -32603, message: "Internal server error" },
408
+ id: null,
409
+ }),
410
+ );
411
+ }
412
+ }
413
+ });
414
+
415
+ httpServer.listen(port, () => {
416
+ debugLog(`[ctxce] HTTP MCP bridge listening on port ${port}`);
417
+ });
418
+ }
419
+
267
420
  function loadConfig(startDir) {
268
421
  try {
269
422
  let dir = startDir;
@@ -295,23 +448,6 @@ function loadConfig(startDir) {
295
448
  return null;
296
449
  }
297
450
 
298
- function buildPingTool() {
299
- return {
300
- name: "ping",
301
- description: "Basic ping tool exposed by the ctx bridge",
302
- inputSchema: {
303
- type: "object",
304
- properties: {
305
- text: {
306
- type: "string",
307
- description: "Optional text to echo back.",
308
- },
309
- },
310
- required: [],
311
- },
312
- };
313
- }
314
-
315
451
  function detectGitBranch(workspace) {
316
452
  try {
317
453
  const out = execSync("git rev-parse --abbrev-ref HEAD", {