@myspec/mcp-server 0.1.2-next.30 → 0.1.2-next.31

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.
Files changed (2) hide show
  1. package/dist/index.js +79 -37
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2896,9 +2896,11 @@ var readFileInput = z7.object({
2896
2896
  includeIgnored: z7.boolean().optional()
2897
2897
  });
2898
2898
  var grepInput = z7.object({
2899
- pattern: z7.string(),
2900
- path: z7.string().optional(),
2901
- isRegex: z7.boolean().optional(),
2899
+ pattern: z7.string().describe(
2900
+ "Search pattern. Interpreted as a regular expression by default (ripgrep syntax), e.g. `foo|bar` or `[Rr]ole`. Set `isRegex: false` to match the pattern as an exact literal string instead."
2901
+ ),
2902
+ path: z7.string().optional().describe("File or directory under --root to search. Defaults to the whole root."),
2903
+ isRegex: z7.boolean().optional().describe("Treat `pattern` as a regex (default true). Set false for literal-string search."),
2902
2904
  caseSensitive: z7.boolean().optional(),
2903
2905
  maxMatches: z7.number().int().positive().optional(),
2904
2906
  contextLines: z7.number().int().nonnegative().optional(),
@@ -3081,6 +3083,12 @@ function paginateLines(lines, offsetArg, limitArg) {
3081
3083
  ...truncated ? { nextOffset: endIdx + 1 } : {}
3082
3084
  };
3083
3085
  }
3086
+ var InvalidRegexError = class extends Error {
3087
+ constructor(message) {
3088
+ super(message);
3089
+ this.name = "InvalidRegexError";
3090
+ }
3091
+ };
3084
3092
  async function handleGrep(args, opts) {
3085
3093
  const subpath = args.path ?? ".";
3086
3094
  let realPath;
@@ -3089,9 +3097,15 @@ async function handleGrep(args, opts) {
3089
3097
  } catch (err) {
3090
3098
  return mapPathError(err);
3091
3099
  }
3100
+ if (!args.includeIgnored) {
3101
+ const check = isIgnored(subpath);
3102
+ if (check.ignored) {
3103
+ return structuredError("path_ignored", `Path matches ignore rule ${check.rule ?? ""}`);
3104
+ }
3105
+ }
3092
3106
  const maxMatches = args.maxMatches ?? GREP_MAX_MATCHES_DEFAULT;
3093
3107
  const contextLines = args.contextLines ?? 0;
3094
- const isRegex = args.isRegex ?? false;
3108
+ const isRegex = args.isRegex ?? true;
3095
3109
  const caseSensitive = args.caseSensitive ?? false;
3096
3110
  const ripgrep = await tryRipgrep(realPath, opts.realRoot, args.pattern, {
3097
3111
  isRegex,
@@ -3106,14 +3120,21 @@ async function handleGrep(args, opts) {
3106
3120
  if (ripgrep.fatal) {
3107
3121
  return structuredError("grep_failed", ripgrep.error);
3108
3122
  }
3109
- return structuredOk(
3110
- await nodeGrep(realPath, opts.realRoot, args.pattern, {
3111
- isRegex,
3112
- caseSensitive,
3113
- maxMatches,
3114
- includeIgnored: args.includeIgnored ?? false
3115
- })
3116
- );
3123
+ try {
3124
+ return structuredOk(
3125
+ await nodeGrep(realPath, opts.realRoot, args.pattern, {
3126
+ isRegex,
3127
+ caseSensitive,
3128
+ maxMatches,
3129
+ includeIgnored: args.includeIgnored ?? false
3130
+ })
3131
+ );
3132
+ } catch (err) {
3133
+ if (err instanceof InvalidRegexError) {
3134
+ return structuredError("grep_failed", err.message);
3135
+ }
3136
+ throw err;
3137
+ }
3117
3138
  }
3118
3139
  async function tryRipgrep(searchRoot, realRoot, pattern, opts) {
3119
3140
  return new Promise((resolveP) => {
@@ -3202,9 +3223,38 @@ function parseRipgrepLine(rawLine, realRoot) {
3202
3223
  }
3203
3224
  var NODE_GREP_FILE_SIZE_CAP = 10 * 1024 * 1024;
3204
3225
  async function nodeGrep(searchRoot, realRoot, pattern, opts) {
3205
- const regex = opts.isRegex ? new RegExp(pattern, opts.caseSensitive ? "" : "i") : new RegExp(escapeRegExp(pattern), opts.caseSensitive ? "" : "i");
3226
+ let regex;
3227
+ try {
3228
+ regex = opts.isRegex ? new RegExp(pattern, opts.caseSensitive ? "" : "i") : new RegExp(escapeRegExp(pattern), opts.caseSensitive ? "" : "i");
3229
+ } catch (err) {
3230
+ throw new InvalidRegexError(errorMessage(err));
3231
+ }
3206
3232
  const matches = [];
3207
3233
  let truncated = false;
3234
+ async function grepOneFile(full) {
3235
+ try {
3236
+ const st = await fs4.stat(full);
3237
+ if (st.size > NODE_GREP_FILE_SIZE_CAP) return;
3238
+ const content = await fs4.readFile(full, "utf-8");
3239
+ const lines = content.split("\n");
3240
+ for (let i = 0; i < lines.length; i += 1) {
3241
+ if (matches.length >= opts.maxMatches) {
3242
+ truncated = true;
3243
+ return;
3244
+ }
3245
+ const line = lines[i];
3246
+ if (line === void 0) continue;
3247
+ if (regex.test(line)) {
3248
+ matches.push({
3249
+ file: relative(realRoot, full) || full,
3250
+ line: i + 1,
3251
+ text: line
3252
+ });
3253
+ }
3254
+ }
3255
+ } catch {
3256
+ }
3257
+ }
3208
3258
  async function walk(dir, prefix) {
3209
3259
  if (matches.length >= opts.maxMatches) {
3210
3260
  truncated = true;
@@ -3229,31 +3279,23 @@ async function nodeGrep(searchRoot, realRoot, pattern, opts) {
3229
3279
  continue;
3230
3280
  }
3231
3281
  if (!item.isFile()) continue;
3232
- try {
3233
- const st = await fs4.stat(full);
3234
- if (st.size > NODE_GREP_FILE_SIZE_CAP) continue;
3235
- const content = await fs4.readFile(full, "utf-8");
3236
- const lines = content.split("\n");
3237
- for (let i = 0; i < lines.length; i += 1) {
3238
- if (matches.length >= opts.maxMatches) {
3239
- truncated = true;
3240
- return;
3241
- }
3242
- const line = lines[i];
3243
- if (line === void 0) continue;
3244
- if (regex.test(line)) {
3245
- matches.push({
3246
- file: relative(realRoot, full) || full,
3247
- line: i + 1,
3248
- text: line
3249
- });
3250
- }
3251
- }
3252
- } catch {
3253
- }
3282
+ await grepOneFile(full);
3283
+ }
3284
+ }
3285
+ let rootIsDir;
3286
+ try {
3287
+ rootIsDir = (await fs4.stat(searchRoot)).isDirectory();
3288
+ } catch {
3289
+ return { matches, truncated };
3290
+ }
3291
+ if (rootIsDir) {
3292
+ await walk(searchRoot, relative(realRoot, searchRoot));
3293
+ } else {
3294
+ const rel = relative(realRoot, searchRoot);
3295
+ if (opts.includeIgnored || !isIgnored(rel).ignored) {
3296
+ await grepOneFile(searchRoot);
3254
3297
  }
3255
3298
  }
3256
- await walk(searchRoot, relative(realRoot, searchRoot));
3257
3299
  return { matches, truncated };
3258
3300
  }
3259
3301
  function escapeRegExp(s) {
@@ -3306,7 +3348,7 @@ function registerLocalFsTools(server, opts) {
3306
3348
  },
3307
3349
  {
3308
3350
  name: "local_fs_grep",
3309
- description: "Search for a pattern under the granted --root (ripgrep with Node fallback).",
3351
+ description: "Search for a regex pattern under the granted --root (ripgrep with Node fallback). Like the standard Grep tool, `pattern` is a regular expression by default; pass `isRegex: false` for a literal-string search. Regex syntax follows ripgrep (Rust regex) when installed, falling back to JavaScript RegExp otherwise, so avoid engine-specific constructs (lookbehind, backreferences).",
3310
3352
  inputSchema: jsonSchemaFromZod(grepInput),
3311
3353
  handler: (args) => handleGrep(args, opts)
3312
3354
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myspec/mcp-server",
3
- "version": "0.1.2-next.30",
3
+ "version": "0.1.2-next.31",
4
4
  "description": "MySpec MCP server — exposes MySpec platform projects, files and attachments to MCP-aware clients via OAuth-authenticated access tokens.",
5
5
  "type": "module",
6
6
  "repository": {