@myspec/mcp-server 0.1.1 → 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.
- package/dist/index.js +82 -40
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2717,7 +2717,7 @@ import { mkdtemp, readFile as readFile2, rm } from "fs/promises";
|
|
|
2717
2717
|
import { tmpdir } from "os";
|
|
2718
2718
|
import { join } from "path";
|
|
2719
2719
|
import { runCli } from "repomix";
|
|
2720
|
-
var PAGE_SIZE_LINES =
|
|
2720
|
+
var PAGE_SIZE_LINES = 2500;
|
|
2721
2721
|
var REPOMIX_TIMEOUT_MS = 12e4;
|
|
2722
2722
|
var CACHE_TTL_MS = 30 * 60 * 1e3;
|
|
2723
2723
|
var CACHE_MAX_ENTRIES = 5;
|
|
@@ -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
|
-
|
|
2901
|
-
|
|
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 ??
|
|
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
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
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,13 +3348,13 @@ 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
|
},
|
|
3313
3355
|
{
|
|
3314
3356
|
name: "local_pack_codebase",
|
|
3315
|
-
description: "Pack the entire local codebase (under the granted --root) into a single plain-text bundle using `npx repomix@latest --style plain --compress`. Prefer this over many `local_fs_read_file` calls when you need broad codebase context. Output is paginated at
|
|
3357
|
+
description: "Pack the entire local codebase (under the granted --root) into a single plain-text bundle using `npx repomix@latest --style plain --compress`. Prefer this over many `local_fs_read_file` calls when you need broad codebase context. Output is paginated at 2500 lines per page; the response includes `outputId`, `page`, `totalPages`, `totalLines`, `pageLines`, `hasMore`, and a `nextPage` hint. Use `local_pack_codebase_read_page` to fetch subsequent pages by `outputId`. Repeat calls with the same `subpath`/`includePatterns`/`ignorePatterns` return the same `outputId` and are served instantly from the 30-minute cache without re-running repomix.",
|
|
3316
3358
|
inputSchema: jsonSchemaFromZod(packCodebaseInput),
|
|
3317
3359
|
handler: async (args) => {
|
|
3318
3360
|
try {
|
|
@@ -3325,7 +3367,7 @@ function registerLocalFsTools(server, opts) {
|
|
|
3325
3367
|
},
|
|
3326
3368
|
{
|
|
3327
3369
|
name: "local_pack_codebase_read_page",
|
|
3328
|
-
description: "Read a specific page of a previously packed codebase output. Provide the `outputId` returned by `local_pack_codebase` and the 1-indexed `page` to fetch. Pages are
|
|
3370
|
+
description: "Read a specific page of a previously packed codebase output. Provide the `outputId` returned by `local_pack_codebase` and the 1-indexed `page` to fetch. Pages are 2500 lines of plain text. Cache entries expire after 30 minutes; on cache miss, re-run `local_pack_codebase` (same args return the same outputId \u2014 re-pack is cheap, served from cache).",
|
|
3329
3371
|
inputSchema: jsonSchemaFromZod(packCodebaseReadPageInput),
|
|
3330
3372
|
handler: (args) => {
|
|
3331
3373
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myspec/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
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": {
|