@freelancercom/phabricator-mcp 2.0.1 → 2.0.3

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.
@@ -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) }] };
@@ -56,6 +58,7 @@ export function registerConpherenceTools(server, client) {
56
58
  title: z.string().optional().describe('New room title'),
57
59
  addParticipantPHIDs: z.array(z.string()).optional().describe('Participant PHIDs to add'),
58
60
  removeParticipantPHIDs: z.array(z.string()).optional().describe('Participant PHIDs to remove'),
61
+ comment: z.string().optional().describe('Send a message alongside the edit (supports Remarkup)'),
59
62
  }, async (params) => {
60
63
  const transactions = [];
61
64
  if (params.title !== undefined) {
@@ -67,6 +70,9 @@ export function registerConpherenceTools(server, client) {
67
70
  if (params.removeParticipantPHIDs !== undefined) {
68
71
  transactions.push({ type: 'participants.remove', value: params.removeParticipantPHIDs });
69
72
  }
73
+ if (params.comment !== undefined) {
74
+ transactions.push({ type: 'comment', value: params.comment });
75
+ }
70
76
  if (transactions.length === 0) {
71
77
  return { content: [{ type: 'text', text: 'No changes specified' }] };
72
78
  }
@@ -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', 'request-review', 'resign', 'commandeer', 'plan-changes', 'close']).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,7 +121,8 @@ 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) }] };
@@ -132,7 +138,8 @@ export function registerDifferentialTools(server, client) {
132
138
  })).optional().describe('Data attachments'),
133
139
  order: z.string().optional().describe('Result order'),
134
140
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
135
- after: z.string().optional().describe('Pagination cursor'),
141
+ after: z.string().optional().describe('Cursor for next-page pagination'),
142
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
136
143
  }, async (params) => {
137
144
  const result = await client.call('differential.changeset.search', params);
138
145
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -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) }] };
@@ -62,7 +66,7 @@ export function registerDiffusionTools(server, client) {
62
66
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
63
67
  });
64
68
  // Read file content from repository
65
- server.tool('phabricator_repository_file_content', 'Read file contents from a Diffusion repository at a given path and commit/branch', {
69
+ server.tool('phabricator_repository_file_content', 'Read file contents from a Diffusion repository at a given path and commit/branch. Returns the file content as a base64-encoded blob. If the file is too large, returns tooHuge: true with no content.', {
66
70
  path: z.string().describe('File path in the repository (e.g., "src/index.ts")'),
67
71
  repository: z.string().optional().describe('Repository callsign, short name, or PHID'),
68
72
  commit: z.string().optional().describe('Commit hash or branch name (default: HEAD)'),
@@ -72,7 +76,14 @@ export function registerDiffusionTools(server, client) {
72
76
  repository: params.repository,
73
77
  commit: params.commit,
74
78
  });
75
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
79
+ if (result.tooHuge || result.tooSlow) {
80
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
81
+ }
82
+ // Fetch actual file content using the returned filePHID
83
+ const fileInfo = await client.call('file.info', {
84
+ phid: result.filePHID,
85
+ });
86
+ return { content: [{ type: 'text', text: JSON.stringify({ ...result, ...fileInfo }, null, 2) }] };
76
87
  });
77
88
  // List branches
78
89
  server.tool('phabricator_branch_search', 'List branches in a Diffusion repository', {
@@ -3,6 +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
7
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
7
8
  after: z.string().optional().describe('Cursor for pagination (chronological key from previous results)'),
8
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) }] };
@@ -78,11 +83,37 @@ export function registerHarbormasterTools(server, client) {
78
83
  server.tool('phabricator_build_command', 'Report build status to Harbormaster. Used by external build systems to notify Phabricator of build results. Provide the build target PHID (use phabricator_build_target_search to find it).', {
79
84
  buildTargetPHID: z.string().describe('Build target PHID to send the message to. Use phabricator_build_target_search to find this.'),
80
85
  type: z.enum(['pass', 'fail', 'work']).describe('Message type: "pass" (build succeeded), "fail" (build failed), "work" (build is still running)'),
86
+ unit: jsonCoerce(z.array(z.object({
87
+ name: z.string().describe('Test name'),
88
+ result: z.string().describe('Result: "pass", "fail", "skip", "broken", "unsound"'),
89
+ namespace: z.string().optional().describe('Test namespace/group'),
90
+ engine: z.string().optional().describe('Test engine name'),
91
+ duration: z.coerce.number().optional().describe('Duration in seconds'),
92
+ path: z.string().optional().describe('File path related to the test'),
93
+ coverage: z.record(z.string(), z.string()).optional().describe('Coverage data as {path: "NNCUUUC..."} where N=not executable, C=covered, U=uncovered'),
94
+ details: z.string().optional().describe('Additional details or failure message'),
95
+ }))).optional().describe('Unit test results to report'),
96
+ lint: jsonCoerce(z.array(z.object({
97
+ name: z.string().describe('Lint message name'),
98
+ code: z.string().describe('Lint rule code'),
99
+ severity: z.string().describe('Severity: "advice", "autofix", "warning", "error", "disabled"'),
100
+ path: z.string().optional().describe('File path'),
101
+ line: z.coerce.number().optional().describe('Line number'),
102
+ char: z.coerce.number().optional().describe('Character offset'),
103
+ description: z.string().optional().describe('Lint message description'),
104
+ }))).optional().describe('Lint results to report'),
81
105
  }, async (params) => {
82
- const result = await client.call('harbormaster.sendmessage', {
106
+ const apiParams = {
83
107
  buildTargetPHID: params.buildTargetPHID,
84
108
  type: params.type,
85
- });
109
+ };
110
+ if (params.unit !== undefined) {
111
+ apiParams.unit = params.unit;
112
+ }
113
+ if (params.lint !== undefined) {
114
+ apiParams.lint = params.lint;
115
+ }
116
+ const result = await client.call('harbormaster.sendmessage', apiParams);
86
117
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
87
118
  });
88
119
  // Search build plans
@@ -96,7 +127,8 @@ export function registerHarbormasterTools(server, client) {
96
127
  })).optional().describe('Search constraints'),
97
128
  order: z.string().optional().describe('Result order'),
98
129
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
99
- 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'),
100
132
  }, async (params) => {
101
133
  const result = await client.call('harbormaster.buildplan.search', params);
102
134
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -23,6 +23,7 @@ export function registerManiphestTools(server, client) {
23
23
  subtaskIDs: z.array(z.coerce.number()).optional().describe('Subtask IDs'),
24
24
  hasParents: z.boolean().optional().describe('Filter to tasks that have parent tasks'),
25
25
  hasSubtasks: z.boolean().optional().describe('Filter to tasks that have 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) }] };
@@ -14,10 +14,13 @@ export function registerPhrictionTools(server, client) {
14
14
  })).optional().describe('Search constraints'),
15
15
  attachments: jsonCoerce(z.object({
16
16
  content: z.boolean().optional().describe('Include document content'),
17
+ subscribers: z.boolean().optional().describe('Include subscriber details'),
18
+ projects: z.boolean().optional().describe('Include tagged projects'),
17
19
  })).optional().describe('Data attachments'),
18
20
  order: z.string().optional().describe('Result order'),
19
21
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
20
- after: z.string().optional().describe('Pagination cursor'),
22
+ after: z.string().optional().describe('Cursor for next-page pagination'),
23
+ before: z.string().optional().describe('Cursor for previous-page pagination'),
21
24
  }, async (params) => {
22
25
  const result = await client.call('phriction.document.search', params);
23
26
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
@@ -27,6 +30,8 @@ export function registerPhrictionTools(server, client) {
27
30
  objectIdentifier: z.string().describe('Document slug, PHID, or ID (e.g., "projects/myproject/")'),
28
31
  title: z.string().optional().describe('Document title'),
29
32
  content: z.string().optional().describe('Document content (Remarkup)'),
33
+ delete: z.boolean().optional().describe('Set to true to delete/archive the document'),
34
+ move: z.string().optional().describe('New slug to move/rename the document to'),
30
35
  addSubscriberPHIDs: z.array(z.string()).optional().describe('Subscriber PHIDs to add'),
31
36
  removeSubscriberPHIDs: z.array(z.string()).optional().describe('Subscriber PHIDs to remove'),
32
37
  }, async (params) => {
@@ -37,6 +42,12 @@ export function registerPhrictionTools(server, client) {
37
42
  if (params.content !== undefined) {
38
43
  transactions.push({ type: 'content', value: params.content });
39
44
  }
45
+ if (params.delete === true) {
46
+ transactions.push({ type: 'delete', value: true });
47
+ }
48
+ if (params.move !== undefined) {
49
+ transactions.push({ type: 'move', value: params.move });
50
+ }
40
51
  if (params.addSubscriberPHIDs !== undefined) {
41
52
  transactions.push({ type: 'subscribers.add', value: params.addSubscriberPHIDs });
42
53
  }
@@ -8,10 +8,12 @@ export function registerProjectTools(server, client) {
8
8
  ids: z.array(z.coerce.number()).optional().describe('Project IDs'),
9
9
  phids: z.array(z.string()).optional().describe('Project PHIDs'),
10
10
  slugs: z.array(z.string()).optional().describe('Project slugs'),
11
- name: z.string().optional().describe('Exact name match'),
11
+ name: z.string().optional().describe('Name substring search'),
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,9 @@ 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)'),
46
+ parent: z.string().optional().describe('Parent project PHID (to create as a subproject)'),
47
+ milestone: z.string().optional().describe('Parent project PHID (to create as a milestone of that project)'),
42
48
  slug: z.string().optional().describe('Project URL slug (replaces ALL existing slugs with this one)'),
43
49
  comment: z.string().optional().describe('Add a comment alongside the edit (supports Remarkup)'),
44
50
  }, async (params) => {
@@ -46,6 +52,15 @@ export function registerProjectTools(server, client) {
46
52
  if (params.name !== undefined) {
47
53
  transactions.push({ type: 'name', value: params.name });
48
54
  }
55
+ if (params.space !== undefined) {
56
+ transactions.push({ type: 'space', value: params.space });
57
+ }
58
+ if (params.parent !== undefined) {
59
+ transactions.push({ type: 'parent', value: params.parent });
60
+ }
61
+ if (params.milestone !== undefined) {
62
+ transactions.push({ type: 'milestone', value: params.milestone });
63
+ }
49
64
  if (params.slug !== undefined) {
50
65
  transactions.push({ type: 'slugs', value: [params.slug] });
51
66
  }
@@ -96,7 +111,8 @@ export function registerProjectTools(server, client) {
96
111
  })).optional().describe('Data attachments'),
97
112
  order: z.string().optional().describe('Result order'),
98
113
  limit: z.coerce.number().max(100).optional().describe('Maximum results (max 100)'),
99
- 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'),
100
116
  }, async (params) => {
101
117
  const result = await client.call('project.column.search', params);
102
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.1",
3
+ "version": "2.0.3",
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",