@myspec/mcp-server 1.1.1-next.176 → 1.1.2-next.178
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/README.md +1 -0
- package/dist/index.js +387 -264
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -339,6 +339,7 @@ Local state is stored under `~/.myspec/` (alongside the on-disk cache root), spl
|
|
|
339
339
|
| `read_spec_file` | `file_id`, optional `revision`, optional `offset`, optional `limit` | UTF-8 content paginated by line (`offset` is 1-based, default/cap 2000 lines, 1 MiB per response) with `content`, `start_line`, `end_line`, `read_lines`, `total_lines`, `truncated`, `next_offset`; plus `content_version` when reading the latest revision (errors for binary or files > 8 MiB — use `download_spec_file` for those). Cached on disk under `MYSPEC_DOWNLOAD_ROOT`, so paging through a file downloads it once |
|
|
340
340
|
| `download_spec_file` | `file_id`, optional `destination_path`, optional `revision`, optional `overwrite` | Downloads the file's raw bytes to a local file — by default under `.specs/` in the working directory (see [Download destinations](#download-destinations)). Handles binary files and files up to 50 MiB. Refuses to replace an existing file unless `overwrite=true`. Returns only metadata (`saved_path`, `bytes_written`, `revision_number`, `destination_source`) — **not** the content, so a large file can be saved locally and read in chunks |
|
|
341
341
|
| `list_spec_file` | `project_id`, optional `path`, optional `trashed`, optional `limit`, optional `offset` | Lists a project's spec files (`file_id`, `file_path`, `file_type`, `file_size_bytes`, `revision_count`, `session_id`, `updated_at`, plus `trashed_at` on Trash Bin entries). Filter to a directory with `path` (exact file or everything under it; `""`/`"/"`/omit = all). `trashed=true` lists the Trash Bin instead of live files. Returns `total`, `limit`, `offset` and, when more records remain, `next_offset` (default 50, capped at 200). Ordered by `file_path` when `path` or `trashed` is given, most-recently-created first otherwise |
|
|
342
|
+
| `move_spec_folder` | `project_id`, `from_path`, `to_path` | Moves or renames a folder under `specs/` (a bundle, or a group folder holding bundles) with everything in it, e.g. `specs/mcp` → `specs/archive/mcp`. A bundle's files always move together; ids and contents stay, only paths change |
|
|
342
343
|
| `move_spec_file_to_trash` | `file_id` | Moves a spec file to the Trash Bin (recoverable — hidden from listings and AI-agent tools, restorable indefinitely). Not a permanent delete |
|
|
343
344
|
| `restore_spec_file_from_trash` | `file_id` | Restores a trashed spec file back to the active file list, reversing `move_spec_file_to_trash` |
|
|
344
345
|
| `get_attachment` | `attachment_id`, optional `include_download_url` | Attachment metadata (looked up org-scoped by id); with `include_download_url>0` also returns a signed download URL |
|
package/dist/index.js
CHANGED
|
@@ -2335,6 +2335,24 @@ function transformFileListResult(raw) {
|
|
|
2335
2335
|
};
|
|
2336
2336
|
}
|
|
2337
2337
|
|
|
2338
|
+
// ../../packages/platform-client/project/types/bundle.types.ts
|
|
2339
|
+
function transformBundleResponse(raw) {
|
|
2340
|
+
return {
|
|
2341
|
+
name: raw.name,
|
|
2342
|
+
sourceSessionId: raw.source_session_id,
|
|
2343
|
+
status: raw.status,
|
|
2344
|
+
completedAt: raw.completed_at ? new Date(raw.completed_at) : void 0,
|
|
2345
|
+
files: raw.files.map(transformSpecFileResponse)
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
function transformSpecFolderMoveResponse(raw) {
|
|
2349
|
+
return {
|
|
2350
|
+
fromPath: raw.from_path,
|
|
2351
|
+
toPath: raw.to_path,
|
|
2352
|
+
files: raw.files.map(transformSpecFileResponse)
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2338
2356
|
// ../../packages/platform-client/project/types/project.types.ts
|
|
2339
2357
|
function toProjectType(raw) {
|
|
2340
2358
|
return raw === "greenfield" || raw === "brownfield" ? raw : void 0;
|
|
@@ -2814,6 +2832,40 @@ var HttpAttachmentClient = class {
|
|
|
2814
2832
|
}
|
|
2815
2833
|
};
|
|
2816
2834
|
|
|
2835
|
+
// ../../packages/platform-client/project/http/bundle.http-client.ts
|
|
2836
|
+
var HttpBundleClient = class {
|
|
2837
|
+
httpClient;
|
|
2838
|
+
constructor(httpClient) {
|
|
2839
|
+
this.httpClient = httpClient;
|
|
2840
|
+
}
|
|
2841
|
+
async listBundles(projectId, jwtToken, opts) {
|
|
2842
|
+
const queryParams = new URLSearchParams();
|
|
2843
|
+
if (opts?.status) queryParams.set("status", opts.status);
|
|
2844
|
+
const query = queryParams.toString();
|
|
2845
|
+
const path4 = query ? `/project/v1/projects/${projectId}/bundles?${query}` : `/project/v1/projects/${projectId}/bundles`;
|
|
2846
|
+
const raw = await this.httpClient.get(path4, jwtToken, {});
|
|
2847
|
+
return raw.bundles.map(transformBundleResponse);
|
|
2848
|
+
}
|
|
2849
|
+
async renameBundle(projectId, req, jwtToken) {
|
|
2850
|
+
const raw = await this.httpClient.post(
|
|
2851
|
+
`/project/v1/projects/${projectId}/renameBundle`,
|
|
2852
|
+
{ old_name: req.oldName, new_name: req.newName },
|
|
2853
|
+
jwtToken,
|
|
2854
|
+
{}
|
|
2855
|
+
);
|
|
2856
|
+
return transformBundleResponse(raw);
|
|
2857
|
+
}
|
|
2858
|
+
async moveSpecFolder(projectId, req, jwtToken) {
|
|
2859
|
+
const raw = await this.httpClient.post(
|
|
2860
|
+
`/project/v1/projects/${projectId}/moveSpecFolder`,
|
|
2861
|
+
{ from_path: req.fromPath, to_path: req.toPath },
|
|
2862
|
+
jwtToken,
|
|
2863
|
+
{}
|
|
2864
|
+
);
|
|
2865
|
+
return transformSpecFolderMoveResponse(raw);
|
|
2866
|
+
}
|
|
2867
|
+
};
|
|
2868
|
+
|
|
2817
2869
|
// ../../packages/platform-client/project/http/file.http-client.ts
|
|
2818
2870
|
var HEADER_EXPECTED_VERSION = "X-Expected-Version";
|
|
2819
2871
|
var HEADER_ACTOR_KIND = "X-Actor-Kind";
|
|
@@ -4086,6 +4138,16 @@ var PlatformClient = class {
|
|
|
4086
4138
|
async trashFile(fileId) {
|
|
4087
4139
|
await this.withTokenRetry((jwt, c) => c.file.trashFile(fileId, jwt, { actorKind: "ai" }));
|
|
4088
4140
|
}
|
|
4141
|
+
/**
|
|
4142
|
+
* Moves a folder under `specs/` (a bundle, or a group folder holding
|
|
4143
|
+
* bundles) and everything in it to a new path. Paths include the `specs/`
|
|
4144
|
+
* root. Only file paths change: ids, contents and storage stay put.
|
|
4145
|
+
*/
|
|
4146
|
+
async moveSpecFolder(projectId, fromPath, toPath) {
|
|
4147
|
+
return this.withTokenRetry(
|
|
4148
|
+
(jwt, c) => c.bundle.moveSpecFolder(projectId, { fromPath, toPath }, jwt)
|
|
4149
|
+
);
|
|
4150
|
+
}
|
|
4089
4151
|
/** Restores a spec file from the Trash Bin back to the active file list. */
|
|
4090
4152
|
async restoreFile(fileId) {
|
|
4091
4153
|
await this.withTokenRetry((jwt, c) => c.file.restoreFile(fileId, jwt));
|
|
@@ -4186,6 +4248,7 @@ var PlatformClient = class {
|
|
|
4186
4248
|
const httpClient = new BaseHttpClient({ baseUrl, logger: silentLogger });
|
|
4187
4249
|
return {
|
|
4188
4250
|
project: new HttpProjectClient(httpClient),
|
|
4251
|
+
bundle: new HttpBundleClient(httpClient),
|
|
4189
4252
|
session: new HttpSpecSessionClient(httpClient),
|
|
4190
4253
|
file: new HttpFileClient(httpClient),
|
|
4191
4254
|
attachment: new HttpAttachmentClient(httpClient),
|
|
@@ -5336,9 +5399,114 @@ var moveSpecFileToTrashTool = {
|
|
|
5336
5399
|
}
|
|
5337
5400
|
};
|
|
5338
5401
|
|
|
5339
|
-
// src/server/tools/
|
|
5402
|
+
// src/server/tools/move_spec_folder.ts
|
|
5340
5403
|
import { z as z16 } from "zod";
|
|
5341
5404
|
|
|
5405
|
+
// src/server/spec-path.ts
|
|
5406
|
+
var MAX_SPEC_DIR_DEPTH = 3;
|
|
5407
|
+
function validateSpecFilePath(filePath) {
|
|
5408
|
+
if (!filePath) {
|
|
5409
|
+
return "file_path is required.";
|
|
5410
|
+
}
|
|
5411
|
+
if (filePath.startsWith("/")) {
|
|
5412
|
+
return "file_path must be a relative path, not an absolute path.";
|
|
5413
|
+
}
|
|
5414
|
+
const segments = filePath.split("/");
|
|
5415
|
+
if (segments.some((s) => s === "..")) {
|
|
5416
|
+
return "file_path must not contain '..' (parent directory) segments.";
|
|
5417
|
+
}
|
|
5418
|
+
if (segments.some((s) => s === "" || s === ".")) {
|
|
5419
|
+
return 'file_path must be a normalized path (no empty, "." or trailing-slash segments).';
|
|
5420
|
+
}
|
|
5421
|
+
const root = segments[0];
|
|
5422
|
+
if (root !== "specs" && root !== "openspec") {
|
|
5423
|
+
return "file_path must be rooted at 'specs' or 'openspec' (e.g. specs/changes/add-oauth-login/proposal.md).";
|
|
5424
|
+
}
|
|
5425
|
+
if (segments.length < 2) {
|
|
5426
|
+
return `file_path must include a filename under '${root}' (e.g. ${root}/proposal.md).`;
|
|
5427
|
+
}
|
|
5428
|
+
const dirLevels = segments.length - 2;
|
|
5429
|
+
if (dirLevels > MAX_SPEC_DIR_DEPTH) {
|
|
5430
|
+
return `file_path must not nest deeper than ${String(MAX_SPEC_DIR_DEPTH)} directory levels below the '${root}' root.`;
|
|
5431
|
+
}
|
|
5432
|
+
return null;
|
|
5433
|
+
}
|
|
5434
|
+
function validateSpecFolderPath(field, folderPath) {
|
|
5435
|
+
const path4 = folderPath.endsWith("/") ? folderPath.slice(0, -1) : folderPath;
|
|
5436
|
+
if (!path4) {
|
|
5437
|
+
return { error: `${field} is required.` };
|
|
5438
|
+
}
|
|
5439
|
+
if (path4.startsWith("/")) {
|
|
5440
|
+
return { error: `${field} must be a relative path, not an absolute path.` };
|
|
5441
|
+
}
|
|
5442
|
+
const segments = path4.split("/");
|
|
5443
|
+
if (segments.some((s) => s === "..")) {
|
|
5444
|
+
return { error: `${field} must not contain '..' (parent directory) segments.` };
|
|
5445
|
+
}
|
|
5446
|
+
if (segments.some((s) => s === "" || s === ".")) {
|
|
5447
|
+
return { error: `${field} must be a normalized path (no empty or "." segments).` };
|
|
5448
|
+
}
|
|
5449
|
+
if (segments[0] === "openspec") {
|
|
5450
|
+
return {
|
|
5451
|
+
error: `${field}: OpenSpec folders cannot be moved; the OpenSpec CLI relies on the change id.`
|
|
5452
|
+
};
|
|
5453
|
+
}
|
|
5454
|
+
if (segments[0] !== "specs" || segments.length < 2) {
|
|
5455
|
+
return { error: `${field} must be a folder under specs/ (e.g. specs/group/my-bundle).` };
|
|
5456
|
+
}
|
|
5457
|
+
if (segments.length - 1 > MAX_SPEC_DIR_DEPTH) {
|
|
5458
|
+
return {
|
|
5459
|
+
error: `${field} must not nest deeper than ${String(MAX_SPEC_DIR_DEPTH)} directory levels below 'specs'.`
|
|
5460
|
+
};
|
|
5461
|
+
}
|
|
5462
|
+
return { path: path4 };
|
|
5463
|
+
}
|
|
5464
|
+
var FILE_TYPE_BY_BASENAME = {
|
|
5465
|
+
constitution: "constitution",
|
|
5466
|
+
requirements: "requirements",
|
|
5467
|
+
solution: "solution",
|
|
5468
|
+
tasks: "tasks",
|
|
5469
|
+
// Enterprise greenfield quality plan (generate-greenfield-enterprise).
|
|
5470
|
+
quality: "quality",
|
|
5471
|
+
proposal: "proposal"
|
|
5472
|
+
};
|
|
5473
|
+
function deriveFileType(filePath) {
|
|
5474
|
+
const filename = filePath.split("/").pop() ?? "";
|
|
5475
|
+
const base = filename.replace(/\.[^.]+$/, "").toLowerCase();
|
|
5476
|
+
return FILE_TYPE_BY_BASENAME[base] ?? "openspec-spec";
|
|
5477
|
+
}
|
|
5478
|
+
|
|
5479
|
+
// src/server/tools/move_spec_folder.ts
|
|
5480
|
+
var inputSchema16 = {
|
|
5481
|
+
project_id: z16.string().min(1).describe("UUID of the project holding the folder."),
|
|
5482
|
+
from_path: z16.string().min(1).describe('Folder to move, under "specs" (e.g. specs/mcp or specs/mcp/my-bundle).'),
|
|
5483
|
+
to_path: z16.string().min(1).describe(
|
|
5484
|
+
'New folder path: "specs" plus 1-3 lowercase kebab-case segments (e.g. specs/archive/mcp). Under the same parent, this renames the folder.'
|
|
5485
|
+
)
|
|
5486
|
+
};
|
|
5487
|
+
var moveSpecFolderTool = {
|
|
5488
|
+
name: "move_spec_folder",
|
|
5489
|
+
description: "Move or rename a folder under specs/ together with everything in it. The folder can be a bundle (it holds spec files directly) or a group folder holding bundles. The files of a bundle always move together; single files cannot be moved out of a bundle. File ids, contents and revisions stay the same; only paths change, so address the files by their new paths afterwards. Refused: OpenSpec folders, specs/memory, a target inside the folder itself, a target that already exists (Trash included) or sits inside a bundle, a folder inside a bundle (move the bundle instead), a folder whose bundle is still generating, and a move that would nest a bundle more than 3 levels below specs/.",
|
|
5490
|
+
inputSchema: inputSchema16,
|
|
5491
|
+
handler: async (args, ctx) => {
|
|
5492
|
+
const from = validateSpecFolderPath("from_path", args.from_path);
|
|
5493
|
+
if (from.error !== void 0) return errorResult(from.error);
|
|
5494
|
+
const to = validateSpecFolderPath("to_path", args.to_path);
|
|
5495
|
+
if (to.error !== void 0) return errorResult(to.error);
|
|
5496
|
+
const moved = await ctx.client.moveSpecFolder(args.project_id, from.path, to.path);
|
|
5497
|
+
return jsonResult({
|
|
5498
|
+
moved: true,
|
|
5499
|
+
from_path: moved.fromPath,
|
|
5500
|
+
to_path: moved.toPath,
|
|
5501
|
+
file_count: moved.files.length,
|
|
5502
|
+
files: moved.files.map((f) => ({ id: f.id, file_path: f.filePath }))
|
|
5503
|
+
});
|
|
5504
|
+
}
|
|
5505
|
+
};
|
|
5506
|
+
|
|
5507
|
+
// src/server/tools/read_attachment.ts
|
|
5508
|
+
import { z as z17 } from "zod";
|
|
5509
|
+
|
|
5342
5510
|
// src/util/paginate-lines.ts
|
|
5343
5511
|
var MAX_LINES_PER_CALL = 2e3;
|
|
5344
5512
|
var MAX_RESPONSE_BYTES = 1 * 1024 * 1024;
|
|
@@ -5449,17 +5617,17 @@ var TEXT_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
|
5449
5617
|
"image/svg+xml"
|
|
5450
5618
|
]);
|
|
5451
5619
|
var GENERIC_MIME_TYPES = /* @__PURE__ */ new Set(["application/octet-stream", ""]);
|
|
5452
|
-
var
|
|
5453
|
-
attachment_id:
|
|
5454
|
-
offset:
|
|
5620
|
+
var inputSchema17 = {
|
|
5621
|
+
attachment_id: z17.string().min(1).describe("UUID of the attachment to read"),
|
|
5622
|
+
offset: z17.number().int().nonnegative().optional().describe(
|
|
5455
5623
|
"1-based line number to start reading from (text attachments only; default 1; 0 is an alias for 1)."
|
|
5456
5624
|
),
|
|
5457
|
-
limit:
|
|
5625
|
+
limit: z17.number().int().positive().optional().describe("Max lines to return (text attachments only; default 2000, capped at 2000).")
|
|
5458
5626
|
};
|
|
5459
5627
|
var readAttachmentTool = {
|
|
5460
5628
|
name: "read_attachment",
|
|
5461
5629
|
description: `Return an attachment's content inline. UTF-8 text is paginated by line (offset/limit, 1 MiB per response). Images (png, jpeg, gif, webp) are returned whole as an MCP image block \u2014 never paginated \u2014 up to the ${String(MAX_ATTACHMENT_MB)} MiB attachment limit; a 10 MiB image is roughly 13.4 MiB of base64, so check file_size_bytes (from get_attachment or list_attachments) before reading a large one. Other binary types (pdf, docx, xlsx, \u2026) cannot be read here; view them in the MySpec webapp.`,
|
|
5462
|
-
inputSchema:
|
|
5630
|
+
inputSchema: inputSchema17,
|
|
5463
5631
|
handler: async (args, ctx) => {
|
|
5464
5632
|
const meta = await ctx.client.getAttachmentMetadata(args.attachment_id);
|
|
5465
5633
|
if (!meta) {
|
|
@@ -5516,18 +5684,18 @@ var readAttachmentTool = {
|
|
|
5516
5684
|
// src/server/tools/read_spec_file.ts
|
|
5517
5685
|
import { promises as fs3 } from "fs";
|
|
5518
5686
|
import { createHash as createHash2 } from "crypto";
|
|
5519
|
-
import { z as
|
|
5687
|
+
import { z as z18 } from "zod";
|
|
5520
5688
|
var MAX_READABLE_SPEC_FILE_BYTES = 8 * 1024 * 1024;
|
|
5521
|
-
var
|
|
5522
|
-
file_id:
|
|
5523
|
-
revision:
|
|
5524
|
-
offset:
|
|
5525
|
-
limit:
|
|
5689
|
+
var inputSchema18 = {
|
|
5690
|
+
file_id: z18.string().min(1).describe("UUID of the file"),
|
|
5691
|
+
revision: z18.number().int().positive().optional().describe("Specific revision number to read (defaults to the latest revision)."),
|
|
5692
|
+
offset: z18.number().int().nonnegative().optional().describe("1-based line number to start reading from (default 1; 0 is an alias for 1)."),
|
|
5693
|
+
limit: z18.number().int().positive().optional().describe("Max lines to return (default 2000, capped at 2000; larger values are clamped).")
|
|
5526
5694
|
};
|
|
5527
5695
|
var readSpecFileTool = {
|
|
5528
5696
|
name: "read_spec_file",
|
|
5529
5697
|
description: "Return a window of a spec file's UTF-8 content, paginated by line (offset/limit, default and cap 2000 lines, 1 MiB per response). Large files are read across multiple calls using `next_offset`. Bytes are cached on disk under the configured cache root (default ~/.myspec) at `project/<projectId>/file/<fileId>/rev/<rev>/<basename>` and reused on subsequent reads of the same revision, so paging through a file downloads it once. Files above 8 MiB and binary content are refused \u2014 use download_spec_file to save those to a local file instead.",
|
|
5530
|
-
inputSchema:
|
|
5698
|
+
inputSchema: inputSchema18,
|
|
5531
5699
|
handler: async (args, ctx) => {
|
|
5532
5700
|
const meta = await ctx.client.getFileMetadata(args.file_id);
|
|
5533
5701
|
const targetRevision = args.revision ?? meta.revisionCount;
|
|
@@ -5657,17 +5825,17 @@ function sha256Prefixed2(content) {
|
|
|
5657
5825
|
}
|
|
5658
5826
|
|
|
5659
5827
|
// src/server/tools/rename_spec_session.ts
|
|
5660
|
-
import { z as
|
|
5661
|
-
var
|
|
5662
|
-
session_id:
|
|
5663
|
-
session_summary:
|
|
5828
|
+
import { z as z19 } from "zod";
|
|
5829
|
+
var inputSchema19 = {
|
|
5830
|
+
session_id: z19.string().min(1).describe("UUID of the spec session to rename"),
|
|
5831
|
+
session_summary: z19.string().min(1).max(SESSION_SUMMARY_MAX_LENGTH).describe(
|
|
5664
5832
|
`New human-readable session title (max ${String(SESSION_SUMMARY_MAX_LENGTH)} characters). Stored as context.sessionSummary \u2014 the field the webapp displays and searches.`
|
|
5665
5833
|
)
|
|
5666
5834
|
};
|
|
5667
5835
|
var renameSpecSessionTool = {
|
|
5668
5836
|
name: "rename_spec_session",
|
|
5669
5837
|
description: "Rename a spec session by updating its context.sessionSummary \u2014 the session's human-readable title (there is no first-class name field). Permitted on both active and archived sessions. All other context keys are preserved untouched.",
|
|
5670
|
-
inputSchema:
|
|
5838
|
+
inputSchema: inputSchema19,
|
|
5671
5839
|
handler: async (args, ctx) => {
|
|
5672
5840
|
if (args.session_summary.length > SESSION_SUMMARY_MAX_LENGTH) {
|
|
5673
5841
|
return errorResult(
|
|
@@ -5701,14 +5869,14 @@ var renameSpecSessionTool = {
|
|
|
5701
5869
|
};
|
|
5702
5870
|
|
|
5703
5871
|
// src/server/tools/restore_spec_file_from_trash.ts
|
|
5704
|
-
import { z as
|
|
5705
|
-
var
|
|
5706
|
-
file_id:
|
|
5872
|
+
import { z as z20 } from "zod";
|
|
5873
|
+
var inputSchema20 = {
|
|
5874
|
+
file_id: z20.string().min(1).describe("UUID of the spec file to restore from the Trash Bin.")
|
|
5707
5875
|
};
|
|
5708
5876
|
var restoreSpecFileFromTrashTool = {
|
|
5709
5877
|
name: "restore_spec_file_from_trash",
|
|
5710
5878
|
description: "Restore a spec file from the Trash Bin back to the active file list, reversing `move_spec_file_to_trash`. The file becomes visible again to the default file list and AI-agent tools.",
|
|
5711
|
-
inputSchema:
|
|
5879
|
+
inputSchema: inputSchema20,
|
|
5712
5880
|
handler: async (args, ctx) => {
|
|
5713
5881
|
await ctx.client.restoreFile(args.file_id);
|
|
5714
5882
|
return jsonResult({ restored: true, file_id: args.file_id });
|
|
@@ -5716,14 +5884,14 @@ var restoreSpecFileFromTrashTool = {
|
|
|
5716
5884
|
};
|
|
5717
5885
|
|
|
5718
5886
|
// src/server/tools/unarchive_project.ts
|
|
5719
|
-
import { z as
|
|
5720
|
-
var
|
|
5721
|
-
project_id:
|
|
5887
|
+
import { z as z21 } from "zod";
|
|
5888
|
+
var inputSchema21 = {
|
|
5889
|
+
project_id: z21.string().min(1).describe("UUID of the project to unarchive.")
|
|
5722
5890
|
};
|
|
5723
5891
|
var unarchiveProjectTool = {
|
|
5724
5892
|
name: "unarchive_project",
|
|
5725
5893
|
description: 'Unarchive a project (sets status to "active"). Idempotent: unarchiving an already-active project succeeds.',
|
|
5726
|
-
inputSchema:
|
|
5894
|
+
inputSchema: inputSchema21,
|
|
5727
5895
|
handler: async (args, ctx) => {
|
|
5728
5896
|
const project = await ctx.client.unarchiveProject(args.project_id);
|
|
5729
5897
|
return jsonResult({ project: projectSummary(project) });
|
|
@@ -5731,14 +5899,14 @@ var unarchiveProjectTool = {
|
|
|
5731
5899
|
};
|
|
5732
5900
|
|
|
5733
5901
|
// src/server/tools/unarchive_spec_session.ts
|
|
5734
|
-
import { z as
|
|
5735
|
-
var
|
|
5736
|
-
session_id:
|
|
5902
|
+
import { z as z22 } from "zod";
|
|
5903
|
+
var inputSchema22 = {
|
|
5904
|
+
session_id: z22.string().min(1).describe("UUID of the spec session to unarchive")
|
|
5737
5905
|
};
|
|
5738
5906
|
var unarchiveSpecSessionTool = {
|
|
5739
5907
|
name: "unarchive_spec_session",
|
|
5740
5908
|
description: "Unarchive a spec session, returning it to the active list.",
|
|
5741
|
-
inputSchema:
|
|
5909
|
+
inputSchema: inputSchema22,
|
|
5742
5910
|
handler: async (args, ctx) => {
|
|
5743
5911
|
let session;
|
|
5744
5912
|
try {
|
|
@@ -5755,19 +5923,19 @@ var unarchiveSpecSessionTool = {
|
|
|
5755
5923
|
};
|
|
5756
5924
|
|
|
5757
5925
|
// src/server/tools/update_project.ts
|
|
5758
|
-
import { z as
|
|
5759
|
-
var
|
|
5760
|
-
project_id:
|
|
5761
|
-
name:
|
|
5762
|
-
description:
|
|
5763
|
-
project_type:
|
|
5926
|
+
import { z as z23 } from "zod";
|
|
5927
|
+
var inputSchema23 = {
|
|
5928
|
+
project_id: z23.string().min(1).describe("UUID of the project to update."),
|
|
5929
|
+
name: z23.string().min(1).optional().describe("New project name."),
|
|
5930
|
+
description: z23.string().optional().describe("New project description."),
|
|
5931
|
+
project_type: z23.enum(["greenfield", "brownfield", "auto"]).optional().describe(
|
|
5764
5932
|
"Codebase type. 'greenfield' or 'brownfield' sets it explicitly; 'auto' clears it so the workflow decides. Omit to leave unchanged."
|
|
5765
5933
|
)
|
|
5766
5934
|
};
|
|
5767
5935
|
var updateProjectTool = {
|
|
5768
5936
|
name: "update_project",
|
|
5769
5937
|
description: "Update a project's metadata (name, description, project_type). Returns the updated metadata.",
|
|
5770
|
-
inputSchema:
|
|
5938
|
+
inputSchema: inputSchema23,
|
|
5771
5939
|
handler: async (args, ctx) => {
|
|
5772
5940
|
if (args.name === void 0 && args.description === void 0 && args.project_type === void 0) {
|
|
5773
5941
|
return errorResult(
|
|
@@ -5790,7 +5958,7 @@ var updateProjectTool = {
|
|
|
5790
5958
|
};
|
|
5791
5959
|
|
|
5792
5960
|
// src/server/tools/update_spec_file.ts
|
|
5793
|
-
import { z as
|
|
5961
|
+
import { z as z24 } from "zod";
|
|
5794
5962
|
|
|
5795
5963
|
// src/server/tools/spec-content-source.ts
|
|
5796
5964
|
import { readFile, stat } from "fs/promises";
|
|
@@ -5846,24 +6014,24 @@ async function resolveSpecContent(input, maxBytes = MAX_LOCAL_SPEC_FILE_BYTES) {
|
|
|
5846
6014
|
}
|
|
5847
6015
|
|
|
5848
6016
|
// src/server/tools/update_spec_file.ts
|
|
5849
|
-
var
|
|
5850
|
-
file_id:
|
|
5851
|
-
project_id:
|
|
5852
|
-
file_path:
|
|
5853
|
-
content:
|
|
6017
|
+
var inputSchema24 = {
|
|
6018
|
+
file_id: z24.string().min(1).optional().describe("UUID of the spec file to overwrite. Provide this or (project_id + file_path)."),
|
|
6019
|
+
project_id: z24.string().min(1).optional().describe("UUID of the project. Required (with file_path) when file_id is not given."),
|
|
6020
|
+
file_path: z24.string().min(1).optional().describe("Path of the spec file. Required (with project_id) when file_id is not given."),
|
|
6021
|
+
content: z24.string().min(1).optional().describe(
|
|
5854
6022
|
"Full UTF-8 replacement content, saved as a new revision. Provide this OR local_file_path (exactly one). Use for in-memory / generated content."
|
|
5855
6023
|
),
|
|
5856
|
-
local_file_path:
|
|
6024
|
+
local_file_path: z24.string().min(1).optional().describe(
|
|
5857
6025
|
"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."
|
|
5858
6026
|
),
|
|
5859
|
-
expected_version:
|
|
6027
|
+
expected_version: z24.number().int().nonnegative().optional().describe(
|
|
5860
6028
|
"The content_version the new body was based on, from the get_spec_file or read_spec_file you worked from. The update is rejected with an actionable error if someone else changed the file since. Omit only when overwriting unconditionally is genuinely intended \u2014 the project may be configured to reject unversioned updates."
|
|
5861
6029
|
)
|
|
5862
6030
|
};
|
|
5863
6031
|
var updateSpecFileTool = {
|
|
5864
6032
|
name: "update_spec_file",
|
|
5865
6033
|
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). Pass `expected_version` from the get_spec_file you based the new body on so a concurrent edit by someone else is reported instead of silently overwritten.",
|
|
5866
|
-
inputSchema:
|
|
6034
|
+
inputSchema: inputSchema24,
|
|
5867
6035
|
handler: async (args, ctx) => {
|
|
5868
6036
|
const resolved = await resolveSpecContent({
|
|
5869
6037
|
content: args.content,
|
|
@@ -5925,7 +6093,7 @@ function conflictMessage(filePath, conflict) {
|
|
|
5925
6093
|
}
|
|
5926
6094
|
|
|
5927
6095
|
// src/server/tools/spec_file_comments.ts
|
|
5928
|
-
import { z as
|
|
6096
|
+
import { z as z25 } from "zod";
|
|
5929
6097
|
var LIST_BODY_PREVIEW_CHARS = 1e3;
|
|
5930
6098
|
var LIST_MAX_THREADS = 100;
|
|
5931
6099
|
function commentSummary(comment, opts = {}) {
|
|
@@ -5971,7 +6139,7 @@ function threadSummary(thread, opts = {}) {
|
|
|
5971
6139
|
};
|
|
5972
6140
|
}
|
|
5973
6141
|
function uuidField(what) {
|
|
5974
|
-
return
|
|
6142
|
+
return z25.uuid().describe(what);
|
|
5975
6143
|
}
|
|
5976
6144
|
var CONFLICT_REASON = {
|
|
5977
6145
|
fileTrashed: "file_trashed",
|
|
@@ -5995,7 +6163,7 @@ function conflictAdvice(err, parts) {
|
|
|
5995
6163
|
}
|
|
5996
6164
|
var listInputSchema = {
|
|
5997
6165
|
file_id: uuidField("UUID of the spec file whose comments to list."),
|
|
5998
|
-
status:
|
|
6166
|
+
status: z25.enum(["open", "resolved", "orphaned"]).optional().describe(
|
|
5999
6167
|
"Narrow to open (unresolved) threads, resolved ones, or orphaned ones whose passage can no longer be located. Omit for all of them."
|
|
6000
6168
|
)
|
|
6001
6169
|
};
|
|
@@ -6019,19 +6187,19 @@ var listSpecFileCommentsTool = {
|
|
|
6019
6187
|
};
|
|
6020
6188
|
var createInputSchema = {
|
|
6021
6189
|
file_id: uuidField("UUID of the spec file to comment on."),
|
|
6022
|
-
body:
|
|
6190
|
+
body: z25.string().min(1).max(MAX_COMMENT_BODY_LENGTH).describe("The comment text, in markdown."),
|
|
6023
6191
|
// The three anchor limits are exported by the contract for exactly this, and
|
|
6024
6192
|
// the context one is SHORT (64). A description reading "the text immediately
|
|
6025
6193
|
// BEFORE the quote" invites a whole preceding sentence, which is the default
|
|
6026
6194
|
// reading rather than a corner case — so the ceiling is enforced in the
|
|
6027
6195
|
// schema, where the model sees it, rather than discovered as a 400.
|
|
6028
|
-
anchor_quote:
|
|
6196
|
+
anchor_quote: z25.string().min(1).max(MAX_ANCHOR_QUOTE_LENGTH).describe(
|
|
6029
6197
|
`The exact passage this comment is about, copied verbatim from the file. The server searches the current content for it, so it must match the text as written. Read the file with read_spec_file first. At most ${String(MAX_ANCHOR_QUOTE_LENGTH)} characters.`
|
|
6030
6198
|
),
|
|
6031
|
-
anchor_prefix:
|
|
6199
|
+
anchor_prefix: z25.string().max(MAX_ANCHOR_CONTEXT_LENGTH).optional().describe(
|
|
6032
6200
|
`The text immediately BEFORE the quote, which disambiguates a passage appearing more than once. At most ${String(MAX_ANCHOR_CONTEXT_LENGTH)} characters \u2014 a few words, not a sentence. Send an empty string when the quote starts the file.`
|
|
6033
6201
|
),
|
|
6034
|
-
anchor_suffix:
|
|
6202
|
+
anchor_suffix: z25.string().max(MAX_ANCHOR_CONTEXT_LENGTH).optional().describe(
|
|
6035
6203
|
`The text immediately AFTER the quote. At most ${String(MAX_ANCHOR_CONTEXT_LENGTH)} characters. Empty when the quote ends the file.`
|
|
6036
6204
|
)
|
|
6037
6205
|
};
|
|
@@ -6072,7 +6240,7 @@ var createSpecFileCommentThreadTool = {
|
|
|
6072
6240
|
var replyInputSchema = {
|
|
6073
6241
|
file_id: uuidField("UUID of the spec file the thread belongs to."),
|
|
6074
6242
|
thread_id: uuidField("UUID of the thread to reply to, from list_spec_file_comments."),
|
|
6075
|
-
body:
|
|
6243
|
+
body: z25.string().min(1).max(MAX_COMMENT_BODY_LENGTH).describe("The reply text, in markdown.")
|
|
6076
6244
|
};
|
|
6077
6245
|
var replySpecFileCommentThreadTool = {
|
|
6078
6246
|
name: "reply_spec_file_comment_thread",
|
|
@@ -6102,8 +6270,8 @@ var replySpecFileCommentThreadTool = {
|
|
|
6102
6270
|
var updateInputSchema = {
|
|
6103
6271
|
file_id: uuidField("UUID of the spec file the comment belongs to."),
|
|
6104
6272
|
comment_id: uuidField("UUID of the comment to edit. Must be your own."),
|
|
6105
|
-
body:
|
|
6106
|
-
expected_version:
|
|
6273
|
+
body: z25.string().min(1).max(MAX_COMMENT_BODY_LENGTH).describe("The replacement text, in markdown. Replaces the comment entirely."),
|
|
6274
|
+
expected_version: z25.number().int().nonnegative().optional().describe(
|
|
6107
6275
|
"The COMMENT's own content_version as last read by list_spec_file_comments \u2014 not the file's. Sending it makes the edit fail rather than silently overwrite one made since."
|
|
6108
6276
|
)
|
|
6109
6277
|
};
|
|
@@ -6162,7 +6330,7 @@ var deleteSpecFileCommentTool = {
|
|
|
6162
6330
|
import { createHash as createHash3 } from "crypto";
|
|
6163
6331
|
import { lstat, readFile as readFile2 } from "fs/promises";
|
|
6164
6332
|
import { basename, extname, isAbsolute as isAbsolute2 } from "path";
|
|
6165
|
-
import { z as
|
|
6333
|
+
import { z as z26 } from "zod";
|
|
6166
6334
|
var DEFAULT_MIME = "application/octet-stream";
|
|
6167
6335
|
var MAX_ATTACHMENT_MB2 = MAX_ATTACHMENT_BYTES / (1024 * 1024);
|
|
6168
6336
|
var STRUCTURED_EXTENSION_MIME = {
|
|
@@ -6267,23 +6435,23 @@ function inferMimeFromName(name) {
|
|
|
6267
6435
|
}
|
|
6268
6436
|
return DEFAULT_MIME;
|
|
6269
6437
|
}
|
|
6270
|
-
var
|
|
6271
|
-
project_id:
|
|
6272
|
-
file_path:
|
|
6438
|
+
var inputSchema25 = {
|
|
6439
|
+
project_id: z26.string().min(1).describe("UUID of the project to attach the file to."),
|
|
6440
|
+
file_path: z26.string().min(1).describe(
|
|
6273
6441
|
"Absolute path to the local file to upload. The MCP server reads this path from its own host."
|
|
6274
6442
|
),
|
|
6275
|
-
file_name:
|
|
6276
|
-
mime_type:
|
|
6443
|
+
file_name: z26.string().min(1).optional().describe("Override the filename recorded on the attachment. Defaults to the path basename."),
|
|
6444
|
+
mime_type: z26.string().min(1).optional().describe(
|
|
6277
6445
|
"Override the MIME type sent to the platform. The platform re-detects from content, so this is only a hint. If omitted, inferred from the file extension \u2014 structured (.pdf, .docx, .xlsx) keep their canonical MIME; common UTF-8 text formats (.xml, .json, .yaml, .html, .csv, .md, source code, ...) are sent as text/plain."
|
|
6278
6446
|
),
|
|
6279
|
-
override:
|
|
6447
|
+
override: z26.boolean().optional().describe(
|
|
6280
6448
|
"When true, any existing (non-deleted) attachment on the project whose recorded filename exactly matches `file_name` (or the path basename) is soft-deleted before the new upload, so the new attachment keeps the original name instead of getting an auto-dedup `(1)` suffix. The platform has no in-place content-replace API, so the new upload always gets a new `attachment_id`; any replaced ids are returned as `overridden_attachment_ids` (array, possibly empty) so callers can refresh stored references. Defaults to false (let the platform auto-dedup)."
|
|
6281
6449
|
)
|
|
6282
6450
|
};
|
|
6283
6451
|
var uploadAttachmentTool = {
|
|
6284
6452
|
name: "upload_attachment",
|
|
6285
6453
|
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_MB2)} MB. Supported content: PDF, DOCX, XLSX, images (PNG, JPEG, GIF, WEBP), and any UTF-8 text file (XML, JSON, YAML, HTML, SVG, Markdown, CSV, source code, ...). Other binary files are rejected by the platform.`,
|
|
6286
|
-
inputSchema:
|
|
6454
|
+
inputSchema: inputSchema25,
|
|
6287
6455
|
handler: async (args, ctx) => {
|
|
6288
6456
|
if (!isAbsolute2(args.file_path)) {
|
|
6289
6457
|
return errorResult(`file_path must be an absolute path; got "${args.file_path}".`);
|
|
@@ -6407,53 +6575,7 @@ var uploadAttachmentTool = {
|
|
|
6407
6575
|
};
|
|
6408
6576
|
|
|
6409
6577
|
// src/server/tools/upload_spec_file.ts
|
|
6410
|
-
import { z as
|
|
6411
|
-
|
|
6412
|
-
// src/server/spec-path.ts
|
|
6413
|
-
var MAX_SPEC_DIR_DEPTH = 3;
|
|
6414
|
-
function validateSpecFilePath(filePath) {
|
|
6415
|
-
if (!filePath) {
|
|
6416
|
-
return "file_path is required.";
|
|
6417
|
-
}
|
|
6418
|
-
if (filePath.startsWith("/")) {
|
|
6419
|
-
return "file_path must be a relative path, not an absolute path.";
|
|
6420
|
-
}
|
|
6421
|
-
const segments = filePath.split("/");
|
|
6422
|
-
if (segments.some((s) => s === "..")) {
|
|
6423
|
-
return "file_path must not contain '..' (parent directory) segments.";
|
|
6424
|
-
}
|
|
6425
|
-
if (segments.some((s) => s === "" || s === ".")) {
|
|
6426
|
-
return 'file_path must be a normalized path (no empty, "." or trailing-slash segments).';
|
|
6427
|
-
}
|
|
6428
|
-
const root = segments[0];
|
|
6429
|
-
if (root !== "specs" && root !== "openspec") {
|
|
6430
|
-
return "file_path must be rooted at 'specs' or 'openspec' (e.g. specs/changes/add-oauth-login/proposal.md).";
|
|
6431
|
-
}
|
|
6432
|
-
if (segments.length < 2) {
|
|
6433
|
-
return `file_path must include a filename under '${root}' (e.g. ${root}/proposal.md).`;
|
|
6434
|
-
}
|
|
6435
|
-
const dirLevels = segments.length - 2;
|
|
6436
|
-
if (dirLevels > MAX_SPEC_DIR_DEPTH) {
|
|
6437
|
-
return `file_path must not nest deeper than ${String(MAX_SPEC_DIR_DEPTH)} directory levels below the '${root}' root.`;
|
|
6438
|
-
}
|
|
6439
|
-
return null;
|
|
6440
|
-
}
|
|
6441
|
-
var FILE_TYPE_BY_BASENAME = {
|
|
6442
|
-
constitution: "constitution",
|
|
6443
|
-
requirements: "requirements",
|
|
6444
|
-
solution: "solution",
|
|
6445
|
-
tasks: "tasks",
|
|
6446
|
-
// Enterprise greenfield quality plan (generate-greenfield-enterprise).
|
|
6447
|
-
quality: "quality",
|
|
6448
|
-
proposal: "proposal"
|
|
6449
|
-
};
|
|
6450
|
-
function deriveFileType(filePath) {
|
|
6451
|
-
const filename = filePath.split("/").pop() ?? "";
|
|
6452
|
-
const base = filename.replace(/\.[^.]+$/, "").toLowerCase();
|
|
6453
|
-
return FILE_TYPE_BY_BASENAME[base] ?? "openspec-spec";
|
|
6454
|
-
}
|
|
6455
|
-
|
|
6456
|
-
// src/server/tools/upload_spec_file.ts
|
|
6578
|
+
import { z as z27 } from "zod";
|
|
6457
6579
|
var SPEC_FILE_TYPES = [
|
|
6458
6580
|
"constitution",
|
|
6459
6581
|
"requirements",
|
|
@@ -6463,23 +6585,23 @@ var SPEC_FILE_TYPES = [
|
|
|
6463
6585
|
"proposal",
|
|
6464
6586
|
"openspec-spec"
|
|
6465
6587
|
];
|
|
6466
|
-
var
|
|
6467
|
-
project_id:
|
|
6468
|
-
file_path:
|
|
6588
|
+
var inputSchema26 = {
|
|
6589
|
+
project_id: z27.string().min(1).describe("UUID of the project to upload the file into."),
|
|
6590
|
+
file_path: z27.string().min(1).describe(
|
|
6469
6591
|
'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).'
|
|
6470
6592
|
),
|
|
6471
|
-
content:
|
|
6593
|
+
content: z27.string().min(1).optional().describe(
|
|
6472
6594
|
"Full UTF-8 text content of the spec file. Provide this OR local_file_path (exactly one). Use for in-memory / generated content."
|
|
6473
6595
|
),
|
|
6474
|
-
local_file_path:
|
|
6596
|
+
local_file_path: z27.string().min(1).optional().describe(
|
|
6475
6597
|
"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."
|
|
6476
6598
|
),
|
|
6477
|
-
file_type:
|
|
6599
|
+
file_type: z27.enum(SPEC_FILE_TYPES).optional().describe("Spec file type. Omit to derive from the filename (e.g. proposal.md \u2192 proposal).")
|
|
6478
6600
|
};
|
|
6479
6601
|
var uploadSpecFileTool = {
|
|
6480
6602
|
name: "upload_spec_file",
|
|
6481
6603
|
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).",
|
|
6482
|
-
inputSchema:
|
|
6604
|
+
inputSchema: inputSchema26,
|
|
6483
6605
|
handler: async (args, ctx) => {
|
|
6484
6606
|
const pathError = validateSpecFilePath(args.file_path);
|
|
6485
6607
|
if (pathError) {
|
|
@@ -6510,16 +6632,16 @@ var uploadSpecFileTool = {
|
|
|
6510
6632
|
};
|
|
6511
6633
|
|
|
6512
6634
|
// src/server/tools/list_artifacts.ts
|
|
6513
|
-
import { z as
|
|
6635
|
+
import { z as z29 } from "zod";
|
|
6514
6636
|
|
|
6515
6637
|
// src/server/tools/artifact-output.ts
|
|
6516
|
-
import { z as
|
|
6517
|
-
var artifactFilesSchema =
|
|
6518
|
-
|
|
6519
|
-
path:
|
|
6520
|
-
content:
|
|
6521
|
-
encoding:
|
|
6522
|
-
content_type:
|
|
6638
|
+
import { z as z28 } from "zod";
|
|
6639
|
+
var artifactFilesSchema = z28.array(
|
|
6640
|
+
z28.object({
|
|
6641
|
+
path: z28.string().min(1).describe('Bundle-relative path, "/"-separated, no leading slash (e.g. assets/logo.svg).'),
|
|
6642
|
+
content: z28.string().describe("The file content, in `encoding` (default utf8)."),
|
|
6643
|
+
encoding: z28.enum(["utf8", "base64"]).optional().describe("utf8 (default) for text; base64 for binary files such as images."),
|
|
6644
|
+
content_type: z28.string().optional().describe("Hint used only for extensions the platform does not recognise.")
|
|
6523
6645
|
})
|
|
6524
6646
|
).describe("Files of the bundle.");
|
|
6525
6647
|
function artifactSummary(a) {
|
|
@@ -6591,15 +6713,15 @@ function artifactCreateConflictMessage(conflict) {
|
|
|
6591
6713
|
// src/server/tools/list_artifacts.ts
|
|
6592
6714
|
var LIST_DEFAULT4 = 50;
|
|
6593
6715
|
var LIST_MAX4 = 100;
|
|
6594
|
-
var
|
|
6595
|
-
project_id:
|
|
6596
|
-
limit:
|
|
6597
|
-
offset:
|
|
6716
|
+
var inputSchema27 = {
|
|
6717
|
+
project_id: z29.string().min(1).describe("UUID of the project whose artifacts to list."),
|
|
6718
|
+
limit: z29.number().int().positive().optional().describe(`Max artifacts to return (default ${String(LIST_DEFAULT4)}, capped at ${String(LIST_MAX4)}).`),
|
|
6719
|
+
offset: z29.number().int().nonnegative().optional().describe("Pagination offset (default 0).")
|
|
6598
6720
|
};
|
|
6599
6721
|
var listArtifactsTool = {
|
|
6600
6722
|
name: "list_artifacts",
|
|
6601
6723
|
description: "List a project's live artifacts (multi-file bundles such as HTML mockups, each versioned as one snapshot) with id, slug, entry path and revision_count. Returns `total` and, when more remain, `next_offset`. Use get_artifact for the file manifest and read_artifact_file for content.",
|
|
6602
|
-
inputSchema:
|
|
6724
|
+
inputSchema: inputSchema27,
|
|
6603
6725
|
handler: async (args, ctx) => {
|
|
6604
6726
|
const limit = clampLimit(args.limit, LIST_DEFAULT4, LIST_MAX4);
|
|
6605
6727
|
const offset = args.offset ?? 0;
|
|
@@ -6617,15 +6739,15 @@ var listArtifactsTool = {
|
|
|
6617
6739
|
};
|
|
6618
6740
|
|
|
6619
6741
|
// src/server/tools/get_artifact.ts
|
|
6620
|
-
import { z as
|
|
6621
|
-
var
|
|
6622
|
-
artifact_id:
|
|
6623
|
-
revision:
|
|
6742
|
+
import { z as z30 } from "zod";
|
|
6743
|
+
var inputSchema28 = {
|
|
6744
|
+
artifact_id: z30.string().min(1).describe("UUID of the artifact."),
|
|
6745
|
+
revision: z30.number().int().positive().optional().describe("Revision whose manifest to return (defaults to the head revision).")
|
|
6624
6746
|
};
|
|
6625
6747
|
var getArtifactTool = {
|
|
6626
6748
|
name: "get_artifact",
|
|
6627
6749
|
description: "Get an artifact's metadata plus the file manifest (path, content type, size, checksum of every file) of its head revision, or of `revision` when given. `revision_count` is the head number AND the value to pass as expected_version when writing; it only ever grows because rollback appends a revision rather than discarding history.",
|
|
6628
|
-
inputSchema:
|
|
6750
|
+
inputSchema: inputSchema28,
|
|
6629
6751
|
handler: async (args, ctx) => {
|
|
6630
6752
|
const detail = await ctx.client.getArtifact(args.artifact_id);
|
|
6631
6753
|
if (args.revision === void 0) {
|
|
@@ -6642,19 +6764,19 @@ var getArtifactTool = {
|
|
|
6642
6764
|
};
|
|
6643
6765
|
|
|
6644
6766
|
// src/server/tools/read_artifact_file.ts
|
|
6645
|
-
import { z as
|
|
6767
|
+
import { z as z31 } from "zod";
|
|
6646
6768
|
var MAX_READABLE_ARTIFACT_FILE_BYTES = 8 * 1024 * 1024;
|
|
6647
|
-
var
|
|
6648
|
-
artifact_id:
|
|
6649
|
-
path:
|
|
6650
|
-
revision:
|
|
6651
|
-
offset:
|
|
6652
|
-
limit:
|
|
6769
|
+
var inputSchema29 = {
|
|
6770
|
+
artifact_id: z31.string().min(1).describe("UUID of the artifact."),
|
|
6771
|
+
path: z31.string().min(1).describe("Bundle-relative path of the file (e.g. index.html, assets/app.js)."),
|
|
6772
|
+
revision: z31.number().int().positive().optional().describe("Revision to read from (defaults to the head revision)."),
|
|
6773
|
+
offset: z31.number().int().nonnegative().optional().describe("1-based line number to start reading from (default 1; 0 is an alias for 1)."),
|
|
6774
|
+
limit: z31.number().int().positive().optional().describe("Max lines to return (default 2000, capped at 2000; larger values are clamped).")
|
|
6653
6775
|
};
|
|
6654
6776
|
var readArtifactFileTool = {
|
|
6655
6777
|
name: "read_artifact_file",
|
|
6656
6778
|
description: "Return one artifact file's UTF-8 content, paginated by line (offset/limit, default and cap 2000 lines, 1 MiB per response) with `total_lines`, `truncated` and `next_offset`. Reads the head revision unless `revision` is given. Binary files (images, fonts) cannot be read through this tool; get_artifact lists their size and checksum.",
|
|
6657
|
-
inputSchema:
|
|
6779
|
+
inputSchema: inputSchema29,
|
|
6658
6780
|
handler: async (args, ctx) => {
|
|
6659
6781
|
const file = await ctx.client.downloadArtifactFile(args.artifact_id, args.path, args.revision);
|
|
6660
6782
|
if (file.content.byteLength > MAX_READABLE_ARTIFACT_FILE_BYTES) {
|
|
@@ -6689,20 +6811,20 @@ var readArtifactFileTool = {
|
|
|
6689
6811
|
};
|
|
6690
6812
|
|
|
6691
6813
|
// src/server/tools/create_artifact.ts
|
|
6692
|
-
import { z as
|
|
6693
|
-
var
|
|
6694
|
-
project_id:
|
|
6695
|
-
name:
|
|
6696
|
-
slug:
|
|
6697
|
-
description:
|
|
6698
|
-
entry_path:
|
|
6699
|
-
message:
|
|
6814
|
+
import { z as z32 } from "zod";
|
|
6815
|
+
var inputSchema30 = {
|
|
6816
|
+
project_id: z32.string().min(1).describe("UUID of the project to create the artifact in."),
|
|
6817
|
+
name: z32.string().min(1).describe("Display name (max 200 characters)."),
|
|
6818
|
+
slug: z32.string().optional().describe("URL-safe identifier, unique within the project; derived from the name when omitted."),
|
|
6819
|
+
description: z32.string().optional().describe("Optional description (max 2000 characters)."),
|
|
6820
|
+
entry_path: z32.string().optional().describe("The file a renderer opens first. Defaults to index.html when present, else the only file."),
|
|
6821
|
+
message: z32.string().optional().describe("Short summary of this first revision (max 500 characters)."),
|
|
6700
6822
|
files: artifactFilesSchema
|
|
6701
6823
|
};
|
|
6702
6824
|
var createArtifactTool = {
|
|
6703
6825
|
name: "create_artifact",
|
|
6704
6826
|
description: "Create an artifact \u2014 a bundle of files (e.g. an HTML mockup: index.html + styles.css + app.js) the platform versions AS ONE snapshot \u2014 with the given files as revision 1. At most 50 files, 2 MiB per file, 10 MiB per revision. Fails with a conflict when the slug is already taken in the project (trashed artifacts still own their slug).",
|
|
6705
|
-
inputSchema:
|
|
6827
|
+
inputSchema: inputSchema30,
|
|
6706
6828
|
handler: async (args, ctx) => {
|
|
6707
6829
|
try {
|
|
6708
6830
|
const result = await ctx.client.createArtifact(args.project_id, {
|
|
@@ -6730,24 +6852,24 @@ var createArtifactTool = {
|
|
|
6730
6852
|
};
|
|
6731
6853
|
|
|
6732
6854
|
// src/server/tools/write_artifact_revision.ts
|
|
6733
|
-
import { z as
|
|
6734
|
-
var
|
|
6735
|
-
artifact_id:
|
|
6855
|
+
import { z as z33 } from "zod";
|
|
6856
|
+
var inputSchema31 = {
|
|
6857
|
+
artifact_id: z33.string().min(1).describe("UUID of the artifact."),
|
|
6736
6858
|
files: artifactFilesSchema,
|
|
6737
|
-
base_revision:
|
|
6859
|
+
base_revision: z33.number().int().positive().optional().describe(
|
|
6738
6860
|
"PATCH mode: start from this revision's files, upsert `files` by path and drop `remove` paths. Omit for FULL mode, where `files` is the complete new file set. Patch mode defaults expected_version to base_revision."
|
|
6739
6861
|
),
|
|
6740
|
-
remove:
|
|
6741
|
-
expected_version:
|
|
6862
|
+
remove: z33.array(z33.string().min(1)).optional().describe("Patch mode only: paths to drop from the base revision."),
|
|
6863
|
+
expected_version: z33.number().int().nonnegative().optional().describe(
|
|
6742
6864
|
"The revision_count this write is based on (from get_artifact). The platform refuses the write with a conflict if the head moved. Defaults to base_revision in patch mode, else the current head."
|
|
6743
6865
|
),
|
|
6744
|
-
message:
|
|
6745
|
-
entry_path:
|
|
6866
|
+
message: z33.string().optional().describe("Short summary of the change (max 500 characters)."),
|
|
6867
|
+
entry_path: z33.string().optional().describe("Change the file a renderer opens first, from this revision on.")
|
|
6746
6868
|
};
|
|
6747
6869
|
var writeArtifactRevisionTool = {
|
|
6748
6870
|
name: "write_artifact_revision",
|
|
6749
6871
|
description: "Write a new revision of an artifact. FULL mode (no base_revision): `files` becomes the whole bundle. PATCH mode (base_revision set): only the changed files are sent, plus `remove` for paths to drop \u2014 the platform stores the complete result either way. Every revision is immutable; the newest N are retained (50 by default, set by the platform; older ones are pruned); use rollback_artifact to restore an earlier one. Read the current head with get_artifact first and pass its revision_count as expected_version.",
|
|
6750
|
-
inputSchema:
|
|
6872
|
+
inputSchema: inputSchema31,
|
|
6751
6873
|
handler: async (args, ctx) => {
|
|
6752
6874
|
if (args.remove && args.remove.length > 0 && args.base_revision === void 0) {
|
|
6753
6875
|
return errorResult("`remove` is only valid in patch mode: set base_revision to the revision you are editing.");
|
|
@@ -6781,17 +6903,17 @@ var writeArtifactRevisionTool = {
|
|
|
6781
6903
|
};
|
|
6782
6904
|
|
|
6783
6905
|
// src/server/tools/rollback_artifact.ts
|
|
6784
|
-
import { z as
|
|
6785
|
-
var
|
|
6786
|
-
artifact_id:
|
|
6787
|
-
revision_number:
|
|
6788
|
-
expected_version:
|
|
6789
|
-
message:
|
|
6906
|
+
import { z as z34 } from "zod";
|
|
6907
|
+
var inputSchema32 = {
|
|
6908
|
+
artifact_id: z34.string().min(1).describe("UUID of the artifact."),
|
|
6909
|
+
revision_number: z34.number().int().positive().describe("The revision whose files to restore."),
|
|
6910
|
+
expected_version: z34.number().int().nonnegative().optional().describe("The revision_count this rollback is based on (from get_artifact); defaults to the current head."),
|
|
6911
|
+
message: z34.string().optional().describe('Optional summary; defaults to "Rolled back to revision N".')
|
|
6790
6912
|
};
|
|
6791
6913
|
var rollbackArtifactTool = {
|
|
6792
6914
|
name: "rollback_artifact",
|
|
6793
6915
|
description: "Restore an earlier revision of an artifact. Like `git revert`, this APPENDS a new revision whose files equal the chosen one \u2014 history is never rewritten, and the new head is revision_count + 1. Only the newest N revisions are retained (50 by default, set by the platform): older ones are pruned, and rolling back to a pruned revision fails with not found \u2014 list the revisions (get_artifact) first when in doubt. Rolling back to the head itself is allowed and recorded with no_change=true.",
|
|
6794
|
-
inputSchema:
|
|
6916
|
+
inputSchema: inputSchema32,
|
|
6795
6917
|
handler: async (args, ctx) => {
|
|
6796
6918
|
try {
|
|
6797
6919
|
const result = await ctx.client.rollbackArtifact(
|
|
@@ -6811,10 +6933,10 @@ var rollbackArtifactTool = {
|
|
|
6811
6933
|
};
|
|
6812
6934
|
|
|
6813
6935
|
// src/server/tools/create_stream_token.ts
|
|
6814
|
-
import { z as
|
|
6936
|
+
import { z as z36 } from "zod";
|
|
6815
6937
|
|
|
6816
6938
|
// src/server/tools/stream-token-output.ts
|
|
6817
|
-
import { z as
|
|
6939
|
+
import { z as z35 } from "zod";
|
|
6818
6940
|
var projectStreamEventTypes = [
|
|
6819
6941
|
"project.updated",
|
|
6820
6942
|
"project.deleted",
|
|
@@ -6833,7 +6955,7 @@ var projectStreamEventTypes = [
|
|
|
6833
6955
|
"spec_session.deleted",
|
|
6834
6956
|
"*"
|
|
6835
6957
|
];
|
|
6836
|
-
var streamEventTypesSchema =
|
|
6958
|
+
var streamEventTypesSchema = z35.array(z35.enum(projectStreamEventTypes)).min(1).describe(
|
|
6837
6959
|
'Event types to receive. Required \u2014 pass ["*"] to follow all of them. spec_file.updated covers new revisions, rollbacks, and moves to or from the trash; spec_file.lock covers acquire, renew, release and takeover; spec_session.updated covers completion, archive, unarchive and rename; spec_file_comment.updated covers a body edit and a soft delete, thread_state_changed covers resolve and unresolve, and anchors_updated is batched across every thread one save moved.'
|
|
6838
6960
|
);
|
|
6839
6961
|
function streamTokenSummary(token) {
|
|
@@ -6863,19 +6985,19 @@ function streamTokenMintOutput(result) {
|
|
|
6863
6985
|
}
|
|
6864
6986
|
|
|
6865
6987
|
// src/server/tools/create_stream_token.ts
|
|
6866
|
-
var
|
|
6867
|
-
resource_type:
|
|
6868
|
-
resource_id:
|
|
6988
|
+
var inputSchema33 = {
|
|
6989
|
+
resource_type: z36.enum(["project"]).default("project").describe("What to follow. Only 'project' is supported today."),
|
|
6990
|
+
resource_id: z36.string().min(1).describe("UUID of the project to stream events for."),
|
|
6869
6991
|
event_types: streamEventTypesSchema,
|
|
6870
|
-
ttl_seconds:
|
|
6992
|
+
ttl_seconds: z36.number().int().positive().optional().describe(
|
|
6871
6993
|
"How long the URL stays valid. Defaults to 24 hours, capped at 7 days. Out-of-range values are clamped rather than rejected \u2014 check expires_at in the result."
|
|
6872
6994
|
),
|
|
6873
|
-
label:
|
|
6995
|
+
label: z36.string().optional().describe("Short human-readable label, so a person can recognise this token in a listing.")
|
|
6874
6996
|
};
|
|
6875
6997
|
var createStreamTokenTool = {
|
|
6876
6998
|
name: "create_stream_token",
|
|
6877
6999
|
description: "Create a short-lived WebSocket URL that streams a project's events in real time \u2014 like an S3 presigned URL, the returned URL is the whole credential and needs no auth header. Choose the project and the event types to follow; the result carries the full wss:// URL. Feed it to any WebSocket client with subprotocol 'v1' (for example Claude Code's Monitor tool). Each text frame is one JSON event carrying a `seq` cursor; reconnect with ?after=<seq> to resume without missing events. Defaults to 24 hours, 7 days maximum. TREAT THE RETURNED URL LIKE A PASSWORD: anyone holding it can read every event on this project until it expires, without authenticating. Pass it directly to the WebSocket client; never write it to a file, a commit, an issue or a chat reply, and never log or repeat it. It is shown once and cannot be retrieved again. If it may have been exposed, revoke it at once with revoke_stream_token \u2014 that is also how you end one early. Prefer the shortest ttl_seconds that covers the work.",
|
|
6878
|
-
inputSchema:
|
|
7000
|
+
inputSchema: inputSchema33,
|
|
6879
7001
|
handler: async (args, ctx) => {
|
|
6880
7002
|
const result = await ctx.client.createStreamToken({
|
|
6881
7003
|
resourceType: args.resource_type,
|
|
@@ -6889,22 +7011,22 @@ var createStreamTokenTool = {
|
|
|
6889
7011
|
};
|
|
6890
7012
|
|
|
6891
7013
|
// src/server/tools/list_stream_tokens.ts
|
|
6892
|
-
import { z as
|
|
7014
|
+
import { z as z37 } from "zod";
|
|
6893
7015
|
var LIST_DEFAULT5 = 50;
|
|
6894
7016
|
var LIST_MAX5 = 200;
|
|
6895
|
-
var
|
|
6896
|
-
resource_id:
|
|
6897
|
-
live_only:
|
|
6898
|
-
mine_only:
|
|
7017
|
+
var inputSchema34 = {
|
|
7018
|
+
resource_id: z37.string().optional().describe("Only tokens following this project."),
|
|
7019
|
+
live_only: z37.boolean().optional().describe("Hide expired and revoked tokens."),
|
|
7020
|
+
mine_only: z37.boolean().optional().describe(
|
|
6899
7021
|
"Only tokens you created. Off by default, so a leaked URL can be found \u2014 note that revoking one you did not mint needs the organisation owner or admin role."
|
|
6900
7022
|
),
|
|
6901
|
-
limit:
|
|
6902
|
-
offset:
|
|
7023
|
+
limit: z37.number().int().positive().optional().describe(`Max tokens to return (default ${String(LIST_DEFAULT5)}, capped at ${String(LIST_MAX5)}).`),
|
|
7024
|
+
offset: z37.number().int().nonnegative().optional().describe("Pagination offset (default 0).")
|
|
6903
7025
|
};
|
|
6904
7026
|
var listStreamTokensTool = {
|
|
6905
7027
|
name: "list_stream_tokens",
|
|
6906
7028
|
description: "List the stream tokens in your organisation, with their scope, expiry and whether each is still live. Secrets are never returned \u2014 only a short prefix to tell tokens apart. Returns `total` and, when more remain, `next_offset`.",
|
|
6907
|
-
inputSchema:
|
|
7029
|
+
inputSchema: inputSchema34,
|
|
6908
7030
|
handler: async (args, ctx) => {
|
|
6909
7031
|
const limit = clampLimit(args.limit, LIST_DEFAULT5, LIST_MAX5);
|
|
6910
7032
|
const offset = args.offset ?? 0;
|
|
@@ -6927,14 +7049,14 @@ var listStreamTokensTool = {
|
|
|
6927
7049
|
};
|
|
6928
7050
|
|
|
6929
7051
|
// src/server/tools/revoke_stream_token.ts
|
|
6930
|
-
import { z as
|
|
6931
|
-
var
|
|
6932
|
-
id:
|
|
7052
|
+
import { z as z38 } from "zod";
|
|
7053
|
+
var inputSchema35 = {
|
|
7054
|
+
id: z38.string().min(1).describe("Token id (from create_stream_token or list_stream_tokens) \u2014 NOT the secret itself.")
|
|
6933
7055
|
};
|
|
6934
7056
|
var revokeStreamTokenTool = {
|
|
6935
7057
|
name: "revoke_stream_token",
|
|
6936
7058
|
description: "Revoke a stream token immediately, closing its URL and deleting the events buffered for it. Use this as soon as a stream is no longer needed, or if its URL may have leaked \u2014 the URL is the credential, so revoking is the only way to stop it before it expires. Revoking a token you minted yourself always works. Revoking one somebody ELSE minted additionally requires the organisation owner or admin role, and is refused with 403 otherwise \u2014 so when hunting a leaked URL you did not create, expect that refusal and ask an organisation owner or admin rather than retrying.",
|
|
6937
|
-
inputSchema:
|
|
7059
|
+
inputSchema: inputSchema35,
|
|
6938
7060
|
handler: async (args, ctx) => {
|
|
6939
7061
|
const token = await ctx.client.revokeStreamToken(args.id);
|
|
6940
7062
|
return jsonResult({ ...streamTokenSummary(token), revoked: true });
|
|
@@ -6942,7 +7064,7 @@ var revokeStreamTokenTool = {
|
|
|
6942
7064
|
};
|
|
6943
7065
|
|
|
6944
7066
|
// src/server/tools/start_spec_session.ts
|
|
6945
|
-
import { z as
|
|
7067
|
+
import { z as z39 } from "zod";
|
|
6946
7068
|
|
|
6947
7069
|
// src/server/tools/start-session-core.ts
|
|
6948
7070
|
var START_SESSION_WAIT_MS = 75e3;
|
|
@@ -7334,28 +7456,28 @@ var START_SESSION_DESCRIPTION_BASE = "Start a NEW MySpec chat session on the use
|
|
|
7334
7456
|
|
|
7335
7457
|
// src/server/tools/start_spec_session.ts
|
|
7336
7458
|
var BRIDGE_COMMAND = "npx -y @myspec/mcp-server reverse --root <path>";
|
|
7337
|
-
var
|
|
7338
|
-
project_id:
|
|
7339
|
-
mode:
|
|
7459
|
+
var inputSchema36 = {
|
|
7460
|
+
project_id: z39.string().min(1).describe("UUID of the project to start the session in."),
|
|
7461
|
+
mode: z39.enum(["generate", "edit"]).describe(
|
|
7340
7462
|
"'generate' writes a new spec bundle from scratch; 'edit' changes the spec files of an existing bundle."
|
|
7341
7463
|
),
|
|
7342
|
-
prompt:
|
|
7464
|
+
prompt: z39.string().min(1).describe(
|
|
7343
7465
|
"The opening message. Write it as the user would: what they want specced, and the repository context you have gathered that they would otherwise have to retype."
|
|
7344
7466
|
),
|
|
7345
|
-
workflow_id:
|
|
7467
|
+
workflow_id: z39.string().optional().describe(
|
|
7346
7468
|
"Workflow variant id, e.g. myspec-greenfield-v1, enterprise-greenfield-v1, speckit-greenfield-v1, openspec-brownfield-v1, myspec-brownfield-v1, myspec-edit-v1, myspec-edit-brownfield-v1. Omit to use the default for the chosen mode. Must agree with mode \u2014 a contradiction is rejected, not silently resolved."
|
|
7347
7469
|
),
|
|
7348
|
-
language:
|
|
7470
|
+
language: z39.enum(SPEC_SESSION_LANGUAGES).optional().describe(
|
|
7349
7471
|
"Output language the session locks to on its first turn. Defaults to 'en'. A session's language is immutable once set."
|
|
7350
7472
|
),
|
|
7351
|
-
model_id:
|
|
7352
|
-
attachment_ids:
|
|
7473
|
+
model_id: z39.string().optional().describe("Model id to run the session with, e.g. openai:gpt-6-sol."),
|
|
7474
|
+
attachment_ids: z39.array(z39.string().min(1)).max(MAX_REFERENCE_IDS).optional().describe(
|
|
7353
7475
|
`Project attachment ids to cite in the opening message, as the composer @-mentions do. At most ${String(MAX_REFERENCE_IDS)}.`
|
|
7354
7476
|
),
|
|
7355
|
-
spec_file_ids:
|
|
7477
|
+
spec_file_ids: z39.array(z39.string().min(1)).max(MAX_REFERENCE_IDS).optional().describe(
|
|
7356
7478
|
`Existing spec file ids to cite in the opening message. Required in practice for edit sessions. At most ${String(MAX_REFERENCE_IDS)}.`
|
|
7357
7479
|
),
|
|
7358
|
-
session_title:
|
|
7480
|
+
session_title: z39.string().min(1).max(SESSION_TITLE_MAX_LENGTH).optional().describe(
|
|
7359
7481
|
`Human-readable session title, stored as context.sessionSummary (max ${String(SESSION_TITLE_MAX_LENGTH)} characters). A workflow that names the session itself during its interview will overwrite it, exactly as in the webapp.`
|
|
7360
7482
|
)
|
|
7361
7483
|
};
|
|
@@ -7417,7 +7539,7 @@ async function resolveSpecFiles(ctx, projectId, ids) {
|
|
|
7417
7539
|
var startSpecSessionTool = {
|
|
7418
7540
|
name: "start_spec_session",
|
|
7419
7541
|
description: START_SESSION_DESCRIPTION_BASE + BRIDGE_ORDERING_NOTE,
|
|
7420
|
-
inputSchema:
|
|
7542
|
+
inputSchema: inputSchema36,
|
|
7421
7543
|
handler: async (args, ctx) => {
|
|
7422
7544
|
const webappUrl = httpUrlOrNull(ctx.webappUrl);
|
|
7423
7545
|
if (webappUrl === null) {
|
|
@@ -7540,7 +7662,7 @@ var startSpecSessionTool = {
|
|
|
7540
7662
|
};
|
|
7541
7663
|
|
|
7542
7664
|
// src/server/tools/tasks.ts
|
|
7543
|
-
import { z as
|
|
7665
|
+
import { z as z40 } from "zod";
|
|
7544
7666
|
var LIST_DEFAULT_TASKS = 100;
|
|
7545
7667
|
var LIST_MAX_TASKS = 500;
|
|
7546
7668
|
var LIST_MAX_OFFSET = 1e6;
|
|
@@ -7548,8 +7670,8 @@ var DETAILS_PREVIEW_CHARS = 500;
|
|
|
7548
7670
|
var ME = "me";
|
|
7549
7671
|
var NONE = "none";
|
|
7550
7672
|
var PRIORITY_NAMES = ["none", "urgent", "high", "medium", "low"];
|
|
7551
|
-
var STATUS =
|
|
7552
|
-
var RELATIONSHIP_TYPE =
|
|
7673
|
+
var STATUS = z40.enum(["pending", "in_progress", "done"]);
|
|
7674
|
+
var RELATIONSHIP_TYPE = z40.enum(["blocks", "blocked_by", "related"]);
|
|
7553
7675
|
function toDay(date) {
|
|
7554
7676
|
return date ? date.toISOString().slice(0, 10) : null;
|
|
7555
7677
|
}
|
|
@@ -7584,33 +7706,33 @@ function taskSummary(task, opts = {}) {
|
|
|
7584
7706
|
updated_at: task.updatedAt?.toISOString()
|
|
7585
7707
|
};
|
|
7586
7708
|
}
|
|
7587
|
-
var userIdField =
|
|
7709
|
+
var userIdField = z40.string().trim().min(1);
|
|
7588
7710
|
async function resolveMe(id, ctx) {
|
|
7589
7711
|
return id === ME ? ctx.client.currentUserId() : id;
|
|
7590
7712
|
}
|
|
7591
7713
|
var UNKNOWN_ME = 'Could not tell who you are from this connection, so "me" cannot be used. Pass your user id instead: list_task_assignees returns it as self_user_id.';
|
|
7592
|
-
var labelField =
|
|
7714
|
+
var labelField = z40.string().trim().min(1).refine((label) => Array.from(label).length <= TASK_LIMITS.maxLabelLength, {
|
|
7593
7715
|
message: `A label is at most ${String(TASK_LIMITS.maxLabelLength)} characters.`
|
|
7594
7716
|
});
|
|
7595
7717
|
function taskIdsField(what) {
|
|
7596
|
-
return
|
|
7718
|
+
return z40.array(z40.uuid()).min(1).max(TASK_LIMITS.maxTasksPerRequest).describe(`${what} At most ${String(TASK_LIMITS.maxTasksPerRequest)} per call.`);
|
|
7597
7719
|
}
|
|
7598
7720
|
var listInputSchema2 = {
|
|
7599
|
-
project_id:
|
|
7721
|
+
project_id: z40.uuid().describe("UUID of the project whose tasks to list."),
|
|
7600
7722
|
status: STATUS.optional().describe("Only tasks with this status."),
|
|
7601
7723
|
assignee_user_id: userIdField.optional().describe(
|
|
7602
7724
|
'Only tasks assigned to this user id (as returned in assignee_user_id or by list_task_assignees). "me" means you; "none" means unassigned tasks.'
|
|
7603
7725
|
),
|
|
7604
|
-
cycle:
|
|
7726
|
+
cycle: z40.number().int().min(1).max(TASK_LIMITS.maxCycle).optional().describe("Only tasks in this cycle (sprint) number."),
|
|
7605
7727
|
label: labelField.optional().describe("Only tasks carrying this label."),
|
|
7606
|
-
file_id:
|
|
7607
|
-
include_details:
|
|
7728
|
+
file_id: z40.uuid().optional().describe("Only tasks from this tasks.md (its spec file id)."),
|
|
7729
|
+
include_details: z40.boolean().optional().describe(
|
|
7608
7730
|
"Also return each task's details (its sub-bullets in tasks.md), previewed to 500 characters. Off by default: they make the result several times larger."
|
|
7609
7731
|
),
|
|
7610
|
-
limit:
|
|
7732
|
+
limit: z40.number().int().min(1).max(LIST_MAX_TASKS).optional().describe(
|
|
7611
7733
|
`Tasks per page (default ${String(LIST_DEFAULT_TASKS)}, at most ${String(LIST_MAX_TASKS)}).`
|
|
7612
7734
|
),
|
|
7613
|
-
offset:
|
|
7735
|
+
offset: z40.number().int().min(0).max(LIST_MAX_OFFSET).optional().describe("How many matching tasks to skip; pass next_offset from the previous page.")
|
|
7614
7736
|
};
|
|
7615
7737
|
function pageLocally(board, query) {
|
|
7616
7738
|
const assignee = query.assigneeUserId;
|
|
@@ -7678,7 +7800,7 @@ var listTasksTool = {
|
|
|
7678
7800
|
}
|
|
7679
7801
|
};
|
|
7680
7802
|
var assigneesInputSchema = {
|
|
7681
|
-
project_id:
|
|
7803
|
+
project_id: z40.uuid().describe("UUID of the project whose tasks are being assigned.")
|
|
7682
7804
|
};
|
|
7683
7805
|
var listTaskAssigneesTool = {
|
|
7684
7806
|
name: "list_task_assignees",
|
|
@@ -7699,29 +7821,29 @@ var listTaskAssigneesTool = {
|
|
|
7699
7821
|
}
|
|
7700
7822
|
};
|
|
7701
7823
|
var updateInputSchema2 = {
|
|
7702
|
-
project_id:
|
|
7824
|
+
project_id: z40.uuid().describe("UUID of the project the tasks belong to."),
|
|
7703
7825
|
task_ids: taskIdsField("The tasks to change (task_id values from list_tasks)."),
|
|
7704
7826
|
assignee_user_id: userIdField.nullable().optional().describe(
|
|
7705
7827
|
'User id to assign ("me" for yourself), or null to unassign. The person must be a member of the organisation who can open the project: list_task_assignees lists them.'
|
|
7706
7828
|
),
|
|
7707
|
-
priority:
|
|
7708
|
-
story_points:
|
|
7709
|
-
cycle:
|
|
7710
|
-
start_date:
|
|
7711
|
-
end_date:
|
|
7712
|
-
add_labels:
|
|
7713
|
-
remove_labels:
|
|
7714
|
-
add_relationships:
|
|
7715
|
-
|
|
7829
|
+
priority: z40.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low."),
|
|
7830
|
+
story_points: z40.number().min(0).max(TASK_LIMITS.maxStoryPoints).nullable().optional().describe("Estimate in story points, or null to clear it."),
|
|
7831
|
+
cycle: z40.number().int().min(1).max(TASK_LIMITS.maxCycle).nullable().optional().describe("Cycle (sprint) number, or null to clear it."),
|
|
7832
|
+
start_date: z40.iso.date().nullable().optional().describe("Start day as YYYY-MM-DD, or null to clear it."),
|
|
7833
|
+
end_date: z40.iso.date().nullable().optional().describe("Due day as YYYY-MM-DD, or null to clear it. Not before the start day."),
|
|
7834
|
+
add_labels: z40.array(labelField).max(TASK_LIMITS.maxLabels).optional().describe(`Labels to add. A task carries at most ${String(TASK_LIMITS.maxLabels)}.`),
|
|
7835
|
+
remove_labels: z40.array(labelField).max(TASK_LIMITS.maxLabels).optional().describe("Labels to remove."),
|
|
7836
|
+
add_relationships: z40.array(
|
|
7837
|
+
z40.object({
|
|
7716
7838
|
type: RELATIONSHIP_TYPE.describe(
|
|
7717
7839
|
"How each changed task stands to task_id: blocks (task_id waits for it), blocked_by (it waits for task_id) or related."
|
|
7718
7840
|
),
|
|
7719
|
-
task_id:
|
|
7841
|
+
task_id: z40.uuid().describe("The other task (a task_id from list_tasks).")
|
|
7720
7842
|
})
|
|
7721
7843
|
).max(TASK_LIMITS.maxRelationships).optional().describe(
|
|
7722
7844
|
`Relationships to set. Two tasks have at most one: setting one replaces whatever the pair had, and the other task sees the inverse (A blocks B is B blocked_by A). A task has at most ${String(TASK_LIMITS.maxRelationships)}.`
|
|
7723
7845
|
),
|
|
7724
|
-
remove_relationships:
|
|
7846
|
+
remove_relationships: z40.array(z40.uuid()).max(TASK_LIMITS.maxRelationships).optional().describe("Task ids to unlink each changed task from, whatever the relationship.")
|
|
7725
7847
|
};
|
|
7726
7848
|
var updateTasksTool = {
|
|
7727
7849
|
name: "update_tasks",
|
|
@@ -7764,13 +7886,13 @@ var updateTasksTool = {
|
|
|
7764
7886
|
}
|
|
7765
7887
|
};
|
|
7766
7888
|
var statusInputSchema = {
|
|
7767
|
-
project_id:
|
|
7889
|
+
project_id: z40.uuid().describe("UUID of the project the tasks belong to."),
|
|
7768
7890
|
task_ids: taskIdsField("The tasks to move (task_id values from list_tasks)."),
|
|
7769
7891
|
status: STATUS.describe("The new status: pending, in_progress or done."),
|
|
7770
|
-
expected_versions:
|
|
7771
|
-
|
|
7772
|
-
spec_file_id:
|
|
7773
|
-
content_version:
|
|
7892
|
+
expected_versions: z40.array(
|
|
7893
|
+
z40.object({
|
|
7894
|
+
spec_file_id: z40.uuid().describe("A tasks.md file (spec_file_id from list_tasks)."),
|
|
7895
|
+
content_version: z40.number().int().min(0).describe("The content_version list_tasks returned for that file.")
|
|
7774
7896
|
})
|
|
7775
7897
|
).max(TASK_LIMITS.maxBoardFiles).optional().describe(
|
|
7776
7898
|
"Optional. The file versions your view of these tasks came from (from list_tasks); only the files these tasks are in are checked, so passing just those is enough. A file that has changed since is not written and its tasks fail with file_changed, so a change never lands on a line you did not see; list again and retry."
|
|
@@ -7839,6 +7961,7 @@ function buildServer(deps) {
|
|
|
7839
7961
|
registerTool(server, uploadSpecFileTool, ctx);
|
|
7840
7962
|
registerTool(server, updateSpecFileTool, ctx);
|
|
7841
7963
|
registerTool(server, moveSpecFileToTrashTool, ctx);
|
|
7964
|
+
registerTool(server, moveSpecFolderTool, ctx);
|
|
7842
7965
|
registerTool(server, restoreSpecFileFromTrashTool, ctx);
|
|
7843
7966
|
registerTool(server, listAttachmentsTool, ctx);
|
|
7844
7967
|
registerTool(server, readAttachmentTool, ctx);
|
|
@@ -8043,7 +8166,7 @@ async function canonicaliseRoot(root) {
|
|
|
8043
8166
|
import { promises as fs4 } from "fs";
|
|
8044
8167
|
import { join as join2, relative } from "path";
|
|
8045
8168
|
import { spawn } from "child_process";
|
|
8046
|
-
import { z as
|
|
8169
|
+
import { z as z41 } from "zod";
|
|
8047
8170
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
8048
8171
|
|
|
8049
8172
|
// src/reverse/ignore.ts
|
|
@@ -8348,37 +8471,37 @@ var READ_FILE_MAX_LINES = 2e3;
|
|
|
8348
8471
|
var LIST_DIR_HARD_CAP = 5e3;
|
|
8349
8472
|
var LIST_DIR_DEFAULT_CAP = 1e3;
|
|
8350
8473
|
var GREP_MAX_MATCHES_DEFAULT = 200;
|
|
8351
|
-
var listDirInput =
|
|
8352
|
-
path:
|
|
8353
|
-
recursive:
|
|
8354
|
-
maxEntries:
|
|
8355
|
-
includeIgnored:
|
|
8474
|
+
var listDirInput = z41.object({
|
|
8475
|
+
path: z41.string(),
|
|
8476
|
+
recursive: z41.boolean().optional(),
|
|
8477
|
+
maxEntries: z41.number().int().positive().optional(),
|
|
8478
|
+
includeIgnored: z41.boolean().optional()
|
|
8356
8479
|
});
|
|
8357
|
-
var readFileInput =
|
|
8358
|
-
path:
|
|
8359
|
-
offset:
|
|
8360
|
-
limit:
|
|
8361
|
-
includeIgnored:
|
|
8480
|
+
var readFileInput = z41.object({
|
|
8481
|
+
path: z41.string(),
|
|
8482
|
+
offset: z41.number().int().nonnegative().optional(),
|
|
8483
|
+
limit: z41.number().int().positive().optional(),
|
|
8484
|
+
includeIgnored: z41.boolean().optional()
|
|
8362
8485
|
});
|
|
8363
|
-
var grepInput =
|
|
8364
|
-
pattern:
|
|
8486
|
+
var grepInput = z41.object({
|
|
8487
|
+
pattern: z41.string().describe(
|
|
8365
8488
|
"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."
|
|
8366
8489
|
),
|
|
8367
|
-
path:
|
|
8368
|
-
isRegex:
|
|
8369
|
-
caseSensitive:
|
|
8370
|
-
maxMatches:
|
|
8371
|
-
contextLines:
|
|
8372
|
-
includeIgnored:
|
|
8490
|
+
path: z41.string().optional().describe("File or directory under --root to search. Defaults to the whole root."),
|
|
8491
|
+
isRegex: z41.boolean().optional().describe("Treat `pattern` as a regex (default true). Set false for literal-string search."),
|
|
8492
|
+
caseSensitive: z41.boolean().optional(),
|
|
8493
|
+
maxMatches: z41.number().int().positive().optional(),
|
|
8494
|
+
contextLines: z41.number().int().nonnegative().optional(),
|
|
8495
|
+
includeIgnored: z41.boolean().optional()
|
|
8373
8496
|
});
|
|
8374
|
-
var packCodebaseInput =
|
|
8375
|
-
subpath:
|
|
8376
|
-
includePatterns:
|
|
8377
|
-
ignorePatterns:
|
|
8497
|
+
var packCodebaseInput = z41.object({
|
|
8498
|
+
subpath: z41.string().optional(),
|
|
8499
|
+
includePatterns: z41.string().optional(),
|
|
8500
|
+
ignorePatterns: z41.string().optional()
|
|
8378
8501
|
});
|
|
8379
|
-
var packCodebaseReadPageInput =
|
|
8380
|
-
outputId:
|
|
8381
|
-
page:
|
|
8502
|
+
var packCodebaseReadPageInput = z41.object({
|
|
8503
|
+
outputId: z41.string(),
|
|
8504
|
+
page: z41.number().int().positive()
|
|
8382
8505
|
});
|
|
8383
8506
|
function structuredError(error, message, extra) {
|
|
8384
8507
|
const payload = { error, message, ...extra ?? {} };
|
|
@@ -8906,7 +9029,7 @@ function jsonSchemaFromZod(schema) {
|
|
|
8906
9029
|
return zodToJson(schema);
|
|
8907
9030
|
}
|
|
8908
9031
|
function zodToJson(schema) {
|
|
8909
|
-
if (schema instanceof
|
|
9032
|
+
if (schema instanceof z41.ZodObject) {
|
|
8910
9033
|
const shape = schema.shape;
|
|
8911
9034
|
const properties = {};
|
|
8912
9035
|
const required = [];
|
|
@@ -8921,19 +9044,19 @@ function zodToJson(schema) {
|
|
|
8921
9044
|
if (required.length > 0) out.required = required;
|
|
8922
9045
|
return out;
|
|
8923
9046
|
}
|
|
8924
|
-
if (schema instanceof
|
|
9047
|
+
if (schema instanceof z41.ZodOptional) {
|
|
8925
9048
|
return zodToJson(schema.unwrap());
|
|
8926
9049
|
}
|
|
8927
|
-
if (schema instanceof
|
|
9050
|
+
if (schema instanceof z41.ZodString) {
|
|
8928
9051
|
return { type: "string" };
|
|
8929
9052
|
}
|
|
8930
|
-
if (schema instanceof
|
|
9053
|
+
if (schema instanceof z41.ZodNumber) {
|
|
8931
9054
|
return { type: "number" };
|
|
8932
9055
|
}
|
|
8933
|
-
if (schema instanceof
|
|
9056
|
+
if (schema instanceof z41.ZodBoolean) {
|
|
8934
9057
|
return { type: "boolean" };
|
|
8935
9058
|
}
|
|
8936
|
-
if (schema instanceof
|
|
9059
|
+
if (schema instanceof z41.ZodEnum) {
|
|
8937
9060
|
return { type: "string", enum: schema.options };
|
|
8938
9061
|
}
|
|
8939
9062
|
return { type: "string" };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myspec/mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2-next.178",
|
|
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": {
|