@parall/cli 1.33.0 → 1.35.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.
@@ -1,3 +1,8 @@
1
1
  import { Command } from 'commander';
2
+ /**
3
+ * Adds the `mcp` subcommand to the CLI, which runs a Parall MCP server over stdio.
4
+ * On invocation it resolves credentials, registers all wiki tools, and connects a
5
+ * stdio transport (server lifecycle log goes to stderr to keep stdout clean for the protocol).
6
+ */
2
7
  export declare function registerMcpCommands(program: Command): void;
3
8
  //# sourceMappingURL=mcp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,QAulBnD"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,QAmBnD"}
@@ -1,8 +1,33 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import * as z from 'zod/v4';
4
- import { resolveCredentials, resolveRuntimeContext } from '../lib/client.js';
5
- import { getWikiBlob, getWikiChangesetDiff, getWikiOutline, queryWiki, getWikiSection, getWikiTree, getWikiWorkspaceDiff, getWikiWorkspaceStatus, listWikis, proposeWikiChangeset, searchWiki, } from '../lib/wiki.js';
3
+ import { resolveCredentials } from '../lib/client.js';
4
+ import { buildWikiTools } from '../lib/wiki-tools.js';
5
+ /**
6
+ * Registers a single wiki tool on the MCP server, wiring its schema/annotations and
7
+ * adapting the tool's `{ text, structuredContent }` result into MCP's content envelope.
8
+ */
9
+ function registerWikiTool(server, tool) {
10
+ server.registerTool(tool.name, {
11
+ title: tool.title,
12
+ description: tool.description,
13
+ annotations: {
14
+ readOnlyHint: tool.readOnly,
15
+ },
16
+ inputSchema: tool.inputSchema,
17
+ outputSchema: tool.outputSchema,
18
+ }, async (args) => {
19
+ const result = await tool.handler(args);
20
+ return {
21
+ content: [{ type: 'text', text: result.text }],
22
+ structuredContent: result.structuredContent,
23
+ };
24
+ });
25
+ }
26
+ /**
27
+ * Adds the `mcp` subcommand to the CLI, which runs a Parall MCP server over stdio.
28
+ * On invocation it resolves credentials, registers all wiki tools, and connects a
29
+ * stdio transport (server lifecycle log goes to stderr to keep stdout clean for the protocol).
30
+ */
6
31
  export function registerMcpCommands(program) {
7
32
  program
8
33
  .command('mcp')
@@ -13,485 +38,9 @@ export function registerMcpCommands(program) {
13
38
  name: 'parall-mcp',
14
39
  version: '1.0.0',
15
40
  });
16
- server.registerTool('wiki_list', {
17
- title: 'List Parall Wikis',
18
- description: 'List Parall wiki repositories that the current credentials can access.',
19
- annotations: {
20
- readOnlyHint: true,
21
- },
22
- outputSchema: {
23
- wikis: z.array(z.object({
24
- id: z.string(),
25
- slug: z.string(),
26
- name: z.string(),
27
- default_branch: z.string(),
28
- })),
29
- },
30
- }, async () => {
31
- const wikis = await listWikis(ctx);
32
- const structuredContent = {
33
- wikis: wikis.map((wiki) => ({
34
- id: wiki.id,
35
- slug: wiki.slug,
36
- name: wiki.name,
37
- default_branch: wiki.default_branch,
38
- })),
39
- };
40
- return {
41
- content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],
42
- structuredContent,
43
- };
44
- });
45
- server.registerTool('wiki_tree', {
46
- title: 'Browse Wiki Tree',
47
- description: 'List files and folders under a Parall wiki path.',
48
- annotations: {
49
- readOnlyHint: true,
50
- },
51
- inputSchema: {
52
- wiki: z.string().describe('Wiki slug or ID'),
53
- path: z.string().optional().describe('Optional tree path prefix inside the wiki'),
54
- },
55
- outputSchema: {
56
- wiki_id: z.string(),
57
- wiki_slug: z.string(),
58
- wiki_name: z.string(),
59
- path: z.string(),
60
- entries: z.array(z.object({
61
- name: z.string(),
62
- path: z.string(),
63
- type: z.enum(['tree', 'blob']),
64
- size: z.number().optional(),
65
- })),
66
- },
67
- }, async ({ wiki, path }) => {
68
- const result = await getWikiTree(ctx, wiki, { path });
69
- const structuredContent = {
70
- wiki_id: result.wiki.id,
71
- wiki_slug: result.wiki.slug,
72
- wiki_name: result.wiki.name,
73
- path: result.path,
74
- entries: result.entries,
75
- };
76
- return {
77
- content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],
78
- structuredContent,
79
- };
80
- });
81
- server.registerTool('wiki_status', {
82
- title: 'Inspect Wiki Workspace Status',
83
- description: 'Inspect the current local mounted wiki workspace before proposing changes.',
84
- annotations: {
85
- readOnlyHint: true,
86
- },
87
- inputSchema: {
88
- wiki: z.string().describe('Wiki slug or ID'),
89
- },
90
- outputSchema: {
91
- wiki_id: z.string(),
92
- wiki_slug: z.string(),
93
- wiki_name: z.string(),
94
- mount_path: z.string(),
95
- mode: z.enum(['read_only', 'read_write']),
96
- default_ref: z.string(),
97
- changed_paths: z.array(z.string()),
98
- diff_files: z.array(z.object({
99
- path: z.string(),
100
- additions: z.number(),
101
- deletions: z.number(),
102
- })),
103
- dirty: z.boolean(),
104
- source: z.literal('local_mount'),
105
- },
106
- }, async ({ wiki }) => {
107
- const result = await getWikiWorkspaceStatus(ctx, wiki);
108
- return {
109
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
110
- structuredContent: result,
111
- };
112
- });
113
- server.registerTool('wiki_diff', {
114
- title: 'Read Wiki Workspace Diff',
115
- description: 'Read the current unified diff for a local mounted wiki workspace.',
116
- annotations: {
117
- readOnlyHint: true,
118
- },
119
- inputSchema: {
120
- wiki: z.string().describe('Wiki slug or ID'),
121
- },
122
- outputSchema: {
123
- wiki_id: z.string(),
124
- wiki_slug: z.string(),
125
- wiki_name: z.string(),
126
- mount_path: z.string(),
127
- changed_paths: z.array(z.string()),
128
- diff_files: z.array(z.object({
129
- path: z.string(),
130
- additions: z.number(),
131
- deletions: z.number(),
132
- })),
133
- patch: z.string(),
134
- source: z.literal('local_mount'),
135
- },
136
- }, async ({ wiki }) => {
137
- const result = await getWikiWorkspaceDiff(ctx, wiki);
138
- return {
139
- content: [{ type: 'text', text: result.patch || JSON.stringify(result, null, 2) }],
140
- structuredContent: result,
141
- };
142
- });
143
- server.registerTool('wiki_blob', {
144
- title: 'Read Wiki File',
145
- description: 'Read a file from a Parall wiki and return its text content.',
146
- annotations: {
147
- readOnlyHint: true,
148
- },
149
- inputSchema: {
150
- wiki: z.string().describe('Wiki slug or ID'),
151
- path: z.string().describe('File path inside the wiki'),
152
- },
153
- outputSchema: {
154
- wiki_id: z.string(),
155
- wiki_slug: z.string(),
156
- wiki_name: z.string(),
157
- content: z.string(),
158
- sha256: z.string(),
159
- size: z.number(),
160
- },
161
- }, async ({ wiki, path }) => {
162
- const result = await getWikiBlob(ctx, wiki, { path });
163
- const structuredContent = {
164
- wiki_id: result.wiki.id,
165
- wiki_slug: result.wiki.slug,
166
- wiki_name: result.wiki.name,
167
- content: result.content,
168
- size: result.size,
169
- };
170
- return {
171
- content: [
172
- { type: 'text', text: result.content || JSON.stringify(structuredContent, null, 2) },
173
- ],
174
- structuredContent,
175
- };
176
- });
177
- server.registerTool('wiki_search', {
178
- title: 'Search Wiki Markdown',
179
- description: 'Search Markdown headings and section bodies inside a Parall wiki. Prefers local mounts when no ref is supplied and agent env is available; otherwise falls back to the Parall API.',
180
- annotations: {
181
- readOnlyHint: true,
182
- },
183
- inputSchema: {
184
- wiki: z.string().describe('Wiki slug or ID'),
185
- query: z.string().describe('Search query'),
186
- ref: z.string().optional().describe('Optional git ref for API-backed search'),
187
- path_prefix: z
188
- .string()
189
- .optional()
190
- .describe('Restrict search to a file or directory path prefix'),
191
- limit: z
192
- .number()
193
- .int()
194
- .positive()
195
- .max(20)
196
- .optional()
197
- .describe('Maximum number of results; default 5'),
198
- include_content: z
199
- .boolean()
200
- .optional()
201
- .describe('Include full matched section content in each result'),
202
- },
203
- outputSchema: {
204
- results: z.array(z.object({
205
- wiki_id: z.string(),
206
- wiki_slug: z.string(),
207
- wiki_name: z.string(),
208
- node_id: z.string(),
209
- path: z.string(),
210
- title: z.string(),
211
- heading_path: z.array(z.string()).optional(),
212
- section_path: z.string(),
213
- level: z.number(),
214
- start_line: z.number(),
215
- end_line: z.number(),
216
- score: z.number(),
217
- snippet: z.string(),
218
- content: z.string().optional(),
219
- source: z.enum(['local_mount', 'api']),
220
- })),
221
- },
222
- }, async ({ wiki, query, ref, path_prefix: pathPrefix, limit, include_content: includeContent, }) => {
223
- const results = await searchWiki(ctx, wiki, query, {
224
- ref,
225
- pathPrefix,
226
- limit,
227
- includeContent,
228
- });
229
- const structuredContent = { results };
230
- return {
231
- content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],
232
- structuredContent,
233
- };
234
- });
235
- server.registerTool('wiki_query', {
236
- title: 'Query Wiki Structurally',
237
- description: 'Run a tree-aware wiki query that first narrows candidate documents and then selects the best sections.',
238
- annotations: {
239
- readOnlyHint: true,
240
- },
241
- inputSchema: {
242
- wiki: z.string().describe('Wiki slug or ID'),
243
- query: z.string().describe('Natural-language query'),
244
- ref: z.string().optional().describe('Optional git ref for API-backed queries'),
245
- path_prefix: z
246
- .string()
247
- .optional()
248
- .describe('Restrict the query to a file or directory path prefix'),
249
- limit: z
250
- .number()
251
- .int()
252
- .positive()
253
- .max(20)
254
- .optional()
255
- .describe('Maximum number of section results; default 5'),
256
- include_content: z
257
- .boolean()
258
- .optional()
259
- .describe('Include full matched section content in each result'),
260
- },
261
- outputSchema: {
262
- wiki_id: z.string(),
263
- wiki_slug: z.string(),
264
- wiki_name: z.string(),
265
- query: z.string(),
266
- ref: z.string().optional(),
267
- path_prefix: z.string().optional(),
268
- generated_at: z.string(),
269
- file_count: z.number(),
270
- source: z.enum(['local_mount', 'api']),
271
- documents: z.array(z.object({
272
- path: z.string(),
273
- title: z.string(),
274
- score: z.number(),
275
- matched_headings: z.array(z.string()),
276
- node_count: z.number(),
277
- })),
278
- sections: z.array(z.object({
279
- wiki_id: z.string(),
280
- wiki_slug: z.string(),
281
- wiki_name: z.string(),
282
- node_id: z.string(),
283
- path: z.string(),
284
- title: z.string(),
285
- heading_path: z.array(z.string()).optional(),
286
- section_path: z.string(),
287
- level: z.number(),
288
- start_line: z.number(),
289
- end_line: z.number(),
290
- score: z.number(),
291
- document_score: z.number(),
292
- snippet: z.string(),
293
- content: z.string().optional(),
294
- source: z.enum(['local_mount', 'api']),
295
- reasoning: z.array(z.string()),
296
- })),
297
- },
298
- }, async ({ wiki, query, ref, path_prefix: pathPrefix, limit, include_content: includeContent, }) => {
299
- const result = await queryWiki(ctx, wiki, query, {
300
- ref,
301
- pathPrefix,
302
- limit,
303
- includeContent,
304
- });
305
- return {
306
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
307
- structuredContent: result,
308
- };
309
- });
310
- server.registerTool('wiki_outline', {
311
- title: 'Read Wiki Outline',
312
- description: 'Return the structural node outline for a Parall wiki or a path prefix within it.',
313
- annotations: {
314
- readOnlyHint: true,
315
- },
316
- inputSchema: {
317
- wiki: z.string().describe('Wiki slug or ID'),
318
- ref: z.string().optional().describe('Optional git ref for API-backed outline'),
319
- path_prefix: z
320
- .string()
321
- .optional()
322
- .describe('Restrict outline to a file or directory path prefix'),
323
- },
324
- outputSchema: {
325
- wiki_id: z.string(),
326
- wiki_slug: z.string(),
327
- wiki_name: z.string(),
328
- ref: z.string().optional(),
329
- path_prefix: z.string().optional(),
330
- generated_at: z.string(),
331
- file_count: z.number(),
332
- source: z.enum(['local_mount', 'api']),
333
- nodes: z.array(z.object({
334
- wiki_id: z.string(),
335
- wiki_slug: z.string(),
336
- wiki_name: z.string(),
337
- node_id: z.string(),
338
- path: z.string(),
339
- title: z.string(),
340
- heading_path: z.array(z.string()).optional(),
341
- section_path: z.string(),
342
- level: z.number(),
343
- start_line: z.number(),
344
- end_line: z.number(),
345
- })),
346
- },
347
- }, async ({ wiki, ref, path_prefix: pathPrefix }) => {
348
- const outline = await getWikiOutline(ctx, wiki, { ref, pathPrefix });
349
- return {
350
- content: [{ type: 'text', text: JSON.stringify(outline, null, 2) }],
351
- structuredContent: outline,
352
- };
353
- });
354
- server.registerTool('wiki_section', {
355
- title: 'Read Wiki Section',
356
- description: 'Read a specific Markdown section from a Parall wiki by node ID. Node IDs come from `wiki_search` results.',
357
- annotations: {
358
- readOnlyHint: true,
359
- },
360
- inputSchema: {
361
- wiki: z.string().describe('Wiki slug or ID'),
362
- node_id: z.string().describe('Section node ID returned by wiki_search'),
363
- ref: z
364
- .string()
365
- .optional()
366
- .describe('Optional git ref; when set, reads from the Parall API instead of a local mount'),
367
- },
368
- outputSchema: {
369
- wiki_id: z.string(),
370
- wiki_slug: z.string(),
371
- wiki_name: z.string(),
372
- node_id: z.string(),
373
- path: z.string(),
374
- title: z.string(),
375
- heading_path: z.array(z.string()).optional(),
376
- section_path: z.string(),
377
- level: z.number(),
378
- start_line: z.number(),
379
- end_line: z.number(),
380
- content: z.string(),
381
- source: z.enum(['local_mount', 'api']),
382
- },
383
- }, async ({ wiki, node_id: nodeId, ref }) => {
384
- const section = await getWikiSection(ctx, wiki, { nodeId, ref });
385
- return {
386
- content: [{ type: 'text', text: section.content || JSON.stringify(section, null, 2) }],
387
- structuredContent: section,
388
- };
389
- });
390
- server.registerTool('wiki_changeset_diff', {
391
- title: 'Read Wiki Changeset Diff',
392
- description: 'Fetch the unified diff and changed file summary for a Parall wiki changeset.',
393
- annotations: {
394
- readOnlyHint: true,
395
- },
396
- inputSchema: {
397
- wiki: z.string().describe('Wiki slug or ID'),
398
- changeset_id: z.string().describe('Wiki changeset ID'),
399
- },
400
- outputSchema: {
401
- wiki_id: z.string(),
402
- wiki_slug: z.string(),
403
- wiki_name: z.string(),
404
- changeset_id: z.string(),
405
- diff: z.object({
406
- patch: z.string(),
407
- files: z.array(z.object({
408
- path: z.string(),
409
- additions: z.number(),
410
- deletions: z.number(),
411
- })),
412
- }),
413
- },
414
- }, async ({ wiki, changeset_id: changesetId }) => {
415
- const diff = await getWikiChangesetDiff(ctx, wiki, changesetId);
416
- return {
417
- content: [{ type: 'text', text: diff.diff.patch || JSON.stringify(diff, null, 2) }],
418
- structuredContent: diff,
419
- };
420
- });
421
- server.registerTool('wiki_propose', {
422
- title: 'Propose Wiki Changeset',
423
- description: 'Create or update a wiki changeset from the current local mounted wiki workspace. Unprotected changesets may auto-merge immediately.',
424
- inputSchema: {
425
- wiki: z.string().describe('Wiki slug or ID'),
426
- title: z.string().min(1).describe('Changeset title'),
427
- message: z
428
- .string()
429
- .optional()
430
- .describe('Changeset commit message (supports prll:// refs)'),
431
- changeset_id: z.string().optional().describe('Existing changeset ID to update'),
432
- source_chat_id: z
433
- .string()
434
- .optional()
435
- .describe('Optional source chat ID for approval workflow linkage'),
436
- source_message_id: z
437
- .string()
438
- .optional()
439
- .describe('Optional source message ID for approval workflow linkage'),
440
- source_run_id: z
441
- .string()
442
- .optional()
443
- .describe('Optional source run ID for approval workflow linkage'),
444
- },
445
- outputSchema: {
446
- wiki_id: z.string(),
447
- wiki_slug: z.string(),
448
- wiki_name: z.string(),
449
- mount_path: z.string(),
450
- changed_paths: z.array(z.string()),
451
- status: z.string(),
452
- auto_merged: z.boolean(),
453
- requires_review: z.boolean(),
454
- next_action: z.string(),
455
- post_merge_sync: z
456
- .object({
457
- applied: z.number(),
458
- fastForwarded: z.number(),
459
- conflicts: z.array(z.unknown()),
460
- failed: z.array(z.unknown()),
461
- })
462
- .nullable(),
463
- post_merge_sync_error: z.string().nullable(),
464
- diff_files: z.array(z.object({
465
- path: z.string(),
466
- additions: z.number(),
467
- deletions: z.number(),
468
- })),
469
- source: z.literal('local_mount'),
470
- changeset: z
471
- .object({
472
- id: z.string(),
473
- wiki_id: z.string(),
474
- title: z.string(),
475
- status: z.string(),
476
- changed_paths: z.array(z.string()).optional(),
477
- })
478
- .passthrough(),
479
- },
480
- }, async ({ wiki, title, message, changeset_id: changesetId, source_chat_id: sourceChatId, source_message_id: sourceMessageId, source_run_id: sourceRunId, }) => {
481
- const runtime = resolveRuntimeContext();
482
- const result = await proposeWikiChangeset(ctx, wiki, {
483
- title,
484
- message,
485
- changesetId,
486
- sourceChatId: sourceChatId ?? runtime.chatId,
487
- sourceMessageId: sourceMessageId ?? runtime.triggerMessageId,
488
- sourceRunId: sourceRunId ?? runtime.sessionId,
489
- });
490
- return {
491
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
492
- structuredContent: result,
493
- };
494
- });
41
+ for (const tool of buildWikiTools(ctx)) {
42
+ registerWikiTool(server, tool);
43
+ }
495
44
  const transport = new StdioServerTransport();
496
45
  await server.connect(transport);
497
46
  console.error('parall MCP server running on stdio');
@@ -1 +1 @@
1
- {"version":3,"file":"wiki.d.ts","sourceRoot":"","sources":["../../src/commands/wiki.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAq2BpD"}
1
+ {"version":3,"file":"wiki.d.ts","sourceRoot":"","sources":["../../src/commands/wiki.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAy3BpD"}