@freelancercom/phabricator-mcp 2.0.16 → 2.0.17

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,5 +1,63 @@
1
1
  import { z } from 'zod';
2
2
  import { jsonCoerce } from './coerce.js';
3
+ /**
4
+ * Resolve revision identifiers (e.g. "D223125", "PHID-DREV-xxx") to PHIDs.
5
+ * Accepts a mix of D-prefixed IDs and raw PHIDs.
6
+ */
7
+ async function resolveRevisionPHIDs(client, ids) {
8
+ const phids = [];
9
+ const namesToLookup = [];
10
+ for (const id of ids) {
11
+ if (id.startsWith('PHID-')) {
12
+ phids.push(id);
13
+ }
14
+ else {
15
+ // Normalize to D-prefixed format if just a number
16
+ const name = /^\d+$/.test(id) ? `D${id}` : id;
17
+ namesToLookup.push(name);
18
+ }
19
+ }
20
+ if (namesToLookup.length > 0) {
21
+ const lookupResult = await client.call('phid.lookup', { names: namesToLookup });
22
+ for (const name of namesToLookup) {
23
+ const entry = lookupResult[name];
24
+ if (entry) {
25
+ phids.push(entry.phid);
26
+ }
27
+ else {
28
+ throw new Error(`Could not resolve revision identifier: ${name}`);
29
+ }
30
+ }
31
+ }
32
+ return phids;
33
+ }
34
+ /**
35
+ * Link or unlink revisions to/from a task by calling differential.revision.edit
36
+ * with tasks.add / tasks.remove transactions.
37
+ */
38
+ async function linkRevisionsToTask(client, taskIdentifier, revisionPHIDs, action) {
39
+ // Resolve the task identifier to a PHID if it's a T-prefixed ID
40
+ let taskPHID = taskIdentifier;
41
+ if (!taskIdentifier.startsWith('PHID-')) {
42
+ const lookupResult = await client.call('phid.lookup', { names: [taskIdentifier] });
43
+ const entry = lookupResult[taskIdentifier];
44
+ if (entry) {
45
+ taskPHID = entry.phid;
46
+ }
47
+ else {
48
+ throw new Error(`Could not resolve task identifier: ${taskIdentifier}`);
49
+ }
50
+ }
51
+ const results = [];
52
+ for (const revPHID of revisionPHIDs) {
53
+ const result = await client.call('differential.revision.edit', {
54
+ objectIdentifier: revPHID,
55
+ transactions: [{ type: `tasks.${action}`, value: [taskPHID] }],
56
+ });
57
+ results.push(result);
58
+ }
59
+ return results;
60
+ }
3
61
  export function registerManiphestTools(server, client) {
4
62
  // Search tasks
5
63
  server.tool('phabricator_task_search', 'Search Maniphest tasks with optional filters', {
@@ -54,7 +112,8 @@ export function registerManiphestTools(server, client) {
54
112
  subtype: z.string().optional().describe('Task subtype (e.g. "default", "incident")'),
55
113
  parentPHIDs: z.array(z.string()).optional().describe('Parent task PHIDs'),
56
114
  subtaskPHIDs: z.array(z.string()).optional().describe('Subtask PHIDs'),
57
- commitPHIDs: z.array(z.string()).optional().describe('Commit PHIDs to associate'),
115
+ commitPHIDs: z.array(z.string()).optional().describe('Commit PHIDs to associate (for actual commits only, not revisions)'),
116
+ revisionIDs: z.array(z.string()).optional().describe('Differential revision IDs to link (e.g. ["D223125"] or ["PHID-DREV-xxx"]). Creates a bidirectional link between the revision and the task.'),
58
117
  points: z.coerce.number().nullable().optional().describe('Story points value (if points are enabled on this instance)'),
59
118
  space: z.string().optional().describe('Space PHID to place the task in (for multi-space installations)'),
60
119
  comment: z.string().optional().describe('Initial comment on the task (supports Remarkup)'),
@@ -108,6 +167,18 @@ export function registerManiphestTools(server, client) {
108
167
  }
109
168
  }
110
169
  const result = await client.call('maniphest.edit', { transactions });
170
+ // Link revisions to the newly created task via differential.revision.edit
171
+ if (params.revisionIDs !== undefined && params.revisionIDs.length > 0) {
172
+ const revPHIDs = await resolveRevisionPHIDs(client, params.revisionIDs);
173
+ const taskId = `T${result.object.id}`;
174
+ const linkResults = await linkRevisionsToTask(client, taskId, revPHIDs, 'add');
175
+ return {
176
+ content: [{
177
+ type: 'text',
178
+ text: JSON.stringify({ ...result, linkedRevisions: linkResults }, null, 2),
179
+ }],
180
+ };
181
+ }
111
182
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
112
183
  });
113
184
  // Edit task
@@ -127,8 +198,10 @@ export function registerManiphestTools(server, client) {
127
198
  removeParentPHIDs: z.array(z.string()).optional().describe('Parent task PHIDs to remove'),
128
199
  addSubtaskPHIDs: z.array(z.string()).optional().describe('Subtask PHIDs to add'),
129
200
  removeSubtaskPHIDs: z.array(z.string()).optional().describe('Subtask PHIDs to remove'),
130
- addCommitPHIDs: z.array(z.string()).optional().describe('Commit PHIDs to associate'),
131
- removeCommitPHIDs: z.array(z.string()).optional().describe('Commit PHIDs to disassociate'),
201
+ addCommitPHIDs: z.array(z.string()).optional().describe('Commit PHIDs to associate (for actual commits only, not revisions)'),
202
+ removeCommitPHIDs: z.array(z.string()).optional().describe('Commit PHIDs to disassociate (for actual commits only, not revisions)'),
203
+ addRevisionIDs: z.array(z.string()).optional().describe('Differential revision IDs to link (e.g. ["D223125"] or ["PHID-DREV-xxx"]). Creates a bidirectional link between the revision and the task.'),
204
+ removeRevisionIDs: z.array(z.string()).optional().describe('Differential revision IDs to unlink (e.g. ["D223125"] or ["PHID-DREV-xxx"]).'),
132
205
  points: z.coerce.number().nullable().optional().describe('Story points value (null to clear)'),
133
206
  columnPHID: z.string().optional().describe('Move to workboard column'),
134
207
  space: z.string().optional().describe('Space PHID to move the task to (for multi-space installations)'),
@@ -201,14 +274,32 @@ export function registerManiphestTools(server, client) {
201
274
  transactions.push({ type: key, value });
202
275
  }
203
276
  }
204
- if (transactions.length === 0) {
277
+ const hasRevisionChanges = (params.addRevisionIDs !== undefined && params.addRevisionIDs.length > 0) ||
278
+ (params.removeRevisionIDs !== undefined && params.removeRevisionIDs.length > 0);
279
+ if (transactions.length === 0 && !hasRevisionChanges) {
205
280
  return { content: [{ type: 'text', text: 'No changes specified' }] };
206
281
  }
207
- const result = await client.call('maniphest.edit', {
208
- objectIdentifier: params.objectIdentifier,
209
- transactions,
210
- });
211
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
282
+ let result = undefined;
283
+ if (transactions.length > 0) {
284
+ result = await client.call('maniphest.edit', {
285
+ objectIdentifier: params.objectIdentifier,
286
+ transactions,
287
+ });
288
+ }
289
+ // Link/unlink revisions via differential.revision.edit
290
+ const revisionResults = {};
291
+ if (params.addRevisionIDs !== undefined && params.addRevisionIDs.length > 0) {
292
+ const revPHIDs = await resolveRevisionPHIDs(client, params.addRevisionIDs);
293
+ revisionResults.added = await linkRevisionsToTask(client, params.objectIdentifier, revPHIDs, 'add');
294
+ }
295
+ if (params.removeRevisionIDs !== undefined && params.removeRevisionIDs.length > 0) {
296
+ const revPHIDs = await resolveRevisionPHIDs(client, params.removeRevisionIDs);
297
+ revisionResults.removed = await linkRevisionsToTask(client, params.objectIdentifier, revPHIDs, 'remove');
298
+ }
299
+ const output = hasRevisionChanges
300
+ ? { ...(result !== undefined ? { task: result } : {}), linkedRevisions: revisionResults }
301
+ : result;
302
+ return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
212
303
  });
213
304
  // Add comment to task
214
305
  server.tool('phabricator_task_add_comment', 'Add a comment to a Maniphest task', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freelancercom/phabricator-mcp",
3
- "version": "2.0.16",
3
+ "version": "2.0.17",
4
4
  "description": "MCP server for Phabricator Conduit API - manage tasks, code reviews, repositories, and more",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",