@context-engine-bridge/context-engine-mcp-bridge 0.0.17 → 0.0.19

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,11 @@
1
+ {
2
+ "enableAllProjectMcpServers": true,
3
+ "enabledMcpjsonServers": [
4
+ "context-engine"
5
+ ],
6
+ "permissions": {
7
+ "allow": [
8
+ "mcp__context-engine__repo_search"
9
+ ]
10
+ }
11
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "active": true,
3
+ "started_at": "2026-01-25T14:25:53.146Z",
4
+ "original_prompt": "/oh-my-claude-sisyphus:ultrawork we need to figure out, research context engine a bit.... how to make our agent tools better via mcp... (the claude.example.md is\n clear) but maybe we need an agents.md, we have claude skills... can we make the descriptors better? research alot fo other\n tools and see",
5
+ "reinforcement_count": 5,
6
+ "last_checked_at": "2026-01-25T14:43:28.449Z"
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@context-engine-bridge/context-engine-mcp-bridge",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Context Engine MCP bridge (http/stdio proxy combining indexer + memory servers)",
5
5
  "bin": {
6
6
  "ctxce": "bin/ctxce.js",
@@ -9,12 +9,20 @@
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "node bin/ctxce.js",
12
- "postinstall": "node -e \"try{require('fs').chmodSync('bin/ctxce.js',0o755)}catch(e){}\""
12
+ "postinstall": "node -e \"try{require('fs').chmodSync('bin/ctxce.js',0o755)}catch(e){}\"",
13
+ "test:e2e": "playwright test",
14
+ "test:e2e:auth": "playwright test --project=auth-enforcement",
15
+ "test:e2e:happy": "playwright test --project=happy-paths-chromium",
16
+ "test:e2e:edge": "playwright test --project=edge-cases",
17
+ "test:e2e:ui": "playwright test --ui"
13
18
  },
14
19
  "dependencies": {
15
20
  "@modelcontextprotocol/sdk": "^1.24.3",
16
21
  "zod": "^3.25.0"
17
22
  },
23
+ "devDependencies": {
24
+ "@playwright/test": "^1.57.0"
25
+ },
18
26
  "publishConfig": {
19
27
  "access": "public"
20
28
  },
package/src/mcpServer.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import process from "node:process";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import { randomUUID } from "node:crypto";
4
5
  import { execSync } from "node:child_process";
5
6
  import { createServer } from "node:http";
6
7
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -175,6 +176,36 @@ function isAuthRejectionError(error) {
175
176
  }
176
177
  }
177
178
 
179
+ /**
180
+ * Format an auth rejection error with actionable information for users.
181
+ * Includes the backend URL and a hint to sign in via VS Code command palette.
182
+ */
183
+ function formatAuthRejectionError(originalError, backendUrl) {
184
+ const originalMsg =
185
+ (originalError && typeof originalError.message === "string" && originalError.message) ||
186
+ (typeof originalError === "string" ? originalError : String(originalError || "Unknown auth error"));
187
+
188
+ const serverInfo = backendUrl ? ` (server: ${backendUrl})` : "";
189
+ const hint = "Run 'Context Engine: Sign In' from the VS Code command palette to authenticate.";
190
+
191
+ return `Authentication failed${serverInfo}: ${originalMsg}. ${hint}`;
192
+ }
193
+
194
+ /**
195
+ * Emit a special log line that the VS Code extension can detect to show a notification toast.
196
+ * Format: [ctxce:auth-error] JSON payload
197
+ */
198
+ function emitAuthErrorNotification(backendUrl, originalError) {
199
+ const payload = {
200
+ type: "auth_rejection",
201
+ backend: backendUrl || "unknown",
202
+ message: String(originalError?.message || originalError || "Authentication failed"),
203
+ hint: "Run 'Context Engine: Sign In' from the VS Code command palette",
204
+ };
205
+ // This special prefix allows the VS Code extension to detect auth errors in stderr
206
+ debugLog(`[ctxce:auth-error] ${JSON.stringify(payload)}`);
207
+ }
208
+
178
209
  function getBridgeRetryAttempts() {
179
210
  try {
180
211
  const raw = process.env.CTXCE_TOOL_RETRY_ATTEMPTS;
@@ -520,8 +551,21 @@ async function createBridgeServer(options) {
520
551
  sessionId = resolveSessionId();
521
552
  }
522
553
 
554
+ // Only fall back to deterministic session if auth is not configured
555
+ // If auth backend is configured but no session found, log warning instead of creating deterministic session
523
556
  if (!sessionId) {
524
- sessionId = `ctxce-${Buffer.from(workspace).toString("hex").slice(0, 24)}`;
557
+ if (authBackendUrl) {
558
+ // Auth is configured but no valid session - don't use deterministic fallback
559
+ debugLog(`[ctxce] WARNING: Auth backend configured (${authBackendUrl}) but no valid session found.`);
560
+ debugLog("[ctxce] To authenticate, run 'Context Engine: Sign In' from the VS Code command palette, or run `ctxce auth login` from the terminal.");
561
+ debugLog("[ctxce] Continuing with deterministic session for backward compatibility, but this may fail if backend requires auth.");
562
+ // Emit notification for VS Code extension
563
+ emitAuthErrorNotification(authBackendUrl, { message: "No valid session found - authentication required" });
564
+ sessionId = `ctxce-${Buffer.from(workspace).toString("hex").slice(0, 24)}`;
565
+ } else {
566
+ // No auth configured - use deterministic session for local-only operation
567
+ sessionId = `ctxce-${Buffer.from(workspace).toString("hex").slice(0, 24)}`;
568
+ }
525
569
  }
526
570
 
527
571
  // Best-effort: inform the indexer of default collection and session.
@@ -531,6 +575,17 @@ async function createBridgeServer(options) {
531
575
  defaultsPayload.collection = defaultCollection;
532
576
  }
533
577
 
578
+ // Include org context from auth entry if available (for org-scoped collection isolation)
579
+ try {
580
+ const authEntry = backendHint ? loadAuthEntry(backendHint) : null;
581
+ if (authEntry && authEntry.org_id) {
582
+ defaultsPayload.org_id = authEntry.org_id;
583
+ defaultsPayload.org_slug = authEntry.org_slug;
584
+ }
585
+ } catch {
586
+ // ignore auth entry lookup failures
587
+ }
588
+
534
589
  const repoName = detectRepoName(workspace, config);
535
590
 
536
591
  try {
@@ -653,7 +708,7 @@ async function createBridgeServer(options) {
653
708
 
654
709
  await initializeRemoteClients(false);
655
710
 
656
- const server = new Server( // TODO: marked as depreciated
711
+ const server = new Server(
657
712
  {
658
713
  name: "ctx-context-engine-bridge",
659
714
  version: "0.0.1",
@@ -782,10 +837,13 @@ async function createBridgeServer(options) {
782
837
 
783
838
  // Backend auth rejection (mcp_auth.py ValidationError) - expire local auth
784
839
  if (isAuthRejectionError(err)) {
840
+ const serverUrl = backendHint || uploadServiceUrl || "unknown server";
785
841
  debugLog(
786
- "[ctxce] tools/call: backend auth rejection; marking local session as expired: " +
842
+ `[ctxce] tools/call: backend auth rejection from ${serverUrl}; marking local session as expired: ` +
787
843
  String(err),
788
844
  );
845
+ // Emit special notification for VS Code extension to detect and show toast
846
+ emitAuthErrorNotification(serverUrl, err);
789
847
  if (backendHint) {
790
848
  try {
791
849
  const entry = loadAuthEntry(backendHint);
@@ -796,6 +854,13 @@ async function createBridgeServer(options) {
796
854
  // ignore failures
797
855
  }
798
856
  }
857
+ // Enhance error with actionable message before throwing
858
+ if (!isTransientToolError(err) || attempt === maxAttempts - 1) {
859
+ const enhancedMessage = formatAuthRejectionError(err, serverUrl);
860
+ const enhancedError = new Error(enhancedMessage);
861
+ enhancedError.cause = err;
862
+ throw enhancedError;
863
+ }
799
864
  }
800
865
 
801
866
  if (!isTransientToolError(err) || attempt === maxAttempts - 1) {
@@ -844,17 +909,73 @@ export async function runMcpServer(options) {
844
909
  }
845
910
 
846
911
  export async function runHttpMcpServer(options) {
847
- const server = await createBridgeServer(options);
912
+ // Multi-client HTTP mode: each MCP client (Claude, Codex, Kiro, Windsurf,
913
+ // Augment, etc.) gets its own session with a dedicated Server+Transport
914
+ // pair. Sessions are tracked by the Mcp-Session-Id header. This allows
915
+ // multiple IDEs to share the same bridge simultaneously.
916
+ //
917
+ // Flow:
918
+ // 1. Client sends POST with initialize request (no session ID)
919
+ // 2. Bridge creates a new Server+Transport, generates a session ID
920
+ // 3. Response includes Mcp-Session-Id header
921
+ // 4. Client includes Mcp-Session-Id on all subsequent requests
922
+ // 5. Bridge routes to the correct Server+Transport for that session
923
+ //
924
+ // Each session has its own upstream connections (indexer, memory) created
925
+ // by createBridgeServer. Sessions are cleaned up after 30 min of inactivity.
926
+
848
927
  const port =
849
928
  typeof options.port === "number"
850
929
  ? options.port
851
930
  : Number.parseInt(process.env.CTXCE_HTTP_PORT || "30810", 10) || 30810;
852
931
 
853
- const transport = new StreamableHTTPServerTransport({
854
- sessionIdGenerator: undefined,
855
- });
932
+ // ──────────────────────────────────────────────────────────────────────
933
+ // Session management
934
+ // ──────────────────────────────────────────────────────────────────────
935
+ const sessions = new Map(); // sessionId → { server, transport, lastUsed }
936
+ const SESSION_TTL_MS = 30 * 60 * 1000; // 30 minutes inactive
937
+ const MAX_SESSIONS = 20;
938
+
939
+ // Periodic cleanup of stale sessions
940
+ const cleanupInterval = setInterval(() => {
941
+ const now = Date.now();
942
+ for (const [sid, session] of sessions) {
943
+ if (now - session.lastUsed > SESSION_TTL_MS) {
944
+ debugLog(`[ctxce] Cleaning up stale MCP session: ${sid}`);
945
+ session.transport.close().catch(() => {});
946
+ session.server.close().catch(() => {});
947
+ sessions.delete(sid);
948
+ }
949
+ }
950
+ }, 60_000);
951
+ cleanupInterval.unref();
952
+
953
+ function evictOldestSession() {
954
+ if (sessions.size < MAX_SESSIONS) return;
955
+ let oldest = null;
956
+ let oldestTime = Infinity;
957
+ for (const [sid, s] of sessions) {
958
+ if (s.lastUsed < oldestTime) {
959
+ oldest = sid;
960
+ oldestTime = s.lastUsed;
961
+ }
962
+ }
963
+ if (oldest) {
964
+ const s = sessions.get(oldest);
965
+ sessions.delete(oldest);
966
+ s.transport.close().catch(() => {});
967
+ s.server.close().catch(() => {});
968
+ debugLog(`[ctxce] Evicted oldest MCP session: ${oldest}`);
969
+ }
970
+ }
856
971
 
857
- await server.connect(transport);
972
+ // Pre-warm: validate upstream connectivity (best-effort)
973
+ try {
974
+ await createBridgeServer(options);
975
+ debugLog("[ctxce] HTTP bridge validated upstream connectivity");
976
+ } catch (err) {
977
+ debugLog("[ctxce] HTTP bridge warmup failed (non-fatal): " + String(err));
978
+ }
858
979
 
859
980
  // Build issuer URL for OAuth
860
981
  // Note: Local-only bridge uses 127.0.0.1. For remote access, this would need to be configurable.
@@ -905,24 +1026,57 @@ export async function runHttpMcpServer(options) {
905
1026
  // MCP Endpoint
906
1027
  // ================================================================
907
1028
 
908
- // Check Bearer token for MCP endpoint (accept /mcp and /mcp/ for compatibility)
1029
+ // Accept /mcp and /mcp/ for compatibility
909
1030
  if (parsedUrl.pathname === "/mcp" || parsedUrl.pathname === "/mcp/") {
1031
+ // ── Bearer token auth (permissive: validate if provided, don't require) ──
910
1032
  const authHeader = req.headers["authorization"] || "";
911
- const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : null;
1033
+ const bearerToken = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : null;
1034
+ if (bearerToken && oauthHandler.hasTokenStore()) {
1035
+ const oauthSessionId = oauthHandler.lookupToken(bearerToken);
1036
+ if (!oauthSessionId) {
1037
+ res.writeHead(401, { "Content-Type": "application/json" });
1038
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "Invalid or expired bearer token" }, id: null }));
1039
+ return;
1040
+ }
1041
+ }
1042
+
1043
+ const mcpSessionId = typeof req.headers["mcp-session-id"] === "string"
1044
+ ? req.headers["mcp-session-id"]
1045
+ : undefined;
1046
+
1047
+ // ── GET: SSE streaming for existing session ──
1048
+ if (req.method === "GET") {
1049
+ if (mcpSessionId && sessions.has(mcpSessionId)) {
1050
+ const session = sessions.get(mcpSessionId);
1051
+ session.lastUsed = Date.now();
1052
+ session.transport.handleRequest(req, res).catch((err) => {
1053
+ debugLog("[ctxce] SSE stream error: " + String(err));
1054
+ });
1055
+ } else {
1056
+ res.writeHead(400, { "Content-Type": "application/json" });
1057
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request: No valid session for SSE" }, id: null }));
1058
+ }
1059
+ return;
1060
+ }
912
1061
 
913
- // TODO: Validate token and inject session
914
- // For now, allow unauthenticated (backward compatible)
1062
+ // ── DELETE: terminate session ──
1063
+ if (req.method === "DELETE") {
1064
+ if (mcpSessionId && sessions.has(mcpSessionId)) {
1065
+ const session = sessions.get(mcpSessionId);
1066
+ sessions.delete(mcpSessionId);
1067
+ session.transport.close().catch(() => {});
1068
+ session.server.close().catch(() => {});
1069
+ debugLog(`[ctxce] Session terminated by client: ${mcpSessionId}`);
1070
+ }
1071
+ res.writeHead(200).end();
1072
+ return;
1073
+ }
915
1074
 
1075
+ // ── POST: MCP JSON-RPC requests ──
916
1076
  if (req.method !== "POST") {
917
1077
  res.statusCode = 405;
918
1078
  res.setHeader("Content-Type", "application/json");
919
- res.end(
920
- JSON.stringify({
921
- jsonrpc: "2.0",
922
- error: { code: -32000, message: "Method not allowed" },
923
- id: null,
924
- }),
925
- );
1079
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "Method not allowed" }, id: null }));
926
1080
  return;
927
1081
  }
928
1082
 
@@ -937,13 +1091,7 @@ export async function runHttpMcpServer(options) {
937
1091
  req.destroy();
938
1092
  res.statusCode = 413;
939
1093
  res.setHeader("Content-Type", "application/json");
940
- res.end(
941
- JSON.stringify({
942
- jsonrpc: "2.0",
943
- error: { code: -32000, message: "Request body too large" },
944
- id: null,
945
- }),
946
- );
1094
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "Request body too large" }, id: null }));
947
1095
  }
948
1096
  });
949
1097
  req.on("end", async () => {
@@ -955,30 +1103,50 @@ export async function runHttpMcpServer(options) {
955
1103
  debugLog("[ctxce] Failed to parse HTTP MCP request body: " + String(err));
956
1104
  res.statusCode = 400;
957
1105
  res.setHeader("Content-Type", "application/json");
958
- res.end(
959
- JSON.stringify({
960
- jsonrpc: "2.0",
961
- error: { code: -32700, message: "Invalid JSON" },
962
- id: null,
963
- }),
964
- );
1106
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32700, message: "Invalid JSON" }, id: null }));
965
1107
  return;
966
1108
  }
967
1109
 
968
1110
  try {
1111
+ // ── Route to existing session ──
1112
+ if (mcpSessionId && sessions.has(mcpSessionId)) {
1113
+ const session = sessions.get(mcpSessionId);
1114
+ session.lastUsed = Date.now();
1115
+ await session.transport.handleRequest(req, res, parsed);
1116
+ return;
1117
+ }
1118
+
1119
+ // ── Unknown session ID (expired or invalid) ──
1120
+ if (mcpSessionId) {
1121
+ res.writeHead(404, { "Content-Type": "application/json" });
1122
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "Session not found. Client must re-initialize." }, id: null }));
1123
+ return;
1124
+ }
1125
+
1126
+ // ── New session (no session ID = first request / initialize) ──
1127
+ evictOldestSession();
1128
+
1129
+ const server = await createBridgeServer(options);
1130
+ const transport = new StreamableHTTPServerTransport({
1131
+ sessionIdGenerator: () => randomUUID(),
1132
+ onsessioninitialized: (newSessionId) => {
1133
+ debugLog(`[ctxce] New MCP session: ${newSessionId} (active: ${sessions.size + 1})`);
1134
+ sessions.set(newSessionId, { server, transport, lastUsed: Date.now() });
1135
+ },
1136
+ onsessionclosed: (closedSessionId) => {
1137
+ debugLog(`[ctxce] MCP session closed by transport: ${closedSessionId}`);
1138
+ sessions.delete(closedSessionId);
1139
+ server.close().catch(() => {});
1140
+ },
1141
+ });
1142
+ await server.connect(transport);
969
1143
  await transport.handleRequest(req, res, parsed);
970
1144
  } catch (err) {
971
1145
  debugLog("[ctxce] Error handling HTTP MCP request: " + String(err));
972
1146
  if (!res.headersSent) {
973
1147
  res.statusCode = 500;
974
1148
  res.setHeader("Content-Type", "application/json");
975
- res.end(
976
- JSON.stringify({
977
- jsonrpc: "2.0",
978
- error: { code: -32603, message: "Internal server error" },
979
- id: null,
980
- }),
981
- );
1149
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null }));
982
1150
  }
983
1151
  }
984
1152
  });
@@ -988,39 +1156,34 @@ export async function runHttpMcpServer(options) {
988
1156
  // 404 for everything else
989
1157
  res.statusCode = 404;
990
1158
  res.setHeader("Content-Type", "application/json");
991
- res.end(
992
- JSON.stringify({
993
- jsonrpc: "2.0",
994
- error: { code: -32000, message: "Not found" },
995
- id: null,
996
- }),
997
- );
1159
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "Not found" }, id: null }));
998
1160
  } catch (err) {
999
1161
  debugLog("[ctxce] Unexpected error in HTTP MCP server: " + String(err));
1000
1162
  if (!res.headersSent) {
1001
1163
  res.statusCode = 500;
1002
1164
  res.setHeader("Content-Type", "application/json");
1003
- res.end(
1004
- JSON.stringify({
1005
- jsonrpc: "2.0",
1006
- error: { code: -32603, message: "Internal server error" },
1007
- id: null,
1008
- }),
1009
- );
1165
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null }));
1010
1166
  }
1011
1167
  }
1012
1168
  });
1013
1169
 
1014
1170
  // Bind to 127.0.0.1 only (localhost) for local-only OAuth security
1015
1171
  httpServer.listen(port, '127.0.0.1', () => {
1016
- debugLog(`[ctxce] HTTP MCP bridge listening on 127.0.0.1:${port}`);
1172
+ debugLog(`[ctxce] HTTP MCP bridge listening on 127.0.0.1:${port} (multi-session enabled)`);
1017
1173
  });
1018
1174
 
1019
1175
  let shuttingDown = false;
1020
1176
  const shutdown = (signal) => {
1021
1177
  if (shuttingDown) return;
1022
1178
  shuttingDown = true;
1023
- debugLog(`[ctxce] Received ${signal}; closing HTTP server (waiting for in-flight requests).`);
1179
+ debugLog(`[ctxce] Received ${signal}; closing HTTP server and ${sessions.size} active session(s).`);
1180
+ clearInterval(cleanupInterval);
1181
+ // Close all active sessions
1182
+ for (const [, session] of sessions) {
1183
+ session.transport.close().catch(() => {});
1184
+ session.server.close().catch(() => {});
1185
+ }
1186
+ sessions.clear();
1024
1187
  httpServer.close(() => {
1025
1188
  debugLog("[ctxce] HTTP server closed.");
1026
1189
  process.exit(0);
@@ -534,7 +534,7 @@ export function handleOAuthStoreSession(req, res) {
534
534
  export function handleOAuthToken(req, res) {
535
535
  let body = "";
536
536
  req.on("data", (chunk) => { body += chunk; });
537
- req.on("end", () => {
537
+ req.on("end", async () => {
538
538
  try {
539
539
  const data = new URLSearchParams(body);
540
540
  const code = data.get("code");
@@ -585,8 +585,24 @@ export function handleOAuthToken(req, res) {
585
585
  return;
586
586
  }
587
587
 
588
- // TODO: Validate PKCE code_verifier against code_challenge
589
- // For now, skip validation (local bridge, trusted)
588
+ // TODO: PKCE validation - disabled for now, no clients implement it yet
589
+ // if (pendingData.codeChallenge && pendingData.codeChallengeMethod === "S256") {
590
+ // const codeVerifier = data.get("code_verifier");
591
+ // if (!codeVerifier) {
592
+ // pendingCodes.delete(code);
593
+ // res.statusCode = 400;
594
+ // res.end(JSON.stringify({ error: "invalid_grant", error_description: "code_verifier required for PKCE" }));
595
+ // return;
596
+ // }
597
+ // const crypto = await import("node:crypto");
598
+ // const expectedChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
599
+ // if (expectedChallenge !== pendingData.codeChallenge) {
600
+ // pendingCodes.delete(code);
601
+ // res.statusCode = 400;
602
+ // res.end(JSON.stringify({ error: "invalid_grant", error_description: "code_verifier validation failed" }));
603
+ // return;
604
+ // }
605
+ // }
590
606
 
591
607
  // Clean up expired tokens periodically to prevent unbounded growth
592
608
  cleanupExpiredTokens();
@@ -651,4 +667,31 @@ export function isOAuthEndpoint(pathname) {
651
667
  );
652
668
  }
653
669
 
670
+ /**
671
+ * Check if the token store has any entries (indicates auth is active)
672
+ * @returns {boolean}
673
+ */
674
+ export function hasTokenStore() {
675
+ return tokenStore.size > 0;
676
+ }
677
+
678
+ /**
679
+ * Look up a bearer token and return the associated session ID
680
+ * @param {string} token - Bearer token to validate
681
+ * @returns {string|null} - Session ID if valid, null otherwise
682
+ */
683
+ export function lookupToken(token) {
684
+ const entry = tokenStore.get(token);
685
+ if (!entry) return null;
686
+
687
+ // Check expiration
688
+ const tokenAge = Date.now() - entry.createdAt;
689
+ if (tokenAge > TOKEN_EXPIRY_MS) {
690
+ tokenStore.delete(token);
691
+ return null;
692
+ }
693
+
694
+ return entry.sessionId || null;
695
+ }
696
+
654
697
  startCleanupInterval();
package/AGENTS.md DELETED
@@ -1,18 +0,0 @@
1
- <!-- Parent: ../AGENTS.md -->
2
- # MCP Bridge CLI (ctxce)
3
-
4
- Node package that exposes indexer+memory as a single MCP server (stdio + HTTP). Optional OAuth/PKCE.
5
-
6
- ## WHERE TO LOOK
7
-
8
- | Task | Location | Notes |
9
- |------|----------|-------|
10
- | CLI entry | `ctx-mcp-bridge/bin/ctxce.js` | wrapper -> src/cli |
11
- | CLI logic | `ctx-mcp-bridge/src/cli.js` | command parsing |
12
- | MCP proxy | `ctx-mcp-bridge/src/mcpServer.js` | forwards tools |
13
- | OAuth/PKCE | `ctx-mcp-bridge/src/oauthHandler.js` | auth flow |
14
- | Path mapping | `ctx-mcp-bridge/src/resultPathMapping.js` | local <-> remote |
15
-
16
- ## KNOWN TODO
17
-
18
- - PKCE verifier validation is still TODO in `ctx-mcp-bridge/src/oauthHandler.js`.