fast-cxt-mcp 1.1.0 → 1.1.2

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,11 +1,11 @@
1
1
  {
2
2
  "name": "fast-cxt-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "AI-driven semantic code search MCP server (Node.js)",
5
5
  "type": "module",
6
6
  "main": "src/server.mjs",
7
7
  "bin": {
8
- "fast-cxt-mcp": "src/server.mjs"
8
+ "fast-context-mcp": "src/server.mjs"
9
9
  },
10
10
  "files": [
11
11
  "src/",
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";
@@ -925,22 +925,24 @@ function getRepoMap(projectRoot, targetDepth = 3, excludePaths = []) {
925
925
  function _parseAnswer(xmlText, projectRoot) {
926
926
  const files = [];
927
927
  const resolvedRoot = resolve(projectRoot);
928
- const fileRegex = /<file\s+path="([^"]+)">([\s\S]*?)<\/file>/g;
928
+ const fileRegex = /<file\s+path=(["'])([^"']+)\1>([\s\S]*?)<\/file>/g;
929
929
  let fm;
930
930
  while ((fm = fileRegex.exec(xmlText)) !== null) {
931
- const vpath = fm[1];
932
- const rel = vpath.replace(/^\/codebase\/?/, "");
931
+ const vpath = fm[2];
932
+ let rel = vpath.replace(/^\/codebase[\/\\]?/, "");
933
+ rel = rel.replace(/^[\/\\]+/, "");
933
934
 
934
935
  // Path safety: reject traversal attempts (../) and paths outside project root
935
936
  const fullPath = resolve(projectRoot, rel);
936
- if (!fullPath.startsWith(resolvedRoot + sep) && fullPath !== resolvedRoot) {
937
+ const relToRoot = relative(resolvedRoot, fullPath);
938
+ if (relToRoot === ".." || relToRoot.startsWith(`..${sep}`) || isAbsolute(relToRoot)) {
937
939
  continue;
938
940
  }
939
941
 
940
942
  const ranges = [];
941
943
  const rangeRegex = /<range>(\d+)-(\d+)<\/range>/g;
942
944
  let rm;
943
- while ((rm = rangeRegex.exec(fm[2])) !== null) {
945
+ while ((rm = rangeRegex.exec(fm[3])) !== null) {
944
946
  ranges.push([parseInt(rm[1], 10), parseInt(rm[2], 10)]);
945
947
  }
946
948
 
package/src/executor.mjs CHANGED
@@ -52,8 +52,12 @@ export class ToolExecutor {
52
52
  * @returns {string}
53
53
  */
54
54
  _real(virtual) {
55
- if (virtual.startsWith("/codebase")) {
56
- const rel = virtual.slice("/codebase".length).replace(/^\/+/, "");
55
+ // Guard against undefined/null from malformed AI responses
56
+ if (virtual == null || typeof virtual !== "string") {
57
+ return this.root;
58
+ }
59
+ if (virtual.startsWith("/codebase") || virtual.startsWith("\\codebase")) {
60
+ const rel = virtual.slice("/codebase".length).replace(/^[\/\\]+/, "");
57
61
  return join(this.root, rel);
58
62
  }
59
63
  return virtual;
@@ -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,11 +366,16 @@ 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
347
378
  const matches = [];
348
- const fullPattern = join(rp, pattern).replace(/\\/g, "/");
349
379
 
350
380
  try {
351
381
  _globWalk(rp, pattern, matches, typeFilter);