@myspec/mcp-server 0.4.0-next.107 → 0.4.0-next.109
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 +2 -2
- package/dist/index.js +422 -68
- 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 |
|
package/dist/index.js
CHANGED
|
@@ -32,10 +32,11 @@ var REFRESH_TOKEN_PATTERN = /"refreshToken"\s*:\s*"[^"]+"/g;
|
|
|
32
32
|
var API_TOKEN_PATTERN = /"apiToken"\s*:\s*"[^"]+"/g;
|
|
33
33
|
var API_TOKEN_VALUE_PATTERN = /\bmsp_pat_[A-Za-z0-9]+/g;
|
|
34
34
|
var ACCESS_TOKEN_PATTERN = /"accessToken"\s*:\s*"[^"]+"/g;
|
|
35
|
-
var SNAKE_TOKEN_PATTERN = /"(access_token|refresh_token|id_token)"\s*:\s*"[^"]+"/g;
|
|
35
|
+
var SNAKE_TOKEN_PATTERN = /"(access_token|refresh_token|id_token|token)"\s*:\s*"[^"]+"/g;
|
|
36
36
|
var JWT_PATTERN = /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
|
|
37
|
+
var STREAM_URL_PATTERN = /\/realtime\/v1\/stream\/[A-Za-z0-9_-]{20,}/g;
|
|
37
38
|
function redactSensitive(value) {
|
|
38
|
-
return value.replace(BEARER_PATTERN, "Bearer [REDACTED]").replace(REFRESH_TOKEN_PATTERN, '"refreshToken":"[REDACTED]"').replace(API_TOKEN_PATTERN, '"apiToken":"[REDACTED]"').replace(API_TOKEN_VALUE_PATTERN, "[REDACTED_API_TOKEN]").replace(ACCESS_TOKEN_PATTERN, '"accessToken":"[REDACTED]"').replace(SNAKE_TOKEN_PATTERN, (_match, key) => `"${key}":"[REDACTED]"`).replace(JWT_PATTERN, "[REDACTED_JWT]");
|
|
39
|
+
return value.replace(BEARER_PATTERN, "Bearer [REDACTED]").replace(REFRESH_TOKEN_PATTERN, '"refreshToken":"[REDACTED]"').replace(API_TOKEN_PATTERN, '"apiToken":"[REDACTED]"').replace(API_TOKEN_VALUE_PATTERN, "[REDACTED_API_TOKEN]").replace(ACCESS_TOKEN_PATTERN, '"accessToken":"[REDACTED]"').replace(SNAKE_TOKEN_PATTERN, (_match, key) => `"${key}":"[REDACTED]"`).replace(JWT_PATTERN, "[REDACTED_JWT]").replace(STREAM_URL_PATTERN, "/realtime/v1/stream/[REDACTED_STREAM_TOKEN]");
|
|
39
40
|
}
|
|
40
41
|
var ConfigError = class extends Error {
|
|
41
42
|
constructor(message) {
|
|
@@ -2716,6 +2717,127 @@ var HttpArtifactClient = class {
|
|
|
2716
2717
|
}
|
|
2717
2718
|
};
|
|
2718
2719
|
|
|
2720
|
+
// ../../packages/platform-client/stream/types/stream-token.types.ts
|
|
2721
|
+
function optionalDate(value) {
|
|
2722
|
+
return value ? new Date(value) : null;
|
|
2723
|
+
}
|
|
2724
|
+
function transformStreamToken(raw) {
|
|
2725
|
+
return {
|
|
2726
|
+
id: raw.id,
|
|
2727
|
+
tokenPrefix: raw.token_prefix,
|
|
2728
|
+
label: raw.label ?? "",
|
|
2729
|
+
resourceType: raw.resource_type,
|
|
2730
|
+
resourceId: raw.resource_id,
|
|
2731
|
+
eventTypes: raw.event_types ?? [],
|
|
2732
|
+
createdBy: raw.created_by,
|
|
2733
|
+
createdAt: new Date(raw.created_at),
|
|
2734
|
+
expiresAt: new Date(raw.expires_at),
|
|
2735
|
+
revokedAt: optionalDate(raw.revoked_at),
|
|
2736
|
+
resourceDeletedAt: optionalDate(raw.resource_deleted_at),
|
|
2737
|
+
lastConnectedAt: optionalDate(raw.last_connected_at),
|
|
2738
|
+
connectCount: raw.connect_count,
|
|
2739
|
+
live: raw.live
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
function transformStreamTokenMintResult(raw) {
|
|
2743
|
+
if (!raw.token || !raw.url) {
|
|
2744
|
+
throw new Error("stream token mint response carried no token or url");
|
|
2745
|
+
}
|
|
2746
|
+
return { ...transformStreamToken(raw), token: raw.token, url: raw.url };
|
|
2747
|
+
}
|
|
2748
|
+
function transformStreamTokenListResult(raw) {
|
|
2749
|
+
return {
|
|
2750
|
+
tokens: (raw.tokens ?? []).map(transformStreamToken),
|
|
2751
|
+
total: raw.total ?? 0
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
function transformStreamCatalogue(raw) {
|
|
2755
|
+
return {
|
|
2756
|
+
resources: (raw.resources ?? []).map((resource) => ({
|
|
2757
|
+
resourceType: resource.resource_type,
|
|
2758
|
+
eventTypes: resource.event_types ?? []
|
|
2759
|
+
})),
|
|
2760
|
+
maxTtlSeconds: raw.max_ttl_seconds,
|
|
2761
|
+
defaultTtlSeconds: raw.default_ttl_seconds
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
function buildCreateStreamTokenBody(request) {
|
|
2765
|
+
const body = {
|
|
2766
|
+
resource_type: request.resourceType,
|
|
2767
|
+
resource_id: request.resourceId
|
|
2768
|
+
};
|
|
2769
|
+
body.event_types = request.eventTypes;
|
|
2770
|
+
if (request.ttlSeconds !== void 0) {
|
|
2771
|
+
body.ttl_seconds = request.ttlSeconds;
|
|
2772
|
+
}
|
|
2773
|
+
if (request.label) {
|
|
2774
|
+
body.label = request.label;
|
|
2775
|
+
}
|
|
2776
|
+
return body;
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
// ../../packages/platform-client/stream/http/stream-token.http-client.ts
|
|
2780
|
+
var BASE_PATH2 = "/realtime/v1";
|
|
2781
|
+
function requestOptions2(options) {
|
|
2782
|
+
return options ?? {};
|
|
2783
|
+
}
|
|
2784
|
+
var HttpStreamTokenClient = class {
|
|
2785
|
+
constructor(httpClient) {
|
|
2786
|
+
this.httpClient = httpClient;
|
|
2787
|
+
}
|
|
2788
|
+
httpClient;
|
|
2789
|
+
async createStreamToken(request, jwtToken, options) {
|
|
2790
|
+
const raw = await this.httpClient.post(
|
|
2791
|
+
`${BASE_PATH2}/stream-tokens`,
|
|
2792
|
+
buildCreateStreamTokenBody(request),
|
|
2793
|
+
jwtToken,
|
|
2794
|
+
requestOptions2(options)
|
|
2795
|
+
);
|
|
2796
|
+
return transformStreamTokenMintResult(raw);
|
|
2797
|
+
}
|
|
2798
|
+
async listStreamTokens(options, jwtToken, httpOptions) {
|
|
2799
|
+
const query = new URLSearchParams();
|
|
2800
|
+
if (options?.resourceId) {
|
|
2801
|
+
query.set("resource_id", options.resourceId);
|
|
2802
|
+
}
|
|
2803
|
+
if (options?.liveOnly) {
|
|
2804
|
+
query.set("live", "true");
|
|
2805
|
+
}
|
|
2806
|
+
if (options?.mineOnly) {
|
|
2807
|
+
query.set("mine", "true");
|
|
2808
|
+
}
|
|
2809
|
+
if (options?.limit !== void 0) {
|
|
2810
|
+
query.set("limit", String(options.limit));
|
|
2811
|
+
}
|
|
2812
|
+
if (options?.offset !== void 0) {
|
|
2813
|
+
query.set("offset", String(options.offset));
|
|
2814
|
+
}
|
|
2815
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
2816
|
+
const raw = await this.httpClient.get(
|
|
2817
|
+
`${BASE_PATH2}/stream-tokens${suffix}`,
|
|
2818
|
+
jwtToken,
|
|
2819
|
+
requestOptions2(httpOptions)
|
|
2820
|
+
);
|
|
2821
|
+
return transformStreamTokenListResult(raw);
|
|
2822
|
+
}
|
|
2823
|
+
async revokeStreamToken(id, jwtToken, options) {
|
|
2824
|
+
const raw = await this.httpClient.delete(
|
|
2825
|
+
`${BASE_PATH2}/stream-tokens/${encodeURIComponent(id)}`,
|
|
2826
|
+
jwtToken,
|
|
2827
|
+
requestOptions2(options)
|
|
2828
|
+
);
|
|
2829
|
+
return transformStreamToken(raw);
|
|
2830
|
+
}
|
|
2831
|
+
async getStreamCatalogue(jwtToken, options) {
|
|
2832
|
+
const raw = await this.httpClient.get(
|
|
2833
|
+
`${BASE_PATH2}/stream-catalogue`,
|
|
2834
|
+
jwtToken,
|
|
2835
|
+
requestOptions2(options)
|
|
2836
|
+
);
|
|
2837
|
+
return transformStreamCatalogue(raw);
|
|
2838
|
+
}
|
|
2839
|
+
};
|
|
2840
|
+
|
|
2719
2841
|
// src/platform/client.ts
|
|
2720
2842
|
var silentLogger = {
|
|
2721
2843
|
debug: () => void 0,
|
|
@@ -3003,9 +3125,24 @@ var PlatformClient = class {
|
|
|
3003
3125
|
session: new HttpSpecSessionClient(httpClient),
|
|
3004
3126
|
file: new HttpFileClient(httpClient),
|
|
3005
3127
|
attachment: new HttpAttachmentClient(httpClient),
|
|
3006
|
-
artifact: new HttpArtifactClient(httpClient)
|
|
3128
|
+
artifact: new HttpArtifactClient(httpClient),
|
|
3129
|
+
streamToken: new HttpStreamTokenClient(httpClient)
|
|
3007
3130
|
};
|
|
3008
3131
|
}
|
|
3132
|
+
// ------------------------------------------------------- stream tokens
|
|
3133
|
+
//
|
|
3134
|
+
// A stream token is a presigned WebSocket URL for a project's event feed —
|
|
3135
|
+
// the URL is the whole credential, which is what lets a tool that takes one
|
|
3136
|
+
// string and nothing else consume the feed.
|
|
3137
|
+
async createStreamToken(request) {
|
|
3138
|
+
return this.withTokenRetry((jwt, c) => c.streamToken.createStreamToken(request, jwt));
|
|
3139
|
+
}
|
|
3140
|
+
async listStreamTokens(opts = {}) {
|
|
3141
|
+
return this.withTokenRetry((jwt, c) => c.streamToken.listStreamTokens(opts, jwt));
|
|
3142
|
+
}
|
|
3143
|
+
async revokeStreamToken(id) {
|
|
3144
|
+
return this.withTokenRetry((jwt, c) => c.streamToken.revokeStreamToken(id, jwt));
|
|
3145
|
+
}
|
|
3009
3146
|
// ----------------------------------------------------------- artifacts
|
|
3010
3147
|
//
|
|
3011
3148
|
// Every write is attributed `actorKind: 'ai'`: an MCP client is an
|
|
@@ -3793,6 +3930,9 @@ function sliceWindow(items, limit, offset) {
|
|
|
3793
3930
|
};
|
|
3794
3931
|
}
|
|
3795
3932
|
function nextOffsetOf(offset, returned, total) {
|
|
3933
|
+
if (returned === 0) {
|
|
3934
|
+
return void 0;
|
|
3935
|
+
}
|
|
3796
3936
|
const end = offset + returned;
|
|
3797
3937
|
return end < total ? end : void 0;
|
|
3798
3938
|
}
|
|
@@ -3870,6 +4010,8 @@ var listProjectsTool = {
|
|
|
3870
4010
|
// src/server/tools/list_spec_file.ts
|
|
3871
4011
|
import { z as z13 } from "zod";
|
|
3872
4012
|
var PAGE_SIZE = 100;
|
|
4013
|
+
var LIST_DEFAULT2 = 50;
|
|
4014
|
+
var LIST_MAX2 = 200;
|
|
3873
4015
|
var inputSchema13 = {
|
|
3874
4016
|
project_id: z13.string().min(1).describe("UUID of the project whose spec files to list."),
|
|
3875
4017
|
path: z13.string().optional().describe(
|
|
@@ -3877,26 +4019,57 @@ var inputSchema13 = {
|
|
|
3877
4019
|
),
|
|
3878
4020
|
trashed: z13.boolean().optional().describe(
|
|
3879
4021
|
"When true, list files in the project Trash Bin instead. When false or omitted, list only live files (trashed files excluded)."
|
|
3880
|
-
)
|
|
4022
|
+
),
|
|
4023
|
+
limit: z13.number().int().positive().optional().describe(
|
|
4024
|
+
`Max files to return (default ${String(LIST_DEFAULT2)}, capped at ${String(LIST_MAX2)}).`
|
|
4025
|
+
),
|
|
4026
|
+
offset: z13.number().int().nonnegative().optional().describe("Pagination offset (default 0)")
|
|
3881
4027
|
};
|
|
3882
4028
|
var listSpecFileTool = {
|
|
3883
4029
|
name: "list_spec_file",
|
|
3884
|
-
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.',
|
|
4030
|
+
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`.',
|
|
3885
4031
|
inputSchema: inputSchema13,
|
|
3886
4032
|
handler: async (args, ctx) => {
|
|
3887
4033
|
const trashed = args.trashed ?? false;
|
|
3888
|
-
const
|
|
4034
|
+
const limit = clampLimit(args.limit, LIST_DEFAULT2, LIST_MAX2);
|
|
4035
|
+
const offset = args.offset ?? 0;
|
|
3889
4036
|
const prefix = normalizePrefix(args.path);
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
files
|
|
3897
|
-
|
|
4037
|
+
if (trashed) {
|
|
4038
|
+
const all2 = await ctx.client.listTrashedFiles(args.project_id);
|
|
4039
|
+
return windowedListResult(args.project_id, prefix, true, all2, limit, offset);
|
|
4040
|
+
}
|
|
4041
|
+
if (prefix === "") {
|
|
4042
|
+
const page = await ctx.client.listFiles(args.project_id, { limit, offset });
|
|
4043
|
+
const next = nextOffsetOf(offset, page.files.length, page.total);
|
|
4044
|
+
return jsonResult({
|
|
4045
|
+
project_id: args.project_id,
|
|
4046
|
+
path: prefix,
|
|
4047
|
+
trashed: false,
|
|
4048
|
+
files: page.files.map(toEntry),
|
|
4049
|
+
total: page.total,
|
|
4050
|
+
limit,
|
|
4051
|
+
offset,
|
|
4052
|
+
...next === void 0 ? {} : { next_offset: next }
|
|
4053
|
+
});
|
|
4054
|
+
}
|
|
4055
|
+
const all = await listAllLiveFiles(ctx.client, args.project_id);
|
|
4056
|
+
return windowedListResult(args.project_id, prefix, false, all, limit, offset);
|
|
3898
4057
|
}
|
|
3899
4058
|
};
|
|
4059
|
+
function windowedListResult(projectId, prefix, trashed, files, limit, offset) {
|
|
4060
|
+
const matches = files.filter((f) => isUnderPath(f.filePath, prefix)).map(toEntry).sort((a, b) => a.file_path.localeCompare(b.file_path));
|
|
4061
|
+
const window = sliceWindow(matches, limit, offset);
|
|
4062
|
+
return jsonResult({
|
|
4063
|
+
project_id: projectId,
|
|
4064
|
+
path: prefix,
|
|
4065
|
+
trashed,
|
|
4066
|
+
files: window.items,
|
|
4067
|
+
total: window.total,
|
|
4068
|
+
limit: window.limit,
|
|
4069
|
+
offset,
|
|
4070
|
+
...window.next_offset === void 0 ? {} : { next_offset: window.next_offset }
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
3900
4073
|
async function listAllLiveFiles(client, projectId) {
|
|
3901
4074
|
const all = [];
|
|
3902
4075
|
let offset = 0;
|
|
@@ -3940,24 +4113,24 @@ function isUnderPath(filePath, prefix) {
|
|
|
3940
4113
|
|
|
3941
4114
|
// src/server/tools/list_spec_sessions.ts
|
|
3942
4115
|
import { z as z14 } from "zod";
|
|
3943
|
-
var
|
|
3944
|
-
var
|
|
4116
|
+
var LIST_DEFAULT3 = 10;
|
|
4117
|
+
var LIST_MAX3 = 20;
|
|
3945
4118
|
var inputSchema14 = {
|
|
3946
4119
|
project_id: z14.string().min(1).describe("UUID of the project whose spec sessions to list."),
|
|
3947
4120
|
archive_filter: z14.enum(["active", "archived", "all"]).optional().describe("Which sessions to list: 'active' (default), 'archived', or 'all'."),
|
|
3948
4121
|
status: z14.enum(["generating", "pending_input", "completed"]).optional().describe("Filter by session status."),
|
|
3949
4122
|
query: z14.string().optional().describe("Search term matched against the session summary."),
|
|
3950
4123
|
limit: z14.number().int().positive().optional().describe(
|
|
3951
|
-
`Max sessions to return (default ${String(
|
|
4124
|
+
`Max sessions to return (default ${String(LIST_DEFAULT3)}, capped at ${String(LIST_MAX3)}).`
|
|
3952
4125
|
),
|
|
3953
4126
|
offset: z14.number().int().nonnegative().optional().describe("Pagination offset (default 0)")
|
|
3954
4127
|
};
|
|
3955
4128
|
var listSpecSessionsTool = {
|
|
3956
4129
|
name: "list_spec_sessions",
|
|
3957
|
-
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(
|
|
4130
|
+
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\`.`,
|
|
3958
4131
|
inputSchema: inputSchema14,
|
|
3959
4132
|
handler: async (args, ctx) => {
|
|
3960
|
-
const limit = clampLimit(args.limit,
|
|
4133
|
+
const limit = clampLimit(args.limit, LIST_DEFAULT3, LIST_MAX3);
|
|
3961
4134
|
const offset = args.offset ?? 0;
|
|
3962
4135
|
const result = await ctx.client.listProjectSessions(args.project_id, {
|
|
3963
4136
|
archiveFilter: args.archive_filter ?? "active",
|
|
@@ -4008,6 +4181,9 @@ var MAX_LINES_PER_CALL = 2e3;
|
|
|
4008
4181
|
var MAX_RESPONSE_BYTES = 1 * 1024 * 1024;
|
|
4009
4182
|
function normalizeNewlines(text) {
|
|
4010
4183
|
const withoutBom = text.startsWith("\uFEFF") ? text.slice(1) : text;
|
|
4184
|
+
if (!withoutBom.includes("\r")) {
|
|
4185
|
+
return withoutBom;
|
|
4186
|
+
}
|
|
4011
4187
|
return withoutBom.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
4012
4188
|
}
|
|
4013
4189
|
function splitIntoLines(text) {
|
|
@@ -4178,20 +4354,27 @@ var readAttachmentTool = {
|
|
|
4178
4354
|
import { promises as fs3 } from "fs";
|
|
4179
4355
|
import { createHash as createHash2 } from "crypto";
|
|
4180
4356
|
import { z as z17 } from "zod";
|
|
4181
|
-
var
|
|
4357
|
+
var MAX_READABLE_SPEC_FILE_BYTES = 8 * 1024 * 1024;
|
|
4182
4358
|
var inputSchema17 = {
|
|
4183
4359
|
file_id: z17.string().min(1).describe("UUID of the file"),
|
|
4184
|
-
revision: z17.number().int().positive().optional().describe("Specific revision number to read (defaults to the latest revision).")
|
|
4360
|
+
revision: z17.number().int().positive().optional().describe("Specific revision number to read (defaults to the latest revision)."),
|
|
4361
|
+
offset: z17.number().int().nonnegative().optional().describe("1-based line number to start reading from (default 1; 0 is an alias for 1)."),
|
|
4362
|
+
limit: z17.number().int().positive().optional().describe("Max lines to return (default 2000, capped at 2000; larger values are clamped).")
|
|
4185
4363
|
};
|
|
4186
4364
|
var readSpecFileTool = {
|
|
4187
4365
|
name: "read_spec_file",
|
|
4188
|
-
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
|
|
4366
|
+
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.",
|
|
4189
4367
|
inputSchema: inputSchema17,
|
|
4190
4368
|
handler: async (args, ctx) => {
|
|
4191
4369
|
const meta = await ctx.client.getFileMetadata(args.file_id);
|
|
4192
4370
|
const targetRevision = args.revision ?? meta.revisionCount;
|
|
4193
4371
|
const isLatestRevision = targetRevision === meta.revisionCount;
|
|
4194
|
-
if (
|
|
4372
|
+
if (args.revision !== void 0 && args.revision > meta.revisionCount) {
|
|
4373
|
+
return errorResult(
|
|
4374
|
+
`revision=${String(args.revision)} is greater than revision_count=${String(meta.revisionCount)} for file ${args.file_id}.`
|
|
4375
|
+
);
|
|
4376
|
+
}
|
|
4377
|
+
if (isLatestRevision && meta.fileSizeBytes > MAX_READABLE_SPEC_FILE_BYTES) {
|
|
4195
4378
|
return errorResult(buildOversizeMessage2(args.file_id, meta.fileSizeBytes));
|
|
4196
4379
|
}
|
|
4197
4380
|
const cacheRoot = resolveDownloadRoot();
|
|
@@ -4208,20 +4391,42 @@ var readSpecFileTool = {
|
|
|
4208
4391
|
`read_spec_file refused to write cache: ${escape}. This indicates an unexpected platform response shape (projectId/fileId/filePath).`
|
|
4209
4392
|
);
|
|
4210
4393
|
}
|
|
4211
|
-
const
|
|
4394
|
+
const cachedSize = await fileSizeOrNull(cachePath);
|
|
4395
|
+
if (cachedSize !== null && cachedSize > MAX_READABLE_SPEC_FILE_BYTES) {
|
|
4396
|
+
return errorResult(buildOversizeMessage2(args.file_id, cachedSize));
|
|
4397
|
+
}
|
|
4398
|
+
const cached = cachedSize === null ? null : await fs3.readFile(cachePath);
|
|
4212
4399
|
const cacheHit = cached !== null && (!isLatestRevision || sha256Prefixed2(cached) === meta.checksum);
|
|
4213
4400
|
let bytes;
|
|
4401
|
+
let effectiveRevision = targetRevision;
|
|
4402
|
+
let effectiveCachePath = cachePath;
|
|
4214
4403
|
if (cacheHit) {
|
|
4215
4404
|
bytes = cached;
|
|
4216
4405
|
} else {
|
|
4217
4406
|
const downloaded = await ctx.client.downloadFile(args.file_id, args.revision);
|
|
4218
4407
|
bytes = downloaded.bytes;
|
|
4219
|
-
if (bytes.byteLength >
|
|
4408
|
+
if (bytes.byteLength > MAX_READABLE_SPEC_FILE_BYTES) {
|
|
4220
4409
|
return errorResult(buildOversizeMessage2(args.file_id, bytes.byteLength));
|
|
4221
4410
|
}
|
|
4222
|
-
|
|
4411
|
+
if (downloaded.revisionNumber !== targetRevision) {
|
|
4412
|
+
effectiveRevision = downloaded.revisionNumber;
|
|
4413
|
+
effectiveCachePath = computeFileCachePath({
|
|
4414
|
+
cacheRoot,
|
|
4415
|
+
projectId: meta.projectId,
|
|
4416
|
+
fileId: meta.id,
|
|
4417
|
+
revision: effectiveRevision,
|
|
4418
|
+
filePath: meta.filePath
|
|
4419
|
+
});
|
|
4420
|
+
const raceEscape = assertWithinRoot(effectiveCachePath, cacheRoot);
|
|
4421
|
+
if (raceEscape) {
|
|
4422
|
+
return errorResult(
|
|
4423
|
+
`read_spec_file refused to write cache: ${raceEscape}. This indicates an unexpected platform response shape (projectId/fileId/filePath).`
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
await writeBytesAtomically(effectiveCachePath, bytes);
|
|
4223
4428
|
}
|
|
4224
|
-
if (bytes.byteLength >
|
|
4429
|
+
if (bytes.byteLength > MAX_READABLE_SPEC_FILE_BYTES) {
|
|
4225
4430
|
return errorResult(buildOversizeMessage2(args.file_id, bytes.byteLength));
|
|
4226
4431
|
}
|
|
4227
4432
|
if (isBinary2(bytes)) {
|
|
@@ -4237,25 +4442,44 @@ var readSpecFileTool = {
|
|
|
4237
4442
|
`File ${args.file_id} is not valid UTF-8 text. Use download_spec_file to save it to a local file.`
|
|
4238
4443
|
);
|
|
4239
4444
|
}
|
|
4445
|
+
const paginated = paginateText(text, args.offset, args.limit);
|
|
4446
|
+
if (!paginated.ok) {
|
|
4447
|
+
return errorResult(paginated.error);
|
|
4448
|
+
}
|
|
4240
4449
|
return jsonResult({
|
|
4241
4450
|
file_id: meta.id,
|
|
4242
|
-
revision_number:
|
|
4451
|
+
revision_number: effectiveRevision,
|
|
4243
4452
|
// Pass this back as update_spec_file's expected_version so an edit made by
|
|
4244
4453
|
// someone else since this read is reported instead of overwritten. It
|
|
4245
4454
|
// describes the file's CURRENT content, so it is emitted only when that is
|
|
4246
4455
|
// what was read — beside a historical revision it would invite an update
|
|
4247
4456
|
// that overwrites everything written since.
|
|
4248
|
-
...
|
|
4457
|
+
// ...and only when it describes THESE bytes. If a concurrent save moved
|
|
4458
|
+
// the revision under us, meta.contentVersion belongs to the older one:
|
|
4459
|
+
// a stale token is fail-safe (the platform answers 409) but it would
|
|
4460
|
+
// cost the model a round trip it cannot diagnose, so omit it and let the
|
|
4461
|
+
// re-read supply a current one.
|
|
4462
|
+
...isLatestRevision && effectiveRevision === targetRevision && meta.contentVersion !== void 0 ? { content_version: meta.contentVersion } : {},
|
|
4249
4463
|
file_path: meta.filePath,
|
|
4250
4464
|
file_type: meta.fileType,
|
|
4251
|
-
cache_path: maskHomedir(
|
|
4465
|
+
cache_path: maskHomedir(effectiveCachePath),
|
|
4252
4466
|
cache_hit: cacheHit,
|
|
4253
|
-
content
|
|
4467
|
+
// `content` plus the window fields (start_line/end_line/read_lines/
|
|
4468
|
+
// total_lines/truncated/next_offset) — the same shape read_attachment
|
|
4469
|
+
// and mcp-server-http's read_spec_file return.
|
|
4470
|
+
...paginated.window
|
|
4254
4471
|
});
|
|
4255
4472
|
}
|
|
4256
4473
|
};
|
|
4257
4474
|
function buildOversizeMessage2(fileId, byteLength) {
|
|
4258
|
-
return `File ${fileId} is ${String(byteLength)} bytes which exceeds the read cap of ${String(
|
|
4475
|
+
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.`;
|
|
4476
|
+
}
|
|
4477
|
+
async function fileSizeOrNull(filePath) {
|
|
4478
|
+
try {
|
|
4479
|
+
return (await fs3.stat(filePath)).size;
|
|
4480
|
+
} catch {
|
|
4481
|
+
return null;
|
|
4482
|
+
}
|
|
4259
4483
|
}
|
|
4260
4484
|
function isBinary2(bytes) {
|
|
4261
4485
|
for (const byte of bytes) {
|
|
@@ -4965,11 +5189,11 @@ function artifactCreateConflictMessage(conflict) {
|
|
|
4965
5189
|
}
|
|
4966
5190
|
|
|
4967
5191
|
// src/server/tools/list_artifacts.ts
|
|
4968
|
-
var
|
|
4969
|
-
var
|
|
5192
|
+
var LIST_DEFAULT4 = 50;
|
|
5193
|
+
var LIST_MAX4 = 100;
|
|
4970
5194
|
var inputSchema26 = {
|
|
4971
5195
|
project_id: z27.string().min(1).describe("UUID of the project whose artifacts to list."),
|
|
4972
|
-
limit: z27.number().int().positive().optional().describe(`Max artifacts to return (default ${String(
|
|
5196
|
+
limit: z27.number().int().positive().optional().describe(`Max artifacts to return (default ${String(LIST_DEFAULT4)}, capped at ${String(LIST_MAX4)}).`),
|
|
4973
5197
|
offset: z27.number().int().nonnegative().optional().describe("Pagination offset (default 0).")
|
|
4974
5198
|
};
|
|
4975
5199
|
var listArtifactsTool = {
|
|
@@ -4977,7 +5201,7 @@ var listArtifactsTool = {
|
|
|
4977
5201
|
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.",
|
|
4978
5202
|
inputSchema: inputSchema26,
|
|
4979
5203
|
handler: async (args, ctx) => {
|
|
4980
|
-
const limit = clampLimit(args.limit,
|
|
5204
|
+
const limit = clampLimit(args.limit, LIST_DEFAULT4, LIST_MAX4);
|
|
4981
5205
|
const offset = args.offset ?? 0;
|
|
4982
5206
|
const page = await ctx.client.listArtifacts(args.project_id, { limit, offset });
|
|
4983
5207
|
const next = nextOffsetOf(offset, page.items.length, page.total);
|
|
@@ -5186,6 +5410,133 @@ var rollbackArtifactTool = {
|
|
|
5186
5410
|
}
|
|
5187
5411
|
};
|
|
5188
5412
|
|
|
5413
|
+
// src/server/tools/create_stream_token.ts
|
|
5414
|
+
import { z as z34 } from "zod";
|
|
5415
|
+
|
|
5416
|
+
// src/server/tools/stream-token-output.ts
|
|
5417
|
+
import { z as z33 } from "zod";
|
|
5418
|
+
var projectStreamEventTypes = [
|
|
5419
|
+
"project.updated",
|
|
5420
|
+
"project.deleted",
|
|
5421
|
+
"spec_file.created",
|
|
5422
|
+
"spec_file.updated",
|
|
5423
|
+
"spec_file.deleted",
|
|
5424
|
+
"spec_file.lock",
|
|
5425
|
+
"attachment.created",
|
|
5426
|
+
"attachment.deleted",
|
|
5427
|
+
"spec_session.created",
|
|
5428
|
+
"spec_session.updated",
|
|
5429
|
+
"spec_session.deleted",
|
|
5430
|
+
"*"
|
|
5431
|
+
];
|
|
5432
|
+
var streamEventTypesSchema = z33.array(z33.enum(projectStreamEventTypes)).min(1).describe(
|
|
5433
|
+
'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.'
|
|
5434
|
+
);
|
|
5435
|
+
function streamTokenSummary(token) {
|
|
5436
|
+
return {
|
|
5437
|
+
id: token.id,
|
|
5438
|
+
token_prefix: token.tokenPrefix,
|
|
5439
|
+
label: token.label,
|
|
5440
|
+
resource_type: token.resourceType,
|
|
5441
|
+
resource_id: token.resourceId,
|
|
5442
|
+
event_types: token.eventTypes,
|
|
5443
|
+
created_by: token.createdBy,
|
|
5444
|
+
created_at: token.createdAt.toISOString(),
|
|
5445
|
+
expires_at: token.expiresAt.toISOString(),
|
|
5446
|
+
live: token.live,
|
|
5447
|
+
connect_count: token.connectCount,
|
|
5448
|
+
...token.revokedAt ? { revoked_at: token.revokedAt.toISOString() } : {},
|
|
5449
|
+
...token.resourceDeletedAt ? { resource_deleted_at: token.resourceDeletedAt.toISOString() } : {},
|
|
5450
|
+
...token.lastConnectedAt ? { last_connected_at: token.lastConnectedAt.toISOString() } : {}
|
|
5451
|
+
};
|
|
5452
|
+
}
|
|
5453
|
+
function streamTokenMintOutput(result) {
|
|
5454
|
+
return {
|
|
5455
|
+
...streamTokenSummary(result),
|
|
5456
|
+
url: result.url,
|
|
5457
|
+
note: "Treat this URL as a password: anyone who has it can read this project's events until it expires, with no further authentication. Pass it straight to a WebSocket client and do NOT write it to a file, a commit, an issue or a chat reply, and do not log or repeat it. It is shown once and cannot be retrieved again. If it may have been exposed, call revoke_stream_token with the id above. Open it with subprotocol 'v1'; each text frame is one JSON event, and reconnecting with ?after=<seq> resumes from the last frame you saw."
|
|
5458
|
+
};
|
|
5459
|
+
}
|
|
5460
|
+
|
|
5461
|
+
// src/server/tools/create_stream_token.ts
|
|
5462
|
+
var inputSchema32 = {
|
|
5463
|
+
resource_type: z34.enum(["project"]).default("project").describe("What to follow. Only 'project' is supported today."),
|
|
5464
|
+
resource_id: z34.string().min(1).describe("UUID of the project to stream events for."),
|
|
5465
|
+
event_types: streamEventTypesSchema,
|
|
5466
|
+
ttl_seconds: z34.number().int().positive().optional().describe(
|
|
5467
|
+
"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."
|
|
5468
|
+
),
|
|
5469
|
+
label: z34.string().optional().describe("Short human-readable label, so a person can recognise this token in a listing.")
|
|
5470
|
+
};
|
|
5471
|
+
var createStreamTokenTool = {
|
|
5472
|
+
name: "create_stream_token",
|
|
5473
|
+
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.",
|
|
5474
|
+
inputSchema: inputSchema32,
|
|
5475
|
+
handler: async (args, ctx) => {
|
|
5476
|
+
const result = await ctx.client.createStreamToken({
|
|
5477
|
+
resourceType: args.resource_type,
|
|
5478
|
+
resourceId: args.resource_id,
|
|
5479
|
+
eventTypes: args.event_types,
|
|
5480
|
+
ttlSeconds: args.ttl_seconds,
|
|
5481
|
+
label: args.label
|
|
5482
|
+
});
|
|
5483
|
+
return jsonResult(streamTokenMintOutput(result));
|
|
5484
|
+
}
|
|
5485
|
+
};
|
|
5486
|
+
|
|
5487
|
+
// src/server/tools/list_stream_tokens.ts
|
|
5488
|
+
import { z as z35 } from "zod";
|
|
5489
|
+
var LIST_DEFAULT5 = 50;
|
|
5490
|
+
var LIST_MAX5 = 200;
|
|
5491
|
+
var inputSchema33 = {
|
|
5492
|
+
resource_id: z35.string().optional().describe("Only tokens following this project."),
|
|
5493
|
+
live_only: z35.boolean().optional().describe("Hide expired and revoked tokens."),
|
|
5494
|
+
mine_only: z35.boolean().optional().describe(
|
|
5495
|
+
"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."
|
|
5496
|
+
),
|
|
5497
|
+
limit: z35.number().int().positive().optional().describe(`Max tokens to return (default ${String(LIST_DEFAULT5)}, capped at ${String(LIST_MAX5)}).`),
|
|
5498
|
+
offset: z35.number().int().nonnegative().optional().describe("Pagination offset (default 0).")
|
|
5499
|
+
};
|
|
5500
|
+
var listStreamTokensTool = {
|
|
5501
|
+
name: "list_stream_tokens",
|
|
5502
|
+
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`.",
|
|
5503
|
+
inputSchema: inputSchema33,
|
|
5504
|
+
handler: async (args, ctx) => {
|
|
5505
|
+
const limit = clampLimit(args.limit, LIST_DEFAULT5, LIST_MAX5);
|
|
5506
|
+
const offset = args.offset ?? 0;
|
|
5507
|
+
const page = await ctx.client.listStreamTokens({
|
|
5508
|
+
resourceId: args.resource_id,
|
|
5509
|
+
liveOnly: args.live_only,
|
|
5510
|
+
mineOnly: args.mine_only,
|
|
5511
|
+
limit,
|
|
5512
|
+
offset
|
|
5513
|
+
});
|
|
5514
|
+
const next = nextOffsetOf(offset, page.tokens.length, page.total);
|
|
5515
|
+
return jsonResult({
|
|
5516
|
+
tokens: page.tokens.map(streamTokenSummary),
|
|
5517
|
+
total: page.total,
|
|
5518
|
+
limit,
|
|
5519
|
+
offset,
|
|
5520
|
+
...next === void 0 ? {} : { next_offset: next }
|
|
5521
|
+
});
|
|
5522
|
+
}
|
|
5523
|
+
};
|
|
5524
|
+
|
|
5525
|
+
// src/server/tools/revoke_stream_token.ts
|
|
5526
|
+
import { z as z36 } from "zod";
|
|
5527
|
+
var inputSchema34 = {
|
|
5528
|
+
id: z36.string().min(1).describe("Token id (from create_stream_token or list_stream_tokens) \u2014 NOT the secret itself.")
|
|
5529
|
+
};
|
|
5530
|
+
var revokeStreamTokenTool = {
|
|
5531
|
+
name: "revoke_stream_token",
|
|
5532
|
+
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.",
|
|
5533
|
+
inputSchema: inputSchema34,
|
|
5534
|
+
handler: async (args, ctx) => {
|
|
5535
|
+
const token = await ctx.client.revokeStreamToken(args.id);
|
|
5536
|
+
return jsonResult({ ...streamTokenSummary(token), revoked: true });
|
|
5537
|
+
}
|
|
5538
|
+
};
|
|
5539
|
+
|
|
5189
5540
|
// src/server/server.ts
|
|
5190
5541
|
function buildServer(deps) {
|
|
5191
5542
|
const server = new McpServer({
|
|
@@ -5223,6 +5574,9 @@ function buildServer(deps) {
|
|
|
5223
5574
|
registerTool(server, createArtifactTool, deps.client);
|
|
5224
5575
|
registerTool(server, writeArtifactRevisionTool, deps.client);
|
|
5225
5576
|
registerTool(server, rollbackArtifactTool, deps.client);
|
|
5577
|
+
registerTool(server, createStreamTokenTool, deps.client);
|
|
5578
|
+
registerTool(server, listStreamTokensTool, deps.client);
|
|
5579
|
+
registerTool(server, revokeStreamTokenTool, deps.client);
|
|
5226
5580
|
return server;
|
|
5227
5581
|
}
|
|
5228
5582
|
async function startStdioServer(deps) {
|
|
@@ -5376,7 +5730,7 @@ async function canonicaliseRoot(root) {
|
|
|
5376
5730
|
import { promises as fs4 } from "fs";
|
|
5377
5731
|
import { join as join2, relative } from "path";
|
|
5378
5732
|
import { spawn } from "child_process";
|
|
5379
|
-
import { z as
|
|
5733
|
+
import { z as z37 } from "zod";
|
|
5380
5734
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5381
5735
|
|
|
5382
5736
|
// src/reverse/ignore.ts
|
|
@@ -5681,37 +6035,37 @@ var READ_FILE_MAX_LINES = 2e3;
|
|
|
5681
6035
|
var LIST_DIR_HARD_CAP = 5e3;
|
|
5682
6036
|
var LIST_DIR_DEFAULT_CAP = 1e3;
|
|
5683
6037
|
var GREP_MAX_MATCHES_DEFAULT = 200;
|
|
5684
|
-
var listDirInput =
|
|
5685
|
-
path:
|
|
5686
|
-
recursive:
|
|
5687
|
-
maxEntries:
|
|
5688
|
-
includeIgnored:
|
|
6038
|
+
var listDirInput = z37.object({
|
|
6039
|
+
path: z37.string(),
|
|
6040
|
+
recursive: z37.boolean().optional(),
|
|
6041
|
+
maxEntries: z37.number().int().positive().optional(),
|
|
6042
|
+
includeIgnored: z37.boolean().optional()
|
|
5689
6043
|
});
|
|
5690
|
-
var readFileInput =
|
|
5691
|
-
path:
|
|
5692
|
-
offset:
|
|
5693
|
-
limit:
|
|
5694
|
-
includeIgnored:
|
|
6044
|
+
var readFileInput = z37.object({
|
|
6045
|
+
path: z37.string(),
|
|
6046
|
+
offset: z37.number().int().nonnegative().optional(),
|
|
6047
|
+
limit: z37.number().int().positive().optional(),
|
|
6048
|
+
includeIgnored: z37.boolean().optional()
|
|
5695
6049
|
});
|
|
5696
|
-
var grepInput =
|
|
5697
|
-
pattern:
|
|
6050
|
+
var grepInput = z37.object({
|
|
6051
|
+
pattern: z37.string().describe(
|
|
5698
6052
|
"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."
|
|
5699
6053
|
),
|
|
5700
|
-
path:
|
|
5701
|
-
isRegex:
|
|
5702
|
-
caseSensitive:
|
|
5703
|
-
maxMatches:
|
|
5704
|
-
contextLines:
|
|
5705
|
-
includeIgnored:
|
|
6054
|
+
path: z37.string().optional().describe("File or directory under --root to search. Defaults to the whole root."),
|
|
6055
|
+
isRegex: z37.boolean().optional().describe("Treat `pattern` as a regex (default true). Set false for literal-string search."),
|
|
6056
|
+
caseSensitive: z37.boolean().optional(),
|
|
6057
|
+
maxMatches: z37.number().int().positive().optional(),
|
|
6058
|
+
contextLines: z37.number().int().nonnegative().optional(),
|
|
6059
|
+
includeIgnored: z37.boolean().optional()
|
|
5706
6060
|
});
|
|
5707
|
-
var packCodebaseInput =
|
|
5708
|
-
subpath:
|
|
5709
|
-
includePatterns:
|
|
5710
|
-
ignorePatterns:
|
|
6061
|
+
var packCodebaseInput = z37.object({
|
|
6062
|
+
subpath: z37.string().optional(),
|
|
6063
|
+
includePatterns: z37.string().optional(),
|
|
6064
|
+
ignorePatterns: z37.string().optional()
|
|
5711
6065
|
});
|
|
5712
|
-
var packCodebaseReadPageInput =
|
|
5713
|
-
outputId:
|
|
5714
|
-
page:
|
|
6066
|
+
var packCodebaseReadPageInput = z37.object({
|
|
6067
|
+
outputId: z37.string(),
|
|
6068
|
+
page: z37.number().int().positive()
|
|
5715
6069
|
});
|
|
5716
6070
|
function structuredError(error, message, extra) {
|
|
5717
6071
|
const payload = { error, message, ...extra ?? {} };
|
|
@@ -6239,7 +6593,7 @@ function jsonSchemaFromZod(schema) {
|
|
|
6239
6593
|
return zodToJson(schema);
|
|
6240
6594
|
}
|
|
6241
6595
|
function zodToJson(schema) {
|
|
6242
|
-
if (schema instanceof
|
|
6596
|
+
if (schema instanceof z37.ZodObject) {
|
|
6243
6597
|
const shape = schema.shape;
|
|
6244
6598
|
const properties = {};
|
|
6245
6599
|
const required = [];
|
|
@@ -6254,19 +6608,19 @@ function zodToJson(schema) {
|
|
|
6254
6608
|
if (required.length > 0) out.required = required;
|
|
6255
6609
|
return out;
|
|
6256
6610
|
}
|
|
6257
|
-
if (schema instanceof
|
|
6611
|
+
if (schema instanceof z37.ZodOptional) {
|
|
6258
6612
|
return zodToJson(schema.unwrap());
|
|
6259
6613
|
}
|
|
6260
|
-
if (schema instanceof
|
|
6614
|
+
if (schema instanceof z37.ZodString) {
|
|
6261
6615
|
return { type: "string" };
|
|
6262
6616
|
}
|
|
6263
|
-
if (schema instanceof
|
|
6617
|
+
if (schema instanceof z37.ZodNumber) {
|
|
6264
6618
|
return { type: "number" };
|
|
6265
6619
|
}
|
|
6266
|
-
if (schema instanceof
|
|
6620
|
+
if (schema instanceof z37.ZodBoolean) {
|
|
6267
6621
|
return { type: "boolean" };
|
|
6268
6622
|
}
|
|
6269
|
-
if (schema instanceof
|
|
6623
|
+
if (schema instanceof z37.ZodEnum) {
|
|
6270
6624
|
return { type: "string", enum: schema.options };
|
|
6271
6625
|
}
|
|
6272
6626
|
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.109",
|
|
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": {
|