fast-context-mcp 1.1.1 → 1.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-context-mcp",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "AI-driven semantic code search MCP server (Node.js)",
5
5
  "type": "module",
6
6
  "main": "src/server.mjs",
package/src/core.mjs CHANGED
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  import { readdirSync, existsSync, statSync } from "node:fs";
15
- import { resolve, join, relative, sep } from "node:path";
15
+ import { resolve, join, relative, sep, isAbsolute } from "node:path";
16
16
  import { gzipSync } from "node:zlib";
17
17
  import { randomUUID } from "node:crypto";
18
18
  import { platform, arch, release, version as osVersion, hostname, cpus, totalmem } from "node:os";
@@ -934,7 +934,8 @@ function _parseAnswer(xmlText, projectRoot) {
934
934
 
935
935
  // Path safety: reject traversal attempts (../) and paths outside project root
936
936
  const fullPath = resolve(projectRoot, rel);
937
- if (!fullPath.startsWith(resolvedRoot + sep) && fullPath !== resolvedRoot) {
937
+ const relToRoot = relative(resolvedRoot, fullPath);
938
+ if (relToRoot === ".." || relToRoot.startsWith(`..${sep}`) || isAbsolute(relToRoot)) {
938
939
  continue;
939
940
  }
940
941
 
package/src/executor.mjs CHANGED
@@ -52,6 +52,10 @@ export class ToolExecutor {
52
52
  * @returns {string}
53
53
  */
54
54
  _real(virtual) {
55
+ // Guard against undefined/null from malformed AI responses
56
+ if (virtual == null || typeof virtual !== "string") {
57
+ return this.root;
58
+ }
55
59
  if (virtual.startsWith("/codebase") || virtual.startsWith("\\codebase")) {
56
60
  const rel = virtual.slice("/codebase".length).replace(/^[\/\\]+/, "");
57
61
  return join(this.root, rel);
@@ -122,6 +126,12 @@ export class ToolExecutor {
122
126
  * @returns {Promise<string>}
123
127
  */
124
128
  async rgAsync(pattern, path, include = null, exclude = null) {
129
+ if (!pattern || typeof pattern !== "string") {
130
+ return "Error: missing or invalid pattern";
131
+ }
132
+ if (!path || typeof path !== "string") {
133
+ return "Error: missing or invalid path";
134
+ }
125
135
  this.collectedRgPatterns.push(pattern);
126
136
  const rp = this._real(path);
127
137
  if (!existsSync(rp)) {
@@ -168,6 +178,12 @@ export class ToolExecutor {
168
178
  * @returns {string}
169
179
  */
170
180
  rg(pattern, path, include = null, exclude = null) {
181
+ if (!pattern || typeof pattern !== "string") {
182
+ return "Error: missing or invalid pattern";
183
+ }
184
+ if (!path || typeof path !== "string") {
185
+ return "Error: missing or invalid path";
186
+ }
171
187
  this.collectedRgPatterns.push(pattern);
172
188
  const rp = this._real(path);
173
189
  if (!existsSync(rp)) {
@@ -215,6 +231,9 @@ export class ToolExecutor {
215
231
  * @returns {string}
216
232
  */
217
233
  readfile(file, startLine = null, endLine = null) {
234
+ if (!file || typeof file !== "string") {
235
+ return "Error: missing or invalid file path";
236
+ }
218
237
  const rp = this._real(file);
219
238
  try {
220
239
  const stat = statSync(rp);
@@ -249,6 +268,9 @@ export class ToolExecutor {
249
268
  * @returns {string}
250
269
  */
251
270
  tree(path, levels = null) {
271
+ if (!path || typeof path !== "string") {
272
+ return "Error: missing or invalid path";
273
+ }
252
274
  const rp = this._real(path);
253
275
  try {
254
276
  const stat = statSync(rp);
@@ -284,6 +306,9 @@ export class ToolExecutor {
284
306
  * @returns {string}
285
307
  */
286
308
  ls(path, longFormat = false, allFiles = false) {
309
+ if (!path || typeof path !== "string") {
310
+ return "Error: missing or invalid path";
311
+ }
287
312
  const rp = this._real(path);
288
313
  try {
289
314
  const stat = statSync(rp);
@@ -341,6 +366,12 @@ export class ToolExecutor {
341
366
  * @returns {string}
342
367
  */
343
368
  glob(pattern, path, typeFilter = "all") {
369
+ if (!pattern || typeof pattern !== "string") {
370
+ return "Error: missing or invalid pattern";
371
+ }
372
+ if (!path || typeof path !== "string") {
373
+ return "Error: missing or invalid path";
374
+ }
344
375
  const rp = this._real(path);
345
376
 
346
377
  // Use recursive readdir + fnmatch since Node 22 globSync may not be available
@@ -378,6 +409,9 @@ export class ToolExecutor {
378
409
  * @returns {Promise<string>}
379
410
  */
380
411
  async execCommandAsync(cmd) {
412
+ if (!cmd || typeof cmd !== "object") {
413
+ return "Error: missing or invalid command";
414
+ }
381
415
  const t = cmd.type || "";
382
416
  switch (t) {
383
417
  case "rg":
@@ -401,6 +435,9 @@ export class ToolExecutor {
401
435
  * @returns {string}
402
436
  */
403
437
  execCommand(cmd) {
438
+ if (!cmd || typeof cmd !== "object") {
439
+ return "Error: missing or invalid command";
440
+ }
404
441
  const t = cmd.type || "";
405
442
  switch (t) {
406
443
  case "rg":
@@ -424,13 +461,14 @@ export class ToolExecutor {
424
461
  * @returns {Promise<string>}
425
462
  */
426
463
  async execToolCallAsync(args) {
464
+ if (!args || typeof args !== "object") {
465
+ return "Error: missing or invalid tool args";
466
+ }
427
467
  const keys = Object.keys(args).filter((k) => k.startsWith("command")).sort();
428
- const tasks = keys
429
- .filter((key) => typeof args[key] === "object")
430
- .map(async (key) => {
431
- const output = await this.execCommandAsync(args[key]);
432
- return `<${key}_result>\n${output}\n</${key}_result>`;
433
- });
468
+ const tasks = keys.map(async (key) => {
469
+ const output = await this.execCommandAsync(args[key]);
470
+ return `<${key}_result>\n${output}\n</${key}_result>`;
471
+ });
434
472
  const results = await Promise.all(tasks);
435
473
  return results.join("");
436
474
  }
@@ -442,12 +480,13 @@ export class ToolExecutor {
442
480
  */
443
481
  execToolCall(args) {
444
482
  const parts = [];
483
+ if (!args || typeof args !== "object") {
484
+ return "Error: missing or invalid tool args";
485
+ }
445
486
  const keys = Object.keys(args).filter((k) => k.startsWith("command")).sort();
446
487
  for (const key of keys) {
447
- if (typeof args[key] === "object") {
448
- const output = this.execCommand(args[key]);
449
- parts.push(`<${key}_result>\n${output}\n</${key}_result>`);
450
- }
488
+ const output = this.execCommand(args[key]);
489
+ parts.push(`<${key}_result>\n${output}\n</${key}_result>`);
451
490
  }
452
491
  return parts.join("");
453
492
  }