@context-engine-bridge/context-engine-mcp-bridge 0.0.28 → 0.0.29

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,6 @@
1
+ {
2
+ "timestamp": "2026-02-20T02:56:43.457Z",
3
+ "backgroundTasks": [],
4
+ "sessionStartTimestamp": "2026-02-20T02:54:38.180Z",
5
+ "sessionId": "391fa45d902019af"
6
+ }
package/AGENTS.md ADDED
@@ -0,0 +1,69 @@
1
+ <!-- Parent: ../AGENTS.md -->
2
+ <!-- Generated: 2026-02-19 | Updated: 2026-02-19 -->
3
+
4
+ # ctx-mcp-bridge
5
+
6
+ ## Purpose
7
+
8
+ The MCP bridge (`ctxce` CLI) is a Model Context Protocol server that aggregates the Context Engine indexer and memory servers into a single unified MCP server. It supports both stdio and HTTP transport modes, making it compatible with MCP clients like Claude Code, Windsurf, Augment, and others. The bridge is primarily launched by the VS Code extension but can run standalone.
9
+
10
+ ## Key Files
11
+
12
+ | File | Description |
13
+ |------|-------------|
14
+ | `bin/ctxce.js` | CLI entry point and executable (chmod +x 755) |
15
+ | `src/cli.js` | Command routing and argument parsing for `mcp-serve`, `mcp-http-serve`, `auth`, `connect` |
16
+ | `src/mcpServer.js` | MCP server implementation with stdio/HTTP transport, tool deduping, and auth handling |
17
+ | `src/authCli.js` | Auth command handlers: `login`, `logout`, `status` with token and password flows |
18
+ | `src/authConfig.js` | Session storage and management in `~/.ctxce/auth.json` |
19
+ | `src/oauthHandler.js` | OAuth protocol support for remote deployments |
20
+ | `src/uploader.js` | Standalone code uploader integration |
21
+ | `src/connectCli.js` | Connection validation and setup helpers |
22
+ | `src/resultPathMapping.js` | Path remapping for tool results (container/host paths) |
23
+ | `package.json` | Node.js package manifest (requires Node >= 18) |
24
+
25
+ ## Subdirectories
26
+
27
+ | Directory | Purpose |
28
+ |-----------|---------|
29
+ | `bin/` | Executable CLI entry point |
30
+ | `docs/` | Debugging guides and documentation |
31
+ | `src/` | Core MCP server and auth logic (see `src/AGENTS.md`) |
32
+
33
+ ## For AI Agents
34
+
35
+ ### Working In This Directory
36
+
37
+ This is a Node.js MCP bridge package. Changes to MCP routing, tool forwarding, or auth handling require updates to `src/cli.js` and `src/mcpServer.js`. The bridge proxies requests between MCP clients and remote indexer/memory HTTP servers, so test both stdio and HTTP modes.
38
+
39
+ ### Testing Requirements
40
+
41
+ - Run with `npm start` or `node bin/ctxce.js --help`
42
+ - Test MCP stdio mode: `ctxce mcp-serve --workspace /tmp/test`
43
+ - Test MCP HTTP mode: `ctxce mcp-http-serve --workspace /tmp/test --port 30810`
44
+ - Test auth commands: `ctxce auth login --backend-url http://localhost:8004 --token TEST_TOKEN`
45
+ - Verify auth state: `ctxce auth status --backend-url http://localhost:8004 --json`
46
+ - E2E tests: `npm run test:e2e`
47
+
48
+ ### Common Patterns
49
+
50
+ - Environment variables: `CTXCE_INDEXER_URL`, `CTXCE_MEMORY_URL`, `CTXCE_HTTP_PORT`, `CTXCE_AUTH_*`
51
+ - Auth sessions stored in `~/.ctxce/auth.json` keyed by backend URL
52
+ - MCP tools are deduplicated and forwarded from indexer and memory servers
53
+ - Path remapping (host paths <-> container paths) handled transparently
54
+ - All MCP requests are logged to stderr or `CTXCE_DEBUG_LOG` if set
55
+
56
+ ## Dependencies
57
+
58
+ ### Internal
59
+ - Context Engine indexer (HTTP endpoint at `CTXCE_INDEXER_URL` or `http://localhost:8003/mcp`)
60
+ - Context Engine memory server (HTTP endpoint at `CTXCE_MEMORY_URL` or `http://localhost:8002/mcp`)
61
+ - Auth backend (optional, at `CTXCE_AUTH_BACKEND_URL` or `http://localhost:8004`)
62
+
63
+ ### External
64
+ - `@modelcontextprotocol/sdk` (^1.24.3) – MCP protocol implementation
65
+ - `zod` (^3.25.0) – Runtime type validation
66
+ - `tar` (^7.5.9) – Archive support for uploads
67
+ - `ignore` (^7.0.5) – .gitignore-style file filtering
68
+
69
+ <!-- MANUAL: Any manually added notes below this line are preserved on regeneration -->
package/bin/AGENTS.md ADDED
@@ -0,0 +1,34 @@
1
+ <!-- Parent: ../AGENTS.md -->
2
+ <!-- Generated: 2026-02-19 | Updated: 2026-02-19 -->
3
+
4
+ # bin
5
+
6
+ ## Purpose
7
+
8
+ Executable CLI entry point for the `ctxce` command (also aliased as `ctxce-bridge`). This directory contains the shebang-wrapped Node.js script that is installed globally or via npm when the package is installed.
9
+
10
+ ## Key Files
11
+
12
+ | File | Description |
13
+ |------|-------------|
14
+ | `ctxce.js` | CLI executable entry point (Node.js script, chmod +x 755) |
15
+
16
+ ## For AI Agents
17
+
18
+ ### Working In This Directory
19
+
20
+ Do not modify `ctxce.js` directly unless changing the CLI bootstrap. The actual CLI logic is in `src/cli.js`. The executable must have a shebang (`#!/usr/bin/env node`) and be marked executable on Unix systems.
21
+
22
+ ### Testing Requirements
23
+
24
+ - Verify executable permission: `ls -l bin/ctxce.js` should show `-rwxr-xr-x`
25
+ - Test global install: `npm install -g` and run `ctxce --help`
26
+ - Test npx mode: `npx @context-engine-bridge/context-engine-mcp-bridge ctxce --help`
27
+ - Postinstall script auto-fixes permissions on non-Windows systems
28
+
29
+ ## Dependencies
30
+
31
+ ### Internal
32
+ - `src/cli.js` – Main CLI router and handler
33
+
34
+ <!-- MANUAL: Any manually added notes below this line are preserved on regeneration -->
package/docs/AGENTS.md ADDED
@@ -0,0 +1,22 @@
1
+ <!-- Parent: ../AGENTS.md -->
2
+ <!-- Generated: 2026-02-19 | Updated: 2026-02-19 -->
3
+
4
+ # docs
5
+
6
+ ## Purpose
7
+
8
+ Debugging guides and developer documentation for the MCP bridge. Currently contains minimal documentation; most usage is covered in the main `README.md`.
9
+
10
+ ## Key Files
11
+
12
+ | File | Description |
13
+ |------|-------------|
14
+ | `debugging.md` | Debug logging and troubleshooting tips |
15
+
16
+ ## For AI Agents
17
+
18
+ ### Working In This Directory
19
+
20
+ This directory is for developer guides, not API documentation (which belongs in the main README). When adding debugging tips or advanced usage patterns, place them here.
21
+
22
+ <!-- MANUAL: Any manually added notes below this line are preserved on regeneration -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@context-engine-bridge/context-engine-mcp-bridge",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "description": "Context Engine MCP bridge (http/stdio proxy combining indexer + memory servers)",
5
5
  "bin": {
6
6
  "ctxce": "bin/ctxce.js",
package/src/AGENTS.md ADDED
@@ -0,0 +1,59 @@
1
+ <!-- Parent: ../AGENTS.md -->
2
+ <!-- Generated: 2026-02-19 | Updated: 2026-02-19 -->
3
+
4
+ # src
5
+
6
+ ## Purpose
7
+
8
+ Core MCP server implementation, auth handling, and CLI routing for the `ctxce` bridge. This module aggregates the Context Engine indexer and memory servers, manages authentication sessions, and handles both stdio and HTTP transport modes.
9
+
10
+ ## Key Files
11
+
12
+ | File | Description |
13
+ |------|-------------|
14
+ | `cli.js` | Main command router for `mcp-serve`, `mcp-http-serve`, `auth`, `connect` subcommands; parses CLI flags and environment variables |
15
+ | `mcpServer.js` | MCP server implementation; proxies requests to indexer/memory servers, dedupes tools, handles auth, supports stdio and HTTP transports |
16
+ | `authCli.js` | Auth command handlers for `login`, `logout`, `status` with token and password flows; manages session lifecycle |
17
+ | `authConfig.js` | Persistent auth state in `~/.ctxce/auth.json`; session loading, saving, TTL handling, OAuth support |
18
+ | `oauthHandler.js` | OAuth protocol implementation for token refresh and remote auth flows |
19
+ | `uploader.js` | Integration with the standalone code uploader (tar archive support, progress tracking) |
20
+ | `connectCli.js` | Connection validation, workspace discovery, and setup helpers |
21
+ | `resultPathMapping.js` | Path remapping for tool results (host paths <-> container paths); handles path translation for Docker environments |
22
+
23
+ ## For AI Agents
24
+
25
+ ### Working In This Directory
26
+
27
+ This is the core business logic. Changes to MCP command handling, tool routing, or auth flows affect both the CLI and the VS Code extension. The server proxies all tool requests to remote indexer/memory servers and dedupes tool lists to prevent duplicates. Auth is optional but handles session TTL, token refresh, and fallback to dev tokens.
28
+
29
+ ### Testing Requirements
30
+
31
+ - Test MCP stdio transport: `node src/cli.js mcp-serve --workspace /tmp/test`
32
+ - Test MCP HTTP transport: `node src/cli.js mcp-http-serve --workspace /tmp/test --port 30810`
33
+ - Test auth login: `node src/cli.js auth login --backend-url http://localhost:8004 --token TOKEN`
34
+ - Test auth status: `node src/cli.js auth status --backend-url http://localhost:8004 --json`
35
+ - Verify tool deduping: Check that tools from indexer and memory are merged without duplicates
36
+ - Test path remapping: Verify that container paths are correctly mapped for Docker environments
37
+ - Run E2E tests: `npm run test:e2e`, `npm run test:e2e:auth`, `npm run test:e2e:happy`, `npm run test:e2e:edge`
38
+
39
+ ### Common Patterns
40
+
41
+ - Environment variables guide server initialization: `CTXCE_INDEXER_URL`, `CTXCE_MEMORY_URL`, `CTXCE_HTTP_PORT`, `CTXCE_AUTH_BACKEND_URL`, `CTXCE_AUTH_ENABLED`, `CTXCE_DEBUG_LOG`
42
+ - Sessions are stored per backend URL in `~/.ctxce/auth.json` with TTL tracking
43
+ - All MCP tools from indexer and memory are merged and deduplicated before listing
44
+ - Path remapping is applied to tool arguments and results for Docker host/container path translation
45
+ - Debug logging goes to stderr and optionally to a file (set `CTXCE_DEBUG_LOG` env var)
46
+ - HTTP transport uses Node.js `createServer` with MCP's `StreamableHTTPServerTransport`
47
+
48
+ ## Dependencies
49
+
50
+ ### Internal
51
+ - `bin/ctxce.js` – CLI entry point that imports and runs these modules
52
+
53
+ ### External
54
+ - `@modelcontextprotocol/sdk` – MCP protocol (Server, StdioServerTransport, StreamableHTTPServerTransport, Client, StreamableHTTPClientTransport)
55
+ - `zod` – Runtime type validation (used in config parsing)
56
+ - `tar` – Archive handling for uploader
57
+ - `ignore` – File filtering for .gitignore patterns
58
+
59
+ <!-- MANUAL: Any manually added notes below this line are preserved on regeneration -->
package/src/connectCli.js CHANGED
@@ -2,7 +2,7 @@ import process from "node:process";
2
2
  import path from "node:path";
3
3
  import fs from "node:fs";
4
4
  import { saveAuthEntry } from "./authConfig.js";
5
- import { indexWorkspace } from "./uploader.js";
5
+ import { indexWorkspace, loadGitignore, isCodeFile } from "./uploader.js";
6
6
 
7
7
  const SAAS_ENDPOINTS = {
8
8
  uploadEndpoint: "https://dev.context-engine.ai/upload",
@@ -160,12 +160,35 @@ async function triggerIndexing(workspace, sessionId, authEntry) {
160
160
  return result.success;
161
161
  }
162
162
 
163
- function startWatcher(workspace, sessionId, authEntry, intervalMs) {
163
+ function startWatcher(workspace, initialSessionId, authEntry, intervalMs) {
164
164
  console.error(`[ctxce] Starting file watcher (sync every ${intervalMs / 1000}s)...`);
165
165
  console.error("[ctxce] Press Ctrl+C to stop.");
166
166
 
167
167
  let isRunning = false;
168
168
  let pendingSync = false;
169
+ let sessionId = initialSessionId;
170
+
171
+ async function refreshSessionIfNeeded() {
172
+ // If the auth entry has an expiry and we're within 5 minutes of it,
173
+ // re-authenticate using the stored API key.
174
+ if (!authEntry || !authEntry.apiKey) return;
175
+ const expiresAt = authEntry.expiresAt;
176
+ if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt) || expiresAt <= 0) return;
177
+ const nowSecs = Math.floor(Date.now() / 1000);
178
+ const remainingSecs = expiresAt - nowSecs;
179
+ if (remainingSecs > 300) return; // still valid for > 5 min
180
+ console.error("[ctxce] Session approaching expiry, refreshing...");
181
+ try {
182
+ const refreshed = await authenticateWithApiKey(authEntry.apiKey);
183
+ if (refreshed && refreshed.sessionId) {
184
+ sessionId = refreshed.sessionId;
185
+ authEntry = refreshed;
186
+ console.error("[ctxce] Session refreshed successfully.");
187
+ }
188
+ } catch (err) {
189
+ console.error(`[ctxce] Session refresh failed: ${err}`);
190
+ }
191
+ }
169
192
 
170
193
  const fileHashes = new Map();
171
194
 
@@ -178,26 +201,29 @@ function startWatcher(workspace, sessionId, authEntry, intervalMs) {
178
201
  }
179
202
  }
180
203
 
204
+ // Use gitignore from uploader.js so the watcher ignores the same files as
205
+ // the bundle creator -- prevents redundant uploads for generated/ignored files.
206
+ const _ig = loadGitignore(workspace);
207
+
181
208
  function scanDirectory(dir, files = []) {
182
209
  try {
183
210
  const entries = fs.readdirSync(dir, { withFileTypes: true });
184
211
  for (const entry of entries) {
212
+ if (entry.isSymbolicLink()) continue;
185
213
  const fullPath = path.join(dir, entry.name);
186
-
187
- if (entry.name.startsWith(".") ||
188
- entry.name === "node_modules" ||
189
- entry.name === "__pycache__" ||
190
- entry.name === "venv" ||
191
- entry.name === ".venv" ||
192
- entry.name === "dist" ||
193
- entry.name === "build") {
194
- continue;
195
- }
214
+ // Normalize to forward slashes for the `ignore` library (expects POSIX paths)
215
+ const relPath = path.relative(workspace, fullPath).split(path.sep).join('/');
196
216
 
217
+ // Use the same ignore rules as the bundle creator
218
+ // Directories need a trailing slash for gitignore pattern matching
197
219
  if (entry.isDirectory()) {
220
+ if (_ig.ignores(relPath + '/')) continue;
198
221
  scanDirectory(fullPath, files);
199
222
  } else if (entry.isFile()) {
200
- files.push(fullPath);
223
+ if (_ig.ignores(relPath)) continue;
224
+ if (isCodeFile(fullPath)) {
225
+ files.push(fullPath);
226
+ }
201
227
  }
202
228
  }
203
229
  } catch {
@@ -242,6 +268,9 @@ function startWatcher(workspace, sessionId, authEntry, intervalMs) {
242
268
  console.error(`[ctxce] [${now}] Syncing changes...`);
243
269
 
244
270
  try {
271
+ // Refresh session before upload if approaching expiry
272
+ await refreshSessionIfNeeded();
273
+
245
274
  const result = await indexWorkspace(
246
275
  workspace,
247
276
  SAAS_ENDPOINTS.uploadEndpoint,
package/src/mcpServer.js CHANGED
@@ -501,7 +501,13 @@ async function createBridgeServer(options) {
501
501
  expiresAt > 0 &&
502
502
  expiresAt < Math.floor(Date.now() / 1000)
503
503
  ) {
504
- debugLog("[ctxce] Stored auth session has local expiry in the past; attempting to use it anyway (server will validate).");
504
+ // Allow 5-minute grace period for clock skew; beyond that, reject
505
+ const expiredSecs = Math.floor(Date.now() / 1000) - expiresAt;
506
+ if (expiredSecs > 300) {
507
+ debugLog(`[ctxce] Session expired ${expiredSecs}s ago (beyond 5min grace), triggering re-auth.`);
508
+ return "";
509
+ }
510
+ debugLog("[ctxce] Session expired but within 5min grace period; using it (server will validate).");
505
511
  return entry.sessionId;
506
512
  }
507
513
  return entry.sessionId;
package/src/uploader.js CHANGED
@@ -36,7 +36,7 @@ const DEFAULT_IGNORES = [
36
36
 
37
37
  const MAX_FILE_SIZE = 10 * 1024 * 1024;
38
38
 
39
- function loadGitignore(workspacePath) {
39
+ export function loadGitignore(workspacePath) {
40
40
  const ig = ignore();
41
41
  ig.add(DEFAULT_IGNORES);
42
42
 
@@ -52,7 +52,7 @@ function loadGitignore(workspacePath) {
52
52
  return ig;
53
53
  }
54
54
 
55
- function isCodeFile(filePath) {
55
+ export function isCodeFile(filePath) {
56
56
  const ext = path.extname(filePath).toLowerCase();
57
57
  if (CODE_EXTS.has(ext)) return true;
58
58
 
@@ -224,19 +224,10 @@ export async function createBundle(workspacePath, options = {}) {
224
224
  export async function uploadBundle(bundlePath, manifest, uploadEndpoint, sessionId, options = {}) {
225
225
  const { log = console.error, orgId, orgSlug } = options;
226
226
 
227
- const bundleData = fs.readFileSync(bundlePath);
227
+ const bundleSize = fs.statSync(bundlePath).size;
228
228
  const boundary = `----ctxce${Date.now()}${Math.random().toString(36).slice(2)}`;
229
229
 
230
- const parts = [];
231
-
232
- parts.push(
233
- `--${boundary}\r\n`,
234
- `Content-Disposition: form-data; name="bundle"; filename="bundle.tar.gz"\r\n`,
235
- `Content-Type: application/gzip\r\n\r\n`,
236
- );
237
- parts.push(bundleData);
238
- parts.push(`\r\n`);
239
-
230
+ // Build form fields (small metadata -- kept in memory)
240
231
  const logicalRepoId = computeLogicalRepoId(manifest.workspace_path);
241
232
  const fields = {
242
233
  workspace_path: manifest.workspace_path,
@@ -246,35 +237,67 @@ export async function uploadBundle(bundlePath, manifest, uploadEndpoint, session
246
237
  logical_repo_id: logicalRepoId,
247
238
  session: sessionId,
248
239
  };
249
-
250
240
  if (orgId) fields.org_id = orgId;
251
241
  if (orgSlug) fields.org_slug = orgSlug;
252
242
 
243
+ // Build multipart preamble (file header) and epilogue (fields + close)
244
+ const filePreamble = Buffer.from(
245
+ `--${boundary}\r\n` +
246
+ `Content-Disposition: form-data; name="bundle"; filename="bundle.tar.gz"\r\n` +
247
+ `Content-Type: application/gzip\r\n\r\n`
248
+ );
249
+ const fileEpilogue = Buffer.from(`\r\n`);
250
+
251
+ let fieldsPart = '';
253
252
  for (const [key, value] of Object.entries(fields)) {
254
253
  if (value) {
255
- parts.push(
256
- `--${boundary}\r\n`,
257
- `Content-Disposition: form-data; name="${key}"\r\n\r\n`,
258
- `${value}\r\n`,
259
- );
254
+ fieldsPart += `--${boundary}\r\n` +
255
+ `Content-Disposition: form-data; name="${key}"\r\n\r\n` +
256
+ `${value}\r\n`;
260
257
  }
261
258
  }
262
-
263
- parts.push(`--${boundary}--\r\n`);
264
-
265
- const bodyParts = parts.map(p => typeof p === "string" ? Buffer.from(p) : p);
266
- const body = Buffer.concat(bodyParts);
259
+ fieldsPart += `--${boundary}--\r\n`;
260
+ const fieldsBuffer = Buffer.from(fieldsPart);
261
+
262
+ // Stream the bundle file instead of loading it entirely into memory.
263
+ // This prevents OOM for large repositories (hundreds of MB bundles).
264
+ const totalLength = filePreamble.length + bundleSize + fileEpilogue.length + fieldsBuffer.length;
265
+
266
+ const { Readable } = await import('node:stream');
267
+ const bodyStream = new Readable({
268
+ read() {
269
+ // Push preamble
270
+ this.push(filePreamble);
271
+ // Push file data in chunks via sync read to keep it simple
272
+ const CHUNK = 256 * 1024; // 256KB chunks
273
+ const fd = fs.openSync(bundlePath, 'r');
274
+ try {
275
+ const buf = Buffer.allocUnsafe(CHUNK);
276
+ let bytesRead;
277
+ while ((bytesRead = fs.readSync(fd, buf, 0, CHUNK)) > 0) {
278
+ this.push(bytesRead === CHUNK ? buf : buf.subarray(0, bytesRead));
279
+ }
280
+ } finally {
281
+ fs.closeSync(fd);
282
+ }
283
+ // Push epilogue + fields + close
284
+ this.push(fileEpilogue);
285
+ this.push(fieldsBuffer);
286
+ this.push(null); // EOF
287
+ }
288
+ });
267
289
 
268
290
  const url = `${uploadEndpoint}/api/v1/delta/upload`;
269
- log(`[uploader] Uploading to ${url}...`);
291
+ log(`[uploader] Uploading to ${url} (${(bundleSize / 1024).toFixed(0)}KB bundle, streaming)...`);
270
292
 
271
293
  const resp = await fetch(url, {
272
294
  method: "POST",
273
295
  headers: {
274
296
  "Content-Type": `multipart/form-data; boundary=${boundary}`,
275
- "Content-Length": String(body.length),
297
+ "Content-Length": String(totalLength),
276
298
  },
277
- body,
299
+ body: bodyStream,
300
+ duplex: "half", // required for streaming request bodies in Node.js fetch
278
301
  });
279
302
 
280
303
  let result;
@@ -298,8 +321,23 @@ export async function uploadBundle(bundlePath, manifest, uploadEndpoint, session
298
321
  return { success: true, result };
299
322
  }
300
323
 
324
+ let _indexInFlight = false;
301
325
  export async function indexWorkspace(workspacePath, uploadEndpoint, sessionId, options = {}) {
302
326
  const { log = console.error, orgId, orgSlug } = options;
327
+ if (_indexInFlight) {
328
+ log("[uploader] indexWorkspace already in progress, skipping concurrent call");
329
+ return { success: false, error: "already_in_progress" };
330
+ }
331
+ _indexInFlight = true;
332
+ try {
333
+ return await _indexWorkspaceInner(workspacePath, uploadEndpoint, sessionId, options);
334
+ } finally {
335
+ _indexInFlight = false;
336
+ }
337
+ }
338
+
339
+ async function _indexWorkspaceInner(workspacePath, uploadEndpoint, sessionId, options = {}) {
340
+ const { log = console.error, orgId, orgSlug } = options;
303
341
 
304
342
  log(`[uploader] Scanning workspace: ${workspacePath}`);
305
343
 
@@ -1,11 +0,0 @@
1
- {
2
- "enableAllProjectMcpServers": true,
3
- "enabledMcpjsonServers": [
4
- "context-engine"
5
- ],
6
- "permissions": {
7
- "allow": [
8
- "mcp__context-engine__repo_search"
9
- ]
10
- }
11
- }