@context-engine-bridge/context-engine-mcp-bridge 0.0.8 → 0.0.10

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/README.md CHANGED
@@ -15,7 +15,7 @@ other MCP clients) as long as the Context Engine stack is running.
15
15
  ## Prerequisites
16
16
 
17
17
  - Node.js **>= 18** (see `engines` in `package.json`).
18
- - A running Context Engine stack (e.g. via `docker-compose.dev-remote.yml`) with:
18
+ - A running Context Engine stack (e.g. via `docker-compose.yml`) with:
19
19
  - MCP indexer HTTP endpoint (default: `http://localhost:8003/mcp`).
20
20
  - MCP memory HTTP endpoint (optional, default: `http://localhost:8002/mcp`).
21
21
  - For optional auth:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@context-engine-bridge/context-engine-mcp-bridge",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
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/mcpServer.js CHANGED
@@ -10,7 +10,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
10
10
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
11
11
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
12
12
  import { loadAnyAuthEntry, loadAuthEntry } from "./authConfig.js";
13
- import { maybeRemapToolResult } from "./resultPathMapping.js";
13
+ import { maybeRemapToolArgs, maybeRemapToolResult } from "./resultPathMapping.js";
14
14
 
15
15
  function debugLog(message) {
16
16
  try {
@@ -503,6 +503,8 @@ async function createBridgeServer(options) {
503
503
  args = obj;
504
504
  }
505
505
 
506
+ args = maybeRemapToolArgs(name, args, workspace);
507
+
506
508
  if (name === "set_session_defaults") {
507
509
  const indexerResult = await indexerClient.callTool({ name, arguments: args });
508
510
  if (memoryClient) {
@@ -28,18 +28,170 @@ function _posixToNative(rel) {
28
28
  }
29
29
  }
30
30
 
31
- function computeWorkspaceRelativePath(containerPath, hostPath) {
31
+ function _nativeToPosix(p) {
32
32
  try {
33
- const cont = typeof containerPath === "string" ? containerPath.trim() : "";
34
- if (cont.startsWith("/work/")) {
35
- const rest = cont.slice("/work/".length);
36
- const parts = rest.split("/").filter(Boolean);
37
- if (parts.length >= 2) {
38
- return parts.slice(1).join("/");
33
+ if (!p) {
34
+ return "";
35
+ }
36
+ return String(p).split(path.sep).join("/");
37
+ } catch {
38
+ return p;
39
+ }
40
+ }
41
+
42
+ function _workPathToRepoRelPosix(p) {
43
+ try {
44
+ const s = typeof p === "string" ? p.trim() : "";
45
+ if (!s || !s.startsWith("/work/")) {
46
+ return null;
47
+ }
48
+ const rest = s.slice("/work/".length);
49
+ const parts = rest.split("/").filter(Boolean);
50
+ if (parts.length >= 2) {
51
+ return parts.slice(1).join("/");
52
+ }
53
+ if (parts.length === 1) {
54
+ return parts[0];
55
+ }
56
+ return "";
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ function normalizeToolArgPath(p, workspaceRoot) {
63
+ try {
64
+ const s = typeof p === "string" ? p.trim() : "";
65
+ if (!s) {
66
+ return p;
67
+ }
68
+
69
+ const root = typeof workspaceRoot === "string" ? workspaceRoot : "";
70
+ const sPosix = s.replace(/\\/g, "/");
71
+
72
+ const fromWork = _workPathToRepoRelPosix(sPosix);
73
+ if (typeof fromWork === "string" && fromWork) {
74
+ return fromWork;
75
+ }
76
+ if (fromWork === "") {
77
+ return p;
78
+ }
79
+
80
+ if (root) {
81
+ try {
82
+ const sNorm = s.replace(/\\/g, path.sep);
83
+ const rootNorm = root.replace(/\\/g, path.sep);
84
+ if (sNorm === rootNorm || sNorm.startsWith(rootNorm + path.sep)) {
85
+ const relNative = path.relative(rootNorm, sNorm);
86
+ const relPosix = _nativeToPosix(relNative);
87
+ if (relPosix && relPosix !== "." && relPosix !== ".." && !relPosix.startsWith("../")) {
88
+ return relPosix;
89
+ }
90
+ }
91
+ } catch {
92
+ // ignore
93
+ }
94
+ try {
95
+ const base = path.posix.basename(root.replace(/\\/g, "/"));
96
+ if (base && sPosix.startsWith(base + "/")) {
97
+ const rest = sPosix.slice((base + "/").length);
98
+ if (rest && rest !== "." && rest !== ".." && !rest.startsWith("../")) {
99
+ return rest;
100
+ }
101
+ }
102
+ } catch {
103
+ // ignore
104
+ }
105
+ }
106
+
107
+ if (sPosix.startsWith("./")) {
108
+ const rest = sPosix.slice(2);
109
+ if (rest && rest !== "." && rest !== ".." && !rest.startsWith("../")) {
110
+ return rest;
111
+ }
112
+ }
113
+ if (sPosix === ".") {
114
+ return "";
115
+ }
116
+ return p;
117
+ } catch {
118
+ return p;
119
+ }
120
+ }
121
+
122
+ function normalizeToolArgGlob(p, workspaceRoot) {
123
+ try {
124
+ const s = typeof p === "string" ? p : "";
125
+ if (!s) {
126
+ return p;
127
+ }
128
+ // TODO(ctxce): If this becomes annoying, consider making glob normalization
129
+ // more conservative (e.g. only strip a repo prefix when followed by "/",
130
+ // and avoid collapsing "<repo>/**" into "**" which can broaden scope).
131
+ if (s.startsWith("!")) {
132
+ const rest = s.slice(1);
133
+ const mapped = normalizeToolArgPath(rest, workspaceRoot);
134
+ if (typeof mapped === "string") {
135
+ return "!" + mapped;
136
+ }
137
+ return p;
138
+ }
139
+ return normalizeToolArgPath(s, workspaceRoot);
140
+ } catch {
141
+ return p;
142
+ }
143
+ }
144
+
145
+ function applyPathMappingToArgs(value, workspaceRoot, keyHint = "") {
146
+ try {
147
+ if (value === null || value === undefined) {
148
+ return value;
149
+ }
150
+
151
+ const key = typeof keyHint === "string" ? keyHint : "";
152
+ const lowered = key.toLowerCase();
153
+ const shouldMapString =
154
+ lowered === "path" ||
155
+ lowered === "under" ||
156
+ lowered === "root" ||
157
+ lowered === "subdir" ||
158
+ lowered === "path_glob" ||
159
+ lowered === "not_glob";
160
+
161
+ if (typeof value === "string") {
162
+ if (!shouldMapString) {
163
+ return value;
39
164
  }
40
- if (parts.length === 1) {
41
- return parts[0];
165
+ if (lowered === "path_glob" || lowered === "not_glob") {
166
+ return normalizeToolArgGlob(value, workspaceRoot);
42
167
  }
168
+ return normalizeToolArgPath(value, workspaceRoot);
169
+ }
170
+
171
+ if (Array.isArray(value)) {
172
+ return value.map((v) => applyPathMappingToArgs(v, workspaceRoot, keyHint));
173
+ }
174
+
175
+ if (typeof value === "object") {
176
+ const out = { ...value };
177
+ for (const [k, v] of Object.entries(out)) {
178
+ out[k] = applyPathMappingToArgs(v, workspaceRoot, k);
179
+ }
180
+ return out;
181
+ }
182
+
183
+ return value;
184
+ } catch {
185
+ return value;
186
+ }
187
+ }
188
+
189
+ function computeWorkspaceRelativePath(containerPath, hostPath) {
190
+ try {
191
+ const cont = typeof containerPath === "string" ? containerPath.trim() : "";
192
+ const rel = _workPathToRepoRelPosix(cont);
193
+ if (typeof rel === "string" && rel) {
194
+ return rel;
43
195
  }
44
196
  } catch {
45
197
  }
@@ -68,15 +220,10 @@ function remapRelatedPathToClient(p, workspaceRoot) {
68
220
  return sNorm;
69
221
  }
70
222
 
71
- if (s.startsWith("/work/")) {
72
- const rest = s.slice("/work/".length);
73
- const parts = rest.split("/").filter(Boolean);
74
- if (parts.length >= 2) {
75
- const rel = parts.slice(1).join("/");
76
- const relNative = _posixToNative(rel);
77
- return path.join(root, relNative);
78
- }
79
- return p;
223
+ const rel = _workPathToRepoRelPosix(s);
224
+ if (typeof rel === "string" && rel) {
225
+ const relNative = _posixToNative(rel);
226
+ return path.join(root, relNative);
80
227
  }
81
228
 
82
229
  // If it's already a relative path, join it to the workspace root.
@@ -201,17 +348,13 @@ function remapStringPath(p, workspaceRoot) {
201
348
  } catch {
202
349
  // ignore
203
350
  }
204
- if (s.startsWith("/work/")) {
205
- const rest = s.slice("/work/".length);
206
- const parts = rest.split("/").filter(Boolean);
207
- if (parts.length >= 2) {
208
- const rel = parts.slice(1).join("/");
209
- const override = envTruthy(process.env.CTXCE_BRIDGE_OVERRIDE_PATH, true);
210
- if (override) {
211
- return rel;
212
- }
213
- return p;
351
+ const rel = _workPathToRepoRelPosix(s);
352
+ if (typeof rel === "string" && rel) {
353
+ const override = envTruthy(process.env.CTXCE_BRIDGE_OVERRIDE_PATH, true);
354
+ if (override) {
355
+ return rel;
214
356
  }
357
+ return p;
215
358
  }
216
359
  return p;
217
360
  } catch {
@@ -336,3 +479,21 @@ export function maybeRemapToolResult(name, result, workspaceRoot) {
336
479
  return result;
337
480
  }
338
481
  }
482
+
483
+ export function maybeRemapToolArgs(name, args, workspaceRoot) {
484
+ try {
485
+ if (!name || !workspaceRoot) {
486
+ return args;
487
+ }
488
+ const enabled = envTruthy(process.env.CTXCE_BRIDGE_MAP_ARGS, true);
489
+ if (!enabled) {
490
+ return args;
491
+ }
492
+ if (args === null || args === undefined || typeof args !== "object") {
493
+ return args;
494
+ }
495
+ return applyPathMappingToArgs(args, workspaceRoot, "");
496
+ } catch {
497
+ return args;
498
+ }
499
+ }