@ai-setting/roy-task-plugin 0.4.2 → 0.6.0
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/dist/claude-plugin/.claude-plugin/plugin.json +19 -0
- package/dist/claude-plugin/.mcp.json +6 -0
- package/dist/claude-plugin/agents/roy.md +115 -0
- package/dist/claude-plugin/commands/task-complete.md +25 -0
- package/dist/claude-plugin/commands/task-create.md +19 -0
- package/dist/claude-plugin/commands/task-list.md +17 -0
- package/dist/claude-plugin/commands/task-milestone.md +39 -0
- package/dist/claude-plugin/commands/task-tree.md +20 -0
- package/dist/claude-plugin/commands/task-update.md +26 -0
- package/dist/claude-plugin/hooks/hooks.json +29 -0
- package/dist/claude-plugin/hooks/session-start.mjs +49 -0
- package/dist/claude-plugin/hooks/stop-check.mjs +222 -0
- package/dist/claude-plugin/mcp/bridge.mjs +482 -0
- package/dist/claude-plugin/mcp/roy-task-mcp-server.mjs +281 -0
- package/dist/claude-plugin/mcp/tools.mjs +240 -0
- package/dist/cli.js +922 -335
- package/package.json +3 -2
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* roy-task MCP server — exposes the task lifecycle to Claude Code as native
|
|
4
|
+
* MCP tools.
|
|
5
|
+
*
|
|
6
|
+
* ## Protocol
|
|
7
|
+
*
|
|
8
|
+
* Newline-delimited JSON-RPC 2.0 over stdin/stdout, per the MCP stdio
|
|
9
|
+
* transport. We implement exactly the methods a task server needs:
|
|
10
|
+
*
|
|
11
|
+
* | method | direction | notes |
|
|
12
|
+
* |-------------------------|-----------------|----------------------------------|
|
|
13
|
+
* | `initialize` | client -> server | we negotiate the protocol version |
|
|
14
|
+
* | `notifications/initialized` | client -> server | acknowledged by doing nothing |
|
|
15
|
+
* | `tools/list` | client -> server | the tool catalogue |
|
|
16
|
+
* | `tools/call` | client -> server | run one tool |
|
|
17
|
+
* | `ping` | client -> server | health check |
|
|
18
|
+
*
|
|
19
|
+
* Unknown methods get JSON-RPC error `-32601`. Notifications (requests with
|
|
20
|
+
* no `id`) get **no** response at all — replying to a notification is a
|
|
21
|
+
* protocol violation and desynchronises the client.
|
|
22
|
+
*
|
|
23
|
+
* ## stdout discipline
|
|
24
|
+
*
|
|
25
|
+
* stdout carries the protocol and nothing else. Every diagnostic goes to
|
|
26
|
+
* stderr. A stray `console.log` would inject garbage into the JSON-RPC stream
|
|
27
|
+
* and make Claude Code drop the server.
|
|
28
|
+
*
|
|
29
|
+
* ## stdio discipline (the other direction)
|
|
30
|
+
*
|
|
31
|
+
* This process reads stdin — that is the transport. It must NOT be spawned
|
|
32
|
+
* with `stdin: "ignore"` (the rule the daemon's spawn adapter enforces for
|
|
33
|
+
* *agent* subprocesses); an MCP server whose stdin is closed can never
|
|
34
|
+
* receive a request.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { createInterface } from "node:readline";
|
|
38
|
+
|
|
39
|
+
import { callTool, openStore, BridgeError, defaultTaskDbPath } from "./bridge.mjs";
|
|
40
|
+
import {
|
|
41
|
+
MCP_TOOLS,
|
|
42
|
+
SUPPORTED_PROTOCOL_VERSIONS,
|
|
43
|
+
DEFAULT_PROTOCOL_VERSION,
|
|
44
|
+
JSON_RPC_ERRORS,
|
|
45
|
+
} from "./tools.mjs";
|
|
46
|
+
|
|
47
|
+
const SERVER_NAME = "roy-task";
|
|
48
|
+
const SERVER_VERSION = "0.5.0";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Write one JSON-RPC message to stdout.
|
|
52
|
+
*
|
|
53
|
+
* `jsonrpc: "2.0"` is **added here**, not by the callers. That is deliberate:
|
|
54
|
+
* the MCP SDK's response schema (`JSONRPCResultResponseSchema`) is `.strict()`
|
|
55
|
+
* and rejects any key it does not know — but it *requires* `jsonrpc` to be
|
|
56
|
+
* exactly `"2.0"`. A response built as `{ id, result }` and written verbatim
|
|
57
|
+
* fails to parse on the client with
|
|
58
|
+
* `Unrecognized keys: "id", "result"` / `expected "2.0"`, and the client then
|
|
59
|
+
* reports "Failed to connect" even though the server is alive and correct.
|
|
60
|
+
*
|
|
61
|
+
* Measured: this exact omission was the reason `claude --plugin-dir` showed
|
|
62
|
+
* `mcp_servers: [{"name":"plugin:roy-task:roy-task","status":"failed"}]` while
|
|
63
|
+
* the same server answered perfectly over a hand-rolled pipe.
|
|
64
|
+
*/
|
|
65
|
+
function send(message) {
|
|
66
|
+
process.stdout.write(`${JSON.stringify({ jsonrpc: "2.0", ...message })}\n`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function sendResult(id, result) {
|
|
70
|
+
send({ jsonrpc: "2.0", id, result });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sendError(id, code, message, data) {
|
|
74
|
+
const error = { code, message };
|
|
75
|
+
if (data !== undefined) error.data = data;
|
|
76
|
+
send({ jsonrpc: "2.0", id, error });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Pick the protocol version to answer with.
|
|
81
|
+
*
|
|
82
|
+
* Echo the client's version when we know it; otherwise offer ours. This is
|
|
83
|
+
* what the official SDK does, and it is what lets an older Claude Code talk to
|
|
84
|
+
* a newer server without a hard failure.
|
|
85
|
+
*/
|
|
86
|
+
export function negotiateProtocolVersion(requested) {
|
|
87
|
+
if (typeof requested === "string" && SUPPORTED_PROTOCOL_VERSIONS.includes(requested)) {
|
|
88
|
+
return requested;
|
|
89
|
+
}
|
|
90
|
+
return DEFAULT_PROTOCOL_VERSION;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Turn a thrown error into an MCP tool result with `isError: true`. */
|
|
94
|
+
function toolError(message) {
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text: message }],
|
|
97
|
+
isError: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The server's identity, shared by `initialize` and `server/discover`.
|
|
103
|
+
*/
|
|
104
|
+
const SERVER_INFO = { name: SERVER_NAME, version: SERVER_VERSION };
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The capabilities we advertise.
|
|
108
|
+
*/
|
|
109
|
+
const SERVER_CAPABILITIES = { tools: { listChanged: false } };
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Handle one parsed JSON-RPC request.
|
|
113
|
+
*
|
|
114
|
+
* Exported for tests: the pure part (dispatch) is separated from the I/O
|
|
115
|
+
* part (stdin/stdout) so the protocol can be exercised without a pipe.
|
|
116
|
+
*/
|
|
117
|
+
export function handleRequest(request, db) {
|
|
118
|
+
const { id, method, params } = request;
|
|
119
|
+
|
|
120
|
+
// Notifications have no id and must not be answered.
|
|
121
|
+
if (id === undefined || id === null) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
switch (method) {
|
|
126
|
+
case "initialize": {
|
|
127
|
+
const requested = params && params.protocolVersion;
|
|
128
|
+
return {
|
|
129
|
+
id,
|
|
130
|
+
result: {
|
|
131
|
+
protocolVersion: negotiateProtocolVersion(requested),
|
|
132
|
+
capabilities: SERVER_CAPABILITIES,
|
|
133
|
+
serverInfo: SERVER_INFO,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* `server/discover` — Claude Code 2.1.285's pre-handshake probe.
|
|
140
|
+
*
|
|
141
|
+
* Measured against 2.1.285: before it sends `initialize`, it sends
|
|
142
|
+
*
|
|
143
|
+
* ```jsonc
|
|
144
|
+
* {"jsonrpc":"2.0","id":"server-discover-probe-1","method":"server/discover",
|
|
145
|
+
* "params":{"_meta":{
|
|
146
|
+
* "io.modelcontextprotocol/protocolVersion":"2026-07-28",
|
|
147
|
+
* "io.modelcontextprotocol/clientInfo":{"name":"claude-code","version":"2.1.285"},
|
|
148
|
+
* "io.modelcontextprotocol/clientCapabilities":{…}}}}
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* A server that answers `-32601 Method not found` is treated by
|
|
152
|
+
* 2.1.285 as **failed to connect** — the whole MCP server is dropped
|
|
153
|
+
* even though the later `initialize` handshake would have succeeded.
|
|
154
|
+
* (Measured: with `server/discover` unhandled the init event reported
|
|
155
|
+
* `{"name":"plugin:roy-task:roy-task","status":"failed"}`; with it
|
|
156
|
+
* handled the same event reported `status:"connected"`.)
|
|
157
|
+
*
|
|
158
|
+
* The response shape mirrors `initialize` — the server describes itself
|
|
159
|
+
* — so the two handlers share `SERVER_INFO` / `SERVER_CAPABILITIES`.
|
|
160
|
+
*
|
|
161
|
+
* This method is **not** in @modelcontextprotocol/sdk 1.32.1, which is why
|
|
162
|
+
* an SDK-based client never exercises it and a hand-rolled server can
|
|
163
|
+
* pass its own tests while failing inside Claude Code.
|
|
164
|
+
*/
|
|
165
|
+
case "server/discover": {
|
|
166
|
+
return {
|
|
167
|
+
id,
|
|
168
|
+
result: {
|
|
169
|
+
protocolVersion: negotiateProtocolVersion(
|
|
170
|
+
(params &&
|
|
171
|
+
params._meta &&
|
|
172
|
+
params._meta["io.modelcontextprotocol/protocolVersion"]) ||
|
|
173
|
+
undefined,
|
|
174
|
+
),
|
|
175
|
+
capabilities: SERVER_CAPABILITIES,
|
|
176
|
+
serverInfo: SERVER_INFO,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
case "ping":
|
|
182
|
+
return { id, result: {} };
|
|
183
|
+
|
|
184
|
+
case "tools/list":
|
|
185
|
+
return { id, result: { tools: MCP_TOOLS } };
|
|
186
|
+
|
|
187
|
+
case "tools/call": {
|
|
188
|
+
const toolName = params && params.name;
|
|
189
|
+
const args = (params && params.arguments) || {};
|
|
190
|
+
if (typeof toolName !== "string") {
|
|
191
|
+
return {
|
|
192
|
+
id,
|
|
193
|
+
error: {
|
|
194
|
+
code: JSON_RPC_ERRORS.INVALID_PARAMS,
|
|
195
|
+
message: "tools/call requires a tool name",
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const value = callTool(db, toolName, args);
|
|
201
|
+
return {
|
|
202
|
+
id,
|
|
203
|
+
result: {
|
|
204
|
+
content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
} catch (err) {
|
|
208
|
+
const message =
|
|
209
|
+
err instanceof BridgeError
|
|
210
|
+
? err.message
|
|
211
|
+
: `tool "${toolName}" failed: ${err && err.message ? err.message : String(err)}`;
|
|
212
|
+
return { id, result: toolError(message) };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
default:
|
|
217
|
+
return {
|
|
218
|
+
id,
|
|
219
|
+
error: {
|
|
220
|
+
code: JSON_RPC_ERRORS.METHOD_NOT_FOUND,
|
|
221
|
+
message: `method not found: ${method}`,
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Read newline-delimited JSON-RPC from stdin and answer on stdout. */
|
|
228
|
+
export function main() {
|
|
229
|
+
const dbPath = process.env.ROY_TASK_DB_PATH || defaultTaskDbPath();
|
|
230
|
+
let db;
|
|
231
|
+
try {
|
|
232
|
+
db = openStore(dbPath);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
process.stderr.write(
|
|
235
|
+
`roy-task mcp server: cannot open task store at ${dbPath}: ${err && err.message}\n`,
|
|
236
|
+
);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
241
|
+
|
|
242
|
+
rl.on("line", (line) => {
|
|
243
|
+
const trimmed = line.trim();
|
|
244
|
+
if (trimmed.length === 0) return;
|
|
245
|
+
|
|
246
|
+
let request;
|
|
247
|
+
try {
|
|
248
|
+
request = JSON.parse(trimmed);
|
|
249
|
+
} catch {
|
|
250
|
+
sendError(null, JSON_RPC_ERRORS.PARSE_ERROR, "invalid JSON");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (typeof request !== "object" || request === null || Array.isArray(request)) {
|
|
255
|
+
sendError(null, JSON_RPC_ERRORS.INVALID_REQUEST, "request must be a JSON object");
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const response = handleRequest(request, db);
|
|
260
|
+
if (response) send(response);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
rl.on("close", () => {
|
|
264
|
+
try {
|
|
265
|
+
db.close();
|
|
266
|
+
} catch {
|
|
267
|
+
// already closed
|
|
268
|
+
}
|
|
269
|
+
process.exit(0);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Only run when executed directly, so tests can import the pure helpers.
|
|
274
|
+
const invokedDirectly =
|
|
275
|
+
process.argv[1] &&
|
|
276
|
+
(process.argv[1].endsWith("roy-task-mcp-server.mjs") ||
|
|
277
|
+
process.argv[1].endsWith("roy-task-mcp-server.js"));
|
|
278
|
+
|
|
279
|
+
if (invokedDirectly) {
|
|
280
|
+
main();
|
|
281
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-dependency MCP server exposing the roy task tools over stdio.
|
|
3
|
+
*
|
|
4
|
+
* ## Why hand-rolled instead of @modelcontextprotocol/sdk
|
|
5
|
+
*
|
|
6
|
+
* The published npm package (`@ai-setting/roy-task-plugin`) is installed on
|
|
7
|
+
* plain Node.js machines with no native build step. Adding the SDK would add a
|
|
8
|
+
* dependency tree to a package whose whole point is "install and it runs".
|
|
9
|
+
* The MCP wire format we need is small and stable:
|
|
10
|
+
*
|
|
11
|
+
* - newline-delimited JSON-RPC 2.0 on stdin/stdout
|
|
12
|
+
* - `initialize` / `tools/list` / `tools/call` (plus `notifications/initialized`)
|
|
13
|
+
*
|
|
14
|
+
* That is implemented here in ~200 lines with nothing but `node:child_process`.
|
|
15
|
+
* The protocol version is negotiated: we echo the client's requested version
|
|
16
|
+
* when it is one we know, else we answer with our own.
|
|
17
|
+
*
|
|
18
|
+
* ## Where the data comes from
|
|
19
|
+
*
|
|
20
|
+
* The task store is **roy-agent's** store (`~/.local/share/roy-agent/tasks.db`),
|
|
21
|
+
* not the plugin's own `~/.roy-task-plugin/tasks.db`. The reason is
|
|
22
|
+
* interoperability: the roy agent inside opencode/claude creates tasks through
|
|
23
|
+
* `roy-agent`'s own task tools, and the progress card the daemon sends is
|
|
24
|
+
* rendered from the *same* rows. If this MCP server wrote to a different
|
|
25
|
+
* database, Claude would create tasks the card can never see.
|
|
26
|
+
*
|
|
27
|
+
* So the tools shell out to the `roy-agent tasks …` CLI, which is the stable,
|
|
28
|
+
* already-migrated, already-schema-validated way to reach that store. We do
|
|
29
|
+
* not re-implement the store, and we do not open the SQLite file ourselves.
|
|
30
|
+
*
|
|
31
|
+
* ## stdio hygiene
|
|
32
|
+
*
|
|
33
|
+
* This process is spawned by Claude Code as an MCP server. It must:
|
|
34
|
+
*
|
|
35
|
+
* - **read** stdin (that is the transport) — obviously not `ignore` here;
|
|
36
|
+
* - **never** write anything but protocol messages to stdout. A stray
|
|
37
|
+
* `console.log` would corrupt the stream, so every log line goes to stderr.
|
|
38
|
+
*
|
|
39
|
+
* The "stdin must be ignore" rule from the daemon's spawn adapter applies to
|
|
40
|
+
* *agent* subprocesses, not to an MCP server — an MCP server whose stdin is
|
|
41
|
+
* closed can never receive a single request.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
export const SUPPORTED_PROTOCOL_VERSIONS = Object.freeze([
|
|
45
|
+
"2025-11-25",
|
|
46
|
+
"2025-06-18",
|
|
47
|
+
"2025-03-26",
|
|
48
|
+
"2024-11-05",
|
|
49
|
+
"2024-10-07",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
export const DEFAULT_PROTOCOL_VERSION = "2025-11-25";
|
|
53
|
+
|
|
54
|
+
/** The JSON-RPC error codes we use. */
|
|
55
|
+
export const JSON_RPC_ERRORS = Object.freeze({
|
|
56
|
+
PARSE_ERROR: -32700,
|
|
57
|
+
INVALID_REQUEST: -32600,
|
|
58
|
+
METHOD_NOT_FOUND: -32601,
|
|
59
|
+
INVALID_PARAMS: -32602,
|
|
60
|
+
INTERNAL_ERROR: -32603,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The task tools we expose.
|
|
65
|
+
*
|
|
66
|
+
* The names mirror roy-agent's own tool names (`task_create`, `task_list`, …)
|
|
67
|
+
* so the mental model transfers one-to-one.
|
|
68
|
+
*/
|
|
69
|
+
export const MCP_TOOLS = Object.freeze([
|
|
70
|
+
{
|
|
71
|
+
name: "task_tree",
|
|
72
|
+
description:
|
|
73
|
+
"Show the task tree. Call this BEFORE creating anything, to decide whether new work belongs under an existing root (parent_task_id) or is genuinely new.",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
type: "object",
|
|
76
|
+
properties: {
|
|
77
|
+
depth: {
|
|
78
|
+
type: "number",
|
|
79
|
+
description: "Tree depth. 1 = roots + their direct children. -1 = unlimited.",
|
|
80
|
+
},
|
|
81
|
+
parent_task_id: {
|
|
82
|
+
type: "number",
|
|
83
|
+
description: "Only show the subtree under this task id.",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "task_create",
|
|
90
|
+
description:
|
|
91
|
+
"Create a tracked task. Task is a first-class citizen: create the task BEFORE doing the work, then update it as you go and complete it when done.",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
title: { type: "string", description: "Imperative, specific one-line title." },
|
|
96
|
+
description: { type: "string", description: "What needs to be done and why." },
|
|
97
|
+
goals_and_expected_deliverables: {
|
|
98
|
+
type: "string",
|
|
99
|
+
description: "Success criteria and expected outputs.",
|
|
100
|
+
},
|
|
101
|
+
priority: {
|
|
102
|
+
type: "string",
|
|
103
|
+
enum: ["low", "medium", "high"],
|
|
104
|
+
description: "Task priority.",
|
|
105
|
+
},
|
|
106
|
+
type: {
|
|
107
|
+
type: "string",
|
|
108
|
+
enum: ["normal", "cycle", "longterm"],
|
|
109
|
+
description: "normal = one-off; cycle = periodic; longterm = ongoing goal.",
|
|
110
|
+
},
|
|
111
|
+
parent_task_id: {
|
|
112
|
+
type: "number",
|
|
113
|
+
description: "Parent task id. Omit to create a root task.",
|
|
114
|
+
},
|
|
115
|
+
project_path: { type: "string", description: "Project root path." },
|
|
116
|
+
context: {
|
|
117
|
+
type: "string",
|
|
118
|
+
description: "JSON string with worktree_path / branch / type etc.",
|
|
119
|
+
},
|
|
120
|
+
tags: {
|
|
121
|
+
type: "array",
|
|
122
|
+
items: { type: "string" },
|
|
123
|
+
description: "Task tags.",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
required: ["title"],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "task_get",
|
|
131
|
+
description: "Get one task's full detail, including its operation records.",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: { id: { type: "number", description: "Task id." } },
|
|
135
|
+
required: ["id"],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "task_list",
|
|
140
|
+
description: "List tasks. Supports filtering by status / priority / type.",
|
|
141
|
+
inputSchema: {
|
|
142
|
+
type: "object",
|
|
143
|
+
properties: {
|
|
144
|
+
status: {
|
|
145
|
+
type: "string",
|
|
146
|
+
enum: ["todo", "active", "completed", "paused", "cancelled", "archived"],
|
|
147
|
+
},
|
|
148
|
+
priority: { type: "string", enum: ["low", "medium", "high"] },
|
|
149
|
+
type: { type: "string", enum: ["normal", "cycle", "longterm"] },
|
|
150
|
+
limit: { type: "number", description: "Max rows (default 20)." },
|
|
151
|
+
offset: { type: "number", description: "Row offset." },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "task_update",
|
|
157
|
+
description:
|
|
158
|
+
"Update a task's status, progress or description. Report progress as you go, not only at the end.",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
id: { type: "number", description: "Task id." },
|
|
163
|
+
status: {
|
|
164
|
+
type: "string",
|
|
165
|
+
enum: ["todo", "active", "completed", "paused", "cancelled", "archived"],
|
|
166
|
+
},
|
|
167
|
+
progress: { type: "number", description: "0-100." },
|
|
168
|
+
current_status: { type: "string", description: "One sentence: where you are right now." },
|
|
169
|
+
description: { type: "string", description: "Replace the description." },
|
|
170
|
+
goals_and_expected_deliverables: { type: "string", description: "Replace the goals." },
|
|
171
|
+
priority: { type: "string", enum: ["low", "medium", "high"] },
|
|
172
|
+
},
|
|
173
|
+
required: ["id"],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "task_operation_create",
|
|
178
|
+
description:
|
|
179
|
+
"Record a milestone (what was reached) and, optionally, a process summary (how you got there, 100-150 chars). Milestones mark state transitions: RED->GREEN, blocker found, decision made.",
|
|
180
|
+
inputSchema: {
|
|
181
|
+
type: "object",
|
|
182
|
+
properties: {
|
|
183
|
+
task_id: { type: "number", description: "Task id." },
|
|
184
|
+
milestone_type: {
|
|
185
|
+
type: "string",
|
|
186
|
+
enum: [
|
|
187
|
+
"create",
|
|
188
|
+
"progress",
|
|
189
|
+
"milestone",
|
|
190
|
+
"problem",
|
|
191
|
+
"solution",
|
|
192
|
+
"decision",
|
|
193
|
+
"review",
|
|
194
|
+
"completed",
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
milestone_title: { type: "string", description: "One-line milestone title." },
|
|
198
|
+
milestone_description: { type: "string", description: "What was reached." },
|
|
199
|
+
process_description: {
|
|
200
|
+
type: "string",
|
|
201
|
+
description: "How you got there. 100-150 characters, strictly.",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
required: ["task_id", "milestone_type", "milestone_title"],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "task_complete",
|
|
209
|
+
description:
|
|
210
|
+
"Close out a task. Provide deliverable_storage_path so the outcome is traceable.",
|
|
211
|
+
inputSchema: {
|
|
212
|
+
type: "object",
|
|
213
|
+
properties: {
|
|
214
|
+
id: { type: "number", description: "Task id." },
|
|
215
|
+
deliverable_storage_path: {
|
|
216
|
+
type: "string",
|
|
217
|
+
description: "Where the work product lives (path / URL / branch).",
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
required: ["id"],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: "task_search",
|
|
225
|
+
description:
|
|
226
|
+
"Search tasks by keywords across title, description, goals, tags and operation records.",
|
|
227
|
+
inputSchema: {
|
|
228
|
+
type: "object",
|
|
229
|
+
properties: {
|
|
230
|
+
keywords: {
|
|
231
|
+
type: "array",
|
|
232
|
+
items: { type: "string" },
|
|
233
|
+
description: "Keywords (AND logic).",
|
|
234
|
+
},
|
|
235
|
+
limit: { type: "number" },
|
|
236
|
+
},
|
|
237
|
+
required: ["keywords"],
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
]);
|