@freelancercom/phabricator-mcp 2.0.2 → 2.0.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/README.md CHANGED
@@ -175,7 +175,7 @@ Add to your `~/.claude/settings.json`:
175
175
  "mcp__phabricator__phabricator_revision_search",
176
176
  "mcp__phabricator__phabricator_diff_search",
177
177
  "mcp__phabricator__phabricator_diff_raw",
178
- "mcp__phabricator__phabricator_changeset_search",
178
+ "mcp__phabricator__phabricator_revision_paths",
179
179
  "mcp__phabricator__phabricator_repository_search",
180
180
  "mcp__phabricator__phabricator_commit_search",
181
181
  "mcp__phabricator__phabricator_repository_browse",
@@ -237,7 +237,7 @@ To allowlist all tools including write operations, use `"mcp__phabricator__*"` i
237
237
  | `phabricator_revision_inline_comment` | Create an inline comment on a specific line of a diff |
238
238
  | `phabricator_diff_raw` | Get the raw diff/patch content for a diff by ID |
239
239
  | `phabricator_diff_search` | Search diffs (code change snapshots within a revision) |
240
- | `phabricator_changeset_search` | Search changesets (individual changed files within a diff) |
240
+ | `phabricator_revision_paths` | Get the list of changed file paths for a revision |
241
241
 
242
242
  ### Repositories (Diffusion)
243
243
 
@@ -251,6 +251,7 @@ To allowlist all tools including write operations, use `"mcp__phabricator__*"` i
251
251
  | `phabricator_tag_search` | List tags in a repository |
252
252
  | `phabricator_repository_file_history` | Get commit history for a file path |
253
253
  | `phabricator_repository_code_search` | Search (grep) file contents within a repository |
254
+ | `phabricator_repository_edit` | Create or edit a Diffusion repository |
254
255
 
255
256
  ### Users
256
257
 
@@ -4,7 +4,7 @@ export function registerAuditTools(server, client) {
4
4
  server.tool('phabricator_audit_query', 'Search commit audit requests. Find commits needing audit, or audits by a specific user. Uses the audit.query endpoint (no modern replacement available).', {
5
5
  auditorPHIDs: z.array(z.string()).optional().describe('Auditor user/project PHIDs'),
6
6
  commitPHIDs: z.array(z.string()).optional().describe('Commit PHIDs to check audit status for'),
7
- status: z.string().optional().describe('Audit status filter: "audit-none", "audit-needs-audit", "audit-accepted", "audit-concern-raised", "audit-requested"'),
7
+ status: z.string().optional().describe('Audit status filter: "audit-status-any" (default), "audit-status-open", "audit-status-concern", "audit-status-accepted", "audit-status-partial"'),
8
8
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
9
9
  offset: z.coerce.number().optional().describe('Result offset for pagination'),
10
10
  }, async (params) => {
@@ -8,13 +8,15 @@ export function registerConpherenceTools(server, client) {
8
8
  ids: z.array(z.coerce.number()).optional().describe('Room IDs'),
9
9
  phids: z.array(z.string()).optional().describe('Room PHIDs'),
10
10
  participants: z.array(z.string()).optional().describe('Participant user PHIDs'),
11
+ query: z.string().optional().describe('Full-text search query'),
11
12
  })).optional().describe('Search constraints'),
12
13
  attachments: jsonCoerce(z.object({
13
14
  participants: z.boolean().optional().describe('Include participant details'),
14
15
  })).optional().describe('Data attachments'),
15
16
  order: z.string().optional().describe('Result order'),
16
17
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
17
- after: z.string().optional().describe('Pagination cursor'),
18
+ after: z.string().optional().describe('Cursor for next-page pagination'),
19
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
18
20
  }, async (params) => {
19
21
  const result = await client.call('conpherence.search', params);
20
22
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -39,7 +41,7 @@ export function registerConpherenceTools(server, client) {
39
41
  participantPHIDs: z.array(z.string()).optional().describe('Participant user PHIDs to add'),
40
42
  }, async (params) => {
41
43
  const transactions = [
42
- { type: 'title', value: params.title },
44
+ { type: 'name', value: params.title },
43
45
  ];
44
46
  if (params.message !== undefined) {
45
47
  transactions.push({ type: 'comment', value: params.message });
@@ -54,12 +56,17 @@ export function registerConpherenceTools(server, client) {
54
56
  server.tool('phabricator_conpherence_edit', 'Edit a Conpherence chat room/thread. Rename it or manage participants.', {
55
57
  objectIdentifier: z.string().describe('Room ID or PHID'),
56
58
  title: z.string().optional().describe('New room title'),
59
+ topic: z.string().optional().describe('Room topic/description'),
57
60
  addParticipantPHIDs: z.array(z.string()).optional().describe('Participant PHIDs to add'),
58
61
  removeParticipantPHIDs: z.array(z.string()).optional().describe('Participant PHIDs to remove'),
62
+ comment: z.string().optional().describe('Send a message alongside the edit (supports Remarkup)'),
59
63
  }, async (params) => {
60
64
  const transactions = [];
61
65
  if (params.title !== undefined) {
62
- transactions.push({ type: 'title', value: params.title });
66
+ transactions.push({ type: 'name', value: params.title });
67
+ }
68
+ if (params.topic !== undefined) {
69
+ transactions.push({ type: 'topic', value: params.topic });
63
70
  }
64
71
  if (params.addParticipantPHIDs !== undefined) {
65
72
  transactions.push({ type: 'participants.add', value: params.addParticipantPHIDs });
@@ -67,6 +74,9 @@ export function registerConpherenceTools(server, client) {
67
74
  if (params.removeParticipantPHIDs !== undefined) {
68
75
  transactions.push({ type: 'participants.remove', value: params.removeParticipantPHIDs });
69
76
  }
77
+ if (params.comment !== undefined) {
78
+ transactions.push({ type: 'comment', value: params.comment });
79
+ }
70
80
  if (transactions.length === 0) {
71
81
  return { content: [{ type: 'text', text: 'No changes specified' }] };
72
82
  }
@@ -3,7 +3,7 @@ import { jsonCoerce } from './coerce.js';
3
3
  export function registerDifferentialTools(server, client) {
4
4
  // Search revisions
5
5
  server.tool('phabricator_revision_search', 'Search Differential revisions (code reviews)', {
6
- queryKey: z.string().optional().describe('Built-in query: "all", "active", "authored", "waiting", "reviewable"'),
6
+ queryKey: z.string().optional().describe('Built-in query: "all", "active", "authored"'),
7
7
  constraints: jsonCoerce(z.object({
8
8
  ids: z.array(z.coerce.number()).optional().describe('Revision IDs'),
9
9
  phids: z.array(z.string()).optional().describe('Revision PHIDs'),
@@ -23,11 +23,12 @@ export function registerDifferentialTools(server, client) {
23
23
  reviewers: z.boolean().optional().describe('Include reviewers'),
24
24
  subscribers: z.boolean().optional().describe('Include subscribers'),
25
25
  projects: z.boolean().optional().describe('Include projects'),
26
- 'reviewers-extra': z.boolean().optional().describe('Include detailed reviewer info with status (accepted, rejected, etc.)'),
26
+ 'reviewers-extra': z.boolean().optional().describe('Include detailed reviewer info with voids and diff context (may not be available on all Phabricator versions)'),
27
27
  })).optional().describe('Data attachments'),
28
28
  order: z.string().optional().describe('Result order: "newest", "oldest", "updated", "relevance"'),
29
29
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
30
- after: z.string().optional().describe('Pagination cursor'),
30
+ after: z.string().optional().describe('Cursor for next-page pagination'),
31
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
31
32
  }, async (params) => {
32
33
  const result = await client.call('differential.revision.search', params);
33
34
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -38,12 +39,13 @@ export function registerDifferentialTools(server, client) {
38
39
  title: z.string().optional().describe('New title'),
39
40
  summary: z.string().optional().describe('New summary'),
40
41
  testPlan: z.string().optional().describe('New test plan'),
41
- addReviewerPHIDs: z.array(z.string()).optional().describe('Add reviewers'),
42
+ addReviewerPHIDs: z.array(z.string()).optional().describe('Add reviewers. Prefix with "blocking(PHID)" to add as blocking reviewer'),
42
43
  removeReviewerPHIDs: z.array(z.string()).optional().describe('Remove reviewers'),
44
+ setReviewerPHIDs: z.array(z.string()).optional().describe('Replace all reviewers with this list. Prefix with "blocking(PHID)" for blocking'),
43
45
  addProjectPHIDs: z.array(z.string()).optional().describe('Add projects'),
44
46
  removeProjectPHIDs: z.array(z.string()).optional().describe('Remove projects'),
45
47
  comment: z.string().optional().describe('Add a comment'),
46
- action: z.enum(['accept', 'reject', 'abandon', 'reclaim', 'request-review', 'resign', 'commandeer', 'plan-changes']).optional().describe('Revision action to take'),
48
+ action: z.enum(['accept', 'reject', 'abandon', 'reclaim', 'reopen', 'request-review', 'resign', 'commandeer', 'plan-changes', 'close', 'draft']).optional().describe('Revision action to take'),
47
49
  addSubscriberPHIDs: z.array(z.string()).optional().describe('Subscriber PHIDs to add'),
48
50
  removeSubscriberPHIDs: z.array(z.string()).optional().describe('Subscriber PHIDs to remove'),
49
51
  repositoryPHID: z.string().optional().describe('Repository PHID to associate with the revision'),
@@ -64,6 +66,9 @@ export function registerDifferentialTools(server, client) {
64
66
  if (params.removeReviewerPHIDs !== undefined) {
65
67
  transactions.push({ type: 'reviewers.remove', value: params.removeReviewerPHIDs });
66
68
  }
69
+ if (params.setReviewerPHIDs !== undefined) {
70
+ transactions.push({ type: 'reviewers.set', value: params.setReviewerPHIDs });
71
+ }
67
72
  if (params.addProjectPHIDs !== undefined) {
68
73
  transactions.push({ type: 'projects.add', value: params.addProjectPHIDs });
69
74
  }
@@ -116,25 +121,19 @@ export function registerDifferentialTools(server, client) {
116
121
  })).optional().describe('Data attachments'),
117
122
  order: z.string().optional().describe('Result order: "newest", "oldest"'),
118
123
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
119
- after: z.string().optional().describe('Pagination cursor'),
124
+ after: z.string().optional().describe('Cursor for next-page pagination'),
125
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
120
126
  }, async (params) => {
121
127
  const result = await client.call('differential.diff.search', params);
122
128
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
123
129
  });
124
- // Search changesets (changed files within a diff)
125
- server.tool('phabricator_changeset_search', 'Search changesets (individual changed files) within a Differential diff. Use phabricator_diff_search to find the diff PHID first.', {
126
- queryKey: z.string().optional().describe('Built-in query: "all"'),
127
- constraints: jsonCoerce(z.object({
128
- diffPHIDs: z.array(z.string()).optional().describe('Diff PHIDs to list changesets for'),
129
- })).optional().describe('Search constraints'),
130
- attachments: jsonCoerce(z.object({
131
- hunks: z.boolean().optional().describe('Include diff hunks (actual changed content)'),
132
- })).optional().describe('Data attachments'),
133
- order: z.string().optional().describe('Result order'),
134
- limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
135
- after: z.string().optional().describe('Pagination cursor'),
130
+ // Get changed file paths for a revision
131
+ server.tool('phabricator_revision_paths', 'Get the list of changed file paths for a Differential revision. Returns an array of file path strings.', {
132
+ revision_id: z.coerce.number().describe('Numeric revision ID (e.g., 123). Do not include the "D" prefix.'),
136
133
  }, async (params) => {
137
- const result = await client.call('differential.changeset.search', params);
134
+ const result = await client.call('differential.getcommitpaths', {
135
+ revision_id: params.revision_id,
136
+ });
138
137
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
139
138
  });
140
139
  // Create inline comment on a diff
@@ -20,7 +20,8 @@ export function registerDiffusionTools(server, client) {
20
20
  })).optional().describe('Data attachments'),
21
21
  order: z.string().optional().describe('Result order'),
22
22
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
23
- after: z.string().optional().describe('Pagination cursor'),
23
+ after: z.string().optional().describe('Cursor for next-page pagination'),
24
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
24
25
  }, async (params) => {
25
26
  const result = await client.call('diffusion.repository.search', params);
26
27
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -34,6 +35,8 @@ export function registerDiffusionTools(server, client) {
34
35
  repositoryPHIDs: z.array(z.string()).optional().describe('Repository PHIDs'),
35
36
  identifiers: z.array(z.string()).optional().describe('Commit identifiers (hashes)'),
36
37
  authorPHIDs: z.array(z.string()).optional().describe('Author PHIDs'),
38
+ responsiblePHIDs: z.array(z.string()).optional().describe('User PHIDs responsible (as author or auditor)'),
39
+ statuses: z.array(z.string()).optional().describe('Audit statuses: audited, needs-audit, concern-raised, partially-audited'),
37
40
  query: z.string().optional().describe('Full-text search query'),
38
41
  })).optional().describe('Search constraints'),
39
42
  attachments: jsonCoerce(z.object({
@@ -43,7 +46,8 @@ export function registerDiffusionTools(server, client) {
43
46
  })).optional().describe('Data attachments'),
44
47
  order: z.string().optional().describe('Result order'),
45
48
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
46
- after: z.string().optional().describe('Pagination cursor'),
49
+ after: z.string().optional().describe('Cursor for next-page pagination'),
50
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
47
51
  }, async (params) => {
48
52
  const result = await client.call('diffusion.commit.search', params);
49
53
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -53,12 +57,11 @@ export function registerDiffusionTools(server, client) {
53
57
  path: z.string().describe('Path to browse (e.g., "/", "/src/")'),
54
58
  repository: z.string().optional().describe('Repository callsign, short name, or PHID'),
55
59
  commit: z.string().optional().describe('Commit hash or branch name (default: HEAD)'),
60
+ needValidityOnly: z.boolean().optional().describe('Only check path validity without loading the full tree'),
61
+ limit: z.coerce.number().optional().describe('Maximum entries to return'),
62
+ offset: z.coerce.number().optional().describe('Result offset for pagination'),
56
63
  }, async (params) => {
57
- const result = await client.call('diffusion.browsequery', {
58
- path: params.path,
59
- repository: params.repository,
60
- commit: params.commit,
61
- });
64
+ const result = await client.call('diffusion.browsequery', params);
62
65
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
63
66
  });
64
67
  // Read file content from repository
@@ -85,6 +88,8 @@ export function registerDiffusionTools(server, client) {
85
88
  server.tool('phabricator_branch_search', 'List branches in a Diffusion repository', {
86
89
  repository: z.string().describe('Repository callsign, short name, or PHID'),
87
90
  contains: z.string().optional().describe('Only branches containing this commit'),
91
+ patterns: z.array(z.string()).optional().describe('Filter branches by glob patterns'),
92
+ closed: z.boolean().optional().describe('Filter by open/closed status (Mercurial only)'),
88
93
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
89
94
  offset: z.coerce.number().optional().describe('Result offset for pagination'),
90
95
  }, async (params) => {
@@ -94,6 +99,9 @@ export function registerDiffusionTools(server, client) {
94
99
  // List tags
95
100
  server.tool('phabricator_tag_search', 'List tags in a Diffusion repository', {
96
101
  repository: z.string().describe('Repository callsign, short name, or PHID'),
102
+ names: z.array(z.string()).optional().describe('Filter to specific tag names'),
103
+ commit: z.string().optional().describe('Show tags reachable from this commit'),
104
+ needMessages: z.boolean().optional().describe('Include tag messages in results'),
97
105
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
98
106
  offset: z.coerce.number().optional().describe('Result offset for pagination'),
99
107
  }, async (params) => {
@@ -105,6 +113,9 @@ export function registerDiffusionTools(server, client) {
105
113
  path: z.string().describe('File path in the repository'),
106
114
  repository: z.string().optional().describe('Repository callsign, short name, or PHID'),
107
115
  commit: z.string().optional().describe('Commit hash or branch to start from (default: HEAD)'),
116
+ against: z.string().optional().describe('Compare against another commit'),
117
+ needDirectChanges: z.boolean().optional().describe('Include direct change info per path entry'),
118
+ needChildChanges: z.boolean().optional().describe('Include child change info per path entry'),
108
119
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
109
120
  offset: z.coerce.number().optional().describe('Result offset for pagination'),
110
121
  }, async (params) => {
@@ -130,4 +141,59 @@ export function registerDiffusionTools(server, client) {
130
141
  });
131
142
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
132
143
  });
144
+ // Create or edit a repository
145
+ server.tool('phabricator_repository_edit', 'Create or edit a Diffusion repository. To create, omit objectIdentifier and provide vcs + name.', {
146
+ objectIdentifier: z.string().optional().describe('Repository PHID, ID, callsign, or short name (omit to create new)'),
147
+ vcs: z.enum(['git', 'hg', 'svn']).optional().describe('Version control system (required for creation)'),
148
+ name: z.string().optional().describe('Repository name'),
149
+ callsign: z.string().optional().describe('Repository callsign (short uppercase identifier)'),
150
+ shortName: z.string().optional().describe('Repository short name (URL slug)'),
151
+ description: z.string().optional().describe('Repository description (Remarkup)'),
152
+ defaultBranch: z.string().optional().describe('Default branch name'),
153
+ status: z.enum(['active', 'inactive']).optional().describe('Repository status'),
154
+ addProjectPHIDs: z.array(z.string()).optional().describe('Project PHIDs to add'),
155
+ removeProjectPHIDs: z.array(z.string()).optional().describe('Project PHIDs to remove'),
156
+ space: z.string().optional().describe('Space PHID (for multi-space installations)'),
157
+ }, async (params) => {
158
+ const transactions = [];
159
+ if (params.vcs !== undefined) {
160
+ transactions.push({ type: 'vcs', value: params.vcs });
161
+ }
162
+ if (params.name !== undefined) {
163
+ transactions.push({ type: 'name', value: params.name });
164
+ }
165
+ if (params.callsign !== undefined) {
166
+ transactions.push({ type: 'callsign', value: params.callsign });
167
+ }
168
+ if (params.shortName !== undefined) {
169
+ transactions.push({ type: 'shortName', value: params.shortName });
170
+ }
171
+ if (params.description !== undefined) {
172
+ transactions.push({ type: 'description', value: params.description });
173
+ }
174
+ if (params.defaultBranch !== undefined) {
175
+ transactions.push({ type: 'defaultBranch', value: params.defaultBranch });
176
+ }
177
+ if (params.status !== undefined) {
178
+ transactions.push({ type: 'status', value: params.status });
179
+ }
180
+ if (params.addProjectPHIDs !== undefined) {
181
+ transactions.push({ type: 'projects.add', value: params.addProjectPHIDs });
182
+ }
183
+ if (params.removeProjectPHIDs !== undefined) {
184
+ transactions.push({ type: 'projects.remove', value: params.removeProjectPHIDs });
185
+ }
186
+ if (params.space !== undefined) {
187
+ transactions.push({ type: 'space', value: params.space });
188
+ }
189
+ if (transactions.length === 0) {
190
+ return { content: [{ type: 'text', text: 'No changes specified' }] };
191
+ }
192
+ const apiParams = { transactions };
193
+ if (params.objectIdentifier !== undefined) {
194
+ apiParams.objectIdentifier = params.objectIdentifier;
195
+ }
196
+ const result = await client.call('diffusion.repository.edit', apiParams);
197
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
198
+ });
133
199
  }
@@ -3,7 +3,7 @@ export function registerFeedTools(server, client) {
3
3
  // Query activity feed
4
4
  server.tool('phabricator_feed_query', 'Query the Phabricator activity feed. Returns recent activity (task updates, revision changes, commits, etc.) as an object keyed by story PHID. Uses feed.query (the only Conduit method for feed data).', {
5
5
  filterPHIDs: z.array(z.string()).optional().describe('Only show activity involving these PHIDs (user, project, task, etc.)'),
6
- view: z.enum(['data', 'text', 'html']).optional().describe('Output format: "data" (structured, default), "text" (human-readable), "html" (rendered HTML)'),
6
+ view: z.enum(['data', 'text', 'html', 'html-summary']).optional().describe('Output format: "data" (structured, default), "text" (human-readable), "html" (rendered HTML), "html-summary" (title only)'),
7
7
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
8
8
  after: z.string().optional().describe('Cursor for pagination (chronological key from previous results)'),
9
9
  before: z.string().optional().describe('Cursor for reverse pagination'),
@@ -20,10 +20,13 @@ export function registerFileTools(server, client) {
20
20
  phids: z.array(z.string()).optional().describe('File PHIDs'),
21
21
  authorPHIDs: z.array(z.string()).optional().describe('Author PHIDs'),
22
22
  names: z.array(z.string()).optional().describe('File names'),
23
+ dateCreatedStart: z.coerce.number().optional().describe('Created after (epoch timestamp)'),
24
+ dateCreatedEnd: z.coerce.number().optional().describe('Created before (epoch timestamp)'),
23
25
  })).optional().describe('Search constraints'),
24
26
  order: z.string().optional().describe('Result order'),
25
27
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
26
- after: z.string().optional().describe('Pagination cursor'),
28
+ after: z.string().optional().describe('Cursor for next-page pagination'),
29
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
27
30
  }, async (params) => {
28
31
  const result = await client.call('file.search', params);
29
32
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -16,7 +16,8 @@ export function registerHarbormasterTools(server, client) {
16
16
  })).optional().describe('Data attachments'),
17
17
  order: z.string().optional().describe('Result order'),
18
18
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
19
- after: z.string().optional().describe('Pagination cursor'),
19
+ after: z.string().optional().describe('Cursor for next-page pagination'),
20
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
20
21
  }, async (params) => {
21
22
  const result = await client.call('harbormaster.buildable.search', params);
22
23
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -30,13 +31,15 @@ export function registerHarbormasterTools(server, client) {
30
31
  buildablePHIDs: z.array(z.string()).optional().describe('Buildable PHIDs'),
31
32
  buildPlanPHIDs: z.array(z.string()).optional().describe('Build plan PHIDs'),
32
33
  statuses: z.array(z.string()).optional().describe('Build statuses: building, passed, failed, aborted, error, paused, deadlocked'),
34
+ initiatorPHIDs: z.array(z.string()).optional().describe('PHIDs of users/objects that initiated the build'),
33
35
  })).optional().describe('Search constraints'),
34
36
  attachments: jsonCoerce(z.object({
35
37
  targets: z.boolean().optional().describe('Include build targets for each build'),
36
38
  })).optional().describe('Data attachments'),
37
39
  order: z.string().optional().describe('Result order'),
38
40
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
39
- after: z.string().optional().describe('Pagination cursor'),
41
+ after: z.string().optional().describe('Cursor for next-page pagination'),
42
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
40
43
  }, async (params) => {
41
44
  const result = await client.call('harbormaster.build.search', params);
42
45
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -51,7 +54,8 @@ export function registerHarbormasterTools(server, client) {
51
54
  })).optional().describe('Search constraints'),
52
55
  order: z.string().optional().describe('Result order'),
53
56
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
54
- after: z.string().optional().describe('Pagination cursor'),
57
+ after: z.string().optional().describe('Cursor for next-page pagination'),
58
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
55
59
  }, async (params) => {
56
60
  const result = await client.call('harbormaster.target.search', params);
57
61
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -69,7 +73,8 @@ export function registerHarbormasterTools(server, client) {
69
73
  })).optional().describe('Data attachments'),
70
74
  order: z.string().optional().describe('Result order'),
71
75
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
72
- after: z.string().optional().describe('Pagination cursor'),
76
+ after: z.string().optional().describe('Cursor for next-page pagination'),
77
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
73
78
  }, async (params) => {
74
79
  const result = await client.call('harbormaster.log.search', params);
75
80
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -122,7 +127,8 @@ export function registerHarbormasterTools(server, client) {
122
127
  })).optional().describe('Search constraints'),
123
128
  order: z.string().optional().describe('Result order'),
124
129
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
125
- after: z.string().optional().describe('Pagination cursor'),
130
+ after: z.string().optional().describe('Cursor for next-page pagination'),
131
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
126
132
  }, async (params) => {
127
133
  const result = await client.call('harbormaster.buildplan.search', params);
128
134
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -21,8 +21,9 @@ export function registerManiphestTools(server, client) {
21
21
  modifiedEnd: z.coerce.number().optional().describe('Modified before (epoch timestamp)'),
22
22
  parentIDs: z.array(z.coerce.number()).optional().describe('Parent task IDs'),
23
23
  subtaskIDs: z.array(z.coerce.number()).optional().describe('Subtask IDs'),
24
- hasParents: z.boolean().optional().describe('Filter to tasks that have parent tasks'),
25
- hasSubtasks: z.boolean().optional().describe('Filter to tasks that have subtasks'),
24
+ hasParents: z.boolean().optional().describe('Filter to tasks that have open parent tasks'),
25
+ hasSubtasks: z.boolean().optional().describe('Filter to tasks that have open subtasks'),
26
+ spacePHIDs: z.array(z.string()).optional().describe('Filter by Space PHIDs (for multi-space installations)'),
26
27
  })).optional().describe('Search constraints'),
27
28
  attachments: jsonCoerce(z.object({
28
29
  columns: z.boolean().optional().describe('Include workboard column info'),
@@ -30,9 +31,10 @@ export function registerManiphestTools(server, client) {
30
31
  subscribers: z.boolean().optional().describe('Include subscriber info'),
31
32
  'custom-fields': z.boolean().optional().describe('Include custom field values in results'),
32
33
  })).optional().describe('Data attachments to include'),
33
- order: z.string().optional().describe('Result order: "priority", "updated", "newest", "oldest"'),
34
+ order: z.string().optional().describe('Result order: "priority", "updated", "newest", "oldest", "title", "relevance"'),
34
35
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
35
- after: z.string().optional().describe('Cursor for pagination'),
36
+ after: z.string().optional().describe('Cursor for next-page pagination'),
37
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
36
38
  }, async (params) => {
37
39
  const result = await client.call('maniphest.search', params);
38
40
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -42,13 +44,14 @@ export function registerManiphestTools(server, client) {
42
44
  title: z.string().describe('Task title'),
43
45
  description: z.string().optional().describe('Task description (supports Remarkup)'),
44
46
  ownerPHID: z.string().optional().describe('Assigned owner PHID'),
45
- priority: z.string().optional().describe('Priority: unbreak, triage, high, normal, low, wish'),
47
+ priority: z.string().optional().describe('Priority keyword (unbreak, triage, high, normal, low, wish) or numeric value'),
46
48
  projectPHIDs: z.array(z.string()).optional().describe('Project PHIDs to tag'),
47
49
  subscriberPHIDs: z.array(z.string()).optional().describe('Subscriber PHIDs'),
48
50
  status: z.string().optional().describe('Initial status'),
49
51
  subtype: z.string().optional().describe('Task subtype (e.g. "default", "incident")'),
50
52
  parentPHIDs: z.array(z.string()).optional().describe('Parent task PHIDs'),
51
53
  subtaskPHIDs: z.array(z.string()).optional().describe('Subtask PHIDs'),
54
+ space: z.string().optional().describe('Space PHID to place the task in (for multi-space installations)'),
52
55
  comment: z.string().optional().describe('Initial comment on the task (supports Remarkup)'),
53
56
  customFields: jsonCoerce(z.record(z.string(), z.unknown())).optional().describe('Custom field transactions. Keys are transaction types (e.g. "custom.my-field"), values are the field values. Check your Phabricator Conduit console (conduit/method/maniphest.edit/) for available fields.'),
54
57
  }, async (params) => {
@@ -82,6 +85,9 @@ export function registerManiphestTools(server, client) {
82
85
  if (params.subtaskPHIDs !== undefined) {
83
86
  transactions.push({ type: 'subtasks.set', value: params.subtaskPHIDs });
84
87
  }
88
+ if (params.space !== undefined) {
89
+ transactions.push({ type: 'space', value: params.space });
90
+ }
85
91
  if (params.comment !== undefined) {
86
92
  transactions.push({ type: 'comment', value: params.comment });
87
93
  }
@@ -111,6 +117,7 @@ export function registerManiphestTools(server, client) {
111
117
  addSubtaskPHIDs: z.array(z.string()).optional().describe('Subtask PHIDs to add'),
112
118
  removeSubtaskPHIDs: z.array(z.string()).optional().describe('Subtask PHIDs to remove'),
113
119
  columnPHID: z.string().optional().describe('Move to workboard column'),
120
+ space: z.string().optional().describe('Space PHID to move the task to (for multi-space installations)'),
114
121
  comment: z.string().optional().describe('Add a comment alongside the edit (supports Remarkup)'),
115
122
  customFields: jsonCoerce(z.record(z.string(), z.unknown())).optional().describe('Custom field transactions. Keys are transaction types (e.g. "custom.my-field"), values are the field values. Check your Phabricator Conduit console (conduit/method/maniphest.edit/) for available fields.'),
116
123
  }, async (params) => {
@@ -160,6 +167,9 @@ export function registerManiphestTools(server, client) {
160
167
  if (params.columnPHID !== undefined) {
161
168
  transactions.push({ type: 'column', value: [{ columnPHID: params.columnPHID }] });
162
169
  }
170
+ if (params.space !== undefined) {
171
+ transactions.push({ type: 'space', value: params.space });
172
+ }
163
173
  if (params.comment !== undefined) {
164
174
  transactions.push({ type: 'comment', value: params.comment });
165
175
  }
@@ -11,6 +11,8 @@ export function registerOwnersTools(server, client) {
11
11
  repositoryPHIDs: z.array(z.string()).optional().describe('Repository PHIDs'),
12
12
  paths: z.array(z.array(z.string())).optional().describe('Code paths as [repositoryPHID, path] pairs (e.g. [["PHID-REPO-xxx", "/src/foo.ts"]])'),
13
13
  statuses: z.array(z.string()).optional().describe('Package statuses'),
14
+ dominion: z.array(z.string()).optional().describe('Ownership strength: "strong" (default) or "weak"'),
15
+ autoReview: z.array(z.string()).optional().describe('Auto-review setting: "none", "subscribe", "review", "block"'),
14
16
  query: z.string().optional().describe('Full-text search query'),
15
17
  })).optional().describe('Search constraints'),
16
18
  attachments: jsonCoerce(z.object({
@@ -19,7 +21,8 @@ export function registerOwnersTools(server, client) {
19
21
  })).optional().describe('Data attachments'),
20
22
  order: z.string().optional().describe('Result order'),
21
23
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
22
- after: z.string().optional().describe('Pagination cursor'),
24
+ after: z.string().optional().describe('Cursor for next-page pagination'),
25
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
23
26
  }, async (params) => {
24
27
  const result = await client.call('owners.search', params);
25
28
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -9,6 +9,7 @@ export function registerPasteTools(server, client) {
9
9
  phids: z.array(z.string()).optional().describe('Paste PHIDs'),
10
10
  authorPHIDs: z.array(z.string()).optional().describe('Author PHIDs'),
11
11
  languages: z.array(z.string()).optional().describe('Languages'),
12
+ statuses: z.array(z.string()).optional().describe('Statuses: active, archived'),
12
13
  query: z.string().optional().describe('Full-text search query'),
13
14
  })).optional().describe('Search constraints'),
14
15
  attachments: jsonCoerce(z.object({
@@ -16,7 +17,8 @@ export function registerPasteTools(server, client) {
16
17
  })).optional().describe('Data attachments'),
17
18
  order: z.string().optional().describe('Result order'),
18
19
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
19
- after: z.string().optional().describe('Pagination cursor'),
20
+ after: z.string().optional().describe('Cursor for next-page pagination'),
21
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
20
22
  }, async (params) => {
21
23
  const result = await client.call('paste.search', params);
22
24
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -56,6 +58,7 @@ export function registerPasteTools(server, client) {
56
58
  status: z.string().optional().describe('Status: active or archived'),
57
59
  addSubscriberPHIDs: z.array(z.string()).optional().describe('Subscriber PHIDs to add'),
58
60
  removeSubscriberPHIDs: z.array(z.string()).optional().describe('Subscriber PHIDs to remove'),
61
+ comment: z.string().optional().describe('Add a comment alongside the edit (supports Remarkup)'),
59
62
  }, async (params) => {
60
63
  const transactions = [];
61
64
  if (params.title !== undefined) {
@@ -76,6 +79,9 @@ export function registerPasteTools(server, client) {
76
79
  if (params.removeSubscriberPHIDs !== undefined) {
77
80
  transactions.push({ type: 'subscribers.remove', value: params.removeSubscriberPHIDs });
78
81
  }
82
+ if (params.comment !== undefined) {
83
+ transactions.push({ type: 'comment', value: params.comment });
84
+ }
79
85
  if (transactions.length === 0) {
80
86
  return { content: [{ type: 'text', text: 'No changes specified' }] };
81
87
  }
@@ -7,6 +7,7 @@ export function registerPhameTools(server, client) {
7
7
  constraints: jsonCoerce(z.object({
8
8
  ids: z.array(z.coerce.number()).optional().describe('Blog IDs'),
9
9
  phids: z.array(z.string()).optional().describe('Blog PHIDs'),
10
+ statuses: z.array(z.string()).optional().describe('Blog statuses'),
10
11
  query: z.string().optional().describe('Full-text search query'),
11
12
  })).optional().describe('Search constraints'),
12
13
  attachments: jsonCoerce(z.object({
@@ -14,7 +15,8 @@ export function registerPhameTools(server, client) {
14
15
  })).optional().describe('Data attachments'),
15
16
  order: z.string().optional().describe('Result order'),
16
17
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
17
- after: z.string().optional().describe('Cursor for pagination'),
18
+ after: z.string().optional().describe('Cursor for next-page pagination'),
19
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
18
20
  }, async (params) => {
19
21
  const result = await client.call('phame.blog.search', params);
20
22
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -26,6 +28,7 @@ export function registerPhameTools(server, client) {
26
28
  ids: z.array(z.coerce.number()).optional().describe('Post IDs'),
27
29
  phids: z.array(z.string()).optional().describe('Post PHIDs'),
28
30
  blogPHIDs: z.array(z.string()).optional().describe('Filter by blog PHIDs'),
31
+ authorPHIDs: z.array(z.string()).optional().describe('Filter by author PHIDs'),
29
32
  visibility: z.array(z.coerce.number()).optional().describe('Visibility: 1 (published), 0 (draft), 2 (archived). Note: use these numeric codes in search; use string names like "published" in create/edit.'),
30
33
  query: z.string().optional().describe('Full-text search query'),
31
34
  })).optional().describe('Search constraints'),
@@ -34,7 +37,8 @@ export function registerPhameTools(server, client) {
34
37
  })).optional().describe('Data attachments'),
35
38
  order: z.string().optional().describe('Result order'),
36
39
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
37
- after: z.string().optional().describe('Cursor for pagination'),
40
+ after: z.string().optional().describe('Cursor for next-page pagination'),
41
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
38
42
  }, async (params) => {
39
43
  const result = await client.call('phame.post.search', params);
40
44
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -8,16 +8,20 @@ export function registerPhrictionTools(server, client) {
8
8
  ids: z.array(z.coerce.number()).optional().describe('Document IDs'),
9
9
  phids: z.array(z.string()).optional().describe('Document PHIDs'),
10
10
  paths: z.array(z.string()).optional().describe('Document paths'),
11
- ancestorPaths: z.array(z.string()).optional().describe('Ancestor paths to search under'),
11
+ parentPaths: z.array(z.string()).optional().describe('Parent paths (direct children only)'),
12
+ ancestorPaths: z.array(z.string()).optional().describe('Ancestor paths to search under (any depth)'),
12
13
  statuses: z.array(z.string()).optional().describe('Document statuses'),
13
14
  query: z.string().optional().describe('Full-text search query'),
14
15
  })).optional().describe('Search constraints'),
15
16
  attachments: jsonCoerce(z.object({
16
17
  content: z.boolean().optional().describe('Include document content'),
18
+ subscribers: z.boolean().optional().describe('Include subscriber details'),
19
+ projects: z.boolean().optional().describe('Include tagged projects'),
17
20
  })).optional().describe('Data attachments'),
18
21
  order: z.string().optional().describe('Result order'),
19
22
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
20
- after: z.string().optional().describe('Pagination cursor'),
23
+ after: z.string().optional().describe('Cursor for next-page pagination'),
24
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
21
25
  }, async (params) => {
22
26
  const result = await client.call('phriction.document.search', params);
23
27
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -12,6 +12,8 @@ export function registerProjectTools(server, client) {
12
12
  members: z.array(z.string()).optional().describe('Member user PHIDs'),
13
13
  watchers: z.array(z.string()).optional().describe('Watcher user PHIDs'),
14
14
  ancestors: z.array(z.string()).optional().describe('Ancestor project PHIDs'),
15
+ parentPHIDs: z.array(z.string()).optional().describe('Parent project PHIDs (find subprojects)'),
16
+ icons: z.array(z.string()).optional().describe('Filter by project icon'),
15
17
  isMilestone: z.boolean().optional().describe('Filter milestones'),
16
18
  isRoot: z.boolean().optional().describe('Filter root projects'),
17
19
  query: z.string().optional().describe('Full-text search query'),
@@ -23,7 +25,8 @@ export function registerProjectTools(server, client) {
23
25
  })).optional().describe('Data attachments'),
24
26
  order: z.string().optional().describe('Result order'),
25
27
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
26
- after: z.string().optional().describe('Pagination cursor'),
28
+ after: z.string().optional().describe('Cursor for next-page pagination'),
29
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
27
30
  }, async (params) => {
28
31
  const result = await client.call('project.search', params);
29
32
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -39,6 +42,7 @@ export function registerProjectTools(server, client) {
39
42
  removeMemberPHIDs: z.array(z.string()).optional().describe('Remove members'),
40
43
  addSubscriberPHIDs: z.array(z.string()).optional().describe('Subscriber PHIDs to add'),
41
44
  removeSubscriberPHIDs: z.array(z.string()).optional().describe('Subscriber PHIDs to remove'),
45
+ space: z.string().optional().describe('Space PHID (for multi-space installations)'),
42
46
  parent: z.string().optional().describe('Parent project PHID (to create as a subproject)'),
43
47
  milestone: z.string().optional().describe('Parent project PHID (to create as a milestone of that project)'),
44
48
  slug: z.string().optional().describe('Project URL slug (replaces ALL existing slugs with this one)'),
@@ -48,6 +52,9 @@ export function registerProjectTools(server, client) {
48
52
  if (params.name !== undefined) {
49
53
  transactions.push({ type: 'name', value: params.name });
50
54
  }
55
+ if (params.space !== undefined) {
56
+ transactions.push({ type: 'space', value: params.space });
57
+ }
51
58
  if (params.parent !== undefined) {
52
59
  transactions.push({ type: 'parent', value: params.parent });
53
60
  }
@@ -104,7 +111,8 @@ export function registerProjectTools(server, client) {
104
111
  })).optional().describe('Data attachments'),
105
112
  order: z.string().optional().describe('Result order'),
106
113
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
107
- after: z.string().optional().describe('Pagination cursor'),
114
+ after: z.string().optional().describe('Cursor for next-page pagination'),
115
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
108
116
  }, async (params) => {
109
117
  const result = await client.call('project.column.search', params);
110
118
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -18,6 +18,8 @@ export function registerUserTools(server, client) {
18
18
  isDisabled: z.boolean().optional().describe('Filter by disabled status'),
19
19
  isBot: z.boolean().optional().describe('Filter by bot status'),
20
20
  isMailingList: z.boolean().optional().describe('Filter by mailing list status'),
21
+ createdStart: z.coerce.number().optional().describe('Created after (epoch timestamp)'),
22
+ createdEnd: z.coerce.number().optional().describe('Created before (epoch timestamp)'),
21
23
  query: z.string().optional().describe('Full-text search query'),
22
24
  })).optional().describe('Search constraints'),
23
25
  attachments: jsonCoerce(z.object({
@@ -25,7 +27,8 @@ export function registerUserTools(server, client) {
25
27
  })).optional().describe('Data attachments'),
26
28
  order: z.string().optional().describe('Result order'),
27
29
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
28
- after: z.string().optional().describe('Pagination cursor'),
30
+ after: z.string().optional().describe('Cursor for next-page pagination'),
31
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
29
32
  }, async (params) => {
30
33
  const result = await client.call('user.search', params);
31
34
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freelancercom/phabricator-mcp",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
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",