@myspec/mcp-server 0.4.0-next.106 → 0.4.0-next.108
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 +8 -2
- package/dist/index.js +771 -63
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -291,9 +291,9 @@ Local state is stored under `~/.myspec/` (alongside the on-disk cache root), spl
|
|
|
291
291
|
| `list_projects` | `query?`, `limit?`, `offset?` | Paginated list of projects |
|
|
292
292
|
| `get_project` | `project_id` | Project + active sessions + files + attachments |
|
|
293
293
|
| `get_spec_file` | `file_id`, optional `include_download_url` | File metadata, including `content_version` (see [Concurrent edits](#concurrent-edits-content_version)); with `include_download_url=N` also returns a download URL targeting revision `N` |
|
|
294
|
-
| `read_spec_file` | `file_id`, optional `revision` | UTF-8 content
|
|
294
|
+
| `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 |
|
|
295
295
|
| `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 |
|
|
296
|
-
| `list_spec_file` | `project_id`, optional `path`, optional `trashed` | Lists a project's spec files (`file_id`, `file_path`, `file_type`, `file_size_bytes`, `revision_count`, `session_id`, `updated_at`). Filter to a directory with `path` (exact file or everything under it; `""`/`"/"`/omit = all). `trashed=true` lists the Trash Bin instead of live files |
|
|
296
|
+
| `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 |
|
|
297
297
|
| `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 |
|
|
298
298
|
| `restore_spec_file_from_trash` | `file_id` | Restores a trashed spec file back to the active file list, reversing `move_spec_file_to_trash` |
|
|
299
299
|
| `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 |
|
|
@@ -306,6 +306,12 @@ Local state is stored under `~/.myspec/` (alongside the on-disk cache root), spl
|
|
|
306
306
|
| `archive_spec_session` | `session_id` | Archives the session (idempotent — re-archiving keeps the original `archived_at`); the required confirmation step before `delete_spec_session` |
|
|
307
307
|
| `unarchive_spec_session` | `session_id` | Returns an archived session to the active list |
|
|
308
308
|
| `delete_spec_session` | `session_id` | Soft-deletes an ARCHIVED session. Refuses non-archived sessions with an archive-first error and never archives on the caller's behalf |
|
|
309
|
+
| `list_artifacts` | `project_id`, optional `limit`, optional `offset` | Paginated list of a project's live artifacts — multi-file bundles (an HTML mockup: `index.html` + `styles.css` + `app.js` + assets) the platform versions as ONE snapshot. Each entry carries `revision_count`, the head number and the `expected_version` to pass when writing |
|
|
310
|
+
| `get_artifact` | `artifact_id`, optional `revision` | Artifact metadata plus the file manifest (path, content type, size, checksum) of the head revision, or of `revision` |
|
|
311
|
+
| `read_artifact_file` | `artifact_id`, `path`, optional `revision`, optional `offset`, optional `limit` | One file's UTF-8 content paginated by line (same `content`/`total_lines`/`truncated`/`next_offset` shape as `read_attachment`); binary files are refused |
|
|
312
|
+
| `create_artifact` | `project_id`, `name`, `files[]` (`path`, `content`, optional `encoding` utf8/base64, optional `content_type`), optional `slug`, `description`, `entry_path`, `message` | Creates the artifact with revision 1. Limits: 50 files, 2 MiB per file, 10 MiB per revision. A taken slug is a conflict |
|
|
313
|
+
| `write_artifact_revision` | `artifact_id`, `files[]`, optional `base_revision` (patch mode), `remove[]`, `expected_version`, `message`, `entry_path` | Appends an immutable revision. Full mode replaces the whole bundle; patch mode (with `base_revision`) sends only changed files plus paths to `remove`. A stale `expected_version` is reported as a conflict naming the new head — re-read with `get_artifact` and retry |
|
|
314
|
+
| `rollback_artifact` | `artifact_id`, `revision_number`, optional `expected_version`, `message` | Restores an earlier revision by APPENDING a revision with its files (`git revert` semantics); history is never rewritten, but only the newest N revisions are retained (50 by default, `ARTIFACT_MAX_REVISIONS` on the platform), so a pruned target answers not found |
|
|
309
315
|
|
|
310
316
|
### Download destinations
|
|
311
317
|
|
package/dist/index.js
CHANGED
|
@@ -1349,7 +1349,10 @@ var KNOWN_CONFLICT_REASONS = [
|
|
|
1349
1349
|
"revision_mismatch",
|
|
1350
1350
|
"path_taken",
|
|
1351
1351
|
"precondition_required",
|
|
1352
|
-
"precondition_malformed"
|
|
1352
|
+
"precondition_malformed",
|
|
1353
|
+
"slug_taken",
|
|
1354
|
+
"blob_reclaimed",
|
|
1355
|
+
"lock_timeout"
|
|
1353
1356
|
];
|
|
1354
1357
|
var PlatformHttpError = class extends Error {
|
|
1355
1358
|
status;
|
|
@@ -2436,6 +2439,283 @@ var HttpSpecSessionClient = class {
|
|
|
2436
2439
|
}
|
|
2437
2440
|
};
|
|
2438
2441
|
|
|
2442
|
+
// ../../packages/platform-client/artifact/types/artifact.types.ts
|
|
2443
|
+
function transformArtifactResponse(raw) {
|
|
2444
|
+
return {
|
|
2445
|
+
id: raw.id,
|
|
2446
|
+
projectId: raw.project_id,
|
|
2447
|
+
org: raw.org,
|
|
2448
|
+
sessionId: raw.session_id ?? null,
|
|
2449
|
+
kind: raw.kind,
|
|
2450
|
+
name: raw.name,
|
|
2451
|
+
slug: raw.slug,
|
|
2452
|
+
description: raw.description ?? "",
|
|
2453
|
+
entryPath: raw.entry_path,
|
|
2454
|
+
revisionCount: raw.revision_count,
|
|
2455
|
+
createdBy: raw.created_by,
|
|
2456
|
+
createdAt: new Date(raw.created_at),
|
|
2457
|
+
updatedAt: new Date(raw.updated_at),
|
|
2458
|
+
trashedAt: raw.trashed_at ? new Date(raw.trashed_at) : void 0,
|
|
2459
|
+
deletedAt: raw.deleted_at ? new Date(raw.deleted_at) : void 0
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
function transformArtifactRevisionResponse(raw) {
|
|
2463
|
+
return {
|
|
2464
|
+
id: raw.id,
|
|
2465
|
+
artifactId: raw.artifact_id,
|
|
2466
|
+
revisionNumber: raw.revision_number,
|
|
2467
|
+
source: raw.source,
|
|
2468
|
+
restoredFromRevision: raw.restored_from_revision,
|
|
2469
|
+
message: raw.message ?? "",
|
|
2470
|
+
changedBy: raw.changed_by,
|
|
2471
|
+
changedByKind: raw.changed_by_kind === "ai" ? "ai" : "human",
|
|
2472
|
+
sessionId: raw.session_id ?? null,
|
|
2473
|
+
fileCount: raw.file_count,
|
|
2474
|
+
totalBytes: raw.total_bytes,
|
|
2475
|
+
manifestChecksum: raw.manifest_checksum,
|
|
2476
|
+
createdAt: new Date(raw.created_at)
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
function transformArtifactFileEntry(raw) {
|
|
2480
|
+
return {
|
|
2481
|
+
path: raw.path,
|
|
2482
|
+
contentType: raw.content_type,
|
|
2483
|
+
sizeBytes: raw.size_bytes,
|
|
2484
|
+
checksum: raw.checksum
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
function transformArtifactRevisionDetail(raw) {
|
|
2488
|
+
return {
|
|
2489
|
+
...transformArtifactRevisionResponse(raw),
|
|
2490
|
+
files: (raw.files ?? []).map(transformArtifactFileEntry)
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
function transformArtifactDetail(raw) {
|
|
2494
|
+
return {
|
|
2495
|
+
artifact: transformArtifactResponse(raw.artifact),
|
|
2496
|
+
head: transformArtifactRevisionDetail(raw.head)
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
function transformArtifactWriteResult(raw) {
|
|
2500
|
+
return {
|
|
2501
|
+
artifact: transformArtifactResponse(raw.artifact),
|
|
2502
|
+
revision: transformArtifactRevisionDetail(raw.revision),
|
|
2503
|
+
noChange: raw.no_change === true
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
function transformArtifactListResult(raw) {
|
|
2507
|
+
return {
|
|
2508
|
+
items: (raw.items ?? []).map(transformArtifactResponse),
|
|
2509
|
+
total: raw.total,
|
|
2510
|
+
limit: raw.limit,
|
|
2511
|
+
offset: raw.offset
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
function transformArtifactRevisionListResult(raw) {
|
|
2515
|
+
return {
|
|
2516
|
+
items: (raw.items ?? []).map(transformArtifactRevisionResponse),
|
|
2517
|
+
total: raw.total,
|
|
2518
|
+
limit: raw.limit,
|
|
2519
|
+
offset: raw.offset
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
function buildFilesBody(files) {
|
|
2523
|
+
return files.map((f) => {
|
|
2524
|
+
const body = { path: f.path, content: f.content };
|
|
2525
|
+
if (f.encoding) {
|
|
2526
|
+
body.encoding = f.encoding;
|
|
2527
|
+
}
|
|
2528
|
+
if (f.contentType) {
|
|
2529
|
+
body.content_type = f.contentType;
|
|
2530
|
+
}
|
|
2531
|
+
return body;
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
function buildCreateArtifactBody(request) {
|
|
2535
|
+
const body = {
|
|
2536
|
+
name: request.name,
|
|
2537
|
+
files: buildFilesBody(request.files)
|
|
2538
|
+
};
|
|
2539
|
+
if (request.slug) body.slug = request.slug;
|
|
2540
|
+
if (request.kind) body.kind = request.kind;
|
|
2541
|
+
if (request.description !== void 0) body.description = request.description;
|
|
2542
|
+
if (request.entryPath) body.entry_path = request.entryPath;
|
|
2543
|
+
if (request.message) body.message = request.message;
|
|
2544
|
+
if (request.sessionId) body.session_id = request.sessionId;
|
|
2545
|
+
return body;
|
|
2546
|
+
}
|
|
2547
|
+
function buildCreateArtifactRevisionBody(request) {
|
|
2548
|
+
const body = { files: buildFilesBody(request.files) };
|
|
2549
|
+
if (request.message) body.message = request.message;
|
|
2550
|
+
if (request.baseRevision !== void 0) body.base_revision = request.baseRevision;
|
|
2551
|
+
if (request.remove && request.remove.length > 0) body.remove = request.remove;
|
|
2552
|
+
if (request.entryPath) body.entry_path = request.entryPath;
|
|
2553
|
+
if (request.sessionId) body.session_id = request.sessionId;
|
|
2554
|
+
return body;
|
|
2555
|
+
}
|
|
2556
|
+
function buildRollbackArtifactBody(request) {
|
|
2557
|
+
const body = { revision_number: request.revisionNumber };
|
|
2558
|
+
if (request.message) body.message = request.message;
|
|
2559
|
+
if (request.sessionId) body.session_id = request.sessionId;
|
|
2560
|
+
return body;
|
|
2561
|
+
}
|
|
2562
|
+
function buildUpdateArtifactBody(request) {
|
|
2563
|
+
const body = {};
|
|
2564
|
+
if (request.name !== void 0) body.name = request.name;
|
|
2565
|
+
if (request.description !== void 0) body.description = request.description;
|
|
2566
|
+
if (request.entryPath !== void 0) body.entry_path = request.entryPath;
|
|
2567
|
+
return body;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
// ../../packages/platform-client/artifact/http/artifact.http-client.ts
|
|
2571
|
+
var BASE_PATH = "/artifact/v1";
|
|
2572
|
+
var HEADER_EXPECTED_VERSION2 = "X-Expected-Version";
|
|
2573
|
+
var HEADER_ACTOR_KIND2 = "X-Actor-Kind";
|
|
2574
|
+
function writeHeaders(opts) {
|
|
2575
|
+
const headers = {};
|
|
2576
|
+
if (opts?.expectedVersion !== void 0) {
|
|
2577
|
+
headers[HEADER_EXPECTED_VERSION2] = String(opts.expectedVersion);
|
|
2578
|
+
}
|
|
2579
|
+
if (opts?.actorKind) {
|
|
2580
|
+
headers[HEADER_ACTOR_KIND2] = opts.actorKind;
|
|
2581
|
+
}
|
|
2582
|
+
return headers;
|
|
2583
|
+
}
|
|
2584
|
+
function requestOptions(headers) {
|
|
2585
|
+
return Object.keys(headers).length > 0 ? { headers } : {};
|
|
2586
|
+
}
|
|
2587
|
+
function encodeArtifactPath(path4) {
|
|
2588
|
+
return path4.split("/").map(encodeURIComponent).join("/");
|
|
2589
|
+
}
|
|
2590
|
+
function paginationQuery(opts) {
|
|
2591
|
+
const params = new URLSearchParams();
|
|
2592
|
+
if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
|
|
2593
|
+
if (opts?.offset !== void 0) params.set("offset", String(opts.offset));
|
|
2594
|
+
if (opts?.kind) params.set("kind", opts.kind);
|
|
2595
|
+
const query = params.toString();
|
|
2596
|
+
return query ? `?${query}` : "";
|
|
2597
|
+
}
|
|
2598
|
+
var HttpArtifactClient = class {
|
|
2599
|
+
httpClient;
|
|
2600
|
+
constructor(httpClient) {
|
|
2601
|
+
this.httpClient = httpClient;
|
|
2602
|
+
}
|
|
2603
|
+
async createArtifact(projectId, request, jwtToken, opts) {
|
|
2604
|
+
const raw = await this.httpClient.post(
|
|
2605
|
+
`${BASE_PATH}/projects/${projectId}/artifacts`,
|
|
2606
|
+
buildCreateArtifactBody(request),
|
|
2607
|
+
jwtToken,
|
|
2608
|
+
requestOptions(writeHeaders(opts))
|
|
2609
|
+
);
|
|
2610
|
+
return transformArtifactWriteResult(raw);
|
|
2611
|
+
}
|
|
2612
|
+
async listArtifacts(projectId, jwtToken, opts) {
|
|
2613
|
+
const raw = await this.httpClient.get(
|
|
2614
|
+
`${BASE_PATH}/projects/${projectId}/artifacts${paginationQuery(opts)}`,
|
|
2615
|
+
jwtToken,
|
|
2616
|
+
{}
|
|
2617
|
+
);
|
|
2618
|
+
return transformArtifactListResult(raw);
|
|
2619
|
+
}
|
|
2620
|
+
async listTrashedArtifacts(projectId, jwtToken) {
|
|
2621
|
+
const raw = await this.httpClient.get(
|
|
2622
|
+
`${BASE_PATH}/projects/${projectId}/artifacts/trashed`,
|
|
2623
|
+
jwtToken,
|
|
2624
|
+
{}
|
|
2625
|
+
);
|
|
2626
|
+
return (raw.items ?? []).map(transformArtifactResponse);
|
|
2627
|
+
}
|
|
2628
|
+
async getArtifact(artifactId, jwtToken) {
|
|
2629
|
+
const raw = await this.httpClient.get(
|
|
2630
|
+
`${BASE_PATH}/artifacts/${artifactId}`,
|
|
2631
|
+
jwtToken,
|
|
2632
|
+
{}
|
|
2633
|
+
);
|
|
2634
|
+
return transformArtifactDetail(raw);
|
|
2635
|
+
}
|
|
2636
|
+
async updateArtifact(artifactId, request, jwtToken) {
|
|
2637
|
+
const raw = await this.httpClient.patch(
|
|
2638
|
+
`${BASE_PATH}/artifacts/${artifactId}`,
|
|
2639
|
+
buildUpdateArtifactBody(request),
|
|
2640
|
+
jwtToken,
|
|
2641
|
+
{}
|
|
2642
|
+
);
|
|
2643
|
+
return transformArtifactResponse(raw);
|
|
2644
|
+
}
|
|
2645
|
+
async trashArtifact(artifactId, jwtToken) {
|
|
2646
|
+
await this.httpClient.delete(`${BASE_PATH}/artifacts/${artifactId}`, jwtToken, {});
|
|
2647
|
+
}
|
|
2648
|
+
async restoreArtifact(artifactId, jwtToken) {
|
|
2649
|
+
await this.httpClient.post(
|
|
2650
|
+
`${BASE_PATH}/artifacts/${artifactId}/restore`,
|
|
2651
|
+
{},
|
|
2652
|
+
jwtToken,
|
|
2653
|
+
{}
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
async deleteArtifactPermanently(artifactId, jwtToken) {
|
|
2657
|
+
await this.httpClient.delete(
|
|
2658
|
+
`${BASE_PATH}/artifacts/${artifactId}/permanent`,
|
|
2659
|
+
jwtToken,
|
|
2660
|
+
{}
|
|
2661
|
+
);
|
|
2662
|
+
}
|
|
2663
|
+
async createRevision(artifactId, request, jwtToken, opts) {
|
|
2664
|
+
const raw = await this.httpClient.post(
|
|
2665
|
+
`${BASE_PATH}/artifacts/${artifactId}/revisions`,
|
|
2666
|
+
buildCreateArtifactRevisionBody(request),
|
|
2667
|
+
jwtToken,
|
|
2668
|
+
requestOptions(writeHeaders(opts))
|
|
2669
|
+
);
|
|
2670
|
+
return transformArtifactWriteResult(raw);
|
|
2671
|
+
}
|
|
2672
|
+
async rollback(artifactId, request, jwtToken, opts) {
|
|
2673
|
+
const raw = await this.httpClient.post(
|
|
2674
|
+
`${BASE_PATH}/artifacts/${artifactId}/rollback`,
|
|
2675
|
+
buildRollbackArtifactBody(request),
|
|
2676
|
+
jwtToken,
|
|
2677
|
+
requestOptions(writeHeaders(opts))
|
|
2678
|
+
);
|
|
2679
|
+
return transformArtifactWriteResult(raw);
|
|
2680
|
+
}
|
|
2681
|
+
async listRevisions(artifactId, jwtToken, opts) {
|
|
2682
|
+
const raw = await this.httpClient.get(
|
|
2683
|
+
`${BASE_PATH}/artifacts/${artifactId}/revisions${paginationQuery(opts)}`,
|
|
2684
|
+
jwtToken,
|
|
2685
|
+
{}
|
|
2686
|
+
);
|
|
2687
|
+
return transformArtifactRevisionListResult(raw);
|
|
2688
|
+
}
|
|
2689
|
+
async getRevision(artifactId, revisionNumber, jwtToken) {
|
|
2690
|
+
const raw = await this.httpClient.get(
|
|
2691
|
+
`${BASE_PATH}/artifacts/${artifactId}/revisions/${revisionNumber}`,
|
|
2692
|
+
jwtToken,
|
|
2693
|
+
{}
|
|
2694
|
+
);
|
|
2695
|
+
return transformArtifactRevisionDetail(raw);
|
|
2696
|
+
}
|
|
2697
|
+
fileUrl(artifactId, path4, revision) {
|
|
2698
|
+
const baseUrl = this.httpClient.getBaseUrl().replace(/\/$/, "");
|
|
2699
|
+
const encoded = encodeArtifactPath(path4);
|
|
2700
|
+
return revision === void 0 ? `${baseUrl}${BASE_PATH}/artifacts/${artifactId}/files/${encoded}` : `${baseUrl}${BASE_PATH}/artifacts/${artifactId}/revisions/${revision}/files/${encoded}`;
|
|
2701
|
+
}
|
|
2702
|
+
async downloadFile(artifactId, path4, jwtToken, opts) {
|
|
2703
|
+
const response = await fetch(this.fileUrl(artifactId, path4, opts?.revision), {
|
|
2704
|
+
method: "GET",
|
|
2705
|
+
headers: { Authorization: `Bearer ${jwtToken}` }
|
|
2706
|
+
});
|
|
2707
|
+
if (!response.ok) {
|
|
2708
|
+
await throwFromResponse(response, "Artifact file download failed");
|
|
2709
|
+
}
|
|
2710
|
+
const buffer = await response.arrayBuffer();
|
|
2711
|
+
return {
|
|
2712
|
+
content: new Uint8Array(buffer),
|
|
2713
|
+
contentType: response.headers.get("Content-Type") ?? "application/octet-stream",
|
|
2714
|
+
checksum: (response.headers.get("ETag") ?? "").replace(/"/g, "").trim()
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2717
|
+
};
|
|
2718
|
+
|
|
2439
2719
|
// src/platform/client.ts
|
|
2440
2720
|
var silentLogger = {
|
|
2441
2721
|
debug: () => void 0,
|
|
@@ -2722,9 +3002,43 @@ var PlatformClient = class {
|
|
|
2722
3002
|
project: new HttpProjectClient(httpClient),
|
|
2723
3003
|
session: new HttpSpecSessionClient(httpClient),
|
|
2724
3004
|
file: new HttpFileClient(httpClient),
|
|
2725
|
-
attachment: new HttpAttachmentClient(httpClient)
|
|
3005
|
+
attachment: new HttpAttachmentClient(httpClient),
|
|
3006
|
+
artifact: new HttpArtifactClient(httpClient)
|
|
2726
3007
|
};
|
|
2727
3008
|
}
|
|
3009
|
+
// ----------------------------------------------------------- artifacts
|
|
3010
|
+
//
|
|
3011
|
+
// Every write is attributed `actorKind: 'ai'`: an MCP client is an
|
|
3012
|
+
// assistant acting on the user's behalf, exactly as for spec files.
|
|
3013
|
+
async listArtifacts(projectId, opts = {}) {
|
|
3014
|
+
return this.withTokenRetry((jwt, c) => c.artifact.listArtifacts(projectId, jwt, opts));
|
|
3015
|
+
}
|
|
3016
|
+
async getArtifact(artifactId) {
|
|
3017
|
+
return this.withTokenRetry((jwt, c) => c.artifact.getArtifact(artifactId, jwt));
|
|
3018
|
+
}
|
|
3019
|
+
async getArtifactRevision(artifactId, revisionNumber) {
|
|
3020
|
+
return this.withTokenRetry((jwt, c) => c.artifact.getRevision(artifactId, revisionNumber, jwt));
|
|
3021
|
+
}
|
|
3022
|
+
async downloadArtifactFile(artifactId, path4, revision) {
|
|
3023
|
+
return this.withTokenRetry(
|
|
3024
|
+
(jwt, c) => c.artifact.downloadFile(artifactId, path4, jwt, revision === void 0 ? void 0 : { revision })
|
|
3025
|
+
);
|
|
3026
|
+
}
|
|
3027
|
+
async createArtifact(projectId, request) {
|
|
3028
|
+
return this.withTokenRetry(
|
|
3029
|
+
(jwt, c) => c.artifact.createArtifact(projectId, request, jwt, { actorKind: "ai" })
|
|
3030
|
+
);
|
|
3031
|
+
}
|
|
3032
|
+
async createArtifactRevision(artifactId, request, expectedVersion) {
|
|
3033
|
+
return this.withTokenRetry(
|
|
3034
|
+
(jwt, c) => c.artifact.createRevision(artifactId, request, jwt, { expectedVersion, actorKind: "ai" })
|
|
3035
|
+
);
|
|
3036
|
+
}
|
|
3037
|
+
async rollbackArtifact(artifactId, request, expectedVersion) {
|
|
3038
|
+
return this.withTokenRetry(
|
|
3039
|
+
(jwt, c) => c.artifact.rollback(artifactId, request, jwt, { expectedVersion, actorKind: "ai" })
|
|
3040
|
+
);
|
|
3041
|
+
}
|
|
2728
3042
|
async withTokenRetry(call) {
|
|
2729
3043
|
const clients = await this.clients();
|
|
2730
3044
|
const jwt = await this.tokenManager.getValidAccessToken();
|
|
@@ -3479,6 +3793,9 @@ function sliceWindow(items, limit, offset) {
|
|
|
3479
3793
|
};
|
|
3480
3794
|
}
|
|
3481
3795
|
function nextOffsetOf(offset, returned, total) {
|
|
3796
|
+
if (returned === 0) {
|
|
3797
|
+
return void 0;
|
|
3798
|
+
}
|
|
3482
3799
|
const end = offset + returned;
|
|
3483
3800
|
return end < total ? end : void 0;
|
|
3484
3801
|
}
|
|
@@ -3556,6 +3873,8 @@ var listProjectsTool = {
|
|
|
3556
3873
|
// src/server/tools/list_spec_file.ts
|
|
3557
3874
|
import { z as z13 } from "zod";
|
|
3558
3875
|
var PAGE_SIZE = 100;
|
|
3876
|
+
var LIST_DEFAULT2 = 50;
|
|
3877
|
+
var LIST_MAX2 = 200;
|
|
3559
3878
|
var inputSchema13 = {
|
|
3560
3879
|
project_id: z13.string().min(1).describe("UUID of the project whose spec files to list."),
|
|
3561
3880
|
path: z13.string().optional().describe(
|
|
@@ -3563,26 +3882,57 @@ var inputSchema13 = {
|
|
|
3563
3882
|
),
|
|
3564
3883
|
trashed: z13.boolean().optional().describe(
|
|
3565
3884
|
"When true, list files in the project Trash Bin instead. When false or omitted, list only live files (trashed files excluded)."
|
|
3566
|
-
)
|
|
3885
|
+
),
|
|
3886
|
+
limit: z13.number().int().positive().optional().describe(
|
|
3887
|
+
`Max files to return (default ${String(LIST_DEFAULT2)}, capped at ${String(LIST_MAX2)}).`
|
|
3888
|
+
),
|
|
3889
|
+
offset: z13.number().int().nonnegative().optional().describe("Pagination offset (default 0)")
|
|
3567
3890
|
};
|
|
3568
3891
|
var listSpecFileTool = {
|
|
3569
3892
|
name: "list_spec_file",
|
|
3570
|
-
description: 'List a project\'s spec files with id, path, and metadata (revision count, file size of the latest revision, file type, owning session, last update). Filter to a directory with `path` (exact file or everything under it; pass "", "/", or omit for all). Set `trashed=true` to list files in the Trash Bin instead of live files.',
|
|
3893
|
+
description: 'List a project\'s spec files with id, path, and metadata (revision count, file size of the latest revision, file type, owning session, last update). Filter to a directory with `path` (exact file or everything under it; pass "", "/", or omit for all). Set `trashed=true` to list files in the Trash Bin instead of live files. Ordered by `file_path` ascending whenever `path` or `trashed` is given, most-recently-created first otherwise. Returns `total` and, when more records remain, `next_offset`.',
|
|
3571
3894
|
inputSchema: inputSchema13,
|
|
3572
3895
|
handler: async (args, ctx) => {
|
|
3573
3896
|
const trashed = args.trashed ?? false;
|
|
3574
|
-
const
|
|
3897
|
+
const limit = clampLimit(args.limit, LIST_DEFAULT2, LIST_MAX2);
|
|
3898
|
+
const offset = args.offset ?? 0;
|
|
3575
3899
|
const prefix = normalizePrefix(args.path);
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
files
|
|
3583
|
-
|
|
3900
|
+
if (trashed) {
|
|
3901
|
+
const all2 = await ctx.client.listTrashedFiles(args.project_id);
|
|
3902
|
+
return windowedListResult(args.project_id, prefix, true, all2, limit, offset);
|
|
3903
|
+
}
|
|
3904
|
+
if (prefix === "") {
|
|
3905
|
+
const page = await ctx.client.listFiles(args.project_id, { limit, offset });
|
|
3906
|
+
const next = nextOffsetOf(offset, page.files.length, page.total);
|
|
3907
|
+
return jsonResult({
|
|
3908
|
+
project_id: args.project_id,
|
|
3909
|
+
path: prefix,
|
|
3910
|
+
trashed: false,
|
|
3911
|
+
files: page.files.map(toEntry),
|
|
3912
|
+
total: page.total,
|
|
3913
|
+
limit,
|
|
3914
|
+
offset,
|
|
3915
|
+
...next === void 0 ? {} : { next_offset: next }
|
|
3916
|
+
});
|
|
3917
|
+
}
|
|
3918
|
+
const all = await listAllLiveFiles(ctx.client, args.project_id);
|
|
3919
|
+
return windowedListResult(args.project_id, prefix, false, all, limit, offset);
|
|
3584
3920
|
}
|
|
3585
3921
|
};
|
|
3922
|
+
function windowedListResult(projectId, prefix, trashed, files, limit, offset) {
|
|
3923
|
+
const matches = files.filter((f) => isUnderPath(f.filePath, prefix)).map(toEntry).sort((a, b) => a.file_path.localeCompare(b.file_path));
|
|
3924
|
+
const window = sliceWindow(matches, limit, offset);
|
|
3925
|
+
return jsonResult({
|
|
3926
|
+
project_id: projectId,
|
|
3927
|
+
path: prefix,
|
|
3928
|
+
trashed,
|
|
3929
|
+
files: window.items,
|
|
3930
|
+
total: window.total,
|
|
3931
|
+
limit: window.limit,
|
|
3932
|
+
offset,
|
|
3933
|
+
...window.next_offset === void 0 ? {} : { next_offset: window.next_offset }
|
|
3934
|
+
});
|
|
3935
|
+
}
|
|
3586
3936
|
async function listAllLiveFiles(client, projectId) {
|
|
3587
3937
|
const all = [];
|
|
3588
3938
|
let offset = 0;
|
|
@@ -3626,24 +3976,24 @@ function isUnderPath(filePath, prefix) {
|
|
|
3626
3976
|
|
|
3627
3977
|
// src/server/tools/list_spec_sessions.ts
|
|
3628
3978
|
import { z as z14 } from "zod";
|
|
3629
|
-
var
|
|
3630
|
-
var
|
|
3979
|
+
var LIST_DEFAULT3 = 10;
|
|
3980
|
+
var LIST_MAX3 = 20;
|
|
3631
3981
|
var inputSchema14 = {
|
|
3632
3982
|
project_id: z14.string().min(1).describe("UUID of the project whose spec sessions to list."),
|
|
3633
3983
|
archive_filter: z14.enum(["active", "archived", "all"]).optional().describe("Which sessions to list: 'active' (default), 'archived', or 'all'."),
|
|
3634
3984
|
status: z14.enum(["generating", "pending_input", "completed"]).optional().describe("Filter by session status."),
|
|
3635
3985
|
query: z14.string().optional().describe("Search term matched against the session summary."),
|
|
3636
3986
|
limit: z14.number().int().positive().optional().describe(
|
|
3637
|
-
`Max sessions to return (default ${String(
|
|
3987
|
+
`Max sessions to return (default ${String(LIST_DEFAULT3)}, capped at ${String(LIST_MAX3)}).`
|
|
3638
3988
|
),
|
|
3639
3989
|
offset: z14.number().int().nonnegative().optional().describe("Pagination offset (default 0)")
|
|
3640
3990
|
};
|
|
3641
3991
|
var listSpecSessionsTool = {
|
|
3642
3992
|
name: "list_spec_sessions",
|
|
3643
|
-
description: `List a project's spec sessions (metadata plus session_summary and context_size_bytes \u2014 never the context itself or the chat transcript). Filter with archive_filter ('active'/'archived'/'all'), status, and query (searches the session summary). Capped at ${String(
|
|
3993
|
+
description: `List a project's spec sessions (metadata plus session_summary and context_size_bytes \u2014 never the context itself or the chat transcript). Filter with archive_filter ('active'/'archived'/'all'), status, and query (searches the session summary). Capped at ${String(LIST_MAX3)} per page; returns \`total\` and \`next_offset\`.`,
|
|
3644
3994
|
inputSchema: inputSchema14,
|
|
3645
3995
|
handler: async (args, ctx) => {
|
|
3646
|
-
const limit = clampLimit(args.limit,
|
|
3996
|
+
const limit = clampLimit(args.limit, LIST_DEFAULT3, LIST_MAX3);
|
|
3647
3997
|
const offset = args.offset ?? 0;
|
|
3648
3998
|
const result = await ctx.client.listProjectSessions(args.project_id, {
|
|
3649
3999
|
archiveFilter: args.archive_filter ?? "active",
|
|
@@ -3694,6 +4044,9 @@ var MAX_LINES_PER_CALL = 2e3;
|
|
|
3694
4044
|
var MAX_RESPONSE_BYTES = 1 * 1024 * 1024;
|
|
3695
4045
|
function normalizeNewlines(text) {
|
|
3696
4046
|
const withoutBom = text.startsWith("\uFEFF") ? text.slice(1) : text;
|
|
4047
|
+
if (!withoutBom.includes("\r")) {
|
|
4048
|
+
return withoutBom;
|
|
4049
|
+
}
|
|
3697
4050
|
return withoutBom.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
3698
4051
|
}
|
|
3699
4052
|
function splitIntoLines(text) {
|
|
@@ -3864,20 +4217,27 @@ var readAttachmentTool = {
|
|
|
3864
4217
|
import { promises as fs3 } from "fs";
|
|
3865
4218
|
import { createHash as createHash2 } from "crypto";
|
|
3866
4219
|
import { z as z17 } from "zod";
|
|
3867
|
-
var
|
|
4220
|
+
var MAX_READABLE_SPEC_FILE_BYTES = 8 * 1024 * 1024;
|
|
3868
4221
|
var inputSchema17 = {
|
|
3869
4222
|
file_id: z17.string().min(1).describe("UUID of the file"),
|
|
3870
|
-
revision: z17.number().int().positive().optional().describe("Specific revision number to read (defaults to the latest revision).")
|
|
4223
|
+
revision: z17.number().int().positive().optional().describe("Specific revision number to read (defaults to the latest revision)."),
|
|
4224
|
+
offset: z17.number().int().nonnegative().optional().describe("1-based line number to start reading from (default 1; 0 is an alias for 1)."),
|
|
4225
|
+
limit: z17.number().int().positive().optional().describe("Max lines to return (default 2000, capped at 2000; larger values are clamped).")
|
|
3871
4226
|
};
|
|
3872
4227
|
var readSpecFileTool = {
|
|
3873
4228
|
name: "read_spec_file",
|
|
3874
|
-
description: "Return a spec file's UTF-8 content. 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
|
|
4229
|
+
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.",
|
|
3875
4230
|
inputSchema: inputSchema17,
|
|
3876
4231
|
handler: async (args, ctx) => {
|
|
3877
4232
|
const meta = await ctx.client.getFileMetadata(args.file_id);
|
|
3878
4233
|
const targetRevision = args.revision ?? meta.revisionCount;
|
|
3879
4234
|
const isLatestRevision = targetRevision === meta.revisionCount;
|
|
3880
|
-
if (
|
|
4235
|
+
if (args.revision !== void 0 && args.revision > meta.revisionCount) {
|
|
4236
|
+
return errorResult(
|
|
4237
|
+
`revision=${String(args.revision)} is greater than revision_count=${String(meta.revisionCount)} for file ${args.file_id}.`
|
|
4238
|
+
);
|
|
4239
|
+
}
|
|
4240
|
+
if (isLatestRevision && meta.fileSizeBytes > MAX_READABLE_SPEC_FILE_BYTES) {
|
|
3881
4241
|
return errorResult(buildOversizeMessage2(args.file_id, meta.fileSizeBytes));
|
|
3882
4242
|
}
|
|
3883
4243
|
const cacheRoot = resolveDownloadRoot();
|
|
@@ -3894,20 +4254,42 @@ var readSpecFileTool = {
|
|
|
3894
4254
|
`read_spec_file refused to write cache: ${escape}. This indicates an unexpected platform response shape (projectId/fileId/filePath).`
|
|
3895
4255
|
);
|
|
3896
4256
|
}
|
|
3897
|
-
const
|
|
4257
|
+
const cachedSize = await fileSizeOrNull(cachePath);
|
|
4258
|
+
if (cachedSize !== null && cachedSize > MAX_READABLE_SPEC_FILE_BYTES) {
|
|
4259
|
+
return errorResult(buildOversizeMessage2(args.file_id, cachedSize));
|
|
4260
|
+
}
|
|
4261
|
+
const cached = cachedSize === null ? null : await fs3.readFile(cachePath);
|
|
3898
4262
|
const cacheHit = cached !== null && (!isLatestRevision || sha256Prefixed2(cached) === meta.checksum);
|
|
3899
4263
|
let bytes;
|
|
4264
|
+
let effectiveRevision = targetRevision;
|
|
4265
|
+
let effectiveCachePath = cachePath;
|
|
3900
4266
|
if (cacheHit) {
|
|
3901
4267
|
bytes = cached;
|
|
3902
4268
|
} else {
|
|
3903
4269
|
const downloaded = await ctx.client.downloadFile(args.file_id, args.revision);
|
|
3904
4270
|
bytes = downloaded.bytes;
|
|
3905
|
-
if (bytes.byteLength >
|
|
4271
|
+
if (bytes.byteLength > MAX_READABLE_SPEC_FILE_BYTES) {
|
|
3906
4272
|
return errorResult(buildOversizeMessage2(args.file_id, bytes.byteLength));
|
|
3907
4273
|
}
|
|
3908
|
-
|
|
4274
|
+
if (downloaded.revisionNumber !== targetRevision) {
|
|
4275
|
+
effectiveRevision = downloaded.revisionNumber;
|
|
4276
|
+
effectiveCachePath = computeFileCachePath({
|
|
4277
|
+
cacheRoot,
|
|
4278
|
+
projectId: meta.projectId,
|
|
4279
|
+
fileId: meta.id,
|
|
4280
|
+
revision: effectiveRevision,
|
|
4281
|
+
filePath: meta.filePath
|
|
4282
|
+
});
|
|
4283
|
+
const raceEscape = assertWithinRoot(effectiveCachePath, cacheRoot);
|
|
4284
|
+
if (raceEscape) {
|
|
4285
|
+
return errorResult(
|
|
4286
|
+
`read_spec_file refused to write cache: ${raceEscape}. This indicates an unexpected platform response shape (projectId/fileId/filePath).`
|
|
4287
|
+
);
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
await writeBytesAtomically(effectiveCachePath, bytes);
|
|
3909
4291
|
}
|
|
3910
|
-
if (bytes.byteLength >
|
|
4292
|
+
if (bytes.byteLength > MAX_READABLE_SPEC_FILE_BYTES) {
|
|
3911
4293
|
return errorResult(buildOversizeMessage2(args.file_id, bytes.byteLength));
|
|
3912
4294
|
}
|
|
3913
4295
|
if (isBinary2(bytes)) {
|
|
@@ -3923,25 +4305,44 @@ var readSpecFileTool = {
|
|
|
3923
4305
|
`File ${args.file_id} is not valid UTF-8 text. Use download_spec_file to save it to a local file.`
|
|
3924
4306
|
);
|
|
3925
4307
|
}
|
|
4308
|
+
const paginated = paginateText(text, args.offset, args.limit);
|
|
4309
|
+
if (!paginated.ok) {
|
|
4310
|
+
return errorResult(paginated.error);
|
|
4311
|
+
}
|
|
3926
4312
|
return jsonResult({
|
|
3927
4313
|
file_id: meta.id,
|
|
3928
|
-
revision_number:
|
|
4314
|
+
revision_number: effectiveRevision,
|
|
3929
4315
|
// Pass this back as update_spec_file's expected_version so an edit made by
|
|
3930
4316
|
// someone else since this read is reported instead of overwritten. It
|
|
3931
4317
|
// describes the file's CURRENT content, so it is emitted only when that is
|
|
3932
4318
|
// what was read — beside a historical revision it would invite an update
|
|
3933
4319
|
// that overwrites everything written since.
|
|
3934
|
-
...
|
|
4320
|
+
// ...and only when it describes THESE bytes. If a concurrent save moved
|
|
4321
|
+
// the revision under us, meta.contentVersion belongs to the older one:
|
|
4322
|
+
// a stale token is fail-safe (the platform answers 409) but it would
|
|
4323
|
+
// cost the model a round trip it cannot diagnose, so omit it and let the
|
|
4324
|
+
// re-read supply a current one.
|
|
4325
|
+
...isLatestRevision && effectiveRevision === targetRevision && meta.contentVersion !== void 0 ? { content_version: meta.contentVersion } : {},
|
|
3935
4326
|
file_path: meta.filePath,
|
|
3936
4327
|
file_type: meta.fileType,
|
|
3937
|
-
cache_path: maskHomedir(
|
|
4328
|
+
cache_path: maskHomedir(effectiveCachePath),
|
|
3938
4329
|
cache_hit: cacheHit,
|
|
3939
|
-
content
|
|
4330
|
+
// `content` plus the window fields (start_line/end_line/read_lines/
|
|
4331
|
+
// total_lines/truncated/next_offset) — the same shape read_attachment
|
|
4332
|
+
// and mcp-server-http's read_spec_file return.
|
|
4333
|
+
...paginated.window
|
|
3940
4334
|
});
|
|
3941
4335
|
}
|
|
3942
4336
|
};
|
|
3943
4337
|
function buildOversizeMessage2(fileId, byteLength) {
|
|
3944
|
-
return `File ${fileId} is ${String(byteLength)} bytes which exceeds the read cap of ${String(
|
|
4338
|
+
return `File ${fileId} is ${String(byteLength)} bytes which exceeds the read cap of ${String(MAX_READABLE_SPEC_FILE_BYTES)} bytes. Use download_spec_file to save it to a local file and read it in chunks.`;
|
|
4339
|
+
}
|
|
4340
|
+
async function fileSizeOrNull(filePath) {
|
|
4341
|
+
try {
|
|
4342
|
+
return (await fs3.stat(filePath)).size;
|
|
4343
|
+
} catch {
|
|
4344
|
+
return null;
|
|
4345
|
+
}
|
|
3945
4346
|
}
|
|
3946
4347
|
function isBinary2(bytes) {
|
|
3947
4348
|
for (const byte of bytes) {
|
|
@@ -4571,6 +4972,307 @@ var uploadSpecFileTool = {
|
|
|
4571
4972
|
}
|
|
4572
4973
|
};
|
|
4573
4974
|
|
|
4975
|
+
// src/server/tools/list_artifacts.ts
|
|
4976
|
+
import { z as z27 } from "zod";
|
|
4977
|
+
|
|
4978
|
+
// src/server/tools/artifact-output.ts
|
|
4979
|
+
import { z as z26 } from "zod";
|
|
4980
|
+
var artifactFilesSchema = z26.array(
|
|
4981
|
+
z26.object({
|
|
4982
|
+
path: z26.string().min(1).describe('Bundle-relative path, "/"-separated, no leading slash (e.g. assets/logo.svg).'),
|
|
4983
|
+
content: z26.string().describe("The file content, in `encoding` (default utf8)."),
|
|
4984
|
+
encoding: z26.enum(["utf8", "base64"]).optional().describe("utf8 (default) for text; base64 for binary files such as images."),
|
|
4985
|
+
content_type: z26.string().optional().describe("Hint used only for extensions the platform does not recognise.")
|
|
4986
|
+
})
|
|
4987
|
+
).describe("Files of the bundle.");
|
|
4988
|
+
function artifactSummary(a) {
|
|
4989
|
+
return {
|
|
4990
|
+
id: a.id,
|
|
4991
|
+
project_id: a.projectId,
|
|
4992
|
+
name: a.name,
|
|
4993
|
+
slug: a.slug,
|
|
4994
|
+
kind: a.kind,
|
|
4995
|
+
description: a.description,
|
|
4996
|
+
entry_path: a.entryPath,
|
|
4997
|
+
revision_count: a.revisionCount,
|
|
4998
|
+
session_id: a.sessionId,
|
|
4999
|
+
created_by: a.createdBy,
|
|
5000
|
+
created_at: a.createdAt,
|
|
5001
|
+
updated_at: a.updatedAt,
|
|
5002
|
+
...a.trashedAt ? { trashed_at: a.trashedAt } : {}
|
|
5003
|
+
};
|
|
5004
|
+
}
|
|
5005
|
+
function fileEntry(f) {
|
|
5006
|
+
return { path: f.path, content_type: f.contentType, size_bytes: f.sizeBytes, checksum: f.checksum };
|
|
5007
|
+
}
|
|
5008
|
+
function revisionSummary(r) {
|
|
5009
|
+
return {
|
|
5010
|
+
id: r.id,
|
|
5011
|
+
revision_number: r.revisionNumber,
|
|
5012
|
+
source: r.source,
|
|
5013
|
+
...r.restoredFromRevision === void 0 ? {} : { restored_from_revision: r.restoredFromRevision },
|
|
5014
|
+
message: r.message,
|
|
5015
|
+
changed_by: r.changedBy,
|
|
5016
|
+
changed_by_kind: r.changedByKind,
|
|
5017
|
+
file_count: r.fileCount,
|
|
5018
|
+
total_bytes: r.totalBytes,
|
|
5019
|
+
manifest_checksum: r.manifestChecksum,
|
|
5020
|
+
created_at: r.createdAt,
|
|
5021
|
+
..."files" in r ? { files: r.files.map(fileEntry) } : {}
|
|
5022
|
+
};
|
|
5023
|
+
}
|
|
5024
|
+
function writeResultOutput(result) {
|
|
5025
|
+
return {
|
|
5026
|
+
artifact: artifactSummary(result.artifact),
|
|
5027
|
+
revision: revisionSummary(result.revision),
|
|
5028
|
+
no_change: result.noChange
|
|
5029
|
+
};
|
|
5030
|
+
}
|
|
5031
|
+
function isArtifactWriteConflict(conflict) {
|
|
5032
|
+
return conflict.reason === "revision_mismatch" || conflict.reason === "blob_reclaimed" || conflict.reason === "lock_timeout" || conflict.reason === "unknown";
|
|
5033
|
+
}
|
|
5034
|
+
function artifactConflictMessage(artifactId, conflict) {
|
|
5035
|
+
if (conflict.reason === "lock_timeout") {
|
|
5036
|
+
return `Artifact ${artifactId} was NOT written: another write or a storage collection held its lock and this call timed out waiting. Nothing changed \u2014 retry the same call as-is.`;
|
|
5037
|
+
}
|
|
5038
|
+
if (conflict.reason === "blob_reclaimed") {
|
|
5039
|
+
return `Artifact ${artifactId} was NOT written: a file this write relied on from an older revision is no longer stored (that revision was pruned). Call get_artifact to read the current head, then resend the full content of every file (a full snapshot, or a patch based on the head).`;
|
|
5040
|
+
}
|
|
5041
|
+
const head = conflict.currentVersion === void 0 ? "" : ` The head is now revision ${String(conflict.currentVersion)}.`;
|
|
5042
|
+
return `Artifact ${artifactId} was NOT written: it changed since the revision this write was based on.${head} Call get_artifact to read the current head, reapply your change to it, and retry with expected_version (or base_revision) set to that head.`;
|
|
5043
|
+
}
|
|
5044
|
+
function artifactCreateConflictMessage(conflict) {
|
|
5045
|
+
if (conflict.reason === "slug_taken") {
|
|
5046
|
+
return "Artifact was NOT created: the slug is already taken in this project (trashed artifacts keep their slug). Pick another slug, or call list_artifacts to find the existing artifact and write a new revision to it instead.";
|
|
5047
|
+
}
|
|
5048
|
+
if (conflict.reason === "lock_timeout") {
|
|
5049
|
+
return "Artifact was NOT created: the platform timed out waiting for a lock. Nothing changed \u2014 retry the same call as-is.";
|
|
5050
|
+
}
|
|
5051
|
+
return `Artifact was NOT created: the platform reported a conflict (${conflict.reason}). Re-read the project's artifacts with list_artifacts before retrying.`;
|
|
5052
|
+
}
|
|
5053
|
+
|
|
5054
|
+
// src/server/tools/list_artifacts.ts
|
|
5055
|
+
var LIST_DEFAULT4 = 50;
|
|
5056
|
+
var LIST_MAX4 = 100;
|
|
5057
|
+
var inputSchema26 = {
|
|
5058
|
+
project_id: z27.string().min(1).describe("UUID of the project whose artifacts to list."),
|
|
5059
|
+
limit: z27.number().int().positive().optional().describe(`Max artifacts to return (default ${String(LIST_DEFAULT4)}, capped at ${String(LIST_MAX4)}).`),
|
|
5060
|
+
offset: z27.number().int().nonnegative().optional().describe("Pagination offset (default 0).")
|
|
5061
|
+
};
|
|
5062
|
+
var listArtifactsTool = {
|
|
5063
|
+
name: "list_artifacts",
|
|
5064
|
+
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.",
|
|
5065
|
+
inputSchema: inputSchema26,
|
|
5066
|
+
handler: async (args, ctx) => {
|
|
5067
|
+
const limit = clampLimit(args.limit, LIST_DEFAULT4, LIST_MAX4);
|
|
5068
|
+
const offset = args.offset ?? 0;
|
|
5069
|
+
const page = await ctx.client.listArtifacts(args.project_id, { limit, offset });
|
|
5070
|
+
const next = nextOffsetOf(offset, page.items.length, page.total);
|
|
5071
|
+
return jsonResult({
|
|
5072
|
+
project_id: args.project_id,
|
|
5073
|
+
artifacts: page.items.map(artifactSummary),
|
|
5074
|
+
total: page.total,
|
|
5075
|
+
limit,
|
|
5076
|
+
offset,
|
|
5077
|
+
...next === void 0 ? {} : { next_offset: next }
|
|
5078
|
+
});
|
|
5079
|
+
}
|
|
5080
|
+
};
|
|
5081
|
+
|
|
5082
|
+
// src/server/tools/get_artifact.ts
|
|
5083
|
+
import { z as z28 } from "zod";
|
|
5084
|
+
var inputSchema27 = {
|
|
5085
|
+
artifact_id: z28.string().min(1).describe("UUID of the artifact."),
|
|
5086
|
+
revision: z28.number().int().positive().optional().describe("Revision whose manifest to return (defaults to the head revision).")
|
|
5087
|
+
};
|
|
5088
|
+
var getArtifactTool = {
|
|
5089
|
+
name: "get_artifact",
|
|
5090
|
+
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.",
|
|
5091
|
+
inputSchema: inputSchema27,
|
|
5092
|
+
handler: async (args, ctx) => {
|
|
5093
|
+
const detail = await ctx.client.getArtifact(args.artifact_id);
|
|
5094
|
+
if (args.revision === void 0) {
|
|
5095
|
+
return jsonResult({ artifact: artifactSummary(detail.artifact), revision: revisionSummary(detail.head) });
|
|
5096
|
+
}
|
|
5097
|
+
if (args.revision > detail.artifact.revisionCount) {
|
|
5098
|
+
return errorResult(
|
|
5099
|
+
`revision=${String(args.revision)} is greater than revision_count=${String(detail.artifact.revisionCount)} for artifact ${args.artifact_id}.`
|
|
5100
|
+
);
|
|
5101
|
+
}
|
|
5102
|
+
const revision = await ctx.client.getArtifactRevision(args.artifact_id, args.revision);
|
|
5103
|
+
return jsonResult({ artifact: artifactSummary(detail.artifact), revision: revisionSummary(revision) });
|
|
5104
|
+
}
|
|
5105
|
+
};
|
|
5106
|
+
|
|
5107
|
+
// src/server/tools/read_artifact_file.ts
|
|
5108
|
+
import { z as z29 } from "zod";
|
|
5109
|
+
var MAX_READABLE_ARTIFACT_FILE_BYTES = 8 * 1024 * 1024;
|
|
5110
|
+
var inputSchema28 = {
|
|
5111
|
+
artifact_id: z29.string().min(1).describe("UUID of the artifact."),
|
|
5112
|
+
path: z29.string().min(1).describe("Bundle-relative path of the file (e.g. index.html, assets/app.js)."),
|
|
5113
|
+
revision: z29.number().int().positive().optional().describe("Revision to read from (defaults to the head revision)."),
|
|
5114
|
+
offset: z29.number().int().nonnegative().optional().describe("1-based line number to start reading from (default 1; 0 is an alias for 1)."),
|
|
5115
|
+
limit: z29.number().int().positive().optional().describe("Max lines to return (default 2000, capped at 2000; larger values are clamped).")
|
|
5116
|
+
};
|
|
5117
|
+
var readArtifactFileTool = {
|
|
5118
|
+
name: "read_artifact_file",
|
|
5119
|
+
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.",
|
|
5120
|
+
inputSchema: inputSchema28,
|
|
5121
|
+
handler: async (args, ctx) => {
|
|
5122
|
+
const file = await ctx.client.downloadArtifactFile(args.artifact_id, args.path, args.revision);
|
|
5123
|
+
if (file.content.byteLength > MAX_READABLE_ARTIFACT_FILE_BYTES) {
|
|
5124
|
+
return errorResult(
|
|
5125
|
+
`File ${args.path} of artifact ${args.artifact_id} is ${String(file.content.byteLength)} bytes, above the ${String(MAX_READABLE_ARTIFACT_FILE_BYTES)}-byte read ceiling.`
|
|
5126
|
+
);
|
|
5127
|
+
}
|
|
5128
|
+
if (isBinary(file.content)) {
|
|
5129
|
+
return errorResult(
|
|
5130
|
+
`File ${args.path} of artifact ${args.artifact_id} appears to be binary (content_type=${file.contentType}, size=${String(file.content.byteLength)} bytes) and cannot be read through this tool.`
|
|
5131
|
+
);
|
|
5132
|
+
}
|
|
5133
|
+
const text = decodeUtf8Strict(file.content);
|
|
5134
|
+
if (text === null) {
|
|
5135
|
+
return errorResult(
|
|
5136
|
+
`File ${args.path} of artifact ${args.artifact_id} is not valid UTF-8 text (content_type=${file.contentType}).`
|
|
5137
|
+
);
|
|
5138
|
+
}
|
|
5139
|
+
const paginated = paginateText(text, args.offset, args.limit);
|
|
5140
|
+
if (!paginated.ok) {
|
|
5141
|
+
return errorResult(paginated.error);
|
|
5142
|
+
}
|
|
5143
|
+
return jsonResult({
|
|
5144
|
+
artifact_id: args.artifact_id,
|
|
5145
|
+
path: args.path,
|
|
5146
|
+
...args.revision === void 0 ? {} : { revision_number: args.revision },
|
|
5147
|
+
content_type: file.contentType,
|
|
5148
|
+
checksum: file.checksum,
|
|
5149
|
+
...paginated.window
|
|
5150
|
+
});
|
|
5151
|
+
}
|
|
5152
|
+
};
|
|
5153
|
+
|
|
5154
|
+
// src/server/tools/create_artifact.ts
|
|
5155
|
+
import { z as z30 } from "zod";
|
|
5156
|
+
var inputSchema29 = {
|
|
5157
|
+
project_id: z30.string().min(1).describe("UUID of the project to create the artifact in."),
|
|
5158
|
+
name: z30.string().min(1).describe("Display name (max 200 characters)."),
|
|
5159
|
+
slug: z30.string().optional().describe("URL-safe identifier, unique within the project; derived from the name when omitted."),
|
|
5160
|
+
description: z30.string().optional().describe("Optional description (max 2000 characters)."),
|
|
5161
|
+
entry_path: z30.string().optional().describe("The file a renderer opens first. Defaults to index.html when present, else the only file."),
|
|
5162
|
+
message: z30.string().optional().describe("Short summary of this first revision (max 500 characters)."),
|
|
5163
|
+
files: artifactFilesSchema
|
|
5164
|
+
};
|
|
5165
|
+
var createArtifactTool = {
|
|
5166
|
+
name: "create_artifact",
|
|
5167
|
+
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).",
|
|
5168
|
+
inputSchema: inputSchema29,
|
|
5169
|
+
handler: async (args, ctx) => {
|
|
5170
|
+
try {
|
|
5171
|
+
const result = await ctx.client.createArtifact(args.project_id, {
|
|
5172
|
+
name: args.name,
|
|
5173
|
+
slug: args.slug,
|
|
5174
|
+
description: args.description,
|
|
5175
|
+
entryPath: args.entry_path,
|
|
5176
|
+
message: args.message,
|
|
5177
|
+
files: args.files.map((f) => ({
|
|
5178
|
+
path: f.path,
|
|
5179
|
+
content: f.content,
|
|
5180
|
+
encoding: f.encoding,
|
|
5181
|
+
contentType: f.content_type
|
|
5182
|
+
}))
|
|
5183
|
+
});
|
|
5184
|
+
return jsonResult(writeResultOutput(result));
|
|
5185
|
+
} catch (err) {
|
|
5186
|
+
const conflict = conflictOf(err);
|
|
5187
|
+
if (conflict) {
|
|
5188
|
+
return errorResult(artifactCreateConflictMessage(conflict));
|
|
5189
|
+
}
|
|
5190
|
+
throw err;
|
|
5191
|
+
}
|
|
5192
|
+
}
|
|
5193
|
+
};
|
|
5194
|
+
|
|
5195
|
+
// src/server/tools/write_artifact_revision.ts
|
|
5196
|
+
import { z as z31 } from "zod";
|
|
5197
|
+
var inputSchema30 = {
|
|
5198
|
+
artifact_id: z31.string().min(1).describe("UUID of the artifact."),
|
|
5199
|
+
files: artifactFilesSchema,
|
|
5200
|
+
base_revision: z31.number().int().positive().optional().describe(
|
|
5201
|
+
"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."
|
|
5202
|
+
),
|
|
5203
|
+
remove: z31.array(z31.string().min(1)).optional().describe("Patch mode only: paths to drop from the base revision."),
|
|
5204
|
+
expected_version: z31.number().int().nonnegative().optional().describe(
|
|
5205
|
+
"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."
|
|
5206
|
+
),
|
|
5207
|
+
message: z31.string().optional().describe("Short summary of the change (max 500 characters)."),
|
|
5208
|
+
entry_path: z31.string().optional().describe("Change the file a renderer opens first, from this revision on.")
|
|
5209
|
+
};
|
|
5210
|
+
var writeArtifactRevisionTool = {
|
|
5211
|
+
name: "write_artifact_revision",
|
|
5212
|
+
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.",
|
|
5213
|
+
inputSchema: inputSchema30,
|
|
5214
|
+
handler: async (args, ctx) => {
|
|
5215
|
+
if (args.remove && args.remove.length > 0 && args.base_revision === void 0) {
|
|
5216
|
+
return errorResult("`remove` is only valid in patch mode: set base_revision to the revision you are editing.");
|
|
5217
|
+
}
|
|
5218
|
+
try {
|
|
5219
|
+
const result = await ctx.client.createArtifactRevision(
|
|
5220
|
+
args.artifact_id,
|
|
5221
|
+
{
|
|
5222
|
+
files: args.files.map((f) => ({
|
|
5223
|
+
path: f.path,
|
|
5224
|
+
content: f.content,
|
|
5225
|
+
encoding: f.encoding,
|
|
5226
|
+
contentType: f.content_type
|
|
5227
|
+
})),
|
|
5228
|
+
remove: args.remove,
|
|
5229
|
+
baseRevision: args.base_revision,
|
|
5230
|
+
message: args.message,
|
|
5231
|
+
entryPath: args.entry_path
|
|
5232
|
+
},
|
|
5233
|
+
args.expected_version
|
|
5234
|
+
);
|
|
5235
|
+
return jsonResult(writeResultOutput(result));
|
|
5236
|
+
} catch (err) {
|
|
5237
|
+
const conflict = conflictOf(err);
|
|
5238
|
+
if (!conflict || !isArtifactWriteConflict(conflict)) {
|
|
5239
|
+
throw err;
|
|
5240
|
+
}
|
|
5241
|
+
return errorResult(artifactConflictMessage(args.artifact_id, conflict));
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5244
|
+
};
|
|
5245
|
+
|
|
5246
|
+
// src/server/tools/rollback_artifact.ts
|
|
5247
|
+
import { z as z32 } from "zod";
|
|
5248
|
+
var inputSchema31 = {
|
|
5249
|
+
artifact_id: z32.string().min(1).describe("UUID of the artifact."),
|
|
5250
|
+
revision_number: z32.number().int().positive().describe("The revision whose files to restore."),
|
|
5251
|
+
expected_version: z32.number().int().nonnegative().optional().describe("The revision_count this rollback is based on (from get_artifact); defaults to the current head."),
|
|
5252
|
+
message: z32.string().optional().describe('Optional summary; defaults to "Rolled back to revision N".')
|
|
5253
|
+
};
|
|
5254
|
+
var rollbackArtifactTool = {
|
|
5255
|
+
name: "rollback_artifact",
|
|
5256
|
+
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.",
|
|
5257
|
+
inputSchema: inputSchema31,
|
|
5258
|
+
handler: async (args, ctx) => {
|
|
5259
|
+
try {
|
|
5260
|
+
const result = await ctx.client.rollbackArtifact(
|
|
5261
|
+
args.artifact_id,
|
|
5262
|
+
{ revisionNumber: args.revision_number, message: args.message },
|
|
5263
|
+
args.expected_version
|
|
5264
|
+
);
|
|
5265
|
+
return jsonResult(writeResultOutput(result));
|
|
5266
|
+
} catch (err) {
|
|
5267
|
+
const conflict = conflictOf(err);
|
|
5268
|
+
if (!conflict || !isArtifactWriteConflict(conflict)) {
|
|
5269
|
+
throw err;
|
|
5270
|
+
}
|
|
5271
|
+
return errorResult(artifactConflictMessage(args.artifact_id, conflict));
|
|
5272
|
+
}
|
|
5273
|
+
}
|
|
5274
|
+
};
|
|
5275
|
+
|
|
4574
5276
|
// src/server/server.ts
|
|
4575
5277
|
function buildServer(deps) {
|
|
4576
5278
|
const server = new McpServer({
|
|
@@ -4602,6 +5304,12 @@ function buildServer(deps) {
|
|
|
4602
5304
|
registerTool(server, archiveSpecSessionTool, deps.client);
|
|
4603
5305
|
registerTool(server, unarchiveSpecSessionTool, deps.client);
|
|
4604
5306
|
registerTool(server, deleteSpecSessionTool, deps.client);
|
|
5307
|
+
registerTool(server, listArtifactsTool, deps.client);
|
|
5308
|
+
registerTool(server, getArtifactTool, deps.client);
|
|
5309
|
+
registerTool(server, readArtifactFileTool, deps.client);
|
|
5310
|
+
registerTool(server, createArtifactTool, deps.client);
|
|
5311
|
+
registerTool(server, writeArtifactRevisionTool, deps.client);
|
|
5312
|
+
registerTool(server, rollbackArtifactTool, deps.client);
|
|
4605
5313
|
return server;
|
|
4606
5314
|
}
|
|
4607
5315
|
async function startStdioServer(deps) {
|
|
@@ -4755,7 +5463,7 @@ async function canonicaliseRoot(root) {
|
|
|
4755
5463
|
import { promises as fs4 } from "fs";
|
|
4756
5464
|
import { join as join2, relative } from "path";
|
|
4757
5465
|
import { spawn } from "child_process";
|
|
4758
|
-
import { z as
|
|
5466
|
+
import { z as z33 } from "zod";
|
|
4759
5467
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4760
5468
|
|
|
4761
5469
|
// src/reverse/ignore.ts
|
|
@@ -5060,37 +5768,37 @@ var READ_FILE_MAX_LINES = 2e3;
|
|
|
5060
5768
|
var LIST_DIR_HARD_CAP = 5e3;
|
|
5061
5769
|
var LIST_DIR_DEFAULT_CAP = 1e3;
|
|
5062
5770
|
var GREP_MAX_MATCHES_DEFAULT = 200;
|
|
5063
|
-
var listDirInput =
|
|
5064
|
-
path:
|
|
5065
|
-
recursive:
|
|
5066
|
-
maxEntries:
|
|
5067
|
-
includeIgnored:
|
|
5771
|
+
var listDirInput = z33.object({
|
|
5772
|
+
path: z33.string(),
|
|
5773
|
+
recursive: z33.boolean().optional(),
|
|
5774
|
+
maxEntries: z33.number().int().positive().optional(),
|
|
5775
|
+
includeIgnored: z33.boolean().optional()
|
|
5068
5776
|
});
|
|
5069
|
-
var readFileInput =
|
|
5070
|
-
path:
|
|
5071
|
-
offset:
|
|
5072
|
-
limit:
|
|
5073
|
-
includeIgnored:
|
|
5777
|
+
var readFileInput = z33.object({
|
|
5778
|
+
path: z33.string(),
|
|
5779
|
+
offset: z33.number().int().nonnegative().optional(),
|
|
5780
|
+
limit: z33.number().int().positive().optional(),
|
|
5781
|
+
includeIgnored: z33.boolean().optional()
|
|
5074
5782
|
});
|
|
5075
|
-
var grepInput =
|
|
5076
|
-
pattern:
|
|
5783
|
+
var grepInput = z33.object({
|
|
5784
|
+
pattern: z33.string().describe(
|
|
5077
5785
|
"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."
|
|
5078
5786
|
),
|
|
5079
|
-
path:
|
|
5080
|
-
isRegex:
|
|
5081
|
-
caseSensitive:
|
|
5082
|
-
maxMatches:
|
|
5083
|
-
contextLines:
|
|
5084
|
-
includeIgnored:
|
|
5787
|
+
path: z33.string().optional().describe("File or directory under --root to search. Defaults to the whole root."),
|
|
5788
|
+
isRegex: z33.boolean().optional().describe("Treat `pattern` as a regex (default true). Set false for literal-string search."),
|
|
5789
|
+
caseSensitive: z33.boolean().optional(),
|
|
5790
|
+
maxMatches: z33.number().int().positive().optional(),
|
|
5791
|
+
contextLines: z33.number().int().nonnegative().optional(),
|
|
5792
|
+
includeIgnored: z33.boolean().optional()
|
|
5085
5793
|
});
|
|
5086
|
-
var packCodebaseInput =
|
|
5087
|
-
subpath:
|
|
5088
|
-
includePatterns:
|
|
5089
|
-
ignorePatterns:
|
|
5794
|
+
var packCodebaseInput = z33.object({
|
|
5795
|
+
subpath: z33.string().optional(),
|
|
5796
|
+
includePatterns: z33.string().optional(),
|
|
5797
|
+
ignorePatterns: z33.string().optional()
|
|
5090
5798
|
});
|
|
5091
|
-
var packCodebaseReadPageInput =
|
|
5092
|
-
outputId:
|
|
5093
|
-
page:
|
|
5799
|
+
var packCodebaseReadPageInput = z33.object({
|
|
5800
|
+
outputId: z33.string(),
|
|
5801
|
+
page: z33.number().int().positive()
|
|
5094
5802
|
});
|
|
5095
5803
|
function structuredError(error, message, extra) {
|
|
5096
5804
|
const payload = { error, message, ...extra ?? {} };
|
|
@@ -5618,7 +6326,7 @@ function jsonSchemaFromZod(schema) {
|
|
|
5618
6326
|
return zodToJson(schema);
|
|
5619
6327
|
}
|
|
5620
6328
|
function zodToJson(schema) {
|
|
5621
|
-
if (schema instanceof
|
|
6329
|
+
if (schema instanceof z33.ZodObject) {
|
|
5622
6330
|
const shape = schema.shape;
|
|
5623
6331
|
const properties = {};
|
|
5624
6332
|
const required = [];
|
|
@@ -5633,19 +6341,19 @@ function zodToJson(schema) {
|
|
|
5633
6341
|
if (required.length > 0) out.required = required;
|
|
5634
6342
|
return out;
|
|
5635
6343
|
}
|
|
5636
|
-
if (schema instanceof
|
|
6344
|
+
if (schema instanceof z33.ZodOptional) {
|
|
5637
6345
|
return zodToJson(schema.unwrap());
|
|
5638
6346
|
}
|
|
5639
|
-
if (schema instanceof
|
|
6347
|
+
if (schema instanceof z33.ZodString) {
|
|
5640
6348
|
return { type: "string" };
|
|
5641
6349
|
}
|
|
5642
|
-
if (schema instanceof
|
|
6350
|
+
if (schema instanceof z33.ZodNumber) {
|
|
5643
6351
|
return { type: "number" };
|
|
5644
6352
|
}
|
|
5645
|
-
if (schema instanceof
|
|
6353
|
+
if (schema instanceof z33.ZodBoolean) {
|
|
5646
6354
|
return { type: "boolean" };
|
|
5647
6355
|
}
|
|
5648
|
-
if (schema instanceof
|
|
6356
|
+
if (schema instanceof z33.ZodEnum) {
|
|
5649
6357
|
return { type: "string", enum: schema.options };
|
|
5650
6358
|
}
|
|
5651
6359
|
return { type: "string" };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myspec/mcp-server",
|
|
3
|
-
"version": "0.4.0-next.
|
|
3
|
+
"version": "0.4.0-next.108",
|
|
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": {
|