task.mjs
233 lines 8.0 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 11 days ago
1 /**
2 * MCP Task read tools — task_list / task_get (Phase 2G-b).
3 *
4 * Delegates to lib/task/task-handlers.mjs for CLI = MCP = Hub parity.
5 *
6 * @see docs/TASK-STORE-CONTRACT-2G.md §4.2
7 */
8
9 import { z } from 'zod';
10 import { loadConfig } from '../../lib/config.mjs';
11 import { handleTaskListRequest, handleTaskGetRequest } from '../../lib/task/task-handlers.mjs';
12 import {
13 handleTaskProposeRequest,
14 handleTaskLoopProposeRequest,
15 handleTaskInstanceMaterializeRequest,
16 } from '../../lib/task/task-write.mjs';
17 import { createProposal } from '../../hub/proposals-store.mjs';
18 import { jsonResponse, jsonError } from '../create-server.mjs';
19
20 /**
21 * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
22 */
23 export function registerTaskTools(server) {
24 server.registerTool(
25 'task_list',
26 {
27 description:
28 'List scope-visible tasks (content-minimized summaries). Same JSON as Hub GET /api/v1/tasks.',
29 inputSchema: {
30 scope: z.enum(['personal', 'project', 'org']).optional().describe('Narrow within authorized scopes only'),
31 workspace_id: z.string().optional().describe('Filter by workspace_id equality'),
32 status: z
33 .enum(['pending', 'in_progress', 'blocked', 'done', 'cancelled'])
34 .optional()
35 .describe('Filter by task status'),
36 kind: z
37 .enum(['personal', 'assignment', 'mentor_checkin', 'org_work_job'])
38 .optional()
39 .describe('Filter by task kind'),
40 limit: z.number().int().min(1).max(500).optional().describe('Max summaries (default 500)'),
41 vault_id: z.string().optional().describe('Vault id (default from config)'),
42 },
43 },
44 async (args) => {
45 try {
46 const config = loadConfig();
47 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
48 const cliScopes = Array.isArray(config.flow?.visible_scopes)
49 ? config.flow.visible_scopes
50 : undefined;
51 const result = handleTaskListRequest({
52 dataDir: config.data_dir,
53 vaultId,
54 cliScopes,
55 scope: args.scope,
56 workspace_id: args.workspace_id,
57 status: args.status,
58 kind: args.kind,
59 limit: args.limit,
60 });
61 if (!result.ok) {
62 return jsonError(result.error, result.code);
63 }
64 return jsonResponse(result.payload);
65 } catch (e) {
66 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
67 }
68 },
69 );
70
71 server.registerTool(
72 'task_get',
73 {
74 description: 'Get one authorized task record. Same JSON as Hub GET /api/v1/tasks/{id}.',
75 inputSchema: {
76 task_id: z.string().describe('Task id (task_<slug>)'),
77 vault_id: z.string().optional().describe('Vault id (default from config)'),
78 },
79 },
80 async (args) => {
81 try {
82 const config = loadConfig();
83 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
84 const cliScopes = Array.isArray(config.flow?.visible_scopes)
85 ? config.flow.visible_scopes
86 : undefined;
87 const result = handleTaskGetRequest({
88 dataDir: config.data_dir,
89 vaultId,
90 taskId: args.task_id,
91 cliScopes,
92 });
93 if (!result.ok) {
94 return jsonError(result.error, result.code);
95 }
96 return jsonResponse(result.payload);
97 } catch (e) {
98 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
99 }
100 },
101 );
102
103 server.registerTool(
104 'task_propose',
105 {
106 description:
107 'Propose a one-time task write (create/status/assign/artifact). Same path as Hub POST /api/v1/tasks/proposals.',
108 inputSchema: {
109 proposal_kind: z
110 .enum(['task_create', 'task_status_update', 'task_assign', 'task_artifact_link'])
111 .optional()
112 .describe('Server-stamped on create; client value selects handler only'),
113 intent: z.string().describe('Untrusted human-readable reason (required)'),
114 task: z.record(z.unknown()).optional().describe('task_create payload'),
115 task_id: z.string().optional(),
116 base_state_id: z.string().optional(),
117 status: z.enum(['pending', 'in_progress', 'blocked', 'done', 'cancelled']).optional(),
118 assignee_ref: z.string().nullable().optional(),
119 assigner_ref: z.string().nullable().optional(),
120 artifact_link: z.object({ kind: z.string(), ref: z.string() }).optional(),
121 vault_id: z.string().optional(),
122 },
123 },
124 async (args) => {
125 try {
126 const config = loadConfig();
127 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
128 const cliScopes = Array.isArray(config.flow?.visible_scopes)
129 ? config.flow.visible_scopes
130 : undefined;
131 const proposalKind = args.proposal_kind || 'task_create';
132 const result = await handleTaskProposeRequest({
133 dataDir: config.data_dir,
134 vaultId,
135 cliScopes,
136 proposalKind,
137 body: args,
138 intent: args.intent,
139 createProposal,
140 });
141 if (!result.ok) {
142 return jsonError(result.error, result.code);
143 }
144 return jsonResponse(result.payload);
145 } catch (e) {
146 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
147 }
148 },
149 );
150
151 server.registerTool(
152 'task_loop_propose',
153 {
154 description:
155 'Propose a task loop series write (create/pause/cancel). Same as Hub POST /api/v1/task-loops/proposals.',
156 inputSchema: {
157 proposal_kind: z.enum(['task_loop_create', 'task_loop_pause', 'task_loop_cancel']).optional(),
158 intent: z.string(),
159 loop: z.record(z.unknown()).optional(),
160 loop_id: z.string().optional(),
161 base_state_id: z.string().optional(),
162 vault_id: z.string().optional(),
163 },
164 },
165 async (args) => {
166 try {
167 const config = loadConfig();
168 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
169 const cliScopes = Array.isArray(config.flow?.visible_scopes)
170 ? config.flow.visible_scopes
171 : undefined;
172 const proposalKind = args.proposal_kind || 'task_loop_create';
173 const result = await handleTaskLoopProposeRequest({
174 dataDir: config.data_dir,
175 vaultId,
176 cliScopes,
177 proposalKind,
178 body: args,
179 intent: args.intent,
180 createProposal,
181 });
182 if (!result.ok) {
183 return jsonError(result.error, result.code);
184 }
185 return jsonResponse(result.payload);
186 } catch (e) {
187 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
188 }
189 },
190 );
191
192 server.registerTool(
193 'task_instance_materialize',
194 {
195 description:
196 'Propose materializing one loop occurrence task. Same as Hub POST /api/v1/task-loops/{loop_id}/instances/proposals.',
197 inputSchema: {
198 loop_id: z.string(),
199 intent: z.string(),
200 occurrence_key: z.string().optional(),
201 occurrence_at: z.string().optional(),
202 due_at: z.string().optional(),
203 title_override: z.string().optional(),
204 base_state_id: z.string().optional(),
205 vault_id: z.string().optional(),
206 },
207 },
208 async (args) => {
209 try {
210 const config = loadConfig();
211 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
212 const cliScopes = Array.isArray(config.flow?.visible_scopes)
213 ? config.flow.visible_scopes
214 : undefined;
215 const result = await handleTaskInstanceMaterializeRequest({
216 dataDir: config.data_dir,
217 vaultId,
218 cliScopes,
219 loopId: args.loop_id,
220 body: args,
221 intent: args.intent,
222 createProposal,
223 });
224 if (!result.ok) {
225 return jsonError(result.error, result.code);
226 }
227 return jsonResponse(result.payload);
228 } catch (e) {
229 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
230 }
231 },
232 );
233 }
File History 1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 11 days ago