@freelancercom/phabricator-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,97 @@
1
+ import { z } from 'zod';
2
+ export function registerDifferentialTools(server, client) {
3
+ // Search revisions
4
+ server.tool('phabricator_revision_search', 'Search Differential revisions (code reviews)', {
5
+ queryKey: z.string().optional().describe('Built-in query: "all", "active", "authored", "waiting"'),
6
+ constraints: z.object({
7
+ ids: z.array(z.number()).optional().describe('Revision IDs'),
8
+ phids: z.array(z.string()).optional().describe('Revision PHIDs'),
9
+ authorPHIDs: z.array(z.string()).optional().describe('Author PHIDs'),
10
+ reviewerPHIDs: z.array(z.string()).optional().describe('Reviewer PHIDs'),
11
+ repositoryPHIDs: z.array(z.string()).optional().describe('Repository PHIDs'),
12
+ statuses: z.array(z.string()).optional().describe('Statuses: needs-review, needs-revision, accepted, published, abandoned, changes-planned'),
13
+ }).optional().describe('Search constraints'),
14
+ attachments: z.object({
15
+ reviewers: z.boolean().optional().describe('Include reviewers'),
16
+ subscribers: z.boolean().optional().describe('Include subscribers'),
17
+ projects: z.boolean().optional().describe('Include projects'),
18
+ }).optional().describe('Data attachments'),
19
+ order: z.string().optional().describe('Result order'),
20
+ limit: z.number().max(100).optional().describe('Maximum results'),
21
+ after: z.string().optional().describe('Pagination cursor'),
22
+ }, async (params) => {
23
+ const result = await client.call('differential.revision.search', params);
24
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
25
+ });
26
+ // Edit revision
27
+ server.tool('phabricator_revision_edit', 'Edit a Differential revision', {
28
+ objectIdentifier: z.string().describe('Revision PHID or ID (e.g., "D123")'),
29
+ title: z.string().optional().describe('New title'),
30
+ summary: z.string().optional().describe('New summary'),
31
+ testPlan: z.string().optional().describe('New test plan'),
32
+ addReviewerPHIDs: z.array(z.string()).optional().describe('Add reviewers'),
33
+ removeReviewerPHIDs: z.array(z.string()).optional().describe('Remove reviewers'),
34
+ addProjectPHIDs: z.array(z.string()).optional().describe('Add projects'),
35
+ removeProjectPHIDs: z.array(z.string()).optional().describe('Remove projects'),
36
+ comment: z.string().optional().describe('Add a comment'),
37
+ }, async (params) => {
38
+ const transactions = [];
39
+ if (params.title !== undefined) {
40
+ transactions.push({ type: 'title', value: params.title });
41
+ }
42
+ if (params.summary !== undefined) {
43
+ transactions.push({ type: 'summary', value: params.summary });
44
+ }
45
+ if (params.testPlan !== undefined) {
46
+ transactions.push({ type: 'testPlan', value: params.testPlan });
47
+ }
48
+ if (params.addReviewerPHIDs !== undefined) {
49
+ transactions.push({ type: 'reviewers.add', value: params.addReviewerPHIDs });
50
+ }
51
+ if (params.removeReviewerPHIDs !== undefined) {
52
+ transactions.push({ type: 'reviewers.remove', value: params.removeReviewerPHIDs });
53
+ }
54
+ if (params.addProjectPHIDs !== undefined) {
55
+ transactions.push({ type: 'projects.add', value: params.addProjectPHIDs });
56
+ }
57
+ if (params.removeProjectPHIDs !== undefined) {
58
+ transactions.push({ type: 'projects.remove', value: params.removeProjectPHIDs });
59
+ }
60
+ if (params.comment !== undefined) {
61
+ transactions.push({ type: 'comment', value: params.comment });
62
+ }
63
+ if (transactions.length === 0) {
64
+ return { content: [{ type: 'text', text: 'No changes specified' }] };
65
+ }
66
+ const result = await client.call('differential.revision.edit', {
67
+ objectIdentifier: params.objectIdentifier,
68
+ transactions,
69
+ });
70
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
71
+ });
72
+ // Get raw diff content
73
+ server.tool('phabricator_get_raw_diff', 'Get the raw diff/patch content for a Differential diff by diff ID. Use phabricator_diff_search to find the diff ID from a revision PHID first.', {
74
+ diffID: z.number().describe('The diff ID (numeric, e.g., 1392561). Use phabricator_diff_search to find this from a revision.'),
75
+ }, async (params) => {
76
+ const result = await client.call('differential.getrawdiff', {
77
+ diffID: params.diffID,
78
+ });
79
+ return { content: [{ type: 'text', text: result }] };
80
+ });
81
+ // Search diffs
82
+ server.tool('phabricator_diff_search', 'Search Differential diffs', {
83
+ constraints: z.object({
84
+ ids: z.array(z.number()).optional().describe('Diff IDs'),
85
+ phids: z.array(z.string()).optional().describe('Diff PHIDs'),
86
+ revisionPHIDs: z.array(z.string()).optional().describe('Revision PHIDs'),
87
+ }).optional().describe('Search constraints'),
88
+ attachments: z.object({
89
+ commits: z.boolean().optional().describe('Include commit info'),
90
+ }).optional().describe('Data attachments'),
91
+ limit: z.number().max(100).optional().describe('Maximum results'),
92
+ after: z.string().optional().describe('Pagination cursor'),
93
+ }, async (params) => {
94
+ const result = await client.call('differential.diff.search', params);
95
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
96
+ });
97
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ConduitClient } from '../client/conduit.js';
3
+ export declare function registerDiffusionTools(server: McpServer, client: ConduitClient): void;
@@ -0,0 +1,48 @@
1
+ import { z } from 'zod';
2
+ export function registerDiffusionTools(server, client) {
3
+ // Search repositories
4
+ server.tool('phabricator_repository_search', 'Search Diffusion repositories', {
5
+ queryKey: z.string().optional().describe('Built-in query: "all", "active"'),
6
+ constraints: z.object({
7
+ ids: z.array(z.number()).optional().describe('Repository IDs'),
8
+ phids: z.array(z.string()).optional().describe('Repository PHIDs'),
9
+ callsigns: z.array(z.string()).optional().describe('Repository callsigns'),
10
+ shortNames: z.array(z.string()).optional().describe('Repository short names'),
11
+ types: z.array(z.string()).optional().describe('VCS types: git, hg, svn'),
12
+ uris: z.array(z.string()).optional().describe('Repository URIs'),
13
+ query: z.string().optional().describe('Full-text search query'),
14
+ }).optional().describe('Search constraints'),
15
+ attachments: z.object({
16
+ uris: z.boolean().optional().describe('Include repository URIs'),
17
+ metrics: z.boolean().optional().describe('Include metrics'),
18
+ projects: z.boolean().optional().describe('Include projects'),
19
+ }).optional().describe('Data attachments'),
20
+ order: z.string().optional().describe('Result order'),
21
+ limit: z.number().max(100).optional().describe('Maximum results'),
22
+ after: z.string().optional().describe('Pagination cursor'),
23
+ }, async (params) => {
24
+ const result = await client.call('diffusion.repository.search', params);
25
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
26
+ });
27
+ // Search commits
28
+ server.tool('phabricator_commit_search', 'Search Diffusion commits', {
29
+ constraints: z.object({
30
+ ids: z.array(z.number()).optional().describe('Commit IDs'),
31
+ phids: z.array(z.string()).optional().describe('Commit PHIDs'),
32
+ repositoryPHIDs: z.array(z.string()).optional().describe('Repository PHIDs'),
33
+ identifiers: z.array(z.string()).optional().describe('Commit identifiers (hashes)'),
34
+ authorPHIDs: z.array(z.string()).optional().describe('Author PHIDs'),
35
+ query: z.string().optional().describe('Full-text search query'),
36
+ }).optional().describe('Search constraints'),
37
+ attachments: z.object({
38
+ projects: z.boolean().optional().describe('Include projects'),
39
+ subscribers: z.boolean().optional().describe('Include subscribers'),
40
+ }).optional().describe('Data attachments'),
41
+ order: z.string().optional().describe('Result order'),
42
+ limit: z.number().max(100).optional().describe('Maximum results'),
43
+ after: z.string().optional().describe('Pagination cursor'),
44
+ }, async (params) => {
45
+ const result = await client.call('diffusion.commit.search', params);
46
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
47
+ });
48
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ConduitClient } from '../client/conduit.js';
3
+ export declare function registerAllTools(server: McpServer, client: ConduitClient): void;
@@ -0,0 +1,22 @@
1
+ import { registerManiphestTools } from './maniphest.js';
2
+ import { registerDifferentialTools } from './differential.js';
3
+ import { registerDiffusionTools } from './diffusion.js';
4
+ import { registerUserTools } from './user.js';
5
+ import { registerProjectTools } from './project.js';
6
+ import { registerPasteTools } from './paste.js';
7
+ import { registerPhrictionTools } from './phriction.js';
8
+ import { registerPhidTools } from './phid.js';
9
+ import { registerPhameTools } from './phame.js';
10
+ import { registerTransactionTools } from './transaction.js';
11
+ export function registerAllTools(server, client) {
12
+ registerManiphestTools(server, client);
13
+ registerDifferentialTools(server, client);
14
+ registerDiffusionTools(server, client);
15
+ registerUserTools(server, client);
16
+ registerProjectTools(server, client);
17
+ registerPasteTools(server, client);
18
+ registerPhrictionTools(server, client);
19
+ registerPhidTools(server, client);
20
+ registerPhameTools(server, client);
21
+ registerTransactionTools(server, client);
22
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ConduitClient } from '../client/conduit.js';
3
+ export declare function registerManiphestTools(server: McpServer, client: ConduitClient): void;
@@ -0,0 +1,129 @@
1
+ import { z } from 'zod';
2
+ export function registerManiphestTools(server, client) {
3
+ // Search tasks
4
+ server.tool('phabricator_task_search', 'Search Maniphest tasks with optional filters', {
5
+ queryKey: z.string().optional().describe('Built-in query: "all", "open", "authored", "assigned"'),
6
+ constraints: z.object({
7
+ ids: z.array(z.number()).optional().describe('Task IDs to search for'),
8
+ phids: z.array(z.string()).optional().describe('Task PHIDs to search for'),
9
+ assigned: z.array(z.string()).optional().describe('Assigned user PHIDs'),
10
+ authorPHIDs: z.array(z.string()).optional().describe('Author PHIDs'),
11
+ statuses: z.array(z.string()).optional().describe('Task statuses: open, resolved, wontfix, invalid, spite, duplicate'),
12
+ priorities: z.array(z.number()).optional().describe('Priority levels'),
13
+ subtypes: z.array(z.string()).optional().describe('Task subtypes'),
14
+ columnPHIDs: z.array(z.string()).optional().describe('Workboard column PHIDs'),
15
+ projectPHIDs: z.array(z.string()).optional().describe('Project PHIDs (tasks tagged with these projects)'),
16
+ query: z.string().optional().describe('Full-text search query'),
17
+ }).optional().describe('Search constraints'),
18
+ attachments: z.object({
19
+ columns: z.boolean().optional().describe('Include workboard column info'),
20
+ projects: z.boolean().optional().describe('Include project info'),
21
+ subscribers: z.boolean().optional().describe('Include subscriber info'),
22
+ }).optional().describe('Data attachments to include'),
23
+ order: z.string().optional().describe('Result order: "priority", "updated", "newest", "oldest"'),
24
+ limit: z.number().max(100).optional().describe('Maximum results (max 100)'),
25
+ after: z.string().optional().describe('Cursor for pagination'),
26
+ }, async (params) => {
27
+ const result = await client.call('maniphest.search', params);
28
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
29
+ });
30
+ // Create task
31
+ server.tool('phabricator_task_create', 'Create a new Maniphest task', {
32
+ title: z.string().describe('Task title'),
33
+ description: z.string().optional().describe('Task description (supports Remarkup)'),
34
+ ownerPHID: z.string().optional().describe('Assigned owner PHID'),
35
+ priority: z.string().optional().describe('Priority: unbreak, triage, high, normal, low, wish'),
36
+ projectPHIDs: z.array(z.string()).optional().describe('Project PHIDs to tag'),
37
+ subscriberPHIDs: z.array(z.string()).optional().describe('Subscriber PHIDs'),
38
+ status: z.string().optional().describe('Initial status'),
39
+ }, async (params) => {
40
+ const transactions = [
41
+ { type: 'title', value: params.title },
42
+ ];
43
+ if (params.description !== undefined) {
44
+ transactions.push({ type: 'description', value: params.description });
45
+ }
46
+ if (params.ownerPHID !== undefined) {
47
+ transactions.push({ type: 'owner', value: params.ownerPHID });
48
+ }
49
+ if (params.priority !== undefined) {
50
+ transactions.push({ type: 'priority', value: params.priority });
51
+ }
52
+ if (params.projectPHIDs !== undefined) {
53
+ transactions.push({ type: 'projects.set', value: params.projectPHIDs });
54
+ }
55
+ if (params.subscriberPHIDs !== undefined) {
56
+ transactions.push({ type: 'subscribers.set', value: params.subscriberPHIDs });
57
+ }
58
+ if (params.status !== undefined) {
59
+ transactions.push({ type: 'status', value: params.status });
60
+ }
61
+ const result = await client.call('maniphest.edit', { transactions });
62
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
63
+ });
64
+ // Edit task
65
+ server.tool('phabricator_task_edit', 'Edit an existing Maniphest task', {
66
+ objectIdentifier: z.string().describe('Task PHID or ID (e.g., "T123" or PHID)'),
67
+ title: z.string().optional().describe('New title'),
68
+ description: z.string().optional().describe('New description'),
69
+ ownerPHID: z.string().nullable().optional().describe('New owner PHID (null to unassign)'),
70
+ priority: z.string().optional().describe('New priority'),
71
+ status: z.string().optional().describe('New status: open, resolved, wontfix, invalid, spite, duplicate'),
72
+ addProjectPHIDs: z.array(z.string()).optional().describe('Project PHIDs to add'),
73
+ removeProjectPHIDs: z.array(z.string()).optional().describe('Project PHIDs to remove'),
74
+ addSubscriberPHIDs: z.array(z.string()).optional().describe('Subscriber PHIDs to add'),
75
+ removeSubscriberPHIDs: z.array(z.string()).optional().describe('Subscriber PHIDs to remove'),
76
+ columnPHID: z.string().optional().describe('Move to workboard column'),
77
+ }, async (params) => {
78
+ const transactions = [];
79
+ if (params.title !== undefined) {
80
+ transactions.push({ type: 'title', value: params.title });
81
+ }
82
+ if (params.description !== undefined) {
83
+ transactions.push({ type: 'description', value: params.description });
84
+ }
85
+ if (params.ownerPHID !== undefined) {
86
+ transactions.push({ type: 'owner', value: params.ownerPHID });
87
+ }
88
+ if (params.priority !== undefined) {
89
+ transactions.push({ type: 'priority', value: params.priority });
90
+ }
91
+ if (params.status !== undefined) {
92
+ transactions.push({ type: 'status', value: params.status });
93
+ }
94
+ if (params.addProjectPHIDs !== undefined) {
95
+ transactions.push({ type: 'projects.add', value: params.addProjectPHIDs });
96
+ }
97
+ if (params.removeProjectPHIDs !== undefined) {
98
+ transactions.push({ type: 'projects.remove', value: params.removeProjectPHIDs });
99
+ }
100
+ if (params.addSubscriberPHIDs !== undefined) {
101
+ transactions.push({ type: 'subscribers.add', value: params.addSubscriberPHIDs });
102
+ }
103
+ if (params.removeSubscriberPHIDs !== undefined) {
104
+ transactions.push({ type: 'subscribers.remove', value: params.removeSubscriberPHIDs });
105
+ }
106
+ if (params.columnPHID !== undefined) {
107
+ transactions.push({ type: 'column', value: [params.columnPHID] });
108
+ }
109
+ if (transactions.length === 0) {
110
+ return { content: [{ type: 'text', text: 'No changes specified' }] };
111
+ }
112
+ const result = await client.call('maniphest.edit', {
113
+ objectIdentifier: params.objectIdentifier,
114
+ transactions,
115
+ });
116
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
117
+ });
118
+ // Add comment to task
119
+ server.tool('phabricator_task_add_comment', 'Add a comment to a Maniphest task', {
120
+ objectIdentifier: z.string().describe('Task PHID or ID (e.g., "T123")'),
121
+ comment: z.string().describe('Comment text (supports Remarkup)'),
122
+ }, async (params) => {
123
+ const result = await client.call('maniphest.edit', {
124
+ objectIdentifier: params.objectIdentifier,
125
+ transactions: [{ type: 'comment', value: params.comment }],
126
+ });
127
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
128
+ });
129
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ConduitClient } from '../client/conduit.js';
3
+ export declare function registerPasteTools(server: McpServer, client: ConduitClient): void;
@@ -0,0 +1,45 @@
1
+ import { z } from 'zod';
2
+ export function registerPasteTools(server, client) {
3
+ // Search pastes
4
+ server.tool('phabricator_paste_search', 'Search Phabricator pastes', {
5
+ queryKey: z.string().optional().describe('Built-in query: "all", "authored"'),
6
+ constraints: z.object({
7
+ ids: z.array(z.number()).optional().describe('Paste IDs'),
8
+ phids: z.array(z.string()).optional().describe('Paste PHIDs'),
9
+ authorPHIDs: z.array(z.string()).optional().describe('Author PHIDs'),
10
+ languages: z.array(z.string()).optional().describe('Languages'),
11
+ query: z.string().optional().describe('Full-text search query'),
12
+ }).optional().describe('Search constraints'),
13
+ attachments: z.object({
14
+ content: z.boolean().optional().describe('Include paste content'),
15
+ }).optional().describe('Data attachments'),
16
+ order: z.string().optional().describe('Result order'),
17
+ limit: z.number().max(100).optional().describe('Maximum results'),
18
+ after: z.string().optional().describe('Pagination cursor'),
19
+ }, async (params) => {
20
+ const result = await client.call('paste.search', params);
21
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
22
+ });
23
+ // Create paste
24
+ server.tool('phabricator_paste_create', 'Create a new Phabricator paste', {
25
+ title: z.string().optional().describe('Paste title'),
26
+ content: z.string().describe('Paste content'),
27
+ language: z.string().optional().describe('Syntax highlighting language'),
28
+ status: z.string().optional().describe('Status: active or archived'),
29
+ }, async (params) => {
30
+ const transactions = [
31
+ { type: 'text', value: params.content },
32
+ ];
33
+ if (params.title !== undefined) {
34
+ transactions.push({ type: 'title', value: params.title });
35
+ }
36
+ if (params.language !== undefined) {
37
+ transactions.push({ type: 'language', value: params.language });
38
+ }
39
+ if (params.status !== undefined) {
40
+ transactions.push({ type: 'status', value: params.status });
41
+ }
42
+ const result = await client.call('paste.edit', { transactions });
43
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
44
+ });
45
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ConduitClient } from '../client/conduit.js';
3
+ export declare function registerPhameTools(server: McpServer, client: ConduitClient): void;
@@ -0,0 +1,102 @@
1
+ import { z } from 'zod';
2
+ export function registerPhameTools(server, client) {
3
+ // Search blogs
4
+ server.tool('phabricator_blog_search', 'Search Phame blogs', {
5
+ queryKey: z.string().optional().describe('Built-in query: "all", "active"'),
6
+ constraints: z.object({
7
+ ids: z.array(z.number()).optional().describe('Blog IDs'),
8
+ phids: z.array(z.string()).optional().describe('Blog PHIDs'),
9
+ query: z.string().optional().describe('Full-text search query'),
10
+ }).optional().describe('Search constraints'),
11
+ order: z.string().optional().describe('Result order'),
12
+ limit: z.number().max(100).optional().describe('Maximum results (max 100)'),
13
+ after: z.string().optional().describe('Cursor for pagination'),
14
+ }, async (params) => {
15
+ const result = await client.call('phame.blog.search', params);
16
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
17
+ });
18
+ // Search blog posts
19
+ server.tool('phabricator_blog_post_search', 'Search Phame blog posts', {
20
+ queryKey: z.string().optional().describe('Built-in query: "all", "live"'),
21
+ constraints: z.object({
22
+ ids: z.array(z.number()).optional().describe('Post IDs'),
23
+ phids: z.array(z.string()).optional().describe('Post PHIDs'),
24
+ blogPHIDs: z.array(z.string()).optional().describe('Filter by blog PHIDs'),
25
+ visibility: z.array(z.string()).optional().describe('Visibility: "published", "draft", "archived"'),
26
+ query: z.string().optional().describe('Full-text search query'),
27
+ }).optional().describe('Search constraints'),
28
+ order: z.string().optional().describe('Result order'),
29
+ limit: z.number().max(100).optional().describe('Maximum results (max 100)'),
30
+ after: z.string().optional().describe('Cursor for pagination'),
31
+ }, async (params) => {
32
+ const result = await client.call('phame.post.search', params);
33
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
34
+ });
35
+ // Create blog post
36
+ server.tool('phabricator_blog_post_create', 'Create a new Phame blog post', {
37
+ title: z.string().describe('Post title'),
38
+ body: z.string().describe('Post body content (supports Remarkup)'),
39
+ blogPHID: z.string().describe('PHID of the blog to post to'),
40
+ subtitle: z.string().optional().describe('Post subtitle'),
41
+ visibility: z.string().optional().describe('Visibility: "published", "draft", "archived" (default: draft)'),
42
+ }, async (params) => {
43
+ const transactions = [
44
+ { type: 'title', value: params.title },
45
+ { type: 'body', value: params.body },
46
+ { type: 'blog', value: params.blogPHID },
47
+ ];
48
+ if (params.subtitle !== undefined) {
49
+ transactions.push({ type: 'subtitle', value: params.subtitle });
50
+ }
51
+ if (params.visibility !== undefined) {
52
+ transactions.push({ type: 'visibility', value: params.visibility });
53
+ }
54
+ const result = await client.call('phame.post.edit', { transactions });
55
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
56
+ });
57
+ // Edit blog post
58
+ server.tool('phabricator_blog_post_edit', 'Edit an existing Phame blog post', {
59
+ objectIdentifier: z.string().describe('Post PHID or ID'),
60
+ title: z.string().optional().describe('New post title'),
61
+ subtitle: z.string().optional().describe('New post subtitle'),
62
+ body: z.string().optional().describe('New post body content (supports Remarkup)'),
63
+ visibility: z.string().optional().describe('Visibility: "published", "draft", "archived"'),
64
+ blogPHID: z.string().optional().describe('Move post to a different blog (PHID)'),
65
+ }, async (params) => {
66
+ const transactions = [];
67
+ if (params.title !== undefined) {
68
+ transactions.push({ type: 'title', value: params.title });
69
+ }
70
+ if (params.subtitle !== undefined) {
71
+ transactions.push({ type: 'subtitle', value: params.subtitle });
72
+ }
73
+ if (params.body !== undefined) {
74
+ transactions.push({ type: 'body', value: params.body });
75
+ }
76
+ if (params.visibility !== undefined) {
77
+ transactions.push({ type: 'visibility', value: params.visibility });
78
+ }
79
+ if (params.blogPHID !== undefined) {
80
+ transactions.push({ type: 'blog', value: params.blogPHID });
81
+ }
82
+ if (transactions.length === 0) {
83
+ return { content: [{ type: 'text', text: 'No changes specified' }] };
84
+ }
85
+ const result = await client.call('phame.post.edit', {
86
+ objectIdentifier: params.objectIdentifier,
87
+ transactions,
88
+ });
89
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
90
+ });
91
+ // Add comment to blog post
92
+ server.tool('phabricator_blog_post_add_comment', 'Add a comment to a Phame blog post', {
93
+ objectIdentifier: z.string().describe('Post PHID or ID'),
94
+ comment: z.string().describe('Comment text (supports Remarkup)'),
95
+ }, async (params) => {
96
+ const result = await client.call('phame.post.edit', {
97
+ objectIdentifier: params.objectIdentifier,
98
+ transactions: [{ type: 'comment', value: params.comment }],
99
+ });
100
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
101
+ });
102
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ConduitClient } from '../client/conduit.js';
3
+ export declare function registerPhidTools(server: McpServer, client: ConduitClient): void;
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ export function registerPhidTools(server, client) {
3
+ // Lookup PHIDs by name
4
+ server.tool('phabricator_phid_lookup', 'Look up PHIDs by human-readable names (e.g., "T123", "D456", "@username")', {
5
+ names: z.array(z.string()).describe('Names to look up (e.g., ["T123", "D456", "@john"])'),
6
+ }, async (params) => {
7
+ const result = await client.call('phid.lookup', { names: params.names });
8
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
9
+ });
10
+ // Query PHID details
11
+ server.tool('phabricator_phid_query', 'Get detailed information about PHIDs', {
12
+ phids: z.array(z.string()).describe('PHIDs to query'),
13
+ }, async (params) => {
14
+ const result = await client.call('phid.query', { phids: params.phids });
15
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
16
+ });
17
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ConduitClient } from '../client/conduit.js';
3
+ export declare function registerPhrictionTools(server: McpServer, client: ConduitClient): void;
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod';
2
+ export function registerPhrictionTools(server, client) {
3
+ // Search wiki documents
4
+ server.tool('phabricator_document_search', 'Search Phriction wiki documents', {
5
+ queryKey: z.string().optional().describe('Built-in query: "all", "active"'),
6
+ constraints: z.object({
7
+ ids: z.array(z.number()).optional().describe('Document IDs'),
8
+ phids: z.array(z.string()).optional().describe('Document PHIDs'),
9
+ paths: z.array(z.string()).optional().describe('Document paths'),
10
+ ancestorPaths: z.array(z.string()).optional().describe('Ancestor paths to search under'),
11
+ statuses: z.array(z.string()).optional().describe('Document statuses'),
12
+ query: z.string().optional().describe('Full-text search query'),
13
+ }).optional().describe('Search constraints'),
14
+ attachments: z.object({
15
+ content: z.boolean().optional().describe('Include document content'),
16
+ }).optional().describe('Data attachments'),
17
+ order: z.string().optional().describe('Result order'),
18
+ limit: z.number().max(100).optional().describe('Maximum results'),
19
+ after: z.string().optional().describe('Pagination cursor'),
20
+ }, async (params) => {
21
+ const result = await client.call('phriction.document.search', params);
22
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
23
+ });
24
+ // Edit wiki document
25
+ server.tool('phabricator_document_edit', 'Edit a Phriction wiki document', {
26
+ slug: z.string().describe('Document path/slug (e.g., "projects/myproject/")'),
27
+ title: z.string().optional().describe('Document title'),
28
+ content: z.string().optional().describe('Document content (Remarkup)'),
29
+ }, async (params) => {
30
+ const transactions = [];
31
+ if (params.title !== undefined) {
32
+ transactions.push({ type: 'title', value: params.title });
33
+ }
34
+ if (params.content !== undefined) {
35
+ transactions.push({ type: 'content', value: params.content });
36
+ }
37
+ if (transactions.length === 0) {
38
+ return { content: [{ type: 'text', text: 'No changes specified' }] };
39
+ }
40
+ const result = await client.call('phriction.document.edit', {
41
+ objectIdentifier: params.slug,
42
+ transactions,
43
+ });
44
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
45
+ });
46
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ConduitClient } from '../client/conduit.js';
3
+ export declare function registerProjectTools(server: McpServer, client: ConduitClient): void;
@@ -0,0 +1,82 @@
1
+ import { z } from 'zod';
2
+ export function registerProjectTools(server, client) {
3
+ // Search projects
4
+ server.tool('phabricator_project_search', 'Search Phabricator projects', {
5
+ queryKey: z.string().optional().describe('Built-in query: "all", "active", "joined"'),
6
+ constraints: z.object({
7
+ ids: z.array(z.number()).optional().describe('Project IDs'),
8
+ phids: z.array(z.string()).optional().describe('Project PHIDs'),
9
+ slugs: z.array(z.string()).optional().describe('Project slugs'),
10
+ name: z.string().optional().describe('Exact name match'),
11
+ members: z.array(z.string()).optional().describe('Member user PHIDs'),
12
+ watchers: z.array(z.string()).optional().describe('Watcher user PHIDs'),
13
+ ancestors: z.array(z.string()).optional().describe('Ancestor project PHIDs'),
14
+ isMilestone: z.boolean().optional().describe('Filter milestones'),
15
+ isRoot: z.boolean().optional().describe('Filter root projects'),
16
+ query: z.string().optional().describe('Full-text search query'),
17
+ }).optional().describe('Search constraints'),
18
+ attachments: z.object({
19
+ members: z.boolean().optional().describe('Include members'),
20
+ watchers: z.boolean().optional().describe('Include watchers'),
21
+ ancestors: z.boolean().optional().describe('Include ancestors'),
22
+ }).optional().describe('Data attachments'),
23
+ order: z.string().optional().describe('Result order'),
24
+ limit: z.number().max(100).optional().describe('Maximum results'),
25
+ after: z.string().optional().describe('Pagination cursor'),
26
+ }, async (params) => {
27
+ const result = await client.call('project.search', params);
28
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
29
+ });
30
+ // Edit project
31
+ server.tool('phabricator_project_edit', 'Edit a Phabricator project', {
32
+ objectIdentifier: z.string().describe('Project PHID or ID'),
33
+ name: z.string().optional().describe('New name'),
34
+ description: z.string().optional().describe('New description'),
35
+ icon: z.string().optional().describe('New icon'),
36
+ color: z.string().optional().describe('New color'),
37
+ addMemberPHIDs: z.array(z.string()).optional().describe('Add members'),
38
+ removeMemberPHIDs: z.array(z.string()).optional().describe('Remove members'),
39
+ }, async (params) => {
40
+ const transactions = [];
41
+ if (params.name !== undefined) {
42
+ transactions.push({ type: 'name', value: params.name });
43
+ }
44
+ if (params.description !== undefined) {
45
+ transactions.push({ type: 'description', value: params.description });
46
+ }
47
+ if (params.icon !== undefined) {
48
+ transactions.push({ type: 'icon', value: params.icon });
49
+ }
50
+ if (params.color !== undefined) {
51
+ transactions.push({ type: 'color', value: params.color });
52
+ }
53
+ if (params.addMemberPHIDs !== undefined) {
54
+ transactions.push({ type: 'members.add', value: params.addMemberPHIDs });
55
+ }
56
+ if (params.removeMemberPHIDs !== undefined) {
57
+ transactions.push({ type: 'members.remove', value: params.removeMemberPHIDs });
58
+ }
59
+ if (transactions.length === 0) {
60
+ return { content: [{ type: 'text', text: 'No changes specified' }] };
61
+ }
62
+ const result = await client.call('project.edit', {
63
+ objectIdentifier: params.objectIdentifier,
64
+ transactions,
65
+ });
66
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
67
+ });
68
+ // Search workboard columns
69
+ server.tool('phabricator_column_search', 'Search project workboard columns', {
70
+ constraints: z.object({
71
+ ids: z.array(z.number()).optional().describe('Column IDs'),
72
+ phids: z.array(z.string()).optional().describe('Column PHIDs'),
73
+ projects: z.array(z.string()).optional().describe('Project PHIDs'),
74
+ }).optional().describe('Search constraints'),
75
+ order: z.string().optional().describe('Result order'),
76
+ limit: z.number().max(100).optional().describe('Maximum results'),
77
+ after: z.string().optional().describe('Pagination cursor'),
78
+ }, async (params) => {
79
+ const result = await client.call('project.column.search', params);
80
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
81
+ });
82
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ConduitClient } from '../client/conduit.js';
3
+ export declare function registerTransactionTools(server: McpServer, client: ConduitClient): void;