@myspec/mcp-server 0.1.2-next.36 → 0.1.2-next.38
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 +151 -43
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -99,7 +99,7 @@ function applyCorsHeaders(res) {
|
|
|
99
99
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
100
100
|
}
|
|
101
101
|
async function waitForOAuthCallback(opts) {
|
|
102
|
-
return new Promise((
|
|
102
|
+
return new Promise((resolve3, reject) => {
|
|
103
103
|
const settledFlag = { value: false };
|
|
104
104
|
const timeoutRef = { handle: void 0 };
|
|
105
105
|
const rlRef = { handle: void 0 };
|
|
@@ -157,7 +157,7 @@ async function waitForOAuthCallback(opts) {
|
|
|
157
157
|
res.setHeader("Content-Type", "text/html");
|
|
158
158
|
res.end(SUCCESS_HTML);
|
|
159
159
|
settle(() => {
|
|
160
|
-
|
|
160
|
+
resolve3({ code });
|
|
161
161
|
});
|
|
162
162
|
});
|
|
163
163
|
server.on("error", (err) => {
|
|
@@ -204,7 +204,7 @@ async function waitForOAuthCallback(opts) {
|
|
|
204
204
|
}
|
|
205
205
|
settle(() => {
|
|
206
206
|
opts.logger("Captured pasted token; finishing sign-in...");
|
|
207
|
-
|
|
207
|
+
resolve3({ code });
|
|
208
208
|
});
|
|
209
209
|
});
|
|
210
210
|
}
|
|
@@ -348,10 +348,10 @@ function createFileCredentialsStore(paths = {}) {
|
|
|
348
348
|
async function loadOauthCreds(target) {
|
|
349
349
|
try {
|
|
350
350
|
if (process.platform !== "win32") {
|
|
351
|
-
const
|
|
352
|
-
if ((
|
|
351
|
+
const stat2 = await fs.stat(target);
|
|
352
|
+
if ((stat2.mode & 63) !== 0) {
|
|
353
353
|
throw new Error(
|
|
354
|
-
`Refusing to load credentials from ${target}: file mode is too permissive (${(
|
|
354
|
+
`Refusing to load credentials from ${target}: file mode is too permissive (${(stat2.mode & 511).toString(8)}). Run \`chmod 600 ${target}\` and retry.`
|
|
355
355
|
);
|
|
356
356
|
}
|
|
357
357
|
}
|
|
@@ -840,7 +840,7 @@ function calculateDelay(attempt, config) {
|
|
|
840
840
|
return Math.round(cappedDelay * jitterMultiplier);
|
|
841
841
|
}
|
|
842
842
|
function sleep(ms) {
|
|
843
|
-
return new Promise((
|
|
843
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
844
844
|
}
|
|
845
845
|
async function withRetry(fn, config = DEFAULT_RETRY_CONFIG, shouldRetry = defaultShouldRetry) {
|
|
846
846
|
const startTime = Date.now();
|
|
@@ -1139,6 +1139,7 @@ function transformSpecFileResponse(raw) {
|
|
|
1139
1139
|
revisionCount: raw.revision_count,
|
|
1140
1140
|
createdAt: new Date(raw.created_at),
|
|
1141
1141
|
updatedAt: new Date(raw.updated_at),
|
|
1142
|
+
trashedAt: raw.trashed_at ? new Date(raw.trashed_at) : void 0,
|
|
1142
1143
|
deletedAt: raw.deleted_at ? new Date(raw.deleted_at) : void 0
|
|
1143
1144
|
};
|
|
1144
1145
|
}
|
|
@@ -1475,18 +1476,49 @@ var HttpFileClient = class {
|
|
|
1475
1476
|
if (opts?.offset !== void 0) queryParams.set("offset", String(opts.offset));
|
|
1476
1477
|
if (opts?.hideArchivedIncomplete)
|
|
1477
1478
|
queryParams.set("hide_archived_incomplete", "true");
|
|
1479
|
+
if (opts?.includeTrashed) queryParams.set("include_trashed", "true");
|
|
1478
1480
|
const query = queryParams.toString();
|
|
1479
1481
|
const path4 = `/project/v1/files?${query}`;
|
|
1480
1482
|
const raw = await this.httpClient.get(path4, jwtToken, {});
|
|
1481
1483
|
return transformFileListResult(raw);
|
|
1482
1484
|
}
|
|
1483
|
-
async
|
|
1485
|
+
async trashFile(fileId, jwtToken) {
|
|
1484
1486
|
await this.httpClient.delete(
|
|
1485
1487
|
`/project/v1/files/${fileId}`,
|
|
1486
1488
|
jwtToken,
|
|
1487
1489
|
{}
|
|
1488
1490
|
);
|
|
1489
1491
|
}
|
|
1492
|
+
async restoreFile(fileId, jwtToken) {
|
|
1493
|
+
await this.httpClient.post(
|
|
1494
|
+
`/project/v1/files/${fileId}/restore`,
|
|
1495
|
+
{},
|
|
1496
|
+
jwtToken,
|
|
1497
|
+
{}
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
async deleteFilePermanently(fileId, jwtToken) {
|
|
1501
|
+
await this.httpClient.delete(
|
|
1502
|
+
`/project/v1/files/${fileId}/permanent`,
|
|
1503
|
+
jwtToken,
|
|
1504
|
+
{}
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
async listTrashedFiles(projectId, jwtToken) {
|
|
1508
|
+
const raw = await this.httpClient.get(
|
|
1509
|
+
`/project/v1/projects/${projectId}/files/trashed`,
|
|
1510
|
+
jwtToken,
|
|
1511
|
+
{}
|
|
1512
|
+
);
|
|
1513
|
+
return (raw.files ?? []).map(transformSpecFileResponse);
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* @deprecated Alias of {@link trashFile}. Moves the file to the Trash Bin
|
|
1517
|
+
* (recoverable); for a permanent delete use {@link deleteFilePermanently}.
|
|
1518
|
+
*/
|
|
1519
|
+
async deleteFile(fileId, jwtToken) {
|
|
1520
|
+
await this.trashFile(fileId, jwtToken);
|
|
1521
|
+
}
|
|
1490
1522
|
async deleteRevision(fileId, revisionNumber, jwtToken) {
|
|
1491
1523
|
const response = await this.httpClient.delete(
|
|
1492
1524
|
`/project/v1/files/${fileId}/revisions/${revisionNumber}`,
|
|
@@ -2448,6 +2480,59 @@ var updateProjectTool = {
|
|
|
2448
2480
|
// src/server/tools/update_spec_file.ts
|
|
2449
2481
|
import { z as z11 } from "zod";
|
|
2450
2482
|
|
|
2483
|
+
// src/server/tools/spec-content-source.ts
|
|
2484
|
+
import { readFile, stat } from "fs/promises";
|
|
2485
|
+
import { isAbsolute, resolve } from "path";
|
|
2486
|
+
var MAX_LOCAL_SPEC_FILE_BYTES = 5 * 1024 * 1024;
|
|
2487
|
+
async function resolveSpecContent(input, maxBytes = MAX_LOCAL_SPEC_FILE_BYTES) {
|
|
2488
|
+
const { content, localFilePath } = input;
|
|
2489
|
+
if (content !== void 0 && localFilePath !== void 0) {
|
|
2490
|
+
return { error: "Provide either content or local_file_path, not both." };
|
|
2491
|
+
}
|
|
2492
|
+
if (content !== void 0) {
|
|
2493
|
+
return { bytes: new TextEncoder().encode(content) };
|
|
2494
|
+
}
|
|
2495
|
+
if (localFilePath === void 0) {
|
|
2496
|
+
return { error: "Provide the file body via content or local_file_path (one is required)." };
|
|
2497
|
+
}
|
|
2498
|
+
const absPath = isAbsolute(localFilePath) ? localFilePath : resolve(process.cwd(), localFilePath);
|
|
2499
|
+
let info;
|
|
2500
|
+
try {
|
|
2501
|
+
info = await stat(absPath);
|
|
2502
|
+
} catch (err) {
|
|
2503
|
+
const code = err.code;
|
|
2504
|
+
if (code === "ENOENT") {
|
|
2505
|
+
return { error: `local file not found: ${absPath}` };
|
|
2506
|
+
}
|
|
2507
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2508
|
+
return { error: `failed to read local file ${absPath}: ${message}` };
|
|
2509
|
+
}
|
|
2510
|
+
if (!info.isFile()) {
|
|
2511
|
+
return { error: `local_file_path is not a regular file: ${absPath}` };
|
|
2512
|
+
}
|
|
2513
|
+
if (info.size === 0) {
|
|
2514
|
+
return { error: `local file is empty: ${absPath}` };
|
|
2515
|
+
}
|
|
2516
|
+
if (info.size > maxBytes) {
|
|
2517
|
+
return {
|
|
2518
|
+
error: `local file is too large (${String(info.size)} bytes); max is ${String(maxBytes)} bytes.`
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
let raw;
|
|
2522
|
+
try {
|
|
2523
|
+
raw = await readFile(absPath);
|
|
2524
|
+
} catch (err) {
|
|
2525
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2526
|
+
return { error: `failed to read local file ${absPath}: ${message}` };
|
|
2527
|
+
}
|
|
2528
|
+
if (raw.byteLength > maxBytes) {
|
|
2529
|
+
return {
|
|
2530
|
+
error: `local file is too large (${String(raw.byteLength)} bytes); max is ${String(maxBytes)} bytes.`
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
2533
|
+
return { bytes: new Uint8Array(raw) };
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2451
2536
|
// src/server/tools/spec-file-output.ts
|
|
2452
2537
|
function specFileSummary(file) {
|
|
2453
2538
|
return {
|
|
@@ -2469,13 +2554,25 @@ var inputSchema11 = {
|
|
|
2469
2554
|
file_id: z11.string().min(1).optional().describe("UUID of the spec file to overwrite. Provide this or (project_id + file_path)."),
|
|
2470
2555
|
project_id: z11.string().min(1).optional().describe("UUID of the project. Required (with file_path) when file_id is not given."),
|
|
2471
2556
|
file_path: z11.string().min(1).optional().describe("Path of the spec file. Required (with project_id) when file_id is not given."),
|
|
2472
|
-
content: z11.string().min(1).
|
|
2557
|
+
content: z11.string().min(1).optional().describe(
|
|
2558
|
+
"Full UTF-8 replacement content, saved as a new revision. Provide this OR local_file_path (exactly one). Use for in-memory / generated content."
|
|
2559
|
+
),
|
|
2560
|
+
local_file_path: z11.string().min(1).optional().describe(
|
|
2561
|
+
"Path to a local file whose bytes become the new revision; the server reads it directly. Provide this OR content (exactly one). Absolute paths are most reliable; a relative path resolves against the MCP server working directory. Point this only at an intended spec file \u2014 its bytes are uploaded to the project as-is; never use it for secrets or unrelated files."
|
|
2562
|
+
)
|
|
2473
2563
|
};
|
|
2474
2564
|
var updateSpecFileTool = {
|
|
2475
2565
|
name: "update_spec_file",
|
|
2476
|
-
description: "Overwrite an existing spec file by saving a new revision
|
|
2566
|
+
description: "Overwrite an existing spec file by saving a new revision. Provide the new body inline via `content` or by pointing at a local file with `local_file_path` \u2014 exactly one is required. Prefer `local_file_path` when the body is already an existing local file (the server reads it directly); use `content` for text generated in-memory. Reference the file by file_id or by (project_id + file_path).",
|
|
2477
2567
|
inputSchema: inputSchema11,
|
|
2478
2568
|
handler: async (args, ctx) => {
|
|
2569
|
+
const resolved = await resolveSpecContent({
|
|
2570
|
+
content: args.content,
|
|
2571
|
+
localFilePath: args.local_file_path
|
|
2572
|
+
});
|
|
2573
|
+
if (resolved.error || !resolved.bytes) {
|
|
2574
|
+
return errorResult(resolved.error ?? "Failed to resolve file content.");
|
|
2575
|
+
}
|
|
2479
2576
|
let fileId;
|
|
2480
2577
|
if (args.file_id) {
|
|
2481
2578
|
const meta = await ctx.client.getFileMetadata(args.file_id).catch(() => null);
|
|
@@ -2494,16 +2591,15 @@ var updateSpecFileTool = {
|
|
|
2494
2591
|
} else {
|
|
2495
2592
|
return errorResult("Provide either file_id, or both project_id and file_path.");
|
|
2496
2593
|
}
|
|
2497
|
-
const
|
|
2498
|
-
const file = await ctx.client.saveSpecFileRevision(fileId, content);
|
|
2594
|
+
const file = await ctx.client.saveSpecFileRevision(fileId, resolved.bytes);
|
|
2499
2595
|
return jsonResult({ file: specFileSummary(file) });
|
|
2500
2596
|
}
|
|
2501
2597
|
};
|
|
2502
2598
|
|
|
2503
2599
|
// src/server/tools/upload_attachment.ts
|
|
2504
2600
|
import { createHash as createHash2 } from "crypto";
|
|
2505
|
-
import { lstat, readFile } from "fs/promises";
|
|
2506
|
-
import { basename, extname, isAbsolute } from "path";
|
|
2601
|
+
import { lstat, readFile as readFile2 } from "fs/promises";
|
|
2602
|
+
import { basename, extname, isAbsolute as isAbsolute2 } from "path";
|
|
2507
2603
|
import { z as z12 } from "zod";
|
|
2508
2604
|
var DEFAULT_MIME = "application/octet-stream";
|
|
2509
2605
|
var MAX_ATTACHMENT_MB = MAX_ATTACHMENT_BYTES / (1024 * 1024);
|
|
@@ -2622,7 +2718,7 @@ var uploadAttachmentTool = {
|
|
|
2622
2718
|
description: `Upload a local file as a new attachment on a project. Reads \`file_path\` from the host running the MCP server, computes its SHA256, then performs the two-phase platform upload. Max ${String(MAX_ATTACHMENT_MB)} MB. Supported content: PDF, DOCX, XLSX, and any UTF-8 text file (XML, JSON, YAML, HTML, Markdown, CSV, source code, ...). Binary files outside the structured types will be rejected by the platform.`,
|
|
2623
2719
|
inputSchema: inputSchema12,
|
|
2624
2720
|
handler: async (args, ctx) => {
|
|
2625
|
-
if (!
|
|
2721
|
+
if (!isAbsolute2(args.file_path)) {
|
|
2626
2722
|
return errorResult(`file_path must be an absolute path; got "${args.file_path}".`);
|
|
2627
2723
|
}
|
|
2628
2724
|
let stats;
|
|
@@ -2658,7 +2754,7 @@ var uploadAttachmentTool = {
|
|
|
2658
2754
|
}
|
|
2659
2755
|
let buf;
|
|
2660
2756
|
try {
|
|
2661
|
-
buf = await
|
|
2757
|
+
buf = await readFile2(args.file_path);
|
|
2662
2758
|
} catch (err) {
|
|
2663
2759
|
const code = err.code;
|
|
2664
2760
|
if (code === "EACCES" || code === "EPERM") {
|
|
@@ -2762,15 +2858,16 @@ function validateSpecFilePath(filePath) {
|
|
|
2762
2858
|
if (segments.some((s) => s === "" || s === ".")) {
|
|
2763
2859
|
return 'file_path must be a normalized path (no empty, "." or trailing-slash segments).';
|
|
2764
2860
|
}
|
|
2765
|
-
|
|
2766
|
-
|
|
2861
|
+
const root = segments[0];
|
|
2862
|
+
if (root !== "specs" && root !== "openspec") {
|
|
2863
|
+
return "file_path must be rooted at 'specs' or 'openspec' (e.g. specs/changes/add-oauth-login/proposal.md).";
|
|
2767
2864
|
}
|
|
2768
2865
|
if (segments.length < 2) {
|
|
2769
|
-
return
|
|
2866
|
+
return `file_path must include a filename under '${root}' (e.g. ${root}/proposal.md).`;
|
|
2770
2867
|
}
|
|
2771
2868
|
const dirLevels = segments.length - 2;
|
|
2772
2869
|
if (dirLevels > MAX_SPEC_DIR_DEPTH) {
|
|
2773
|
-
return `file_path must not nest deeper than ${String(MAX_SPEC_DIR_DEPTH)} directory levels below
|
|
2870
|
+
return `file_path must not nest deeper than ${String(MAX_SPEC_DIR_DEPTH)} directory levels below the '${root}' root.`;
|
|
2774
2871
|
}
|
|
2775
2872
|
return null;
|
|
2776
2873
|
}
|
|
@@ -2799,20 +2896,32 @@ var SPEC_FILE_TYPES = [
|
|
|
2799
2896
|
var inputSchema13 = {
|
|
2800
2897
|
project_id: z13.string().min(1).describe("UUID of the project to upload the file into."),
|
|
2801
2898
|
file_path: z13.string().min(1).describe(
|
|
2802
|
-
'Path of the new spec file, rooted at "specs" with at most 3 directory levels below
|
|
2899
|
+
'Path of the new spec file, rooted at "specs" or "openspec" with at most 3 directory levels below the root (e.g. specs/changes/add-oauth-login/proposal.md).'
|
|
2900
|
+
),
|
|
2901
|
+
content: z13.string().min(1).optional().describe(
|
|
2902
|
+
"Full UTF-8 text content of the spec file. Provide this OR local_file_path (exactly one). Use for in-memory / generated content."
|
|
2903
|
+
),
|
|
2904
|
+
local_file_path: z13.string().min(1).optional().describe(
|
|
2905
|
+
"Path to a local file whose bytes become the spec file body; the server reads it directly. Provide this OR content (exactly one). Absolute paths are most reliable; a relative path resolves against the MCP server working directory. Point this only at an intended spec file \u2014 its bytes are uploaded to the project as-is; never use it for secrets or unrelated files."
|
|
2803
2906
|
),
|
|
2804
|
-
content: z13.string().min(1).describe("Full UTF-8 text content of the spec file (non-empty)."),
|
|
2805
2907
|
file_type: z13.enum(SPEC_FILE_TYPES).optional().describe("Spec file type. Omit to derive from the filename (e.g. proposal.md \u2192 proposal).")
|
|
2806
2908
|
};
|
|
2807
2909
|
var uploadSpecFileTool = {
|
|
2808
2910
|
name: "upload_spec_file",
|
|
2809
|
-
description: "Upload a new, session-less spec file into a project
|
|
2911
|
+
description: "Upload a new, session-less spec file into a project. Provide the file body inline via `content` or by pointing at a local file with `local_file_path` \u2014 exactly one is required. Prefer `local_file_path` when the body is already an existing local file (the server reads it directly, avoiding sending large content through the model); use `content` for text generated in-memory. Rejects if a file already exists at the same path (use update_spec_file to overwrite).",
|
|
2810
2912
|
inputSchema: inputSchema13,
|
|
2811
2913
|
handler: async (args, ctx) => {
|
|
2812
2914
|
const pathError = validateSpecFilePath(args.file_path);
|
|
2813
2915
|
if (pathError) {
|
|
2814
2916
|
return errorResult(pathError);
|
|
2815
2917
|
}
|
|
2918
|
+
const resolved = await resolveSpecContent({
|
|
2919
|
+
content: args.content,
|
|
2920
|
+
localFilePath: args.local_file_path
|
|
2921
|
+
});
|
|
2922
|
+
if (resolved.error || !resolved.bytes) {
|
|
2923
|
+
return errorResult(resolved.error ?? "Failed to resolve file content.");
|
|
2924
|
+
}
|
|
2816
2925
|
const existing = await ctx.client.findSpecFileByPath(args.project_id, args.file_path);
|
|
2817
2926
|
if (existing) {
|
|
2818
2927
|
return errorResult(
|
|
@@ -2820,12 +2929,11 @@ var uploadSpecFileTool = {
|
|
|
2820
2929
|
);
|
|
2821
2930
|
}
|
|
2822
2931
|
const fileType = args.file_type ?? deriveFileType(args.file_path);
|
|
2823
|
-
const content = new TextEncoder().encode(args.content);
|
|
2824
2932
|
const file = await ctx.client.uploadSpecFile({
|
|
2825
2933
|
projectId: args.project_id,
|
|
2826
2934
|
filePath: args.file_path,
|
|
2827
2935
|
fileType,
|
|
2828
|
-
content
|
|
2936
|
+
content: resolved.bytes
|
|
2829
2937
|
});
|
|
2830
2938
|
return jsonResult({ file: specFileSummary(file) });
|
|
2831
2939
|
}
|
|
@@ -2917,13 +3025,13 @@ var WebSocketClientTransport = class {
|
|
|
2917
3025
|
return Promise.resolve();
|
|
2918
3026
|
}
|
|
2919
3027
|
send(message) {
|
|
2920
|
-
return new Promise((
|
|
3028
|
+
return new Promise((resolve3, reject) => {
|
|
2921
3029
|
this.ws.send(JSON.stringify(message), (err) => {
|
|
2922
3030
|
if (err) {
|
|
2923
3031
|
reject(err);
|
|
2924
3032
|
return;
|
|
2925
3033
|
}
|
|
2926
|
-
|
|
3034
|
+
resolve3();
|
|
2927
3035
|
});
|
|
2928
3036
|
});
|
|
2929
3037
|
}
|
|
@@ -2957,7 +3065,7 @@ var WebSocketClientTransport = class {
|
|
|
2957
3065
|
|
|
2958
3066
|
// src/reverse/safe-path.ts
|
|
2959
3067
|
import { realpath } from "fs/promises";
|
|
2960
|
-
import { isAbsolute as
|
|
3068
|
+
import { isAbsolute as isAbsolute3, resolve as resolve2, sep } from "path";
|
|
2961
3069
|
var PathOutsideRootError = class extends Error {
|
|
2962
3070
|
constructor(userPath) {
|
|
2963
3071
|
super(`path_outside_root: ${userPath}`);
|
|
@@ -2975,10 +3083,10 @@ var AbsolutePathError = class extends Error {
|
|
|
2975
3083
|
userPath;
|
|
2976
3084
|
};
|
|
2977
3085
|
async function resolveSafePath(realRoot, userPath, opts = {}) {
|
|
2978
|
-
if (
|
|
3086
|
+
if (isAbsolute3(userPath)) {
|
|
2979
3087
|
throw new AbsolutePathError(userPath);
|
|
2980
3088
|
}
|
|
2981
|
-
const joined =
|
|
3089
|
+
const joined = resolve2(realRoot, userPath);
|
|
2982
3090
|
let resolved;
|
|
2983
3091
|
try {
|
|
2984
3092
|
resolved = await realpath(joined);
|
|
@@ -3083,7 +3191,7 @@ function ripgrepIgnoreGlobs() {
|
|
|
3083
3191
|
|
|
3084
3192
|
// src/reverse/pack-codebase.ts
|
|
3085
3193
|
import { createHash as createHash3 } from "crypto";
|
|
3086
|
-
import { mkdtemp, readFile as
|
|
3194
|
+
import { mkdtemp, readFile as readFile3, rm } from "fs/promises";
|
|
3087
3195
|
import { tmpdir } from "os";
|
|
3088
3196
|
import { join } from "path";
|
|
3089
3197
|
import { runCli } from "repomix";
|
|
@@ -3220,7 +3328,7 @@ async function invokeRepomix(exec, targetDir, args, timeoutMs) {
|
|
|
3220
3328
|
const execPromise = exec(["."], targetDir, options);
|
|
3221
3329
|
try {
|
|
3222
3330
|
await withTimeout(execPromise, timeoutMs);
|
|
3223
|
-
const content = await
|
|
3331
|
+
const content = await readFile3(outputFile, "utf-8");
|
|
3224
3332
|
await rm(workDir, { recursive: true, force: true });
|
|
3225
3333
|
return content;
|
|
3226
3334
|
} catch (err) {
|
|
@@ -3370,20 +3478,20 @@ async function handleRead(args, opts) {
|
|
|
3370
3478
|
return structuredError("path_ignored", `Path matches ignore rule ${check.rule ?? ""}`);
|
|
3371
3479
|
}
|
|
3372
3480
|
}
|
|
3373
|
-
let
|
|
3481
|
+
let stat2;
|
|
3374
3482
|
try {
|
|
3375
|
-
|
|
3483
|
+
stat2 = await fs4.stat(realPath);
|
|
3376
3484
|
} catch (err) {
|
|
3377
3485
|
return structuredError("stat_failed", errorMessage(err));
|
|
3378
3486
|
}
|
|
3379
|
-
if (!
|
|
3487
|
+
if (!stat2.isFile()) {
|
|
3380
3488
|
return structuredError("not_a_file", `Not a regular file: ${args.path}`);
|
|
3381
3489
|
}
|
|
3382
|
-
if (
|
|
3490
|
+
if (stat2.size > READ_FILE_MAX_BYTES) {
|
|
3383
3491
|
return structuredError(
|
|
3384
3492
|
"file_too_large",
|
|
3385
|
-
`File is ${String(
|
|
3386
|
-
{ sizeBytes:
|
|
3493
|
+
`File is ${String(stat2.size)} bytes; the read cap is ${String(READ_FILE_MAX_BYTES)} bytes. Use local_fs_grep to find the lines you need, or local_pack_codebase to pull the whole tree.`,
|
|
3494
|
+
{ sizeBytes: stat2.size, maxBytes: READ_FILE_MAX_BYTES }
|
|
3387
3495
|
);
|
|
3388
3496
|
}
|
|
3389
3497
|
let raw;
|
|
@@ -3988,7 +4096,7 @@ async function runOneConnection(opts) {
|
|
|
3988
4096
|
pongTimer = null;
|
|
3989
4097
|
}
|
|
3990
4098
|
});
|
|
3991
|
-
return new Promise((
|
|
4099
|
+
return new Promise((resolve3) => {
|
|
3992
4100
|
ws.once("close", (code, reasonBuf) => {
|
|
3993
4101
|
clearInterval(pingTimer);
|
|
3994
4102
|
if (pongTimer) clearTimeout(pongTimer);
|
|
@@ -3998,7 +4106,7 @@ async function runOneConnection(opts) {
|
|
|
3998
4106
|
`
|
|
3999
4107
|
);
|
|
4000
4108
|
const terminal = TERMINAL_CLOSE_CODES.has(code);
|
|
4001
|
-
|
|
4109
|
+
resolve3({
|
|
4002
4110
|
terminal,
|
|
4003
4111
|
reason: `code=${String(code)} ${reasonStr}`,
|
|
4004
4112
|
upMs: Date.now() - connectedAt,
|
|
@@ -4008,10 +4116,10 @@ async function runOneConnection(opts) {
|
|
|
4008
4116
|
});
|
|
4009
4117
|
}
|
|
4010
4118
|
function waitForOpen(ws) {
|
|
4011
|
-
return new Promise((
|
|
4119
|
+
return new Promise((resolve3, reject) => {
|
|
4012
4120
|
const onOpen = () => {
|
|
4013
4121
|
ws.removeListener("error", onError);
|
|
4014
|
-
|
|
4122
|
+
resolve3();
|
|
4015
4123
|
};
|
|
4016
4124
|
const onError = (err) => {
|
|
4017
4125
|
ws.removeListener("open", onOpen);
|
|
@@ -4022,7 +4130,7 @@ function waitForOpen(ws) {
|
|
|
4022
4130
|
});
|
|
4023
4131
|
}
|
|
4024
4132
|
function sleep2(ms) {
|
|
4025
|
-
return new Promise((
|
|
4133
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
4026
4134
|
}
|
|
4027
4135
|
|
|
4028
4136
|
// src/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myspec/mcp-server",
|
|
3
|
-
"version": "0.1.2-next.
|
|
3
|
+
"version": "0.1.2-next.38",
|
|
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": {
|