@myspec/mcp-server 1.1.1-next.175 → 1.1.1-next.176
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 +42 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -363,9 +363,9 @@ 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` |
|
|
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`, `relationships` (`{type, task_id}`: this task `blocks`, is `blocked_by`, or is `related` to that task), 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
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 |
|
|
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[]`, `add_relationships[]` (`{type, task_id}`, ≤ 50), `remove_relationships[]` (task ids, ≤ 50) | Edits planning fields on every task given. A field left out is left alone; `null` clears it. Two tasks have at most one relationship: setting one replaces the pair's old one, and the other task sees the inverse. 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
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 |
|
|
370
370
|
|
|
371
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.
|
package/dist/index.js
CHANGED
|
@@ -2390,12 +2390,15 @@ var SPEC_FILE_LOCK_TTL_MS = 2 * 6e4;
|
|
|
2390
2390
|
// ../../packages/platform-client/project/types/task.types.ts
|
|
2391
2391
|
var TASK_STATUSES = ["pending", "in_progress", "done"];
|
|
2392
2392
|
var TASK_PRIORITIES = [0, 1, 2, 3, 4];
|
|
2393
|
+
var TASK_RELATIONSHIP_TYPES = ["blocks", "blocked_by", "related"];
|
|
2393
2394
|
var TASK_LIMITS = {
|
|
2394
2395
|
maxLabels: 20,
|
|
2395
2396
|
maxLabelLength: 40,
|
|
2396
2397
|
maxStoryPoints: 9999.99,
|
|
2397
2398
|
maxCycle: 1e5,
|
|
2398
2399
|
maxTasksPerRequest: 200,
|
|
2400
|
+
/** The most relationships one task has, and the most one change adds (or removes). */
|
|
2401
|
+
maxRelationships: 50,
|
|
2399
2402
|
/** The most tasks one page of a listing holds. */
|
|
2400
2403
|
maxPageSize: 500,
|
|
2401
2404
|
/** The most tasks.md files one board lists (and the most expected_versions a status change takes). */
|
|
@@ -2414,6 +2417,11 @@ function toStatus(raw) {
|
|
|
2414
2417
|
function toPriority(raw) {
|
|
2415
2418
|
return TASK_PRIORITIES.includes(raw) ? raw : 0;
|
|
2416
2419
|
}
|
|
2420
|
+
function toRelationships(raw) {
|
|
2421
|
+
return (raw ?? []).flatMap(
|
|
2422
|
+
(r) => TASK_RELATIONSHIP_TYPES.includes(r.type) ? [{ type: r.type, taskId: r.task_id }] : []
|
|
2423
|
+
);
|
|
2424
|
+
}
|
|
2417
2425
|
function toOutcome(raw) {
|
|
2418
2426
|
return raw === "updated" || raw === "unchanged" || raw === "removed" ? raw : "failed";
|
|
2419
2427
|
}
|
|
@@ -2440,6 +2448,7 @@ function transformProjectTask(raw) {
|
|
|
2440
2448
|
startAt: parseTimestamp(raw.start_at),
|
|
2441
2449
|
endAt: parseTimestamp(raw.end_at),
|
|
2442
2450
|
labels: raw.labels ?? [],
|
|
2451
|
+
relationships: toRelationships(raw.relationships),
|
|
2443
2452
|
updatedBy: raw.updated_by ?? null,
|
|
2444
2453
|
createdAt: parseTimestamp(raw.created_at),
|
|
2445
2454
|
updatedAt: parseTimestamp(raw.updated_at)
|
|
@@ -2527,8 +2536,15 @@ function toRawUpdateTasksRequest(taskIds, patch) {
|
|
|
2527
2536
|
const body = { task_ids: taskIds, set: toRawPlanningSet(patch) };
|
|
2528
2537
|
if (patch.addLabels?.length) body.add_labels = patch.addLabels;
|
|
2529
2538
|
if (patch.removeLabels?.length) body.remove_labels = patch.removeLabels;
|
|
2539
|
+
addRawRelationshipEdits(body, patch);
|
|
2530
2540
|
return body;
|
|
2531
2541
|
}
|
|
2542
|
+
function addRawRelationshipEdits(body, patch) {
|
|
2543
|
+
if (patch.addRelationships?.length) {
|
|
2544
|
+
body.add_relationships = patch.addRelationships.map((r) => ({ type: r.type, task_id: r.taskId }));
|
|
2545
|
+
}
|
|
2546
|
+
if (patch.removeRelationships?.length) body.remove_relationships = patch.removeRelationships;
|
|
2547
|
+
}
|
|
2532
2548
|
function toRawPlanningSet(patch) {
|
|
2533
2549
|
const set = {};
|
|
2534
2550
|
if ("assigneeUserId" in patch) set.assignee_user_id = patch.assigneeUserId ?? null;
|
|
@@ -2548,6 +2564,7 @@ function toRawTaskChange(change) {
|
|
|
2548
2564
|
if (Object.keys(set).length > 0) raw.set = set;
|
|
2549
2565
|
if (patch.addLabels?.length) raw.add_labels = patch.addLabels;
|
|
2550
2566
|
if (patch.removeLabels?.length) raw.remove_labels = patch.removeLabels;
|
|
2567
|
+
addRawRelationshipEdits(raw, patch);
|
|
2551
2568
|
}
|
|
2552
2569
|
return raw;
|
|
2553
2570
|
}
|
|
@@ -7532,6 +7549,7 @@ var ME = "me";
|
|
|
7532
7549
|
var NONE = "none";
|
|
7533
7550
|
var PRIORITY_NAMES = ["none", "urgent", "high", "medium", "low"];
|
|
7534
7551
|
var STATUS = z39.enum(["pending", "in_progress", "done"]);
|
|
7552
|
+
var RELATIONSHIP_TYPE = z39.enum(["blocks", "blocked_by", "related"]);
|
|
7535
7553
|
function toDay(date) {
|
|
7536
7554
|
return date ? date.toISOString().slice(0, 10) : null;
|
|
7537
7555
|
}
|
|
@@ -7558,6 +7576,8 @@ function taskSummary(task, opts = {}) {
|
|
|
7558
7576
|
start_date: toDay(task.startAt),
|
|
7559
7577
|
end_date: toDay(task.endAt),
|
|
7560
7578
|
labels: task.labels,
|
|
7579
|
+
// "this task <type> task_id": blocks, blocked_by or related.
|
|
7580
|
+
relationships: task.relationships.map((r) => ({ type: r.type, task_id: r.taskId })),
|
|
7561
7581
|
...withDetails ? { details: truncated ? task.body.slice(0, DETAILS_PREVIEW_CHARS) : task.body } : {},
|
|
7562
7582
|
// Only ever present and true.
|
|
7563
7583
|
...truncated ? { details_truncated: true } : {},
|
|
@@ -7610,7 +7630,7 @@ function pageLocally(board, query) {
|
|
|
7610
7630
|
}
|
|
7611
7631
|
var listTasksTool = {
|
|
7612
7632
|
name: "list_tasks",
|
|
7613
|
-
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
|
|
7633
|
+
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), labels and relationships with other tasks ({type, task_id}: this task blocks, is blocked_by, or is related to that task). 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.`,
|
|
7614
7634
|
inputSchema: listInputSchema2,
|
|
7615
7635
|
handler: async (args, ctx) => {
|
|
7616
7636
|
let assignee;
|
|
@@ -7690,11 +7710,22 @@ var updateInputSchema2 = {
|
|
|
7690
7710
|
start_date: z39.iso.date().nullable().optional().describe("Start day as YYYY-MM-DD, or null to clear it."),
|
|
7691
7711
|
end_date: z39.iso.date().nullable().optional().describe("Due day as YYYY-MM-DD, or null to clear it. Not before the start day."),
|
|
7692
7712
|
add_labels: z39.array(labelField).max(TASK_LIMITS.maxLabels).optional().describe(`Labels to add. A task carries at most ${String(TASK_LIMITS.maxLabels)}.`),
|
|
7693
|
-
remove_labels: z39.array(labelField).max(TASK_LIMITS.maxLabels).optional().describe("Labels to remove.")
|
|
7713
|
+
remove_labels: z39.array(labelField).max(TASK_LIMITS.maxLabels).optional().describe("Labels to remove."),
|
|
7714
|
+
add_relationships: z39.array(
|
|
7715
|
+
z39.object({
|
|
7716
|
+
type: RELATIONSHIP_TYPE.describe(
|
|
7717
|
+
"How each changed task stands to task_id: blocks (task_id waits for it), blocked_by (it waits for task_id) or related."
|
|
7718
|
+
),
|
|
7719
|
+
task_id: z39.uuid().describe("The other task (a task_id from list_tasks).")
|
|
7720
|
+
})
|
|
7721
|
+
).max(TASK_LIMITS.maxRelationships).optional().describe(
|
|
7722
|
+
`Relationships to set. Two tasks have at most one: setting one replaces whatever the pair had, and the other task sees the inverse (A blocks B is B blocked_by A). A task has at most ${String(TASK_LIMITS.maxRelationships)}.`
|
|
7723
|
+
),
|
|
7724
|
+
remove_relationships: z39.array(z39.uuid()).max(TASK_LIMITS.maxRelationships).optional().describe("Task ids to unlink each changed task from, whatever the relationship.")
|
|
7694
7725
|
};
|
|
7695
7726
|
var updateTasksTool = {
|
|
7696
7727
|
name: "update_tasks",
|
|
7697
|
-
description: "Edit the planning fields of one or more tasks: assignee, priority, story points, cycle, start and due dates, and
|
|
7728
|
+
description: "Edit the planning fields of one or more tasks: assignee, priority, story points, cycle, start and due dates, labels, and relationships with other tasks (blocks, blocked_by, related). The same change applies to every task given. A field left out is left alone; null clears it. Labels and relationships 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.",
|
|
7698
7729
|
inputSchema: updateInputSchema2,
|
|
7699
7730
|
handler: async (args, ctx) => {
|
|
7700
7731
|
const patch = {};
|
|
@@ -7716,9 +7747,16 @@ var updateTasksTool = {
|
|
|
7716
7747
|
}
|
|
7717
7748
|
if (args.add_labels?.length) patch.addLabels = args.add_labels;
|
|
7718
7749
|
if (args.remove_labels?.length) patch.removeLabels = args.remove_labels;
|
|
7750
|
+
if (args.add_relationships?.length) {
|
|
7751
|
+
patch.addRelationships = args.add_relationships.map((r) => ({
|
|
7752
|
+
type: r.type,
|
|
7753
|
+
taskId: r.task_id
|
|
7754
|
+
}));
|
|
7755
|
+
}
|
|
7756
|
+
if (args.remove_relationships?.length) patch.removeRelationships = args.remove_relationships;
|
|
7719
7757
|
if (Object.keys(patch).length === 0) {
|
|
7720
7758
|
return errorResult(
|
|
7721
|
-
"Nothing to change: pass at least one of assignee_user_id, priority, story_points, cycle, start_date, end_date, add_labels or
|
|
7759
|
+
"Nothing to change: pass at least one of assignee_user_id, priority, story_points, cycle, start_date, end_date, add_labels, remove_labels, add_relationships or remove_relationships."
|
|
7722
7760
|
);
|
|
7723
7761
|
}
|
|
7724
7762
|
const tasks = await ctx.client.updateProjectTasks(args.project_id, args.task_ids, patch);
|
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.176",
|
|
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": {
|