@opvs-ai/mcp 0.5.2 → 0.5.4

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/index.js CHANGED
@@ -17,20 +17,17 @@
17
17
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
18
18
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
19
19
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
20
- import { createClient, buildUrl, getResponseText, listWorkspaces, apiUnauthPost, apiUnauthGet, saveWorkspace, setCurrentWorkspace, DEFAULT_WORKSPACE, } from "@opvs-ai/core";
21
- // ── Helpers ──────────────────────────────────────────────────────
22
- function ok(text) {
23
- return { content: [{ type: "text", text }] };
24
- }
25
- function err(msg) {
26
- return {
27
- content: [{ type: "text", text: `Error: ${msg}` }],
28
- isError: true,
29
- };
30
- }
31
- function resolveWorkspace(args) {
32
- return args.workspace || process.env.OPVS_WORKSPACE || undefined;
33
- }
20
+ import { createClient, buildUrl, getResponseText } from "@opvs-ai/core";
21
+ // ── Handler modules ─────────────────────────────────────────────
22
+ import { TOOLS as BOARD_TOOLS, handleTool as handleBoard } from "./handlers/board.js";
23
+ import { TOOLS as AGENT_TOOLS, handleTool as handleAgent } from "./handlers/agent.js";
24
+ import { TOOLS as DOCS_TOOLS, handleTool as handleDocs } from "./handlers/docs.js";
25
+ import { TOOLS as VIEWS_TOOLS, handleTool as handleViews } from "./handlers/views.js";
26
+ import { TOOLS as STAGES_TOOLS, handleTool as handleStages } from "./handlers/stages.js";
27
+ import { TOOLS as AUTH_TOOLS, handleTool as handleAuth } from "./handlers/auth.js";
28
+ import { GENERATED_TOOLS, handleGeneratedTool } from "./generated-tools.js";
29
+ import { err } from "./handlers/types.js";
30
+ // ── API client ──────────────────────────────────────────────────
34
31
  async function callApi(method, path, wsSlug, body) {
35
32
  const client = createClient(wsSlug);
36
33
  let res;
@@ -53,1477 +50,35 @@ async function callApi(method, path, wsSlug, body) {
53
50
  }
54
51
  return getResponseText(res);
55
52
  }
56
- function getFormat() {
57
- // MCP always uses JSON YAML is for CLI display only.
58
- // Claude Desktop and other MCP clients expect structured JSON responses.
59
- return "json";
53
+ function ok(text) {
54
+ return { content: [{ type: "text", text }] };
60
55
  }
61
- // ── Shared workspace property (added to every tool) ─────────────
62
- const WORKSPACE_PROP = {
63
- workspace: {
64
- type: "string",
65
- description: "Workspace slug to use. If omitted, uses the current workspace. Use list_workspaces to see available workspaces.",
66
- },
67
- };
68
- // ── Tool Definitions ─────────────────────────────────────────────
56
+ // ── Tool registry ───────────────────────────────────────────────
69
57
  const TOOLS = [
70
- // ── Workspace & Auth ──
71
- {
72
- name: "list_workspaces",
73
- description: "List all configured workspaces with their brand names and API URLs. Call this first if you're unsure which workspace to use.",
74
- inputSchema: {
75
- type: "object",
76
- properties: {},
77
- },
78
- },
79
- {
80
- name: "get_auth_status",
81
- description: "Check who you are authenticated as — returns your user ID, email, brand, and permission scopes. Useful to verify access before other operations.",
82
- inputSchema: {
83
- type: "object",
84
- properties: { ...WORKSPACE_PROP },
85
- },
86
- },
87
- // ── Boards ──
88
- {
89
- name: "list_boards",
90
- description: "List all boards in your workspace. Returns board IDs, names, task counts, and column counts.",
91
- inputSchema: {
92
- type: "object",
93
- properties: { ...WORKSPACE_PROP },
94
- },
95
- },
96
- {
97
- name: "get_board",
98
- description: "Get board details including all columns with their IDs and names. Use this to discover column IDs for move_task or create_task.",
99
- inputSchema: {
100
- type: "object",
101
- properties: {
102
- board_id: { type: "string", description: "Board UUID" },
103
- ...WORKSPACE_PROP,
104
- },
105
- required: ["board_id"],
106
- },
107
- },
108
- {
109
- name: "create_board",
110
- description: "Create a new board. Default columns: Backlog, To Do, In Progress, Review, Done. Visibility: 'public' (default) or 'private'.",
111
- inputSchema: {
112
- type: "object",
113
- properties: {
114
- name: { type: "string", description: "Board name" },
115
- description: { type: "string", description: "Board description" },
116
- visibility: { type: "string", description: "public or private (default: public)" },
117
- ...WORKSPACE_PROP,
118
- },
119
- required: ["name"],
120
- },
121
- },
122
- {
123
- name: "update_board",
124
- description: "Update a board's name, description, or visibility. Provide only the fields you want to change.",
125
- inputSchema: {
126
- type: "object",
127
- properties: {
128
- board_id: { type: "string", description: "Board UUID" },
129
- name: { type: "string", description: "New name" },
130
- description: { type: "string", description: "New description" },
131
- visibility: { type: "string", description: "public or private" },
132
- ...WORKSPACE_PROP,
133
- },
134
- required: ["board_id"],
135
- },
136
- },
137
- {
138
- name: "delete_board",
139
- description: "Delete a board (soft delete — can be recovered by an admin).",
140
- inputSchema: {
141
- type: "object",
142
- properties: {
143
- board_id: { type: "string", description: "Board UUID" },
144
- ...WORKSPACE_PROP,
145
- },
146
- required: ["board_id"],
147
- },
148
- },
149
- // ── Tasks ──
150
- {
151
- name: "list_tasks",
152
- description: "List tasks on a board or assigned to you. Use self=true to find your assigned work across all boards (recommended starting point). Filter by status (pending, assigned, in_progress, review, done, failed) or priority (low, medium, high, critical).",
153
- inputSchema: {
154
- type: "object",
155
- properties: {
156
- board_id: {
157
- type: "string",
158
- description: "Board UUID (required unless self=true)",
159
- },
160
- self: {
161
- type: "boolean",
162
- description: "If true, list only tasks assigned to you across all boards",
163
- },
164
- status: {
165
- type: "string",
166
- description: "Filter: pending, assigned, in_progress, review, done, failed",
167
- },
168
- priority: {
169
- type: "string",
170
- description: "Filter: low, medium, high, critical",
171
- },
172
- limit: { type: "number", description: "Max results (default 25)" },
173
- ...WORKSPACE_PROP,
174
- },
175
- },
176
- },
177
- {
178
- name: "get_task",
179
- description: "Get full details of a single task including description, agent instructions, comments, assignees, labels, and metadata.",
180
- inputSchema: {
181
- type: "object",
182
- properties: {
183
- task_id: { type: "string", description: "Task UUID" },
184
- ...WORKSPACE_PROP,
185
- },
186
- required: ["task_id"],
187
- },
188
- },
189
- {
190
- name: "create_task",
191
- description: "Create a new task on a board. Use assigned_to_agent_id='self' to assign to yourself. column_id accepts a UUID or column name (e.g. 'In Progress'). If column_id is omitted, the task goes to the first column. Priority: low, medium (default), high, critical.",
192
- inputSchema: {
193
- type: "object",
194
- properties: {
195
- board_id: { type: "string", description: "Board UUID" },
196
- title: { type: "string", description: "Task title" },
197
- description: {
198
- type: "string",
199
- description: "Task description (markdown). Supports full PRDs, plans, and specs — no length limit.",
200
- },
201
- priority: {
202
- type: "string",
203
- description: "low, medium, high, critical (default: medium)",
204
- },
205
- column_id: { type: "string", description: "Column UUID or name (e.g. 'In Progress'). Defaults to first column." },
206
- labels: {
207
- type: "array",
208
- items: { type: "string" },
209
- description: "Task labels/tags",
210
- },
211
- due_date: { type: "string", description: "Due date (YYYY-MM-DD)" },
212
- agent_instructions: { type: "string", description: "Detailed instructions for the assigned agent" },
213
- assigned_to_agent_id: { type: "string", description: "Agent UUID to assign to, or 'self' to assign to yourself" },
214
- ...WORKSPACE_PROP,
215
- },
216
- required: ["board_id", "title"],
217
- },
218
- },
219
- {
220
- name: "update_task",
221
- description: "Update a task's fields. Status flow: pending → assigned → in_progress → review → done (or failed). Use assigned_to_agent_id='self' to assign to yourself. Provide only the fields you want to change.",
222
- inputSchema: {
223
- type: "object",
224
- properties: {
225
- task_id: { type: "string", description: "Task UUID" },
226
- status: {
227
- type: "string",
228
- description: "New status: pending, assigned, in_progress, review, done, failed",
229
- },
230
- priority: {
231
- type: "string",
232
- description: "New priority: low, medium, high, critical",
233
- },
234
- title: { type: "string", description: "New title" },
235
- description: { type: "string", description: "New description (markdown)" },
236
- result: { type: "string", description: "Agent result summary — set this when completing work" },
237
- labels: {
238
- type: "array",
239
- items: { type: "string" },
240
- description: "New labels (replaces existing)",
241
- },
242
- due_date: { type: "string", description: "Due date (YYYY-MM-DD)" },
243
- assigned_to_agent_id: { type: "string", description: "Agent UUID to assign to, or 'self' to assign to yourself" },
244
- ...WORKSPACE_PROP,
245
- },
246
- required: ["task_id"],
247
- },
248
- },
249
- {
250
- name: "delete_task",
251
- description: "Delete a task (soft delete — can be recovered by an admin).",
252
- inputSchema: {
253
- type: "object",
254
- properties: {
255
- task_id: { type: "string", description: "Task UUID" },
256
- ...WORKSPACE_PROP,
257
- },
258
- required: ["task_id"],
259
- },
260
- },
261
- {
262
- name: "move_task",
263
- description: "Move a task to a different column. column_id accepts a UUID or column name (e.g. 'Done', 'In Progress'). Use get_board or list_columns to see available columns and their names.",
264
- inputSchema: {
265
- type: "object",
266
- properties: {
267
- task_id: { type: "string", description: "Task UUID" },
268
- column_id: { type: "string", description: "Target column UUID or name (e.g. 'Done')" },
269
- position: { type: "number", description: "Position within column (0 = top, optional)" },
270
- ...WORKSPACE_PROP,
271
- },
272
- required: ["task_id", "column_id"],
273
- },
274
- },
275
- {
276
- name: "list_subtasks",
277
- description: "List subtasks of a parent task. Returns child tasks with their status and priority.",
278
- inputSchema: {
279
- type: "object",
280
- properties: {
281
- task_id: { type: "string", description: "Parent task UUID" },
282
- ...WORKSPACE_PROP,
283
- },
284
- required: ["task_id"],
285
- },
286
- },
287
- // ── Comments ──
288
- {
289
- name: "add_comment",
290
- description: "Add a comment to a task. Use for progress updates, notes, questions, or deliverables. Supports markdown formatting.",
291
- inputSchema: {
292
- type: "object",
293
- properties: {
294
- task_id: { type: "string", description: "Task UUID" },
295
- content: {
296
- type: "string",
297
- description: "Comment text (markdown supported)",
298
- },
299
- ...WORKSPACE_PROP,
300
- },
301
- required: ["task_id", "content"],
302
- },
303
- },
304
- {
305
- name: "list_comments",
306
- description: "List comments on a task, ordered newest first. Returns comment content, author, and timestamps.",
307
- inputSchema: {
308
- type: "object",
309
- properties: {
310
- task_id: { type: "string", description: "Task UUID" },
311
- limit: { type: "number", description: "Max results (default 25)" },
312
- ...WORKSPACE_PROP,
313
- },
314
- required: ["task_id"],
315
- },
316
- },
317
- // ── Views (agent-optimized) ──
318
- {
319
- name: "get_board_session",
320
- description: "RECOMMENDED FIRST CALL when starting work on a board. Returns everything in one call: board info, all columns with their UUIDs and names (needed for move_task), and your assigned tasks. Much more efficient than calling get_board + list_tasks separately.",
321
- inputSchema: {
322
- type: "object",
323
- properties: {
324
- board_id: { type: "string", description: "Board UUID" },
325
- ...WORKSPACE_PROP,
326
- },
327
- required: ["board_id"],
328
- },
329
- },
330
- {
331
- name: "get_board_overview",
332
- description: "Get board overview with column structure, task counts per column, and board metadata. Lighter than get_board_session (no task details).",
333
- inputSchema: {
334
- type: "object",
335
- properties: {
336
- board_id: { type: "string", description: "Board UUID" },
337
- ...WORKSPACE_PROP,
338
- },
339
- required: ["board_id"],
340
- },
341
- },
342
- // ── Session ──
343
- {
344
- name: "get_session",
345
- description: "Get your assigned tasks across all boards. Use this to find what you should work on. For a specific board's full context (columns, tasks), use get_board_session instead.",
346
- inputSchema: {
347
- type: "object",
348
- properties: {
349
- board_id: {
350
- type: "string",
351
- description: "Optional: scope to a specific board",
352
- },
353
- ...WORKSPACE_PROP,
354
- },
355
- },
356
- },
357
- // ── Activity ──
358
- {
359
- name: "list_activity",
360
- description: "Get recent activity feed (who did what, when). Provide exactly ONE of board_id or task_id. Shows task creation, status changes, comments, assignments, and moves. Ordered newest first.",
361
- inputSchema: {
362
- type: "object",
363
- properties: {
364
- board_id: { type: "string", description: "Board UUID — activity for the whole board" },
365
- task_id: { type: "string", description: "Task UUID — activity for a single task" },
366
- limit: { type: "number", description: "Max results (default 25)" },
367
- ...WORKSPACE_PROP,
368
- },
369
- },
370
- },
371
- // ── Metrics ──
372
- {
373
- name: "get_board_summary",
374
- description: "Get board KPIs: task counts by status, workload by assignee, priority distribution, and recent activity stats. Useful for reporting and understanding board health.",
375
- inputSchema: {
376
- type: "object",
377
- properties: {
378
- board_id: { type: "string", description: "Board UUID" },
379
- days: { type: "number", description: "Time window in days (default 7)" },
380
- ...WORKSPACE_PROP,
381
- },
382
- required: ["board_id"],
383
- },
384
- },
385
- {
386
- name: "get_task_velocity",
387
- description: "Get task velocity: tasks created vs completed per day over time. Useful for tracking team throughput and trends.",
388
- inputSchema: {
389
- type: "object",
390
- properties: {
391
- days: { type: "number", description: "Time window in days (default 30)" },
392
- ...WORKSPACE_PROP,
393
- },
394
- },
395
- },
396
- // ── Columns ──
397
- {
398
- name: "list_columns",
399
- description: "List all columns on a board with their IDs, names, and positions. Use this to find column IDs needed by move_task and create_task (or just pass column names directly).",
400
- inputSchema: {
401
- type: "object",
402
- properties: {
403
- board_id: { type: "string", description: "Board UUID" },
404
- ...WORKSPACE_PROP,
405
- },
406
- required: ["board_id"],
407
- },
408
- },
409
- {
410
- name: "create_column",
411
- description: "Create a new column on a board. New column is added at the end. Use reorder_columns to change position.",
412
- inputSchema: {
413
- type: "object",
414
- properties: {
415
- board_id: { type: "string", description: "Board UUID" },
416
- name: { type: "string", description: "Column name (e.g. 'In Review', 'Blocked')" },
417
- color: { type: "string", description: "Column color hex (e.g. '#3B82F6')" },
418
- wip_limit: { type: "number", description: "Max tasks allowed in this column" },
419
- ...WORKSPACE_PROP,
420
- },
421
- required: ["board_id", "name"],
422
- },
423
- },
424
- {
425
- name: "reorder_columns",
426
- description: "Set the order of columns on a board. Pass ALL column UUIDs in the desired order — any omitted columns will be removed.",
427
- inputSchema: {
428
- type: "object",
429
- properties: {
430
- board_id: { type: "string", description: "Board UUID" },
431
- column_ids: {
432
- type: "array",
433
- items: { type: "string" },
434
- description: "ALL column UUIDs in desired left-to-right order",
435
- },
436
- ...WORKSPACE_PROP,
437
- },
438
- required: ["board_id", "column_ids"],
439
- },
440
- },
441
- // ── Files ──
442
- {
443
- name: "list_files",
444
- description: "List file attachments on a task. Returns file names, sizes, types, and IDs. Use get_file for download URLs.",
445
- inputSchema: {
446
- type: "object",
447
- properties: {
448
- task_id: { type: "string", description: "Task UUID" },
449
- ...WORKSPACE_PROP,
450
- },
451
- required: ["task_id"],
452
- },
453
- },
454
- {
455
- name: "get_file",
456
- description: "Get file metadata including name, size, MIME type, and download URL. Note: file upload is only available via the CLI (opvs files upload).",
457
- inputSchema: {
458
- type: "object",
459
- properties: {
460
- file_id: { type: "string", description: "File UUID" },
461
- ...WORKSPACE_PROP,
462
- },
463
- required: ["file_id"],
464
- },
465
- },
466
- // ── Docs ──
467
- {
468
- name: "list_docs",
469
- description: "List all AgentDocs documentation projects in your workspace. Returns project names, slugs, and page counts.",
470
- inputSchema: {
471
- type: "object",
472
- properties: { ...WORKSPACE_PROP },
473
- },
474
- },
475
- {
476
- name: "get_doc",
477
- description: "Get a documentation page by project and slug. The project parameter accepts a slug (e.g. 'platform-core-docs') or UUID.",
478
- inputSchema: {
479
- type: "object",
480
- properties: {
481
- project: { type: "string", description: "Project slug or UUID (e.g. 'platform-core-docs')" },
482
- slug: { type: "string", description: "Page slug" },
483
- version: { type: "number", description: "Specific version number (optional, defaults to latest)" },
484
- ...WORKSPACE_PROP,
485
- },
486
- required: ["project", "slug"],
487
- },
488
- },
489
- {
490
- name: "create_doc",
491
- description: "Create a new documentation page in a project. Content supports full markdown. Each page has a unique slug within its project.",
492
- inputSchema: {
493
- type: "object",
494
- properties: {
495
- project: { type: "string", description: "Project slug or UUID" },
496
- title: { type: "string", description: "Page title" },
497
- slug: { type: "string", description: "Page slug (URL-friendly, must be unique in project)" },
498
- content: { type: "string", description: "Page content (markdown)" },
499
- message: { type: "string", description: "Commit message (default: 'Created via MCP')" },
500
- ...WORKSPACE_PROP,
501
- },
502
- required: ["project", "title", "slug"],
503
- },
504
- },
505
- {
506
- name: "update_doc",
507
- description: "Update a documentation page's content. Creates a new version — previous versions are preserved and accessible by version number.",
508
- inputSchema: {
509
- type: "object",
510
- properties: {
511
- project: { type: "string", description: "Project slug or UUID" },
512
- slug: { type: "string", description: "Page slug" },
513
- content: { type: "string", description: "New content (markdown)" },
514
- title: { type: "string", description: "New title (optional)" },
515
- message: { type: "string", description: "Commit message (default: 'Updated via MCP')" },
516
- ...WORKSPACE_PROP,
517
- },
518
- required: ["project", "slug", "content"],
519
- },
520
- },
521
- {
522
- name: "search_docs",
523
- description: "Full-text search across documentation. Searches page titles and content. Optionally scope to a single project.",
524
- inputSchema: {
525
- type: "object",
526
- properties: {
527
- query: { type: "string", description: "Search query (searches titles and content)" },
528
- project: { type: "string", description: "Project slug or UUID to scope search (optional)" },
529
- limit: { type: "number", description: "Max results (default 10)" },
530
- ...WORKSPACE_PROP,
531
- },
532
- required: ["query"],
533
- },
534
- },
535
- // ── Docs: Project CRUD ──
536
- {
537
- name: "create_project",
538
- description: "Create a new documentation project. Returns the project with its ID and slug.",
539
- inputSchema: {
540
- type: "object",
541
- properties: {
542
- name: { type: "string", description: "Project name" },
543
- description: { type: "string", description: "Project description" },
544
- visibility: { type: "string", description: "public or private (default: public)" },
545
- ...WORKSPACE_PROP,
546
- },
547
- required: ["name"],
548
- },
549
- },
550
- {
551
- name: "update_project",
552
- description: "Update a documentation project's name, description, or visibility. Brand admin only.",
553
- inputSchema: {
554
- type: "object",
555
- properties: {
556
- project: { type: "string", description: "Project slug or UUID" },
557
- name: { type: "string", description: "New name" },
558
- description: { type: "string", description: "New description" },
559
- visibility: { type: "string", description: "public or private" },
560
- ...WORKSPACE_PROP,
561
- },
562
- required: ["project"],
563
- },
564
- },
565
- {
566
- name: "delete_project",
567
- description: "Soft-delete a documentation project. Brand admin only.",
568
- inputSchema: {
569
- type: "object",
570
- properties: {
571
- project: { type: "string", description: "Project slug or UUID" },
572
- ...WORKSPACE_PROP,
573
- },
574
- required: ["project"],
575
- },
576
- },
577
- // ── Docs: Page Operations ──
578
- {
579
- name: "list_pages",
580
- description: "List pages in a documentation project. Use view='tree' for nested structure with children.",
581
- inputSchema: {
582
- type: "object",
583
- properties: {
584
- project: { type: "string", description: "Project slug or UUID" },
585
- view: { type: "string", description: "flat or tree (default: flat)" },
586
- limit: { type: "number", description: "Max results (default 100)" },
587
- ...WORKSPACE_PROP,
588
- },
589
- required: ["project"],
590
- },
591
- },
592
- {
593
- name: "move_page",
594
- description: "Move a page to a new path or change sort order within its folder. Does not create a revision.",
595
- inputSchema: {
596
- type: "object",
597
- properties: {
598
- project: { type: "string", description: "Project slug or UUID" },
599
- slug: { type: "string", description: "Page slug" },
600
- path: { type: "string", description: "New tree path (e.g. '/guides')" },
601
- sort_order: { type: "number", description: "New sort order within folder" },
602
- ...WORKSPACE_PROP,
603
- },
604
- required: ["project", "slug"],
605
- },
606
- },
607
- {
608
- name: "delete_page",
609
- description: "Soft-delete a documentation page.",
610
- inputSchema: {
611
- type: "object",
612
- properties: {
613
- project: { type: "string", description: "Project slug or UUID" },
614
- slug: { type: "string", description: "Page slug" },
615
- ...WORKSPACE_PROP,
616
- },
617
- required: ["project", "slug"],
618
- },
619
- },
620
- // ── Docs: Revision History ──
621
- {
622
- name: "page_history",
623
- description: "Get revision history for a page. Returns all past versions with dates and authors.",
624
- inputSchema: {
625
- type: "object",
626
- properties: {
627
- page_id: { type: "string", description: "Page UUID" },
628
- limit: { type: "number", description: "Max results (default 25)" },
629
- ...WORKSPACE_PROP,
630
- },
631
- required: ["page_id"],
632
- },
633
- },
634
- {
635
- name: "page_diff",
636
- description: "Compute unified diff between two revisions. Omit 'from' for parent comparison.",
637
- inputSchema: {
638
- type: "object",
639
- properties: {
640
- page_id: { type: "string", description: "Page UUID" },
641
- from_rev: { type: "string", description: "From revision ID (optional — defaults to parent)" },
642
- to_rev: { type: "string", description: "To revision ID (optional — defaults to latest)" },
643
- ...WORKSPACE_PROP,
644
- },
645
- required: ["page_id"],
646
- },
647
- },
648
- {
649
- name: "page_rollback",
650
- description: "Rollback a page to a previous revision. Non-destructive — creates a new revision with the old content.",
651
- inputSchema: {
652
- type: "object",
653
- properties: {
654
- page_id: { type: "string", description: "Page UUID" },
655
- revision_id: { type: "string", description: "Target revision ID to restore" },
656
- message: { type: "string", description: "Commit message (default: 'Rollback via MCP')" },
657
- ...WORKSPACE_PROP,
658
- },
659
- required: ["page_id", "revision_id"],
660
- },
661
- },
662
- // ── Docs: Media ──
663
- {
664
- name: "list_media",
665
- description: "List media files in a documentation project. Returns filenames, sizes, URLs, and markdown embed snippets.",
666
- inputSchema: {
667
- type: "object",
668
- properties: {
669
- project: { type: "string", description: "Project slug or UUID" },
670
- limit: { type: "number", description: "Max results (default 25)" },
671
- ...WORKSPACE_PROP,
672
- },
673
- required: ["project"],
674
- },
675
- },
676
- // ── Docs: Bulk & Export ──
677
- {
678
- name: "bulk_create_pages",
679
- description: "Bulk create or update multiple pages at once. Used for multi-page writes. Each page needs title, slug, content_md, commit_message.",
680
- inputSchema: {
681
- type: "object",
682
- properties: {
683
- project: { type: "string", description: "Project slug or UUID" },
684
- pages: {
685
- type: "array",
686
- items: {
687
- type: "object",
688
- properties: {
689
- title: { type: "string" },
690
- slug: { type: "string" },
691
- content_md: { type: "string" },
692
- commit_message: { type: "string" },
693
- path: { type: "string" },
694
- },
695
- required: ["title", "slug", "content_md", "commit_message"],
696
- },
697
- description: "Array of page objects",
698
- },
699
- branch: { type: "string", description: "Target branch (default: main)" },
700
- ...WORKSPACE_PROP,
701
- },
702
- required: ["project", "pages"],
703
- },
704
- },
705
- // ── Agent Optimization ──
706
- {
707
- name: "agent_my_tasks",
708
- description: "Get tasks assigned to you across ALL boards in one call. More comprehensive than list_tasks with self=true — aggregates from every board and includes board names.",
709
- inputSchema: {
710
- type: "object",
711
- properties: {
712
- board_id: { type: "string", description: "Scope to a specific board (optional)" },
713
- status: { type: "string", description: "Filter: pending, assigned, in_progress, review, done, failed" },
714
- limit: { type: "number", description: "Max results" },
715
- ...WORKSPACE_PROP,
716
- },
717
- },
718
- },
719
- {
720
- name: "agent_my_delegations",
721
- description: "List tasks you delegated to other agents. Complement to agent_my_tasks — shows tasks you handed off, not tasks assigned to you.",
722
- inputSchema: {
723
- type: "object",
724
- properties: {
725
- status: { type: "string", description: "Filter: pending, assigned, in_progress, review, done, failed" },
726
- limit: { type: "number", description: "Max results" },
727
- ...WORKSPACE_PROP,
728
- },
729
- },
730
- },
731
- {
732
- name: "agent_batch_create_tasks",
733
- description: "Create multiple tasks at once. Each task needs board_id and title. Returns list of created IDs and any errors.",
734
- inputSchema: {
735
- type: "object",
736
- properties: {
737
- tasks: {
738
- type: "array",
739
- items: {
740
- type: "object",
741
- properties: {
742
- board_id: { type: "string", description: "Board UUID" },
743
- title: { type: "string", description: "Task title" },
744
- description: { type: "string", description: "Task description" },
745
- priority: { type: "string", description: "low, medium, high, critical" },
746
- column_id: { type: "string", description: "Column UUID or name" },
747
- agent_instructions: { type: "string", description: "Agent instructions" },
748
- },
749
- required: ["board_id", "title"],
750
- },
751
- description: "Array of task objects to create",
752
- },
753
- ...WORKSPACE_PROP,
754
- },
755
- required: ["tasks"],
756
- },
757
- },
758
- {
759
- name: "agent_report_progress",
760
- description: "Report progress on a task. Updates metadata and adds a progress comment. Use this for long-running tasks to keep stakeholders informed.",
761
- inputSchema: {
762
- type: "object",
763
- properties: {
764
- task_id: { type: "string", description: "Task UUID" },
765
- percent: { type: "number", description: "Progress percentage (0-100)" },
766
- message: { type: "string", description: "Progress message" },
767
- current_operation: { type: "string", description: "What you're currently doing" },
768
- ...WORKSPACE_PROP,
769
- },
770
- required: ["task_id", "percent"],
771
- },
772
- },
773
- {
774
- name: "agent_complete_task",
775
- description: "Mark a task as done or failed. Automatically walks through intermediate status transitions (pending→assigned→in_progress→done). Adds a completion comment.",
776
- inputSchema: {
777
- type: "object",
778
- properties: {
779
- task_id: { type: "string", description: "Task UUID" },
780
- status: { type: "string", description: "done or failed (default: done)" },
781
- summary: { type: "string", description: "Completion summary (added as comment)" },
782
- result_data: {
783
- type: "object",
784
- description: "Structured result data (JSON object)",
785
- },
786
- ...WORKSPACE_PROP,
787
- },
788
- required: ["task_id"],
789
- },
790
- },
791
- {
792
- name: "agent_delegate_task",
793
- description: "Reassign a task to another agent by their slug (e.g. 'maya', 'grant'). Resolves the slug to a UUID, updates assignment, and adds a delegation comment.",
794
- inputSchema: {
795
- type: "object",
796
- properties: {
797
- task_id: { type: "string", description: "Task UUID" },
798
- agent_slug: { type: "string", description: "Target agent's slug (e.g. 'maya')" },
799
- instructions: { type: "string", description: "Instructions for the target agent" },
800
- ...WORKSPACE_PROP,
801
- },
802
- required: ["task_id", "agent_slug"],
803
- },
804
- },
805
- {
806
- name: "search_tasks",
807
- description: "Full-text search across all tasks/cards. Searches titles, descriptions, and comments.",
808
- inputSchema: {
809
- type: "object",
810
- properties: {
811
- query: { type: "string", description: "Search query" },
812
- board_id: { type: "string", description: "Scope to a specific board (optional)" },
813
- limit: { type: "number", description: "Max results (default 25)" },
814
- ...WORKSPACE_PROP,
815
- },
816
- required: ["query"],
817
- },
818
- },
819
- {
820
- name: "batch_get_tasks",
821
- description: "Get multiple tasks by IDs in one call (max 50). More efficient than calling get_task multiple times.",
822
- inputSchema: {
823
- type: "object",
824
- properties: {
825
- ids: {
826
- type: "array",
827
- items: { type: "string" },
828
- description: "Array of task UUIDs (max 50)",
829
- },
830
- ...WORKSPACE_PROP,
831
- },
832
- required: ["ids"],
833
- },
834
- },
835
- // ── Stages ──
836
- {
837
- name: "list_stages",
838
- description: "List stages (sequential in-card steps) for a task. Stages enable multi-agent handoff workflows within a single task.",
839
- inputSchema: {
840
- type: "object",
841
- properties: {
842
- task_id: { type: "string", description: "Task UUID" },
843
- ...WORKSPACE_PROP,
844
- },
845
- required: ["task_id"],
846
- },
847
- },
848
- {
849
- name: "create_stage",
850
- description: "Create a stage on a task. The first stage auto-activates. Subsequent stages stay pending until the previous one is completed via handoff.",
851
- inputSchema: {
852
- type: "object",
853
- properties: {
854
- task_id: { type: "string", description: "Task UUID" },
855
- title: { type: "string", description: "Stage title" },
856
- description: { type: "string", description: "Stage description" },
857
- assignee_agent_id: { type: "string", description: "Agent UUID to assign this stage to" },
858
- agent_instructions: { type: "string", description: "Instructions for the agent handling this stage" },
859
- position: { type: "number", description: "Position in stage order" },
860
- ...WORKSPACE_PROP,
861
- },
862
- required: ["task_id", "title"],
863
- },
864
- },
865
- {
866
- name: "update_stage",
867
- description: "Update a stage (pending or active only). Provide only the fields you want to change.",
868
- inputSchema: {
869
- type: "object",
870
- properties: {
871
- task_id: { type: "string", description: "Task UUID" },
872
- stage_id: { type: "string", description: "Stage UUID" },
873
- title: { type: "string", description: "New title" },
874
- description: { type: "string", description: "New description" },
875
- assignee_agent_id: { type: "string", description: "New agent assignment" },
876
- agent_instructions: { type: "string", description: "New agent instructions" },
877
- ...WORKSPACE_PROP,
878
- },
879
- required: ["task_id", "stage_id"],
880
- },
881
- },
882
- {
883
- name: "complete_stage",
884
- description: "Complete a stage and hand off to the next one. Adds a handoff comment. If this is the last stage, the task can be completed.",
885
- inputSchema: {
886
- type: "object",
887
- properties: {
888
- task_id: { type: "string", description: "Task UUID" },
889
- stage_id: { type: "string", description: "Stage UUID to complete" },
890
- comment: { type: "string", description: "Handoff comment for the next stage's assignee" },
891
- ...WORKSPACE_PROP,
892
- },
893
- required: ["task_id", "stage_id"],
894
- },
895
- },
896
- {
897
- name: "reorder_stages",
898
- description: "Reorder pending stages on a task. Active and completed stages cannot be reordered.",
899
- inputSchema: {
900
- type: "object",
901
- properties: {
902
- task_id: { type: "string", description: "Task UUID" },
903
- order: {
904
- type: "array",
905
- items: { type: "string" },
906
- description: "Stage UUIDs in desired order",
907
- },
908
- ...WORKSPACE_PROP,
909
- },
910
- required: ["task_id", "order"],
911
- },
912
- },
913
- // ── Task Links ──
914
- {
915
- name: "list_task_links",
916
- description: "List all links for a task (incoming and outgoing). Link types: spawned, blocked_by, related.",
917
- inputSchema: {
918
- type: "object",
919
- properties: {
920
- task_id: { type: "string", description: "Task UUID" },
921
- ...WORKSPACE_PROP,
922
- },
923
- required: ["task_id"],
924
- },
925
- },
926
- {
927
- name: "create_task_link",
928
- description: "Create a link between two tasks. Types: 'spawned' (this task created the target), 'blocked_by' (this task is blocked by target), 'related' (general relation).",
929
- inputSchema: {
930
- type: "object",
931
- properties: {
932
- task_id: { type: "string", description: "Source task UUID" },
933
- target_task_id: { type: "string", description: "Target task UUID" },
934
- link_type: { type: "string", description: "spawned, blocked_by, or related (default: related)" },
935
- ...WORKSPACE_PROP,
936
- },
937
- required: ["task_id", "target_task_id"],
938
- },
939
- },
940
- {
941
- name: "delete_task_link",
942
- description: "Delete a task link by its ID.",
943
- inputSchema: {
944
- type: "object",
945
- properties: {
946
- link_id: { type: "string", description: "Link UUID" },
947
- ...WORKSPACE_PROP,
948
- },
949
- required: ["link_id"],
950
- },
951
- },
952
- // ── Auth ──
953
- {
954
- name: "request_access",
955
- description: "Request access to OPVS workspaces by admin email. Sends an approval email to the admin. After the admin clicks approve, use check_access_status to complete authentication. Returns a request_id and poll_token needed for check_access_status.",
956
- inputSchema: {
957
- type: "object",
958
- properties: {
959
- email: { type: "string", description: "Admin email address (the person who will approve access)" },
960
- api_url: { type: "string", description: "API URL (default: https://app.opvs.ai)" },
961
- project: { type: "string", description: "Project name (shown in approval email)" },
962
- },
963
- required: ["email"],
964
- },
965
- },
966
- {
967
- name: "check_access_status",
968
- description: "Check the status of an access request. Call this after request_access to see if the admin has approved. Returns 'pending' (keep checking), 'active' (success — workspaces are now configured), 'denied', or 'expired'.",
969
- inputSchema: {
970
- type: "object",
971
- properties: {
972
- request_id: { type: "string", description: "The request_id from request_access" },
973
- poll_token: { type: "string", description: "The poll_token from request_access" },
974
- api_url: { type: "string", description: "API URL (must match the one used in request_access)" },
975
- },
976
- required: ["request_id", "poll_token"],
977
- },
978
- },
58
+ ...BOARD_TOOLS,
59
+ ...AGENT_TOOLS,
60
+ ...DOCS_TOOLS,
61
+ ...VIEWS_TOOLS,
62
+ ...STAGES_TOOLS,
63
+ ...AUTH_TOOLS,
979
64
  ];
980
- // ── Tool Handlers ────────────────────────────────────────────────
65
+ // ── Handler router ──────────────────────────────────────────────
66
+ const handlers = [handleBoard, handleAgent, handleDocs, handleViews, handleStages];
981
67
  async function handleTool(name, args) {
982
- const fmt = getFormat();
983
- const ws = resolveWorkspace(args);
984
- switch (name) {
985
- // ── Workspace & Auth ──
986
- case "list_workspaces": {
987
- const workspaces = listWorkspaces();
988
- if (workspaces.length === 0) {
989
- return ok("No workspaces configured. Run `opvs auth request -w <slug> -e <email>` to add one.");
990
- }
991
- const lines = workspaces.map((w) => {
992
- const marker = w.isCurrent ? " (current)" : "";
993
- return `- ${w.slug}: ${w.brand_name || "(no brand)"}${marker}\n api_url: ${w.api_url}`;
994
- });
995
- return ok(`workspaces:\n${lines.join("\n")}\n`);
996
- }
997
- case "get_auth_status": {
998
- const text = await callApi("GET", "/api/v1/auth/me", ws);
999
- return ok(text);
1000
- }
1001
- // ── Boards ──
1002
- case "list_boards": {
1003
- const url = buildUrl("/api/v1/board/boards", undefined, fmt);
1004
- return ok(await callApi("GET", url, ws));
1005
- }
1006
- case "get_board": {
1007
- const url = buildUrl(`/api/v1/board/boards/${args.board_id}`, undefined, fmt);
1008
- return ok(await callApi("GET", url, ws));
1009
- }
1010
- case "create_board": {
1011
- const body = { name: args.name };
1012
- if (args.description)
1013
- body.description = args.description;
1014
- if (args.visibility)
1015
- body.visibility = args.visibility;
1016
- return ok(await callApi("POST", "/api/v1/board/boards", ws, body));
1017
- }
1018
- case "update_board": {
1019
- const body = {};
1020
- if (args.name)
1021
- body.name = args.name;
1022
- if (args.description)
1023
- body.description = args.description;
1024
- if (args.visibility)
1025
- body.visibility = args.visibility;
1026
- if (Object.keys(body).length === 0) {
1027
- return err("Nothing to update. Provide name, description, or visibility.");
1028
- }
1029
- return ok(await callApi("PATCH", `/api/v1/board/boards/${args.board_id}`, ws, body));
1030
- }
1031
- case "delete_board": {
1032
- await callApi("DELETE", `/api/v1/board/boards/${args.board_id}`, ws);
1033
- return ok(`Deleted board ${args.board_id}`);
1034
- }
1035
- // ── Tasks ──
1036
- case "list_tasks": {
1037
- const params = {};
1038
- if (args.status)
1039
- params.status = String(args.status);
1040
- if (args.priority)
1041
- params.priority = String(args.priority);
1042
- if (args.limit)
1043
- params.limit = String(args.limit);
1044
- if (args.self) {
1045
- const url = buildUrl("/api/v1/board/agent-api/my-tasks", params, fmt);
1046
- return ok(await callApi("GET", url, ws));
1047
- }
1048
- if (!args.board_id) {
1049
- return err("Provide board_id or set self=true");
1050
- }
1051
- const url = buildUrl(`/api/v1/board/boards/${args.board_id}/tasks`, params, fmt);
1052
- return ok(await callApi("GET", url, ws));
1053
- }
1054
- case "get_task": {
1055
- const url = buildUrl(`/api/v1/board/tasks/${args.task_id}`, undefined, fmt);
1056
- return ok(await callApi("GET", url, ws));
1057
- }
1058
- case "create_task": {
1059
- const body = { title: args.title };
1060
- if (args.description)
1061
- body.description = args.description;
1062
- if (args.priority)
1063
- body.priority = args.priority;
1064
- if (args.column_id)
1065
- body.column_id = args.column_id;
1066
- if (args.labels)
1067
- body.labels = args.labels;
1068
- if (args.due_date)
1069
- body.due_date = args.due_date;
1070
- if (args.agent_instructions)
1071
- body.agent_instructions = args.agent_instructions;
1072
- if (args.assigned_to_agent_id)
1073
- body.assigned_to_agent_id = args.assigned_to_agent_id;
1074
- return ok(await callApi("POST", `/api/v1/board/boards/${args.board_id}/tasks`, ws, body));
1075
- }
1076
- case "update_task": {
1077
- const body = {};
1078
- if (args.status)
1079
- body.status = args.status;
1080
- if (args.priority)
1081
- body.priority = args.priority;
1082
- if (args.title)
1083
- body.title = args.title;
1084
- if (args.description)
1085
- body.description = args.description;
1086
- if (args.result)
1087
- body.agent_result_summary = args.result;
1088
- if (args.labels)
1089
- body.labels = args.labels;
1090
- if (args.due_date)
1091
- body.due_date = args.due_date;
1092
- if (args.assigned_to_agent_id)
1093
- body.assigned_to_agent_id = args.assigned_to_agent_id;
1094
- if (Object.keys(body).length === 0) {
1095
- return err("Nothing to update. Provide status, priority, title, description, result, labels, due_date, or assigned_to_agent_id.");
1096
- }
1097
- return ok(await callApi("PATCH", `/api/v1/board/tasks/${args.task_id}`, ws, body));
1098
- }
1099
- case "delete_task": {
1100
- await callApi("DELETE", `/api/v1/board/tasks/${args.task_id}`, ws);
1101
- return ok(`Deleted task ${args.task_id}`);
1102
- }
1103
- case "move_task": {
1104
- const body = { column_id: args.column_id };
1105
- if (args.position !== undefined)
1106
- body.position = args.position;
1107
- return ok(await callApi("POST", `/api/v1/board/tasks/${args.task_id}/move`, ws, body));
1108
- }
1109
- case "list_subtasks": {
1110
- const url = buildUrl(`/api/v1/board/tasks/${args.task_id}/subtasks`, undefined, fmt);
1111
- return ok(await callApi("GET", url, ws));
1112
- }
1113
- // ── Comments ──
1114
- case "add_comment": {
1115
- return ok(await callApi("POST", `/api/v1/board/tasks/${args.task_id}/comments`, ws, { content: args.content }));
1116
- }
1117
- case "list_comments": {
1118
- const params = {};
1119
- if (args.limit)
1120
- params.limit = String(args.limit);
1121
- const url = buildUrl(`/api/v1/board/tasks/${args.task_id}/comments`, params, fmt);
1122
- return ok(await callApi("GET", url, ws));
1123
- }
1124
- // ── Views ──
1125
- case "get_board_session": {
1126
- const url = buildUrl(`/api/v1/board/views/boards/${args.board_id}/session`, undefined, fmt || "yaml");
1127
- return ok(await callApi("GET", url, ws));
1128
- }
1129
- case "get_board_overview": {
1130
- const url = buildUrl(`/api/v1/board/views/boards/${args.board_id}/overview`, undefined, fmt || "yaml");
1131
- return ok(await callApi("GET", url, ws));
1132
- }
1133
- // ── Session ──
1134
- case "get_session": {
1135
- if (args.board_id) {
1136
- const url = buildUrl(`/api/v1/board/boards/${args.board_id}`, undefined, fmt);
1137
- return ok(await callApi("GET", url, ws));
1138
- }
1139
- const url = buildUrl("/api/v1/board/agent-api/my-tasks", undefined, fmt);
1140
- return ok(await callApi("GET", url, ws));
1141
- }
1142
- // ── Activity ──
1143
- case "list_activity": {
1144
- const params = {};
1145
- if (args.limit)
1146
- params.per_page = String(args.limit);
1147
- if (args.task_id) {
1148
- const url = buildUrl(`/api/v1/board/activity/tasks/${args.task_id}`, params, fmt);
1149
- return ok(await callApi("GET", url, ws));
1150
- }
1151
- if (args.board_id) {
1152
- const url = buildUrl(`/api/v1/board/activity/boards/${args.board_id}`, params, fmt);
1153
- return ok(await callApi("GET", url, ws));
1154
- }
1155
- return err("Provide board_id or task_id");
1156
- }
1157
- // ── Metrics ──
1158
- case "get_board_summary": {
1159
- const params = { board_id: String(args.board_id) };
1160
- if (args.days)
1161
- params.days = String(args.days);
1162
- const url = buildUrl("/api/v1/board/metrics/board-summary", params, fmt);
1163
- return ok(await callApi("GET", url, ws));
1164
- }
1165
- case "get_task_velocity": {
1166
- const params = {};
1167
- if (args.days)
1168
- params.days = String(args.days);
1169
- const url = buildUrl("/api/v1/board/metrics/task-velocity", params, fmt);
1170
- return ok(await callApi("GET", url, ws));
1171
- }
1172
- // ── Columns ──
1173
- case "list_columns": {
1174
- const url = buildUrl(`/api/v1/board/boards/${args.board_id}/columns`, undefined, fmt);
1175
- return ok(await callApi("GET", url, ws));
1176
- }
1177
- case "create_column": {
1178
- const body = { name: args.name };
1179
- if (args.color)
1180
- body.color = args.color;
1181
- if (args.wip_limit !== undefined)
1182
- body.wip_limit = args.wip_limit;
1183
- return ok(await callApi("POST", `/api/v1/board/boards/${args.board_id}/columns`, ws, body));
1184
- }
1185
- case "reorder_columns": {
1186
- return ok(await callApi("POST", `/api/v1/board/boards/${args.board_id}/columns/reorder`, ws, { column_ids: args.column_ids }));
1187
- }
1188
- // ── Files ──
1189
- case "list_files": {
1190
- const url = buildUrl(`/api/v1/board/tasks/${args.task_id}/files`, undefined, fmt);
1191
- return ok(await callApi("GET", url, ws));
1192
- }
1193
- case "get_file": {
1194
- return ok(await callApi("GET", `/api/v1/board/files/${args.file_id}`, ws));
1195
- }
1196
- // ── Docs ──
1197
- case "list_docs": {
1198
- const url = buildUrl("/api/v1/docs/projects", undefined, fmt);
1199
- return ok(await callApi("GET", url, ws));
1200
- }
1201
- case "get_doc": {
1202
- const params = {};
1203
- if (args.version)
1204
- params.version = String(args.version);
1205
- const url = buildUrl(`/api/v1/docs/projects/${args.project}/pages/${args.slug}`, params, fmt);
1206
- return ok(await callApi("GET", url, ws));
1207
- }
1208
- case "create_doc": {
1209
- const body = {
1210
- title: String(args.title),
1211
- slug: String(args.slug),
1212
- commit_message: String(args.message || "Created via MCP"),
1213
- };
1214
- if (args.content)
1215
- body.content_md = String(args.content);
1216
- return ok(await callApi("POST", `/api/v1/docs/projects/${args.project}/pages`, ws, body));
1217
- }
1218
- case "update_doc": {
1219
- const body = {
1220
- content_md: String(args.content),
1221
- commit_message: String(args.message || "Updated via MCP"),
1222
- };
1223
- if (args.title)
1224
- body.title = String(args.title);
1225
- return ok(await callApi("PUT", `/api/v1/docs/projects/${args.project}/pages/${args.slug}`, ws, body));
1226
- }
1227
- case "search_docs": {
1228
- const params = { q: String(args.query) };
1229
- if (args.project)
1230
- params.project = String(args.project);
1231
- if (args.limit)
1232
- params.limit = String(args.limit);
1233
- const url = buildUrl("/api/v1/docs/search", params, fmt);
1234
- return ok(await callApi("GET", url, ws));
1235
- }
1236
- // ── Docs: Project CRUD ──
1237
- case "create_project": {
1238
- const body = { name: args.name };
1239
- if (args.description)
1240
- body.description = args.description;
1241
- if (args.visibility)
1242
- body.visibility = args.visibility;
1243
- return ok(await callApi("POST", "/api/v1/docs/projects", ws, body));
1244
- }
1245
- case "update_project": {
1246
- const body = {};
1247
- if (args.name)
1248
- body.name = args.name;
1249
- if (args.description)
1250
- body.description = args.description;
1251
- if (args.visibility)
1252
- body.visibility = args.visibility;
1253
- if (Object.keys(body).length === 0) {
1254
- return err("Nothing to update. Provide name, description, or visibility.");
1255
- }
1256
- return ok(await callApi("PATCH", `/api/v1/docs/projects/${args.project}`, ws, body));
1257
- }
1258
- case "delete_project": {
1259
- await callApi("DELETE", `/api/v1/docs/projects/${args.project}`, ws);
1260
- return ok(`Deleted project ${args.project}`);
1261
- }
1262
- // ── Docs: Page Operations ──
1263
- case "list_pages": {
1264
- const params = {};
1265
- if (args.view)
1266
- params.view = String(args.view);
1267
- if (args.limit)
1268
- params.limit = String(args.limit);
1269
- const url = buildUrl(`/api/v1/docs/projects/${args.project}/pages`, params, fmt);
1270
- return ok(await callApi("GET", url, ws));
1271
- }
1272
- case "move_page": {
1273
- const body = {};
1274
- if (args.path)
1275
- body.path = args.path;
1276
- if (args.sort_order !== undefined)
1277
- body.sort_order = args.sort_order;
1278
- if (Object.keys(body).length === 0) {
1279
- return err("Provide path or sort_order.");
1280
- }
1281
- return ok(await callApi("PATCH", `/api/v1/docs/projects/${args.project}/pages/${args.slug}/move`, ws, body));
1282
- }
1283
- case "delete_page": {
1284
- await callApi("DELETE", `/api/v1/docs/projects/${args.project}/pages/${args.slug}`, ws);
1285
- return ok(`Deleted page ${args.slug}`);
1286
- }
1287
- // ── Docs: Revision History ──
1288
- case "page_history": {
1289
- const params = {};
1290
- if (args.limit)
1291
- params.limit = String(args.limit);
1292
- const url = buildUrl(`/api/v1/docs/pages/${args.page_id}/history`, params, fmt);
1293
- return ok(await callApi("GET", url, ws));
1294
- }
1295
- case "page_diff": {
1296
- const params = {};
1297
- if (args.from_rev)
1298
- params.from = String(args.from_rev);
1299
- if (args.to_rev)
1300
- params.to = String(args.to_rev);
1301
- const url = buildUrl(`/api/v1/docs/pages/${args.page_id}/diff`, params, fmt);
1302
- return ok(await callApi("GET", url, ws));
1303
- }
1304
- case "page_rollback": {
1305
- const body = {
1306
- revision_id: args.revision_id,
1307
- commit_message: String(args.message || "Rollback via MCP"),
1308
- };
1309
- return ok(await callApi("POST", `/api/v1/docs/pages/${args.page_id}/rollback`, ws, body));
1310
- }
1311
- // ── Docs: Media ──
1312
- case "list_media": {
1313
- const params = {};
1314
- if (args.limit)
1315
- params.limit = String(args.limit);
1316
- const url = buildUrl(`/api/v1/docs/projects/${args.project}/media`, params, fmt);
1317
- return ok(await callApi("GET", url, ws));
1318
- }
1319
- // ── Docs: Bulk ──
1320
- case "bulk_create_pages": {
1321
- const body = { pages: args.pages };
1322
- if (args.branch)
1323
- body.branch = args.branch;
1324
- return ok(await callApi("POST", `/api/v1/docs/projects/${args.project}/pages/bulk`, ws, body));
1325
- }
1326
- // ── Agent Optimization ──
1327
- case "agent_my_tasks": {
1328
- const params = {};
1329
- if (args.board_id)
1330
- params.board_id = String(args.board_id);
1331
- if (args.status)
1332
- params.status = String(args.status);
1333
- if (args.limit)
1334
- params.limit = String(args.limit);
1335
- const url = buildUrl("/api/v1/board/agent-api/my-tasks", params, fmt);
1336
- return ok(await callApi("GET", url, ws));
1337
- }
1338
- case "agent_my_delegations": {
1339
- const params = {};
1340
- if (args.status)
1341
- params.status = String(args.status);
1342
- if (args.limit)
1343
- params.limit = String(args.limit);
1344
- const url = buildUrl("/api/v1/board/agent-api/my-delegations", params, fmt);
1345
- return ok(await callApi("GET", url, ws));
1346
- }
1347
- case "agent_batch_create_tasks": {
1348
- return ok(await callApi("POST", "/api/v1/board/agent-api/tasks/batch", ws, { tasks: args.tasks }));
1349
- }
1350
- case "agent_report_progress": {
1351
- const body = { percent: args.percent };
1352
- if (args.message)
1353
- body.message = args.message;
1354
- if (args.current_operation)
1355
- body.current_operation = args.current_operation;
1356
- return ok(await callApi("POST", `/api/v1/board/agent-api/tasks/${args.task_id}/progress`, ws, body));
1357
- }
1358
- case "agent_complete_task": {
1359
- const body = {};
1360
- if (args.status)
1361
- body.status = args.status;
1362
- if (args.summary)
1363
- body.summary = args.summary;
1364
- if (args.result_data)
1365
- body.result_data = args.result_data;
1366
- return ok(await callApi("POST", `/api/v1/board/agent-api/tasks/${args.task_id}/complete`, ws, body));
1367
- }
1368
- case "agent_delegate_task": {
1369
- const body = { agent_slug: args.agent_slug };
1370
- if (args.instructions)
1371
- body.instructions = args.instructions;
1372
- return ok(await callApi("POST", `/api/v1/board/agent-api/tasks/${args.task_id}/delegate`, ws, body));
1373
- }
1374
- case "search_tasks": {
1375
- const params = { q: String(args.query) };
1376
- if (args.board_id)
1377
- params.board_id = String(args.board_id);
1378
- if (args.limit)
1379
- params.limit = String(args.limit);
1380
- const url = buildUrl("/api/v1/board/cards/search", params, fmt);
1381
- return ok(await callApi("GET", url, ws));
1382
- }
1383
- case "batch_get_tasks": {
1384
- const ids = args.ids.join(",");
1385
- const url = buildUrl("/api/v1/board/tasks/batch", { ids }, fmt);
1386
- return ok(await callApi("GET", url, ws));
1387
- }
1388
- // ── Stages ──
1389
- case "list_stages": {
1390
- const url = buildUrl(`/api/v1/board/tasks/${args.task_id}/stages`, undefined, fmt);
1391
- return ok(await callApi("GET", url, ws));
1392
- }
1393
- case "create_stage": {
1394
- const body = { title: args.title };
1395
- if (args.description)
1396
- body.description = args.description;
1397
- if (args.assignee_agent_id)
1398
- body.assignee_agent_id = args.assignee_agent_id;
1399
- if (args.agent_instructions)
1400
- body.agent_instructions = args.agent_instructions;
1401
- if (args.position !== undefined)
1402
- body.position = args.position;
1403
- return ok(await callApi("POST", `/api/v1/board/tasks/${args.task_id}/stages`, ws, body));
1404
- }
1405
- case "update_stage": {
1406
- const body = {};
1407
- if (args.title)
1408
- body.title = args.title;
1409
- if (args.description)
1410
- body.description = args.description;
1411
- if (args.assignee_agent_id)
1412
- body.assignee_agent_id = args.assignee_agent_id;
1413
- if (args.agent_instructions)
1414
- body.agent_instructions = args.agent_instructions;
1415
- if (Object.keys(body).length === 0) {
1416
- return err("Nothing to update. Provide title, description, assignee_agent_id, or agent_instructions.");
1417
- }
1418
- return ok(await callApi("PATCH", `/api/v1/board/tasks/${args.task_id}/stages/${args.stage_id}`, ws, body));
1419
- }
1420
- case "complete_stage": {
1421
- const body = {};
1422
- if (args.comment)
1423
- body.comment = args.comment;
1424
- return ok(await callApi("POST", `/api/v1/board/tasks/${args.task_id}/stages/${args.stage_id}/handoff`, ws, body));
1425
- }
1426
- case "reorder_stages": {
1427
- return ok(await callApi("POST", `/api/v1/board/tasks/${args.task_id}/stages/reorder`, ws, { order: args.order }));
1428
- }
1429
- // ── Task Links ──
1430
- case "list_task_links": {
1431
- const url = buildUrl(`/api/v1/board/tasks/${args.task_id}/links`, undefined, fmt);
1432
- return ok(await callApi("GET", url, ws));
1433
- }
1434
- case "create_task_link": {
1435
- const body = {
1436
- target_task_id: args.target_task_id,
1437
- link_type: args.link_type || "related",
1438
- };
1439
- return ok(await callApi("POST", `/api/v1/board/tasks/${args.task_id}/links`, ws, body));
1440
- }
1441
- case "delete_task_link": {
1442
- await callApi("DELETE", `/api/v1/board/task-links/${args.link_id}`, ws);
1443
- return ok(`Deleted link ${args.link_id}`);
1444
- }
1445
- // ── Auth ──
1446
- case "request_access": {
1447
- const baseUrl = args.api_url || DEFAULT_WORKSPACE.api_url;
1448
- try {
1449
- const res = await apiUnauthPost(baseUrl, "/api/v1/auth/pat/request-by-email", {
1450
- owner_email: args.email,
1451
- project_name: args.project || null,
1452
- requested_scopes: ["board:read", "board:write"],
1453
- ttl_hours: 72,
1454
- fingerprint: { agent_type: "Claude Desktop (MCP)", hostname: "mcp-server" },
1455
- });
1456
- const data = await res.json();
1457
- const brandCount = data.brands?.length ?? 0;
1458
- const brandList = data.brands?.map(b => b.brand_name).join(", ") || "unknown";
1459
- return ok(`Approval email sent to ${args.email}.\n` +
1460
- `Found ${brandCount} workspace${brandCount !== 1 ? "s" : ""}: ${brandList}\n\n` +
1461
- `Save these for check_access_status:\n` +
1462
- ` request_id: ${data.request_id}\n` +
1463
- ` poll_token: ${data.poll_token}\n` +
1464
- ` api_url: ${baseUrl}\n\n` +
1465
- `Ask the admin to check their email and click "Approve Access". ` +
1466
- `Then call check_access_status with the request_id and poll_token above.`);
1467
- }
1468
- catch (e) {
1469
- const error = e;
1470
- return err(error.detail || error.message || "Failed to request access");
1471
- }
1472
- }
1473
- case "check_access_status": {
1474
- const baseUrl = args.api_url || DEFAULT_WORKSPACE.api_url;
1475
- try {
1476
- const res = await apiUnauthGet(baseUrl, `/api/v1/auth/pat/request/${args.request_id}/status`, String(args.poll_token));
1477
- const data = await res.json();
1478
- if (data.status === "pending") {
1479
- return ok("Status: pending — the admin has not yet approved. Try again in a few seconds.");
1480
- }
1481
- if (data.status === "denied") {
1482
- return ok("Status: denied — the admin rejected the access request.");
1483
- }
1484
- if (data.status === "expired") {
1485
- return ok("Status: expired — the request timed out. Use request_access to try again.");
1486
- }
1487
- if (data.status === "active") {
1488
- // Save workspace tokens to config
1489
- if (data.workspaces?.length) {
1490
- for (const ws of data.workspaces) {
1491
- saveWorkspace(ws.brand_slug, {
1492
- api_url: baseUrl,
1493
- token: ws.token,
1494
- brand_id: ws.brand_id,
1495
- brand_name: ws.brand_name,
1496
- });
1497
- }
1498
- setCurrentWorkspace(data.workspaces[0].brand_slug);
1499
- const slugs = data.workspaces.map(ws => ws.brand_slug).join(", ");
1500
- return ok(`Status: active — access granted!\n` +
1501
- `${data.workspaces.length} workspace${data.workspaces.length !== 1 ? "s" : ""} configured: ${slugs}\n` +
1502
- `Current workspace: ${data.workspaces[0].brand_slug}\n\n` +
1503
- `You can now use all OPVS tools. Use list_workspaces to see available workspaces.`);
1504
- }
1505
- else if (data.token) {
1506
- // Single-brand fallback
1507
- return ok(`Status: active — access granted!\n` +
1508
- `Brand: ${data.brand_name} (ID: ${data.brand_id})\n` +
1509
- `Token received. Configure your workspace to use it.`);
1510
- }
1511
- }
1512
- return ok(`Status: ${data.status}`);
1513
- }
1514
- catch (e) {
1515
- const error = e;
1516
- return err(error.detail || error.message || "Failed to check status");
1517
- }
1518
- }
1519
- default:
1520
- return err(`Unknown tool: ${name}`);
68
+ // Try each domain handler
69
+ for (const handler of handlers) {
70
+ const result = await handler(name, args, callApi, buildUrl);
71
+ if (result !== null)
72
+ return result;
1521
73
  }
74
+ // Auth handler has a different signature (no buildUrl)
75
+ const authResult = await handleAuth(name, args, callApi);
76
+ if (authResult !== null)
77
+ return authResult;
78
+ return err(`Unknown tool: ${name}`);
1522
79
  }
1523
- // ── Server Setup ─────────────────────────────────────────────────
1524
- const server = new Server({ name: "opvs", version: "0.5.2" }, { capabilities: { tools: {} } });
1525
- // ── opvsHUB Generated Tools ──────────────────────────────────────
1526
- import { GENERATED_TOOLS, handleGeneratedTool } from "./generated-tools.js";
80
+ // ── Server setup ────────────────────────────────────────────────
81
+ const server = new Server({ name: "opvs", version: "0.5.4" }, { capabilities: { tools: {} } });
1527
82
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
1528
83
  tools: [...TOOLS, ...GENERATED_TOOLS],
1529
84
  }));