@myspec/mcp-server 0.4.0-next.107 → 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 +2 -2
- package/dist/index.js +120 -33
- 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
|
@@ -3793,6 +3793,9 @@ function sliceWindow(items, limit, offset) {
|
|
|
3793
3793
|
};
|
|
3794
3794
|
}
|
|
3795
3795
|
function nextOffsetOf(offset, returned, total) {
|
|
3796
|
+
if (returned === 0) {
|
|
3797
|
+
return void 0;
|
|
3798
|
+
}
|
|
3796
3799
|
const end = offset + returned;
|
|
3797
3800
|
return end < total ? end : void 0;
|
|
3798
3801
|
}
|
|
@@ -3870,6 +3873,8 @@ var listProjectsTool = {
|
|
|
3870
3873
|
// src/server/tools/list_spec_file.ts
|
|
3871
3874
|
import { z as z13 } from "zod";
|
|
3872
3875
|
var PAGE_SIZE = 100;
|
|
3876
|
+
var LIST_DEFAULT2 = 50;
|
|
3877
|
+
var LIST_MAX2 = 200;
|
|
3873
3878
|
var inputSchema13 = {
|
|
3874
3879
|
project_id: z13.string().min(1).describe("UUID of the project whose spec files to list."),
|
|
3875
3880
|
path: z13.string().optional().describe(
|
|
@@ -3877,26 +3882,57 @@ var inputSchema13 = {
|
|
|
3877
3882
|
),
|
|
3878
3883
|
trashed: z13.boolean().optional().describe(
|
|
3879
3884
|
"When true, list files in the project Trash Bin instead. When false or omitted, list only live files (trashed files excluded)."
|
|
3880
|
-
)
|
|
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)")
|
|
3881
3890
|
};
|
|
3882
3891
|
var listSpecFileTool = {
|
|
3883
3892
|
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.',
|
|
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`.',
|
|
3885
3894
|
inputSchema: inputSchema13,
|
|
3886
3895
|
handler: async (args, ctx) => {
|
|
3887
3896
|
const trashed = args.trashed ?? false;
|
|
3888
|
-
const
|
|
3897
|
+
const limit = clampLimit(args.limit, LIST_DEFAULT2, LIST_MAX2);
|
|
3898
|
+
const offset = args.offset ?? 0;
|
|
3889
3899
|
const prefix = normalizePrefix(args.path);
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
files
|
|
3897
|
-
|
|
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);
|
|
3898
3920
|
}
|
|
3899
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
|
+
}
|
|
3900
3936
|
async function listAllLiveFiles(client, projectId) {
|
|
3901
3937
|
const all = [];
|
|
3902
3938
|
let offset = 0;
|
|
@@ -3940,24 +3976,24 @@ function isUnderPath(filePath, prefix) {
|
|
|
3940
3976
|
|
|
3941
3977
|
// src/server/tools/list_spec_sessions.ts
|
|
3942
3978
|
import { z as z14 } from "zod";
|
|
3943
|
-
var
|
|
3944
|
-
var
|
|
3979
|
+
var LIST_DEFAULT3 = 10;
|
|
3980
|
+
var LIST_MAX3 = 20;
|
|
3945
3981
|
var inputSchema14 = {
|
|
3946
3982
|
project_id: z14.string().min(1).describe("UUID of the project whose spec sessions to list."),
|
|
3947
3983
|
archive_filter: z14.enum(["active", "archived", "all"]).optional().describe("Which sessions to list: 'active' (default), 'archived', or 'all'."),
|
|
3948
3984
|
status: z14.enum(["generating", "pending_input", "completed"]).optional().describe("Filter by session status."),
|
|
3949
3985
|
query: z14.string().optional().describe("Search term matched against the session summary."),
|
|
3950
3986
|
limit: z14.number().int().positive().optional().describe(
|
|
3951
|
-
`Max sessions to return (default ${String(
|
|
3987
|
+
`Max sessions to return (default ${String(LIST_DEFAULT3)}, capped at ${String(LIST_MAX3)}).`
|
|
3952
3988
|
),
|
|
3953
3989
|
offset: z14.number().int().nonnegative().optional().describe("Pagination offset (default 0)")
|
|
3954
3990
|
};
|
|
3955
3991
|
var listSpecSessionsTool = {
|
|
3956
3992
|
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(
|
|
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\`.`,
|
|
3958
3994
|
inputSchema: inputSchema14,
|
|
3959
3995
|
handler: async (args, ctx) => {
|
|
3960
|
-
const limit = clampLimit(args.limit,
|
|
3996
|
+
const limit = clampLimit(args.limit, LIST_DEFAULT3, LIST_MAX3);
|
|
3961
3997
|
const offset = args.offset ?? 0;
|
|
3962
3998
|
const result = await ctx.client.listProjectSessions(args.project_id, {
|
|
3963
3999
|
archiveFilter: args.archive_filter ?? "active",
|
|
@@ -4008,6 +4044,9 @@ var MAX_LINES_PER_CALL = 2e3;
|
|
|
4008
4044
|
var MAX_RESPONSE_BYTES = 1 * 1024 * 1024;
|
|
4009
4045
|
function normalizeNewlines(text) {
|
|
4010
4046
|
const withoutBom = text.startsWith("\uFEFF") ? text.slice(1) : text;
|
|
4047
|
+
if (!withoutBom.includes("\r")) {
|
|
4048
|
+
return withoutBom;
|
|
4049
|
+
}
|
|
4011
4050
|
return withoutBom.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
4012
4051
|
}
|
|
4013
4052
|
function splitIntoLines(text) {
|
|
@@ -4178,20 +4217,27 @@ var readAttachmentTool = {
|
|
|
4178
4217
|
import { promises as fs3 } from "fs";
|
|
4179
4218
|
import { createHash as createHash2 } from "crypto";
|
|
4180
4219
|
import { z as z17 } from "zod";
|
|
4181
|
-
var
|
|
4220
|
+
var MAX_READABLE_SPEC_FILE_BYTES = 8 * 1024 * 1024;
|
|
4182
4221
|
var inputSchema17 = {
|
|
4183
4222
|
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).")
|
|
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).")
|
|
4185
4226
|
};
|
|
4186
4227
|
var readSpecFileTool = {
|
|
4187
4228
|
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
|
|
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.",
|
|
4189
4230
|
inputSchema: inputSchema17,
|
|
4190
4231
|
handler: async (args, ctx) => {
|
|
4191
4232
|
const meta = await ctx.client.getFileMetadata(args.file_id);
|
|
4192
4233
|
const targetRevision = args.revision ?? meta.revisionCount;
|
|
4193
4234
|
const isLatestRevision = targetRevision === meta.revisionCount;
|
|
4194
|
-
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) {
|
|
4195
4241
|
return errorResult(buildOversizeMessage2(args.file_id, meta.fileSizeBytes));
|
|
4196
4242
|
}
|
|
4197
4243
|
const cacheRoot = resolveDownloadRoot();
|
|
@@ -4208,20 +4254,42 @@ var readSpecFileTool = {
|
|
|
4208
4254
|
`read_spec_file refused to write cache: ${escape}. This indicates an unexpected platform response shape (projectId/fileId/filePath).`
|
|
4209
4255
|
);
|
|
4210
4256
|
}
|
|
4211
|
-
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);
|
|
4212
4262
|
const cacheHit = cached !== null && (!isLatestRevision || sha256Prefixed2(cached) === meta.checksum);
|
|
4213
4263
|
let bytes;
|
|
4264
|
+
let effectiveRevision = targetRevision;
|
|
4265
|
+
let effectiveCachePath = cachePath;
|
|
4214
4266
|
if (cacheHit) {
|
|
4215
4267
|
bytes = cached;
|
|
4216
4268
|
} else {
|
|
4217
4269
|
const downloaded = await ctx.client.downloadFile(args.file_id, args.revision);
|
|
4218
4270
|
bytes = downloaded.bytes;
|
|
4219
|
-
if (bytes.byteLength >
|
|
4271
|
+
if (bytes.byteLength > MAX_READABLE_SPEC_FILE_BYTES) {
|
|
4220
4272
|
return errorResult(buildOversizeMessage2(args.file_id, bytes.byteLength));
|
|
4221
4273
|
}
|
|
4222
|
-
|
|
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);
|
|
4223
4291
|
}
|
|
4224
|
-
if (bytes.byteLength >
|
|
4292
|
+
if (bytes.byteLength > MAX_READABLE_SPEC_FILE_BYTES) {
|
|
4225
4293
|
return errorResult(buildOversizeMessage2(args.file_id, bytes.byteLength));
|
|
4226
4294
|
}
|
|
4227
4295
|
if (isBinary2(bytes)) {
|
|
@@ -4237,25 +4305,44 @@ var readSpecFileTool = {
|
|
|
4237
4305
|
`File ${args.file_id} is not valid UTF-8 text. Use download_spec_file to save it to a local file.`
|
|
4238
4306
|
);
|
|
4239
4307
|
}
|
|
4308
|
+
const paginated = paginateText(text, args.offset, args.limit);
|
|
4309
|
+
if (!paginated.ok) {
|
|
4310
|
+
return errorResult(paginated.error);
|
|
4311
|
+
}
|
|
4240
4312
|
return jsonResult({
|
|
4241
4313
|
file_id: meta.id,
|
|
4242
|
-
revision_number:
|
|
4314
|
+
revision_number: effectiveRevision,
|
|
4243
4315
|
// Pass this back as update_spec_file's expected_version so an edit made by
|
|
4244
4316
|
// someone else since this read is reported instead of overwritten. It
|
|
4245
4317
|
// describes the file's CURRENT content, so it is emitted only when that is
|
|
4246
4318
|
// what was read — beside a historical revision it would invite an update
|
|
4247
4319
|
// that overwrites everything written since.
|
|
4248
|
-
...
|
|
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 } : {},
|
|
4249
4326
|
file_path: meta.filePath,
|
|
4250
4327
|
file_type: meta.fileType,
|
|
4251
|
-
cache_path: maskHomedir(
|
|
4328
|
+
cache_path: maskHomedir(effectiveCachePath),
|
|
4252
4329
|
cache_hit: cacheHit,
|
|
4253
|
-
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
|
|
4254
4334
|
});
|
|
4255
4335
|
}
|
|
4256
4336
|
};
|
|
4257
4337
|
function buildOversizeMessage2(fileId, byteLength) {
|
|
4258
|
-
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
|
+
}
|
|
4259
4346
|
}
|
|
4260
4347
|
function isBinary2(bytes) {
|
|
4261
4348
|
for (const byte of bytes) {
|
|
@@ -4965,11 +5052,11 @@ function artifactCreateConflictMessage(conflict) {
|
|
|
4965
5052
|
}
|
|
4966
5053
|
|
|
4967
5054
|
// src/server/tools/list_artifacts.ts
|
|
4968
|
-
var
|
|
4969
|
-
var
|
|
5055
|
+
var LIST_DEFAULT4 = 50;
|
|
5056
|
+
var LIST_MAX4 = 100;
|
|
4970
5057
|
var inputSchema26 = {
|
|
4971
5058
|
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(
|
|
5059
|
+
limit: z27.number().int().positive().optional().describe(`Max artifacts to return (default ${String(LIST_DEFAULT4)}, capped at ${String(LIST_MAX4)}).`),
|
|
4973
5060
|
offset: z27.number().int().nonnegative().optional().describe("Pagination offset (default 0).")
|
|
4974
5061
|
};
|
|
4975
5062
|
var listArtifactsTool = {
|
|
@@ -4977,7 +5064,7 @@ var listArtifactsTool = {
|
|
|
4977
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.",
|
|
4978
5065
|
inputSchema: inputSchema26,
|
|
4979
5066
|
handler: async (args, ctx) => {
|
|
4980
|
-
const limit = clampLimit(args.limit,
|
|
5067
|
+
const limit = clampLimit(args.limit, LIST_DEFAULT4, LIST_MAX4);
|
|
4981
5068
|
const offset = args.offset ?? 0;
|
|
4982
5069
|
const page = await ctx.client.listArtifacts(args.project_id, { limit, offset });
|
|
4983
5070
|
const next = nextOffsetOf(offset, page.items.length, page.total);
|
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": {
|