@myspec/mcp-server 1.1.1-next.164 → 1.1.1-next.166
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 +4 -0
- package/dist/index.js +559 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -363,6 +363,10 @@ Local state is stored under `~/.myspec/` (alongside the on-disk cache root), spl
|
|
|
363
363
|
| `reply_spec_file_comment_thread` | `file_id`, `thread_id`, `body` | Replies to an existing thread. Conflicts on a trashed file |
|
|
364
364
|
| `update_spec_file_comment` | `file_id`, `comment_id`, `body`, optional `expected_version` | Edits the body of one of YOUR OWN comments; the platform refuses somebody else's. `expected_version` is the **comment's** own `content_version`, not the file's — pairing it with the file would fail an edit because somebody saved the document. Works on a trashed file |
|
|
365
365
|
| `delete_spec_file_comment` | `file_id`, `comment_id` | Soft-deletes one of YOUR OWN comments. Three outcomes, reported in the result: a reply disappears (`comment: null`); a first comment with live replies becomes a tombstone so the thread keeps a head (`comment` is the tombstone); a first comment with no live replies takes the whole thread (`thread_deleted: true`) |
|
|
366
|
+
| `list_tasks` | `project_id`, optional `status`, `assignee_user_id` (a user id, `me` or `none`), `cycle`, `label`, `file_id`, `include_details`, `limit`, `offset` | The project's task board: every checkbox task in its tasks.md files, in file order, each with a stable `task_id`, `status` (`pending`/`in_progress`/`done`), assignee, `priority` (0 none … 4 low, plus `priority_name`), `story_points`, `cycle`, `start_date`/`end_date` (calendar days, `YYYY-MM-DD`), `labels`, and with `include_details` a 500-character preview of its details. Also the files (with the `content_version` their tasks reflect) and the labels and cycles in use. Filtered and paged by the platform: 100 tasks by default (`limit` up to 500), with `total_tasks` and, while more remain, `next_offset` |
|
|
367
|
+
| `list_task_assignees` | `project_id` | Who the project's tasks can be assigned to: the org's people who can open the project (exactly who `update_tasks` accepts), each with `user_id`, `username` and `display_name`, plus your own `self_user_id`. May include people who left the org but whose profile remains; at most 1000 (`truncated`) |
|
|
368
|
+
| `update_tasks` | `project_id`, `task_ids[]` (≤ 200), any of `assignee_user_id`, `priority`, `story_points`, `cycle`, `start_date`, `end_date`, `add_labels[]`, `remove_labels[]` | Edits planning fields on every task given. A field left out is left alone; `null` clears it. The assignee must be an org member who can open the project (`list_task_assignees`); `me` means you. All or nothing: an unknown id fails the call. Returns the tasks as they are now |
|
|
369
|
+
| `set_task_status` | `project_id`, `task_ids[]` (≤ 200), `status`, optional `expected_versions[]` (`{spec_file_id, content_version}` from `list_tasks`) | Rewrites the tasks' checkboxes in tasks.md (`[ ]`, `[-]`, `[x]`), one revision per file, recorded as an AI change. A file that changed since the pinned version is not written (`file_changed`). Partial: returns `updated`/`unchanged`/`removed`/`failed` counts, an outcome per task (with an error code such as `conflict` or `quota_exceeded`) and per file, and the tasks as they are now; an error result only when every task failed |
|
|
366
370
|
|
|
367
371
|
There is deliberately **no tool to resolve, unresolve or re-anchor a thread**. Those three are human-only: resolving declares a review finished, and re-anchoring is the repair a person makes after the automatic pass has already failed to locate the passage. The platform refuses them for an AI caller with a 403, so a tool would only ever return an error.
|
|
368
372
|
|
package/dist/index.js
CHANGED
|
@@ -1841,6 +1841,24 @@ function isProjectPermissionDeniedError(err) {
|
|
|
1841
1841
|
const message = err.message;
|
|
1842
1842
|
return typeof message === "string" && message.includes("PROJECT_PERMISSION_DENIED:");
|
|
1843
1843
|
}
|
|
1844
|
+
var TASK_BOARD_DISABLED_CODE = "task_board_disabled";
|
|
1845
|
+
var PlatformTaskBoardDisabledError = class extends PlatformHttpError {
|
|
1846
|
+
code = TASK_BOARD_DISABLED_CODE;
|
|
1847
|
+
constructor(message, details) {
|
|
1848
|
+
super(message, 403, details);
|
|
1849
|
+
this.name = "PlatformTaskBoardDisabledError";
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1852
|
+
function isTaskBoardDisabledError(err) {
|
|
1853
|
+
if (err instanceof PlatformTaskBoardDisabledError) {
|
|
1854
|
+
return true;
|
|
1855
|
+
}
|
|
1856
|
+
if (typeof err !== "object" || err === null) {
|
|
1857
|
+
return false;
|
|
1858
|
+
}
|
|
1859
|
+
const code = err.code;
|
|
1860
|
+
return typeof code === "string" && code.toLowerCase() === TASK_BOARD_DISABLED_CODE;
|
|
1861
|
+
}
|
|
1844
1862
|
function isPlatformHttpError(err) {
|
|
1845
1863
|
return typeof err === "object" && err !== null && err[PLATFORM_ERROR_BRAND] === true;
|
|
1846
1864
|
}
|
|
@@ -1933,6 +1951,9 @@ function createPlatformError(message, status, details, code) {
|
|
|
1933
1951
|
if (status === 403 && typeof code === "string" && code.toLowerCase() === PROJECT_PERMISSION_DENIED_CODE) {
|
|
1934
1952
|
return new PlatformPermissionError(message, details);
|
|
1935
1953
|
}
|
|
1954
|
+
if (status === 403 && typeof code === "string" && code.toLowerCase() === TASK_BOARD_DISABLED_CODE) {
|
|
1955
|
+
return new PlatformTaskBoardDisabledError(message, details);
|
|
1956
|
+
}
|
|
1936
1957
|
return new PlatformHttpError(message, status, details);
|
|
1937
1958
|
}
|
|
1938
1959
|
function truncate(text) {
|
|
@@ -1958,6 +1979,23 @@ async function throwFromResponse(response, fallbackMessage) {
|
|
|
1958
1979
|
throw createPlatformError(`HTTP ${response.status}: ${message}`, response.status, details, code);
|
|
1959
1980
|
}
|
|
1960
1981
|
|
|
1982
|
+
// ../../packages/platform-client/shared/jwt.ts
|
|
1983
|
+
function jwtSubject(token) {
|
|
1984
|
+
const raw = token.startsWith("Bearer ") ? token.slice("Bearer ".length) : token;
|
|
1985
|
+
const payloadSegment = raw.split(".")[1];
|
|
1986
|
+
if (!payloadSegment) {
|
|
1987
|
+
return null;
|
|
1988
|
+
}
|
|
1989
|
+
try {
|
|
1990
|
+
const base64 = payloadSegment.replace(/-/g, "+").replace(/_/g, "/");
|
|
1991
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
1992
|
+
const payload = JSON.parse(atob(padded));
|
|
1993
|
+
return typeof payload.sub === "string" && payload.sub !== "" ? payload.sub : null;
|
|
1994
|
+
} catch {
|
|
1995
|
+
return null;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1961
1999
|
// ../../packages/shared/logger.ts
|
|
1962
2000
|
var LOG_LEVEL_PRIORITY = {
|
|
1963
2001
|
debug: 0,
|
|
@@ -2318,13 +2356,146 @@ function transformSpecSessionResponse(raw) {
|
|
|
2318
2356
|
// ../../packages/platform-client/project/types/lock.types.ts
|
|
2319
2357
|
var SPEC_FILE_LOCK_TTL_MS = 2 * 6e4;
|
|
2320
2358
|
|
|
2359
|
+
// ../../packages/platform-client/project/types/task.types.ts
|
|
2360
|
+
var TASK_STATUSES = ["pending", "in_progress", "done"];
|
|
2361
|
+
var TASK_PRIORITIES = [0, 1, 2, 3, 4];
|
|
2362
|
+
var TASK_LIMITS = {
|
|
2363
|
+
maxLabels: 20,
|
|
2364
|
+
maxLabelLength: 40,
|
|
2365
|
+
maxStoryPoints: 9999.99,
|
|
2366
|
+
maxCycle: 1e5,
|
|
2367
|
+
maxTasksPerRequest: 200,
|
|
2368
|
+
/** The most tasks one page of a listing holds. */
|
|
2369
|
+
maxPageSize: 500,
|
|
2370
|
+
/** The most tasks.md files one board lists (and the most expected_versions a status change takes). */
|
|
2371
|
+
maxBoardFiles: 500
|
|
2372
|
+
};
|
|
2373
|
+
function parseTimestamp(raw) {
|
|
2374
|
+
if (!raw) return null;
|
|
2375
|
+
const d = new Date(raw);
|
|
2376
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
2377
|
+
}
|
|
2378
|
+
function toStatus(raw) {
|
|
2379
|
+
return TASK_STATUSES.includes(raw) ? raw : "pending";
|
|
2380
|
+
}
|
|
2381
|
+
function toPriority(raw) {
|
|
2382
|
+
return TASK_PRIORITIES.includes(raw) ? raw : 0;
|
|
2383
|
+
}
|
|
2384
|
+
function toOutcome(raw) {
|
|
2385
|
+
return raw === "updated" || raw === "unchanged" || raw === "removed" ? raw : "failed";
|
|
2386
|
+
}
|
|
2387
|
+
function transformProjectTask(raw) {
|
|
2388
|
+
return {
|
|
2389
|
+
id: raw.id,
|
|
2390
|
+
projectId: raw.project_id,
|
|
2391
|
+
specFileId: raw.spec_file_id,
|
|
2392
|
+
parentTaskId: raw.parent_task_id ?? null,
|
|
2393
|
+
key: raw.key,
|
|
2394
|
+
title: raw.title,
|
|
2395
|
+
section: raw.section,
|
|
2396
|
+
position: raw.position,
|
|
2397
|
+
depth: raw.depth,
|
|
2398
|
+
status: toStatus(raw.status),
|
|
2399
|
+
body: raw.body,
|
|
2400
|
+
removedAt: parseTimestamp(raw.removed_at),
|
|
2401
|
+
statusChangedAt: parseTimestamp(raw.status_changed_at),
|
|
2402
|
+
completedAt: parseTimestamp(raw.completed_at),
|
|
2403
|
+
assigneeUserId: raw.assignee_user_id ?? null,
|
|
2404
|
+
priority: toPriority(raw.priority),
|
|
2405
|
+
storyPoints: raw.story_points ?? null,
|
|
2406
|
+
cycle: raw.cycle ?? null,
|
|
2407
|
+
startAt: parseTimestamp(raw.start_at),
|
|
2408
|
+
endAt: parseTimestamp(raw.end_at),
|
|
2409
|
+
labels: raw.labels ?? [],
|
|
2410
|
+
updatedBy: raw.updated_by ?? null,
|
|
2411
|
+
createdAt: parseTimestamp(raw.created_at),
|
|
2412
|
+
updatedAt: parseTimestamp(raw.updated_at)
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
function transformTaskBoard(raw) {
|
|
2416
|
+
return {
|
|
2417
|
+
projectId: raw.project_id,
|
|
2418
|
+
files: (raw.files ?? []).map((f) => ({
|
|
2419
|
+
specFileId: f.spec_file_id,
|
|
2420
|
+
filePath: f.file_path,
|
|
2421
|
+
contentVersion: f.content_version,
|
|
2422
|
+
stale: f.stale
|
|
2423
|
+
})),
|
|
2424
|
+
tasks: (raw.tasks ?? []).map(transformProjectTask),
|
|
2425
|
+
labels: raw.labels ?? [],
|
|
2426
|
+
cycles: raw.cycles ?? [],
|
|
2427
|
+
filesTruncated: raw.files_truncated === true,
|
|
2428
|
+
...typeof raw.total === "number" ? {
|
|
2429
|
+
page: {
|
|
2430
|
+
total: raw.total,
|
|
2431
|
+
offset: raw.offset ?? 0,
|
|
2432
|
+
limit: raw.limit ?? 0,
|
|
2433
|
+
nextOffset: typeof raw.next_offset === "number" ? raw.next_offset : null
|
|
2434
|
+
}
|
|
2435
|
+
} : {}
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
function toTaskListQueryString(query) {
|
|
2439
|
+
if (!query) return "";
|
|
2440
|
+
const params = new URLSearchParams();
|
|
2441
|
+
if (query.status) params.set("status", query.status);
|
|
2442
|
+
if (query.assigneeUserId) params.set("assignee_user_id", query.assigneeUserId);
|
|
2443
|
+
if (query.cycle !== void 0) params.set("cycle", String(query.cycle));
|
|
2444
|
+
if (query.label) params.set("label", query.label);
|
|
2445
|
+
if (query.specFileId) params.set("spec_file_id", query.specFileId);
|
|
2446
|
+
if (query.limit !== void 0) params.set("limit", String(query.limit));
|
|
2447
|
+
if (query.offset !== void 0) params.set("offset", String(query.offset));
|
|
2448
|
+
const qs = params.toString();
|
|
2449
|
+
return qs ? `?${qs}` : "";
|
|
2450
|
+
}
|
|
2451
|
+
function transformSetTaskStatusResult(raw) {
|
|
2452
|
+
return {
|
|
2453
|
+
results: (raw.results ?? []).map((r) => ({
|
|
2454
|
+
taskId: r.task_id,
|
|
2455
|
+
outcome: toOutcome(r.outcome),
|
|
2456
|
+
...r.error ? { error: r.error } : {}
|
|
2457
|
+
})),
|
|
2458
|
+
files: (raw.files ?? []).map((f) => ({
|
|
2459
|
+
specFileId: f.spec_file_id,
|
|
2460
|
+
contentVersion: f.content_version,
|
|
2461
|
+
written: f.written,
|
|
2462
|
+
...f.error ? { error: f.error } : {}
|
|
2463
|
+
})),
|
|
2464
|
+
tasks: (raw.tasks ?? []).map(transformProjectTask)
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
function transformTaskAssigneeList(raw) {
|
|
2468
|
+
return {
|
|
2469
|
+
assignees: (raw.assignees ?? []).map((a) => ({
|
|
2470
|
+
userId: a.user_id,
|
|
2471
|
+
username: a.username ?? "",
|
|
2472
|
+
displayName: a.display_name ?? ""
|
|
2473
|
+
})),
|
|
2474
|
+
selfUserId: raw.self_user_id ?? "",
|
|
2475
|
+
truncated: raw.truncated === true
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
function toRawUpdateTasksRequest(taskIds, patch) {
|
|
2479
|
+
const set = {};
|
|
2480
|
+
if ("assigneeUserId" in patch) set.assignee_user_id = patch.assigneeUserId ?? null;
|
|
2481
|
+
if ("priority" in patch) set.priority = patch.priority ?? null;
|
|
2482
|
+
if ("storyPoints" in patch) set.story_points = patch.storyPoints ?? null;
|
|
2483
|
+
if ("cycle" in patch) set.cycle = patch.cycle ?? null;
|
|
2484
|
+
if ("startAt" in patch) set.start_at = patch.startAt ? patch.startAt.toISOString() : null;
|
|
2485
|
+
if ("endAt" in patch) set.end_at = patch.endAt ? patch.endAt.toISOString() : null;
|
|
2486
|
+
const body = { task_ids: taskIds, set };
|
|
2487
|
+
if (patch.addLabels?.length) body.add_labels = patch.addLabels;
|
|
2488
|
+
if (patch.removeLabels?.length) body.remove_labels = patch.removeLabels;
|
|
2489
|
+
return body;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2321
2492
|
// ../../packages/platform-client/project/types/comment.types.ts
|
|
2322
2493
|
var COMMENT_THREAD_FILTER_PARAM = "status";
|
|
2323
2494
|
var COMMENT_UNRESOLVED_ONLY_PARAM = "unresolved_only";
|
|
2324
2495
|
var MAX_COMMENT_BODY_LENGTH = 1e4;
|
|
2325
2496
|
var MAX_ANCHOR_QUOTE_LENGTH = 2e3;
|
|
2326
2497
|
var MAX_ANCHOR_CONTEXT_LENGTH = 64;
|
|
2327
|
-
function
|
|
2498
|
+
function parseTimestamp2(raw) {
|
|
2328
2499
|
if (!raw) {
|
|
2329
2500
|
return null;
|
|
2330
2501
|
}
|
|
@@ -2368,9 +2539,9 @@ function transformSpecFileCommentResponse(raw) {
|
|
|
2368
2539
|
body: raw.body,
|
|
2369
2540
|
deleted: raw.deleted,
|
|
2370
2541
|
contentVersion: raw.content_version,
|
|
2371
|
-
createdAt:
|
|
2372
|
-
updatedAt:
|
|
2373
|
-
deletedAt:
|
|
2542
|
+
createdAt: parseTimestamp2(raw.created_at),
|
|
2543
|
+
updatedAt: parseTimestamp2(raw.updated_at),
|
|
2544
|
+
deletedAt: parseTimestamp2(raw.deleted_at)
|
|
2374
2545
|
};
|
|
2375
2546
|
}
|
|
2376
2547
|
function transformSpecFileCommentThreadResponse(raw) {
|
|
@@ -2390,10 +2561,10 @@ function transformSpecFileCommentThreadResponse(raw) {
|
|
|
2390
2561
|
anchorStatus: toAnchorStatus(raw.anchor_status),
|
|
2391
2562
|
createdBy: raw.created_by,
|
|
2392
2563
|
createdByKind: toCommentActorKind(raw.created_by_kind),
|
|
2393
|
-
resolvedAt:
|
|
2564
|
+
resolvedAt: parseTimestamp2(raw.resolved_at),
|
|
2394
2565
|
resolvedBy: raw.resolved_by ?? null,
|
|
2395
|
-
createdAt:
|
|
2396
|
-
updatedAt:
|
|
2566
|
+
createdAt: parseTimestamp2(raw.created_at),
|
|
2567
|
+
updatedAt: parseTimestamp2(raw.updated_at),
|
|
2397
2568
|
// Always an array: consumers map over it on every render, so the empty case
|
|
2398
2569
|
// must not be a value each one has to guard.
|
|
2399
2570
|
comments: (raw.comments ?? []).map(transformSpecFileCommentResponse)
|
|
@@ -3152,6 +3323,55 @@ var HttpSpecFileCommentClient = class {
|
|
|
3152
3323
|
}
|
|
3153
3324
|
};
|
|
3154
3325
|
|
|
3326
|
+
// ../../packages/platform-client/project/http/task.http-client.ts
|
|
3327
|
+
var HEADER_ACTOR_KIND3 = "X-Actor-Kind";
|
|
3328
|
+
function tasksPath(projectId) {
|
|
3329
|
+
return `/project/v1/projects/${encodeURIComponent(projectId)}/tasks`;
|
|
3330
|
+
}
|
|
3331
|
+
var HttpProjectTaskClient = class {
|
|
3332
|
+
httpClient;
|
|
3333
|
+
constructor(httpClient) {
|
|
3334
|
+
this.httpClient = httpClient;
|
|
3335
|
+
}
|
|
3336
|
+
async listProjectTasks(projectId, jwtToken, query) {
|
|
3337
|
+
const raw = await this.httpClient.get(
|
|
3338
|
+
`${tasksPath(projectId)}${toTaskListQueryString(query)}`,
|
|
3339
|
+
jwtToken,
|
|
3340
|
+
{}
|
|
3341
|
+
);
|
|
3342
|
+
return transformTaskBoard(raw);
|
|
3343
|
+
}
|
|
3344
|
+
async listProjectTaskAssignees(projectId, jwtToken) {
|
|
3345
|
+
const raw = await this.httpClient.get(`${tasksPath(projectId)}/assignees`, jwtToken, {});
|
|
3346
|
+
return transformTaskAssigneeList(raw);
|
|
3347
|
+
}
|
|
3348
|
+
async updateProjectTasks(projectId, taskIds, patch, jwtToken) {
|
|
3349
|
+
const raw = await this.httpClient.patch(
|
|
3350
|
+
tasksPath(projectId),
|
|
3351
|
+
toRawUpdateTasksRequest(taskIds, patch),
|
|
3352
|
+
jwtToken,
|
|
3353
|
+
{}
|
|
3354
|
+
);
|
|
3355
|
+
return (raw.tasks ?? []).map(transformProjectTask);
|
|
3356
|
+
}
|
|
3357
|
+
async setProjectTaskStatus(projectId, taskIds, status, jwtToken, opts) {
|
|
3358
|
+
const body = {
|
|
3359
|
+
task_ids: taskIds,
|
|
3360
|
+
status
|
|
3361
|
+
};
|
|
3362
|
+
if (opts?.expectedVersions && Object.keys(opts.expectedVersions).length > 0) {
|
|
3363
|
+
body.expected_versions = opts.expectedVersions;
|
|
3364
|
+
}
|
|
3365
|
+
const raw = await this.httpClient.post(
|
|
3366
|
+
`${tasksPath(projectId)}/status`,
|
|
3367
|
+
body,
|
|
3368
|
+
jwtToken,
|
|
3369
|
+
opts?.actorKind ? { headers: { [HEADER_ACTOR_KIND3]: opts.actorKind } } : {}
|
|
3370
|
+
);
|
|
3371
|
+
return transformSetTaskStatusResult(raw);
|
|
3372
|
+
}
|
|
3373
|
+
};
|
|
3374
|
+
|
|
3155
3375
|
// ../../packages/platform-client/artifact/types/artifact.types.ts
|
|
3156
3376
|
function transformArtifactResponse(raw) {
|
|
3157
3377
|
return {
|
|
@@ -3283,14 +3503,14 @@ function buildUpdateArtifactBody(request) {
|
|
|
3283
3503
|
// ../../packages/platform-client/artifact/http/artifact.http-client.ts
|
|
3284
3504
|
var BASE_PATH = "/artifact/v1";
|
|
3285
3505
|
var HEADER_EXPECTED_VERSION3 = "X-Expected-Version";
|
|
3286
|
-
var
|
|
3506
|
+
var HEADER_ACTOR_KIND4 = "X-Actor-Kind";
|
|
3287
3507
|
function writeHeaders(opts) {
|
|
3288
3508
|
const headers = {};
|
|
3289
3509
|
if (opts?.expectedVersion !== void 0) {
|
|
3290
3510
|
headers[HEADER_EXPECTED_VERSION3] = String(opts.expectedVersion);
|
|
3291
3511
|
}
|
|
3292
3512
|
if (opts?.actorKind) {
|
|
3293
|
-
headers[
|
|
3513
|
+
headers[HEADER_ACTOR_KIND4] = opts.actorKind;
|
|
3294
3514
|
}
|
|
3295
3515
|
return headers;
|
|
3296
3516
|
}
|
|
@@ -3868,7 +4088,8 @@ var PlatformClient = class {
|
|
|
3868
4088
|
attachment: new HttpAttachmentClient(httpClient),
|
|
3869
4089
|
artifact: new HttpArtifactClient(httpClient),
|
|
3870
4090
|
streamToken: new HttpStreamTokenClient(httpClient),
|
|
3871
|
-
comment: new HttpSpecFileCommentClient(httpClient)
|
|
4091
|
+
comment: new HttpSpecFileCommentClient(httpClient),
|
|
4092
|
+
task: new HttpProjectTaskClient(httpClient)
|
|
3872
4093
|
};
|
|
3873
4094
|
}
|
|
3874
4095
|
// ------------------------------------------------------- stream tokens
|
|
@@ -3972,6 +4193,41 @@ var PlatformClient = class {
|
|
|
3972
4193
|
(jwt, c) => c.comment.deleteComment(fileId, commentId, jwt, { actorKind: "ai" })
|
|
3973
4194
|
);
|
|
3974
4195
|
}
|
|
4196
|
+
// ---------------------------------------------------------------- tasks
|
|
4197
|
+
//
|
|
4198
|
+
// The project task board. A status change rewrites tasks.md, so it carries
|
|
4199
|
+
// `actorKind: 'ai'` like every other file write here: the revision is then
|
|
4200
|
+
// recorded as the assistant's, not filed under the user's name. A planning
|
|
4201
|
+
// edit writes no file and the platform records no actor kind for it.
|
|
4202
|
+
/** The live tasks of a project's tasks.md files: all, or those `query` keeps. */
|
|
4203
|
+
async listProjectTasks(projectId, query) {
|
|
4204
|
+
return this.withTokenRetry((jwt, c) => c.task.listProjectTasks(projectId, jwt, query));
|
|
4205
|
+
}
|
|
4206
|
+
/** Who the project's tasks can be assigned to, and the caller's own user id. */
|
|
4207
|
+
async listProjectTaskAssignees(projectId) {
|
|
4208
|
+
return this.withTokenRetry((jwt, c) => c.task.listProjectTaskAssignees(projectId, jwt));
|
|
4209
|
+
}
|
|
4210
|
+
/**
|
|
4211
|
+
* The caller's own user id, read from the access token (unverified: only to
|
|
4212
|
+
* turn "me" into an id the platform then checks). Null when the token does
|
|
4213
|
+
* not carry one.
|
|
4214
|
+
*/
|
|
4215
|
+
async currentUserId() {
|
|
4216
|
+
return jwtSubject(await this.tokenManager.getValidAccessToken());
|
|
4217
|
+
}
|
|
4218
|
+
/** Applies one planning patch to up to 200 tasks; all or nothing. */
|
|
4219
|
+
async updateProjectTasks(projectId, taskIds, patch) {
|
|
4220
|
+
return this.withTokenRetry((jwt, c) => c.task.updateProjectTasks(projectId, taskIds, patch, jwt));
|
|
4221
|
+
}
|
|
4222
|
+
/**
|
|
4223
|
+
* Sets the status of up to 200 tasks by rewriting their checkboxes.
|
|
4224
|
+
* `expectedVersions` pins the file versions the caller's view came from.
|
|
4225
|
+
*/
|
|
4226
|
+
async setProjectTaskStatus(projectId, taskIds, status, expectedVersions) {
|
|
4227
|
+
return this.withTokenRetry(
|
|
4228
|
+
(jwt, c) => c.task.setProjectTaskStatus(projectId, taskIds, status, jwt, { actorKind: "ai", expectedVersions })
|
|
4229
|
+
);
|
|
4230
|
+
}
|
|
3975
4231
|
async withTokenRetry(call) {
|
|
3976
4232
|
const clients = await this.clients();
|
|
3977
4233
|
const jwt = await this.tokenManager.getValidAccessToken();
|
|
@@ -7180,6 +7436,258 @@ var startSpecSessionTool = {
|
|
|
7180
7436
|
}
|
|
7181
7437
|
};
|
|
7182
7438
|
|
|
7439
|
+
// src/server/tools/tasks.ts
|
|
7440
|
+
import { z as z39 } from "zod";
|
|
7441
|
+
var LIST_DEFAULT_TASKS = 100;
|
|
7442
|
+
var LIST_MAX_TASKS = 500;
|
|
7443
|
+
var LIST_MAX_OFFSET = 1e6;
|
|
7444
|
+
var DETAILS_PREVIEW_CHARS = 500;
|
|
7445
|
+
var ME = "me";
|
|
7446
|
+
var NONE = "none";
|
|
7447
|
+
var PRIORITY_NAMES = ["none", "urgent", "high", "medium", "low"];
|
|
7448
|
+
var STATUS = z39.enum(["pending", "in_progress", "done"]);
|
|
7449
|
+
function toDay(date) {
|
|
7450
|
+
return date ? date.toISOString().slice(0, 10) : null;
|
|
7451
|
+
}
|
|
7452
|
+
function fromDay(day) {
|
|
7453
|
+
return /* @__PURE__ */ new Date(`${day}T00:00:00Z`);
|
|
7454
|
+
}
|
|
7455
|
+
function taskSummary(task, opts = {}) {
|
|
7456
|
+
const withDetails = opts.details === true && task.body !== "";
|
|
7457
|
+
const truncated = withDetails && task.body.length > DETAILS_PREVIEW_CHARS;
|
|
7458
|
+
return {
|
|
7459
|
+
task_id: task.id,
|
|
7460
|
+
spec_file_id: task.specFileId,
|
|
7461
|
+
// The id written in the file ("1", "1.1", "T001"); '' when it has none.
|
|
7462
|
+
key: task.key,
|
|
7463
|
+
title: task.title,
|
|
7464
|
+
section: task.section,
|
|
7465
|
+
parent_task_id: task.parentTaskId,
|
|
7466
|
+
status: task.status,
|
|
7467
|
+
assignee_user_id: task.assigneeUserId,
|
|
7468
|
+
priority: task.priority,
|
|
7469
|
+
priority_name: PRIORITY_NAMES[task.priority],
|
|
7470
|
+
story_points: task.storyPoints,
|
|
7471
|
+
cycle: task.cycle,
|
|
7472
|
+
start_date: toDay(task.startAt),
|
|
7473
|
+
end_date: toDay(task.endAt),
|
|
7474
|
+
labels: task.labels,
|
|
7475
|
+
...withDetails ? { details: truncated ? task.body.slice(0, DETAILS_PREVIEW_CHARS) : task.body } : {},
|
|
7476
|
+
// Only ever present and true.
|
|
7477
|
+
...truncated ? { details_truncated: true } : {},
|
|
7478
|
+
updated_at: task.updatedAt?.toISOString()
|
|
7479
|
+
};
|
|
7480
|
+
}
|
|
7481
|
+
var userIdField = z39.string().trim().min(1);
|
|
7482
|
+
async function resolveMe(id, ctx) {
|
|
7483
|
+
return id === ME ? ctx.client.currentUserId() : id;
|
|
7484
|
+
}
|
|
7485
|
+
var UNKNOWN_ME = 'Could not tell who you are from this connection, so "me" cannot be used. Pass your user id instead: list_task_assignees returns it as self_user_id.';
|
|
7486
|
+
var labelField = z39.string().trim().min(1).refine((label) => Array.from(label).length <= TASK_LIMITS.maxLabelLength, {
|
|
7487
|
+
message: `A label is at most ${String(TASK_LIMITS.maxLabelLength)} characters.`
|
|
7488
|
+
});
|
|
7489
|
+
function taskIdsField(what) {
|
|
7490
|
+
return z39.array(z39.uuid()).min(1).max(TASK_LIMITS.maxTasksPerRequest).describe(`${what} At most ${String(TASK_LIMITS.maxTasksPerRequest)} per call.`);
|
|
7491
|
+
}
|
|
7492
|
+
var listInputSchema2 = {
|
|
7493
|
+
project_id: z39.uuid().describe("UUID of the project whose tasks to list."),
|
|
7494
|
+
status: STATUS.optional().describe("Only tasks with this status."),
|
|
7495
|
+
assignee_user_id: userIdField.optional().describe(
|
|
7496
|
+
'Only tasks assigned to this user id (as returned in assignee_user_id or by list_task_assignees). "me" means you; "none" means unassigned tasks.'
|
|
7497
|
+
),
|
|
7498
|
+
cycle: z39.number().int().min(1).max(TASK_LIMITS.maxCycle).optional().describe("Only tasks in this cycle (sprint) number."),
|
|
7499
|
+
label: labelField.optional().describe("Only tasks carrying this label."),
|
|
7500
|
+
file_id: z39.uuid().optional().describe("Only tasks from this tasks.md (its spec file id)."),
|
|
7501
|
+
include_details: z39.boolean().optional().describe(
|
|
7502
|
+
"Also return each task's details (its sub-bullets in tasks.md), previewed to 500 characters. Off by default: they make the result several times larger."
|
|
7503
|
+
),
|
|
7504
|
+
limit: z39.number().int().min(1).max(LIST_MAX_TASKS).optional().describe(
|
|
7505
|
+
`Tasks per page (default ${String(LIST_DEFAULT_TASKS)}, at most ${String(LIST_MAX_TASKS)}).`
|
|
7506
|
+
),
|
|
7507
|
+
offset: z39.number().int().min(0).max(LIST_MAX_OFFSET).optional().describe("How many matching tasks to skip; pass next_offset from the previous page.")
|
|
7508
|
+
};
|
|
7509
|
+
function pageLocally(board, query) {
|
|
7510
|
+
const assignee = query.assigneeUserId;
|
|
7511
|
+
const label = query.label?.split(/\s+/).filter(Boolean).join(" ");
|
|
7512
|
+
const matching = board.tasks.filter(
|
|
7513
|
+
(task) => (query.status === void 0 || task.status === query.status) && (assignee === void 0 || (assignee === NONE ? task.assigneeUserId === null : task.assigneeUserId === assignee)) && (query.cycle === void 0 || task.cycle === query.cycle) && (label === void 0 || task.labels.includes(label)) && (query.specFileId === void 0 || task.specFileId === query.specFileId)
|
|
7514
|
+
);
|
|
7515
|
+
const offset = query.offset ?? 0;
|
|
7516
|
+
const tasks = matching.slice(offset, offset + (query.limit ?? LIST_DEFAULT_TASKS));
|
|
7517
|
+
const next = offset + tasks.length;
|
|
7518
|
+
return {
|
|
7519
|
+
tasks,
|
|
7520
|
+
total: matching.length,
|
|
7521
|
+
offset,
|
|
7522
|
+
nextOffset: next < matching.length ? next : null
|
|
7523
|
+
};
|
|
7524
|
+
}
|
|
7525
|
+
var listTasksTool = {
|
|
7526
|
+
name: "list_tasks",
|
|
7527
|
+
description: `List a project's tasks: every checkbox task in its tasks.md files, in file order, each with a stable task_id, status (pending, in_progress, done), assignee, priority (0 none, 1 urgent, 2 high, 3 medium, 4 low), story points, cycle, start/end dates (YYYY-MM-DD) and labels. Also returns the files (with the content_version their tasks reflect), and the labels and cycles in use. Filter by status, assignee ("me" for yourself, "none" for unassigned), cycle, label or file. Paged: 100 tasks by default (limit up to 500), with total_tasks and, while more remain, next_offset. Details (sub-bullets) only with include_details. Use the task_id values with update_tasks and set_task_status.`,
|
|
7528
|
+
inputSchema: listInputSchema2,
|
|
7529
|
+
handler: async (args, ctx) => {
|
|
7530
|
+
let assignee;
|
|
7531
|
+
if (args.assignee_user_id !== void 0) {
|
|
7532
|
+
const resolved = await resolveMe(args.assignee_user_id, ctx);
|
|
7533
|
+
if (resolved === null) return errorResult(UNKNOWN_ME);
|
|
7534
|
+
assignee = resolved;
|
|
7535
|
+
}
|
|
7536
|
+
const query = {
|
|
7537
|
+
...args.status !== void 0 ? { status: args.status } : {},
|
|
7538
|
+
...assignee !== void 0 ? { assigneeUserId: assignee } : {},
|
|
7539
|
+
...args.cycle !== void 0 ? { cycle: args.cycle } : {},
|
|
7540
|
+
...args.label !== void 0 ? { label: args.label } : {},
|
|
7541
|
+
...args.file_id !== void 0 ? { specFileId: args.file_id } : {},
|
|
7542
|
+
limit: args.limit ?? LIST_DEFAULT_TASKS,
|
|
7543
|
+
offset: args.offset ?? 0
|
|
7544
|
+
};
|
|
7545
|
+
const board = await ctx.client.listProjectTasks(args.project_id, query);
|
|
7546
|
+
const page = board.page ? {
|
|
7547
|
+
tasks: board.tasks,
|
|
7548
|
+
total: board.page.total,
|
|
7549
|
+
offset: board.page.offset,
|
|
7550
|
+
nextOffset: board.page.nextOffset
|
|
7551
|
+
} : pageLocally(board, query);
|
|
7552
|
+
return jsonResult({
|
|
7553
|
+
project_id: board.projectId,
|
|
7554
|
+
files: board.files.map((file) => ({
|
|
7555
|
+
spec_file_id: file.specFileId,
|
|
7556
|
+
file_path: file.filePath,
|
|
7557
|
+
content_version: file.contentVersion,
|
|
7558
|
+
// The file could not be re-read just now: its tasks are from the last read.
|
|
7559
|
+
...file.stale ? { stale: true } : {}
|
|
7560
|
+
})),
|
|
7561
|
+
// Only ever present and true: the project has more than 500 tasks.md
|
|
7562
|
+
// files and the rest are left out.
|
|
7563
|
+
...board.filesTruncated ? { files_truncated: true } : {},
|
|
7564
|
+
tasks: page.tasks.map((task) => taskSummary(task, { details: args.include_details })),
|
|
7565
|
+
total_tasks: page.total,
|
|
7566
|
+
offset: page.offset,
|
|
7567
|
+
// Only while more remain, so its absence means "that was the last page".
|
|
7568
|
+
...page.nextOffset !== null ? { next_offset: page.nextOffset } : {},
|
|
7569
|
+
labels: board.labels,
|
|
7570
|
+
cycles: board.cycles
|
|
7571
|
+
});
|
|
7572
|
+
}
|
|
7573
|
+
};
|
|
7574
|
+
var assigneesInputSchema = {
|
|
7575
|
+
project_id: z39.uuid().describe("UUID of the project whose tasks are being assigned.")
|
|
7576
|
+
};
|
|
7577
|
+
var listTaskAssigneesTool = {
|
|
7578
|
+
name: "list_task_assignees",
|
|
7579
|
+
description: `List who a project's tasks can be assigned to: the organisation's people who can open the project, which is exactly who update_tasks accepts as assignee_user_id. Each has a user_id, a username (handle) and a display_name ("" when unset). Also returns your own user id as self_user_id. May include people who have left the organisation but whose profile remains. At most 1000 people; truncated is true when the list was cut.`,
|
|
7580
|
+
inputSchema: assigneesInputSchema,
|
|
7581
|
+
handler: async (args, ctx) => {
|
|
7582
|
+
const list = await ctx.client.listProjectTaskAssignees(args.project_id);
|
|
7583
|
+
return jsonResult({
|
|
7584
|
+
assignees: list.assignees.map((a) => ({
|
|
7585
|
+
user_id: a.userId,
|
|
7586
|
+
username: a.username,
|
|
7587
|
+
display_name: a.displayName
|
|
7588
|
+
})),
|
|
7589
|
+
self_user_id: list.selfUserId,
|
|
7590
|
+
// Only ever present and true.
|
|
7591
|
+
...list.truncated ? { truncated: true } : {}
|
|
7592
|
+
});
|
|
7593
|
+
}
|
|
7594
|
+
};
|
|
7595
|
+
var updateInputSchema2 = {
|
|
7596
|
+
project_id: z39.uuid().describe("UUID of the project the tasks belong to."),
|
|
7597
|
+
task_ids: taskIdsField("The tasks to change (task_id values from list_tasks)."),
|
|
7598
|
+
assignee_user_id: userIdField.nullable().optional().describe(
|
|
7599
|
+
'User id to assign ("me" for yourself), or null to unassign. The person must be a member of the organisation who can open the project: list_task_assignees lists them.'
|
|
7600
|
+
),
|
|
7601
|
+
priority: z39.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low."),
|
|
7602
|
+
story_points: z39.number().min(0).max(TASK_LIMITS.maxStoryPoints).nullable().optional().describe("Estimate in story points, or null to clear it."),
|
|
7603
|
+
cycle: z39.number().int().min(1).max(TASK_LIMITS.maxCycle).nullable().optional().describe("Cycle (sprint) number, or null to clear it."),
|
|
7604
|
+
start_date: z39.iso.date().nullable().optional().describe("Start day as YYYY-MM-DD, or null to clear it."),
|
|
7605
|
+
end_date: z39.iso.date().nullable().optional().describe("Due day as YYYY-MM-DD, or null to clear it. Not before the start day."),
|
|
7606
|
+
add_labels: z39.array(labelField).max(TASK_LIMITS.maxLabels).optional().describe(`Labels to add. A task carries at most ${String(TASK_LIMITS.maxLabels)}.`),
|
|
7607
|
+
remove_labels: z39.array(labelField).max(TASK_LIMITS.maxLabels).optional().describe("Labels to remove.")
|
|
7608
|
+
};
|
|
7609
|
+
var updateTasksTool = {
|
|
7610
|
+
name: "update_tasks",
|
|
7611
|
+
description: "Edit the planning fields of one or more tasks: assignee, priority, story points, cycle, start and due dates, and labels. The same change applies to every task given. A field left out is left alone; null clears it. Labels are added and removed, not replaced. Does not change status or the task's text, which live in tasks.md: use set_task_status for status. All or nothing: an unknown task id fails the whole call. Returns the tasks as they are now.",
|
|
7612
|
+
inputSchema: updateInputSchema2,
|
|
7613
|
+
handler: async (args, ctx) => {
|
|
7614
|
+
const patch = {};
|
|
7615
|
+
if (args.assignee_user_id === null) {
|
|
7616
|
+
patch.assigneeUserId = null;
|
|
7617
|
+
} else if (args.assignee_user_id !== void 0) {
|
|
7618
|
+
const assignee = await resolveMe(args.assignee_user_id, ctx);
|
|
7619
|
+
if (assignee === null) return errorResult(UNKNOWN_ME);
|
|
7620
|
+
patch.assigneeUserId = assignee;
|
|
7621
|
+
}
|
|
7622
|
+
if (args.priority !== void 0) patch.priority = args.priority;
|
|
7623
|
+
if (args.story_points !== void 0) patch.storyPoints = args.story_points;
|
|
7624
|
+
if (args.cycle !== void 0) patch.cycle = args.cycle;
|
|
7625
|
+
if (args.start_date !== void 0) {
|
|
7626
|
+
patch.startAt = args.start_date === null ? null : fromDay(args.start_date);
|
|
7627
|
+
}
|
|
7628
|
+
if (args.end_date !== void 0) {
|
|
7629
|
+
patch.endAt = args.end_date === null ? null : fromDay(args.end_date);
|
|
7630
|
+
}
|
|
7631
|
+
if (args.add_labels?.length) patch.addLabels = args.add_labels;
|
|
7632
|
+
if (args.remove_labels?.length) patch.removeLabels = args.remove_labels;
|
|
7633
|
+
if (Object.keys(patch).length === 0) {
|
|
7634
|
+
return errorResult(
|
|
7635
|
+
"Nothing to change: pass at least one of assignee_user_id, priority, story_points, cycle, start_date, end_date, add_labels or remove_labels."
|
|
7636
|
+
);
|
|
7637
|
+
}
|
|
7638
|
+
const tasks = await ctx.client.updateProjectTasks(args.project_id, args.task_ids, patch);
|
|
7639
|
+
return jsonResult({ tasks: tasks.map((task) => taskSummary(task)) });
|
|
7640
|
+
}
|
|
7641
|
+
};
|
|
7642
|
+
var statusInputSchema = {
|
|
7643
|
+
project_id: z39.uuid().describe("UUID of the project the tasks belong to."),
|
|
7644
|
+
task_ids: taskIdsField("The tasks to move (task_id values from list_tasks)."),
|
|
7645
|
+
status: STATUS.describe("The new status: pending, in_progress or done."),
|
|
7646
|
+
expected_versions: z39.array(
|
|
7647
|
+
z39.object({
|
|
7648
|
+
spec_file_id: z39.uuid().describe("A tasks.md file (spec_file_id from list_tasks)."),
|
|
7649
|
+
content_version: z39.number().int().min(0).describe("The content_version list_tasks returned for that file.")
|
|
7650
|
+
})
|
|
7651
|
+
).max(TASK_LIMITS.maxBoardFiles).optional().describe(
|
|
7652
|
+
"Optional. The file versions your view of these tasks came from (from list_tasks); only the files these tasks are in are checked, so passing just those is enough. A file that has changed since is not written and its tasks fail with file_changed, so a change never lands on a line you did not see; list again and retry."
|
|
7653
|
+
)
|
|
7654
|
+
};
|
|
7655
|
+
var setTaskStatusTool = {
|
|
7656
|
+
name: "set_task_status",
|
|
7657
|
+
description: "Change the status of one or more tasks by rewriting their checkbox in the project's tasks.md files: `[ ]` pending, `[-]` in progress, `[x]` done. Each affected file gets one new revision, recorded as an AI change; nothing else in the file changes. Partial by nature: a file that cannot be written (it kept changing, it changed since the expected_versions you passed, the organisation is out of storage, or it was moved to the trash) fails only its own tasks. Returns an outcome per task (updated, unchanged, removed: no longer in its file, failed: with an error code), an outcome per file, and the tasks as they are now. Marked as an error only when every task failed.",
|
|
7658
|
+
inputSchema: statusInputSchema,
|
|
7659
|
+
handler: async (args, ctx) => {
|
|
7660
|
+
const expected = args.expected_versions?.length ? Object.fromEntries(args.expected_versions.map((v) => [v.spec_file_id, v.content_version])) : void 0;
|
|
7661
|
+
const result = await ctx.client.setProjectTaskStatus(
|
|
7662
|
+
args.project_id,
|
|
7663
|
+
args.task_ids,
|
|
7664
|
+
args.status,
|
|
7665
|
+
expected
|
|
7666
|
+
);
|
|
7667
|
+
const count = (outcome) => result.results.filter((r) => r.outcome === outcome).length;
|
|
7668
|
+
const failed = count("failed");
|
|
7669
|
+
const body = jsonResult({
|
|
7670
|
+
updated: count("updated"),
|
|
7671
|
+
unchanged: count("unchanged"),
|
|
7672
|
+
removed: count("removed"),
|
|
7673
|
+
failed,
|
|
7674
|
+
results: result.results.map((r) => ({
|
|
7675
|
+
task_id: r.taskId,
|
|
7676
|
+
outcome: r.outcome,
|
|
7677
|
+
...r.error ? { error: r.error } : {}
|
|
7678
|
+
})),
|
|
7679
|
+
files: result.files.map((file) => ({
|
|
7680
|
+
spec_file_id: file.specFileId,
|
|
7681
|
+
content_version: file.contentVersion,
|
|
7682
|
+
written: file.written,
|
|
7683
|
+
...file.error ? { error: file.error } : {}
|
|
7684
|
+
})),
|
|
7685
|
+
tasks: result.tasks.map((task) => taskSummary(task))
|
|
7686
|
+
});
|
|
7687
|
+
return failed > 0 && failed === result.results.length ? { ...body, isError: true } : body;
|
|
7688
|
+
}
|
|
7689
|
+
};
|
|
7690
|
+
|
|
7183
7691
|
// src/server/server.ts
|
|
7184
7692
|
function buildServer(deps) {
|
|
7185
7693
|
const server = new McpServer({
|
|
@@ -7231,6 +7739,10 @@ function buildServer(deps) {
|
|
|
7231
7739
|
registerTool(server, replySpecFileCommentThreadTool, ctx);
|
|
7232
7740
|
registerTool(server, updateSpecFileCommentTool, ctx);
|
|
7233
7741
|
registerTool(server, deleteSpecFileCommentTool, ctx);
|
|
7742
|
+
registerTool(server, listTasksTool, ctx);
|
|
7743
|
+
registerTool(server, listTaskAssigneesTool, ctx);
|
|
7744
|
+
registerTool(server, updateTasksTool, ctx);
|
|
7745
|
+
registerTool(server, setTaskStatusTool, ctx);
|
|
7234
7746
|
return server;
|
|
7235
7747
|
}
|
|
7236
7748
|
async function startStdioServer(deps) {
|
|
@@ -7249,6 +7761,11 @@ function wrapToolHandler(tool, ctx) {
|
|
|
7249
7761
|
if (isProjectPermissionDeniedError(err)) {
|
|
7250
7762
|
return errorResult(`${tool.name} failed: ${permissionDeniedMessage(err)}`);
|
|
7251
7763
|
}
|
|
7764
|
+
if (isTaskBoardDisabledError(err)) {
|
|
7765
|
+
return errorResult(
|
|
7766
|
+
`${tool.name} failed: the task board is turned off in this MySpec environment, so the task tools are unavailable here until it is turned on.`
|
|
7767
|
+
);
|
|
7768
|
+
}
|
|
7252
7769
|
const message = err instanceof Error ? err.message : String(err);
|
|
7253
7770
|
return errorResult(`${tool.name} failed: ${message}`);
|
|
7254
7771
|
}
|
|
@@ -7402,7 +7919,7 @@ async function canonicaliseRoot(root) {
|
|
|
7402
7919
|
import { promises as fs4 } from "fs";
|
|
7403
7920
|
import { join as join2, relative } from "path";
|
|
7404
7921
|
import { spawn } from "child_process";
|
|
7405
|
-
import { z as
|
|
7922
|
+
import { z as z40 } from "zod";
|
|
7406
7923
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
7407
7924
|
|
|
7408
7925
|
// src/reverse/ignore.ts
|
|
@@ -7707,37 +8224,37 @@ var READ_FILE_MAX_LINES = 2e3;
|
|
|
7707
8224
|
var LIST_DIR_HARD_CAP = 5e3;
|
|
7708
8225
|
var LIST_DIR_DEFAULT_CAP = 1e3;
|
|
7709
8226
|
var GREP_MAX_MATCHES_DEFAULT = 200;
|
|
7710
|
-
var listDirInput =
|
|
7711
|
-
path:
|
|
7712
|
-
recursive:
|
|
7713
|
-
maxEntries:
|
|
7714
|
-
includeIgnored:
|
|
8227
|
+
var listDirInput = z40.object({
|
|
8228
|
+
path: z40.string(),
|
|
8229
|
+
recursive: z40.boolean().optional(),
|
|
8230
|
+
maxEntries: z40.number().int().positive().optional(),
|
|
8231
|
+
includeIgnored: z40.boolean().optional()
|
|
7715
8232
|
});
|
|
7716
|
-
var readFileInput =
|
|
7717
|
-
path:
|
|
7718
|
-
offset:
|
|
7719
|
-
limit:
|
|
7720
|
-
includeIgnored:
|
|
8233
|
+
var readFileInput = z40.object({
|
|
8234
|
+
path: z40.string(),
|
|
8235
|
+
offset: z40.number().int().nonnegative().optional(),
|
|
8236
|
+
limit: z40.number().int().positive().optional(),
|
|
8237
|
+
includeIgnored: z40.boolean().optional()
|
|
7721
8238
|
});
|
|
7722
|
-
var grepInput =
|
|
7723
|
-
pattern:
|
|
8239
|
+
var grepInput = z40.object({
|
|
8240
|
+
pattern: z40.string().describe(
|
|
7724
8241
|
"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."
|
|
7725
8242
|
),
|
|
7726
|
-
path:
|
|
7727
|
-
isRegex:
|
|
7728
|
-
caseSensitive:
|
|
7729
|
-
maxMatches:
|
|
7730
|
-
contextLines:
|
|
7731
|
-
includeIgnored:
|
|
8243
|
+
path: z40.string().optional().describe("File or directory under --root to search. Defaults to the whole root."),
|
|
8244
|
+
isRegex: z40.boolean().optional().describe("Treat `pattern` as a regex (default true). Set false for literal-string search."),
|
|
8245
|
+
caseSensitive: z40.boolean().optional(),
|
|
8246
|
+
maxMatches: z40.number().int().positive().optional(),
|
|
8247
|
+
contextLines: z40.number().int().nonnegative().optional(),
|
|
8248
|
+
includeIgnored: z40.boolean().optional()
|
|
7732
8249
|
});
|
|
7733
|
-
var packCodebaseInput =
|
|
7734
|
-
subpath:
|
|
7735
|
-
includePatterns:
|
|
7736
|
-
ignorePatterns:
|
|
8250
|
+
var packCodebaseInput = z40.object({
|
|
8251
|
+
subpath: z40.string().optional(),
|
|
8252
|
+
includePatterns: z40.string().optional(),
|
|
8253
|
+
ignorePatterns: z40.string().optional()
|
|
7737
8254
|
});
|
|
7738
|
-
var packCodebaseReadPageInput =
|
|
7739
|
-
outputId:
|
|
7740
|
-
page:
|
|
8255
|
+
var packCodebaseReadPageInput = z40.object({
|
|
8256
|
+
outputId: z40.string(),
|
|
8257
|
+
page: z40.number().int().positive()
|
|
7741
8258
|
});
|
|
7742
8259
|
function structuredError(error, message, extra) {
|
|
7743
8260
|
const payload = { error, message, ...extra ?? {} };
|
|
@@ -8265,7 +8782,7 @@ function jsonSchemaFromZod(schema) {
|
|
|
8265
8782
|
return zodToJson(schema);
|
|
8266
8783
|
}
|
|
8267
8784
|
function zodToJson(schema) {
|
|
8268
|
-
if (schema instanceof
|
|
8785
|
+
if (schema instanceof z40.ZodObject) {
|
|
8269
8786
|
const shape = schema.shape;
|
|
8270
8787
|
const properties = {};
|
|
8271
8788
|
const required = [];
|
|
@@ -8280,19 +8797,19 @@ function zodToJson(schema) {
|
|
|
8280
8797
|
if (required.length > 0) out.required = required;
|
|
8281
8798
|
return out;
|
|
8282
8799
|
}
|
|
8283
|
-
if (schema instanceof
|
|
8800
|
+
if (schema instanceof z40.ZodOptional) {
|
|
8284
8801
|
return zodToJson(schema.unwrap());
|
|
8285
8802
|
}
|
|
8286
|
-
if (schema instanceof
|
|
8803
|
+
if (schema instanceof z40.ZodString) {
|
|
8287
8804
|
return { type: "string" };
|
|
8288
8805
|
}
|
|
8289
|
-
if (schema instanceof
|
|
8806
|
+
if (schema instanceof z40.ZodNumber) {
|
|
8290
8807
|
return { type: "number" };
|
|
8291
8808
|
}
|
|
8292
|
-
if (schema instanceof
|
|
8809
|
+
if (schema instanceof z40.ZodBoolean) {
|
|
8293
8810
|
return { type: "boolean" };
|
|
8294
8811
|
}
|
|
8295
|
-
if (schema instanceof
|
|
8812
|
+
if (schema instanceof z40.ZodEnum) {
|
|
8296
8813
|
return { type: "string", enum: schema.options };
|
|
8297
8814
|
}
|
|
8298
8815
|
return { type: "string" };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myspec/mcp-server",
|
|
3
|
-
"version": "1.1.1-next.
|
|
3
|
+
"version": "1.1.1-next.166",
|
|
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": {
|