@freelancercom/phabricator-mcp 1.0.0 → 1.0.2

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
@@ -11,7 +11,7 @@ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that
11
11
  ### Claude Code (CLI)
12
12
 
13
13
  ```bash
14
- claude mcp add --scope user phabricator -- npx github:freelancer/phabricator-mcp
14
+ claude mcp add --scope user phabricator -- npx @freelancercom/phabricator-mcp@latest
15
15
  ```
16
16
 
17
17
  Or with environment variables (if not using `~/.arcrc`):
@@ -20,7 +20,7 @@ Or with environment variables (if not using `~/.arcrc`):
20
20
  claude mcp add --scope user phabricator \
21
21
  -e PHABRICATOR_URL=https://phabricator.example.com \
22
22
  -e PHABRICATOR_API_TOKEN=api-xxxxx \
23
- -- npx github:freelancer/phabricator-mcp
23
+ -- npx @freelancercom/phabricator-mcp@latest
24
24
  ```
25
25
 
26
26
  The `--scope user` flag installs the server globally, making it available in all projects.
@@ -34,7 +34,7 @@ Add to your Codex config (`~/.codex/config.json`):
34
34
  "mcpServers": {
35
35
  "phabricator": {
36
36
  "command": "npx",
37
- "args": ["github:freelancer/phabricator-mcp"],
37
+ "args": ["@freelancercom/phabricator-mcp@latest"],
38
38
  "env": {
39
39
  "PHABRICATOR_URL": "https://phabricator.example.com",
40
40
  "PHABRICATOR_API_TOKEN": "api-xxxxxxxxxxxxx"
@@ -54,7 +54,7 @@ Add to your opencode config (`~/.config/opencode/config.json`):
54
54
  "servers": {
55
55
  "phabricator": {
56
56
  "command": "npx",
57
- "args": ["github:freelancer/phabricator-mcp"],
57
+ "args": ["@freelancercom/phabricator-mcp@latest"],
58
58
  "env": {
59
59
  "PHABRICATOR_URL": "https://phabricator.example.com",
60
60
  "PHABRICATOR_API_TOKEN": "api-xxxxxxxxxxxxx"
@@ -74,7 +74,7 @@ Add to your VS Code `settings.json`:
74
74
  "claude.mcpServers": {
75
75
  "phabricator": {
76
76
  "command": "npx",
77
- "args": ["github:freelancer/phabricator-mcp"],
77
+ "args": ["@freelancercom/phabricator-mcp@latest"],
78
78
  "env": {
79
79
  "PHABRICATOR_URL": "https://phabricator.example.com",
80
80
  "PHABRICATOR_API_TOKEN": "api-xxxxxxxxxxxxx"
@@ -93,7 +93,7 @@ Add to your Cursor MCP config (`~/.cursor/mcp.json`):
93
93
  "mcpServers": {
94
94
  "phabricator": {
95
95
  "command": "npx",
96
- "args": ["github:freelancer/phabricator-mcp"],
96
+ "args": ["@freelancercom/phabricator-mcp@latest"],
97
97
  "env": {
98
98
  "PHABRICATOR_URL": "https://phabricator.example.com",
99
99
  "PHABRICATOR_API_TOKEN": "api-xxxxxxxxxxxxx"
@@ -112,7 +112,7 @@ Add to your VS Code `settings.json`:
112
112
  "github.copilot.chat.mcp.servers": {
113
113
  "phabricator": {
114
114
  "command": "npx",
115
- "args": ["github:freelancer/phabricator-mcp"],
115
+ "args": ["@freelancercom/phabricator-mcp@latest"],
116
116
  "env": {
117
117
  "PHABRICATOR_URL": "https://phabricator.example.com",
118
118
  "PHABRICATOR_API_TOKEN": "api-xxxxxxxxxxxxx"
@@ -122,6 +122,32 @@ Add to your VS Code `settings.json`:
122
122
  }
123
123
  ```
124
124
 
125
+ ## Upgrading
126
+
127
+ The default install uses `@freelancercom/phabricator-mcp@latest`, which tells npx to check for updates on each run. No action needed.
128
+
129
+ If you pinned a specific version (e.g. `@freelancercom/phabricator-mcp@1.0.0`) or omitted the version suffix, npx caches the package and won't pick up new versions. To upgrade:
130
+
131
+ ```bash
132
+ npx clear-npx-cache
133
+ ```
134
+
135
+ Then restart your MCP client.
136
+
137
+ ### Migrating from `github:freelancer/phabricator-mcp`
138
+
139
+ If you previously installed using the GitHub URL, update your config to use the npm package instead:
140
+
141
+ ```bash
142
+ # Remove old server
143
+ claude mcp remove phabricator -s user
144
+
145
+ # Add new one
146
+ claude mcp add --scope user phabricator -- npx @freelancercom/phabricator-mcp@latest
147
+ ```
148
+
149
+ For JSON configs, replace `["github:freelancer/phabricator-mcp"]` with `["@freelancercom/phabricator-mcp@latest"]` in your args.
150
+
125
151
  ## Configuration
126
152
 
127
153
  The server automatically reads configuration from `~/.arcrc` (created by [Arcanist](https://secure.phabricator.com/book/phabricator/article/arcanist/)). No additional configuration is needed if you've already set up `arc`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freelancercom/phabricator-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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",
@@ -1 +0,0 @@
1
- export {};
@@ -1,97 +0,0 @@
1
- import { describe, it, mock, beforeEach, afterEach } from 'node:test';
2
- import assert from 'node:assert';
3
- import { ConduitClient, ConduitError } from './conduit.js';
4
- describe('ConduitClient', () => {
5
- const mockConfig = {
6
- phabricatorUrl: 'https://phabricator.example.com',
7
- apiToken: 'api-test-token',
8
- };
9
- let originalFetch;
10
- beforeEach(() => {
11
- originalFetch = global.fetch;
12
- });
13
- afterEach(() => {
14
- global.fetch = originalFetch;
15
- });
16
- it('should construct correct API URL', async () => {
17
- let capturedUrl;
18
- global.fetch = mock.fn(async (url) => {
19
- capturedUrl = url;
20
- return new Response(JSON.stringify({ result: {}, error_code: null, error_info: null }));
21
- });
22
- const client = new ConduitClient(mockConfig);
23
- await client.call('user.whoami');
24
- assert.strictEqual(capturedUrl, 'https://phabricator.example.com/api/user.whoami');
25
- });
26
- it('should include API token in request body', async () => {
27
- let capturedBody;
28
- global.fetch = mock.fn(async (_url, init) => {
29
- capturedBody = init?.body;
30
- return new Response(JSON.stringify({ result: {}, error_code: null, error_info: null }));
31
- });
32
- const client = new ConduitClient(mockConfig);
33
- await client.call('user.whoami');
34
- assert.ok(capturedBody);
35
- const params = new URLSearchParams(capturedBody);
36
- const paramsJson = JSON.parse(params.get('params'));
37
- assert.strictEqual(paramsJson.__conduit__.token, 'api-test-token');
38
- });
39
- it('should pass parameters to the API', async () => {
40
- let capturedBody;
41
- global.fetch = mock.fn(async (_url, init) => {
42
- capturedBody = init?.body;
43
- return new Response(JSON.stringify({ result: {}, error_code: null, error_info: null }));
44
- });
45
- const client = new ConduitClient(mockConfig);
46
- await client.call('maniphest.search', { queryKey: 'assigned', limit: 10 });
47
- const params = new URLSearchParams(capturedBody);
48
- const paramsJson = JSON.parse(params.get('params'));
49
- assert.strictEqual(paramsJson.queryKey, 'assigned');
50
- assert.strictEqual(paramsJson.limit, 10);
51
- });
52
- it('should return result on success', async () => {
53
- const expectedResult = { userName: 'testuser', realName: 'Test User' };
54
- global.fetch = mock.fn(async () => {
55
- return new Response(JSON.stringify({ result: expectedResult, error_code: null, error_info: null }));
56
- });
57
- const client = new ConduitClient(mockConfig);
58
- const result = await client.call('user.whoami');
59
- assert.deepStrictEqual(result, expectedResult);
60
- });
61
- it('should throw ConduitError on API error', async () => {
62
- global.fetch = mock.fn(async () => {
63
- return new Response(JSON.stringify({
64
- result: null,
65
- error_code: 'ERR-CONDUIT-CORE',
66
- error_info: 'Invalid token',
67
- }));
68
- });
69
- const client = new ConduitClient(mockConfig);
70
- await assert.rejects(() => client.call('user.whoami'), (err) => {
71
- assert.ok(err instanceof ConduitError);
72
- assert.strictEqual(err.code, 'ERR-CONDUIT-CORE');
73
- assert.strictEqual(err.message, 'Invalid token');
74
- return true;
75
- });
76
- });
77
- it('should throw ConduitError on HTTP error', async () => {
78
- global.fetch = mock.fn(async () => {
79
- return new Response('Not Found', { status: 404, statusText: 'Not Found' });
80
- });
81
- const client = new ConduitClient(mockConfig);
82
- await assert.rejects(() => client.call('user.whoami'), (err) => {
83
- assert.ok(err instanceof ConduitError);
84
- assert.strictEqual(err.code, 'HTTP_ERROR');
85
- assert.ok(err.message.includes('404'));
86
- return true;
87
- });
88
- });
89
- });
90
- describe('ConduitError', () => {
91
- it('should have correct name and properties', () => {
92
- const error = new ConduitError('TEST_CODE', 'Test message');
93
- assert.strictEqual(error.name, 'ConduitError');
94
- assert.strictEqual(error.code, 'TEST_CODE');
95
- assert.strictEqual(error.message, 'Test message');
96
- });
97
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,54 +0,0 @@
1
- import { describe, it, beforeEach, afterEach } from 'node:test';
2
- import assert from 'node:assert';
3
- describe('loadConfig', () => {
4
- const originalEnv = { ...process.env };
5
- beforeEach(() => {
6
- // Clear relevant env vars
7
- delete process.env.PHABRICATOR_URL;
8
- delete process.env.PHABRICATOR_API_TOKEN;
9
- });
10
- afterEach(() => {
11
- process.env = { ...originalEnv };
12
- });
13
- it('should load config from environment variables', async () => {
14
- process.env.PHABRICATOR_URL = 'https://phabricator.example.com';
15
- process.env.PHABRICATOR_API_TOKEN = 'api-test-token';
16
- // Re-import to get fresh module
17
- const { loadConfig } = await import('./config.js');
18
- const config = loadConfig();
19
- assert.strictEqual(config.phabricatorUrl, 'https://phabricator.example.com');
20
- assert.strictEqual(config.apiToken, 'api-test-token');
21
- });
22
- it('should strip trailing slash from URL', async () => {
23
- process.env.PHABRICATOR_URL = 'https://phabricator.example.com/';
24
- process.env.PHABRICATOR_API_TOKEN = 'api-test-token';
25
- const { loadConfig } = await import('./config.js');
26
- const config = loadConfig();
27
- assert.strictEqual(config.phabricatorUrl, 'https://phabricator.example.com');
28
- });
29
- it('should throw error for invalid URL', async () => {
30
- process.env.PHABRICATOR_URL = 'not-a-valid-url';
31
- process.env.PHABRICATOR_API_TOKEN = 'api-test-token';
32
- const { loadConfig } = await import('./config.js?v=1');
33
- assert.throws(() => loadConfig(), /Invalid url/);
34
- });
35
- it('should throw error for empty token', async () => {
36
- process.env.PHABRICATOR_URL = 'https://phabricator.example.com';
37
- process.env.PHABRICATOR_API_TOKEN = '';
38
- // When token is empty string, it should either throw or fall back to arcrc
39
- // Since arcrc exists on this machine, it will use that - so we skip this test
40
- // if arcrc is present. The important thing is it doesn't accept empty string.
41
- const { loadConfig } = await import('./config.js?v=2');
42
- // This test verifies the schema validation - empty string should fail zod validation
43
- // but it may fall back to arcrc first, so we just verify it doesn't crash
44
- try {
45
- const config = loadConfig();
46
- // If it succeeds, it used arcrc fallback which is fine
47
- assert.ok(config.apiToken.length > 0);
48
- }
49
- catch (e) {
50
- // If it throws, that's also fine - means it correctly rejected empty token
51
- assert.ok(e.message.includes('too_small') || e.message.includes('API_TOKEN'));
52
- }
53
- });
54
- });