@bgx4k3p/huly-mcp-server 2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 bgx4k3p
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,457 @@
1
+ # Huly MCP Server
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
4
+ [![Node.js](https://img.shields.io/badge/Node.js-22+-339933?logo=node.js&logoColor=white)](https://nodejs.org)
5
+ [![MCP](https://img.shields.io/badge/MCP-compatible-blue)](https://modelcontextprotocol.io)
6
+ [![Tests](https://img.shields.io/badge/tests-162%20passing-brightgreen)](test/integration.test.mjs)
7
+ [![Coverage](https://img.shields.io/badge/coverage-100%25%20methods-brightgreen)](test/integration.test.mjs)
8
+ [![npm](https://img.shields.io/npm/v/@bgx4k3p/huly-mcp-server)](https://www.npmjs.com/package/@bgx4k3p/huly-mcp-server)
9
+ [![Huly SDK](https://img.shields.io/badge/Huly%20SDK-0.7.x-purple)](https://huly.io)
10
+ [![Docker](https://img.shields.io/badge/Docker-ready-2496ED?logo=docker&logoColor=white)](Dockerfile)
11
+
12
+ MCP and HTTP REST server providing full coverage of the
13
+ [Huly](https://huly.io) SDK — issues, projects, workspaces, members,
14
+ and account management. Tested against self-hosted Huly.
15
+ May also work with [Huly Cloud](https://app.huly.io) (not yet tested).
16
+
17
+ ## Why This Exists
18
+
19
+ Huly has no REST API. The only programmatic access is through their JavaScript SDK,
20
+ which connects via WebSocket. This server wraps that SDK and exposes **MCP tools**
21
+ and a **full HTTP REST API** with OpenAPI spec, authentication, rate limiting, and SSE events.
22
+
23
+ ## Quick Start
24
+
25
+ ```bash
26
+ git clone https://github.com/bgx4k3p/huly-mcp-server.git
27
+ cd huly-mcp-server
28
+ npm install
29
+ ```
30
+
31
+ ### Authentication
32
+
33
+ You can authenticate with either **email/password** or a **token**.
34
+
35
+ #### Email and Password
36
+
37
+ ```bash
38
+ export HULY_URL=https://your-huly-instance.com
39
+ export HULY_EMAIL=your@email.com
40
+ export HULY_PASSWORD=your-password
41
+ export HULY_WORKSPACE=your-workspace
42
+ ```
43
+
44
+ #### Token (recommended)
45
+
46
+ Get a token from your Huly credentials — no env vars needed beforehand:
47
+
48
+ ```bash
49
+ node src/index.mjs --get-token -e your@email.com -p your-password -u https://your-huly-instance.com
50
+ ```
51
+
52
+ Then use it:
53
+
54
+ ```bash
55
+ export HULY_URL=https://your-huly-instance.com
56
+ export HULY_TOKEN=<paste-token-from-above>
57
+ export HULY_WORKSPACE=your-workspace
58
+ ```
59
+
60
+ The token does not expire. You can store it in a secrets manager or
61
+ `~/.secrets` file and stop exposing your password in environment variables.
62
+
63
+ ---
64
+
65
+ ## Integrations
66
+
67
+ ### Claude Code (MCP)
68
+
69
+ Add to your `.mcp.json`:
70
+
71
+ ```json
72
+ {
73
+ "mcpServers": {
74
+ "huly": {
75
+ "command": "node",
76
+ "args": ["path/to/huly-mcp-server/src/index.mjs"],
77
+ "env": {
78
+ "HULY_URL": "https://your-huly-instance.com",
79
+ "HULY_EMAIL": "${HULY_EMAIL}",
80
+ "HULY_PASSWORD": "${HULY_PASSWORD}",
81
+ "HULY_WORKSPACE": "${HULY_WORKSPACE}"
82
+ }
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ Then ask Claude things like:
89
+
90
+ - "List my issues in the OPS project"
91
+ - "Create a bug report for the login page crash"
92
+ - "Summarize the PROJ project — what's overdue?"
93
+ - "Break down this feature into subtasks using the feature template"
94
+
95
+ All tools have detailed descriptions optimized for AI agents.
96
+ MCP Resources are also available at `huly://projects/{id}` and `huly://issues/{id}`.
97
+
98
+ ### n8n / Automation Workflows
99
+
100
+ Start the HTTP server:
101
+
102
+ ```bash
103
+ npm run start:server
104
+ # Listening on port 3001
105
+ ```
106
+
107
+ Use HTTP Request nodes pointing to `http://localhost:3001/api/...`:
108
+
109
+ ```bash
110
+ # List projects
111
+ curl http://localhost:3001/api/projects
112
+
113
+ # Create an issue
114
+ curl -X POST http://localhost:3001/api/projects/OPS/issues \
115
+ -H "Content-Type: application/json" \
116
+ -d '{"title": "New issue", "priority": "high"}'
117
+
118
+ # Get project summary
119
+ curl http://localhost:3001/api/projects/OPS/summary
120
+ ```
121
+
122
+ OpenAPI spec available at `GET /api/openapi.json` for auto-discovery in n8n and other tools.
123
+
124
+ ### Docker
125
+
126
+ ```bash
127
+ docker build -t huly-mcp-server .
128
+
129
+ docker run -d \
130
+ -p 3001:3001 \
131
+ -e HULY_URL=https://your-huly-instance.com \
132
+ -e HULY_EMAIL=admin@example.com \
133
+ -e HULY_PASSWORD=secret \
134
+ -e HULY_WORKSPACE=my-workspace \
135
+ huly-mcp-server
136
+ ```
137
+
138
+ For MCP stdio mode in Docker:
139
+
140
+ ```bash
141
+ docker run -i \
142
+ -e HULY_URL=https://your-huly-instance.com \
143
+ -e HULY_EMAIL=admin@example.com \
144
+ -e HULY_PASSWORD=secret \
145
+ -e HULY_WORKSPACE=my-workspace \
146
+ huly-mcp-server node src/mcp.mjs
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Server Configuration
152
+
153
+ These settings control the MCP server itself — authentication, rate limiting,
154
+ connection pooling. They are separate from the Huly credentials above.
155
+
156
+ ### Environment Variables
157
+
158
+ | Variable | Required | Default | Description |
159
+ | --- | --- | --- | --- |
160
+ | **Huly Connection** | | | |
161
+ | `HULY_URL` | No | `http://localhost:8087` | Huly instance URL |
162
+ | `HULY_TOKEN` | No | - | Auth token (alternative to email/password) |
163
+ | `HULY_EMAIL` | No | - | Huly login email (required if no token) |
164
+ | `HULY_PASSWORD` | No | - | Huly login password (required if no token) |
165
+ | `HULY_WORKSPACE` | Yes* | - | Default workspace slug |
166
+ | **Server Settings** | | | |
167
+ | `PORT` | No | `3001` | HTTP server port |
168
+ | `MCP_AUTH_TOKEN` | No | - | Bearer token for HTTP server auth (disabled if unset) |
169
+ | `HULY_RATE_LIMIT` | No | `100` | Max requests per minute per IP |
170
+ | `HULY_POOL_TTL_MS` | No | `1800000` | Connection pool TTL in ms (30 min) |
171
+
172
+ *`HULY_WORKSPACE` is required for MCP mode. For HTTP mode it can be
173
+ omitted if every request specifies a workspace via header or query param.
174
+
175
+ ### HTTP Server Authentication
176
+
177
+ The HTTP server optionally requires a bearer token. This protects **your server**
178
+ from unauthorized access — it's separate from Huly's own authentication.
179
+
180
+ ```bash
181
+ # Generate a token
182
+ openssl rand -hex 32
183
+
184
+ # Start with auth enabled
185
+ MCP_AUTH_TOKEN=your-token-here npm run start:server
186
+
187
+ # Clients must include it in requests
188
+ curl -H "Authorization: Bearer your-token-here" http://localhost:3001/api/projects
189
+ ```
190
+
191
+ If `MCP_AUTH_TOKEN` is not set, auth is disabled (fine for local-only usage).
192
+
193
+ MCP stdio mode (Claude Code) does not use this token — stdio is inherently local.
194
+
195
+ ### Rate Limiting
196
+
197
+ Per-IP rate limiting is always active. Response headers show current state:
198
+
199
+ ```text
200
+ X-RateLimit-Limit: 100
201
+ X-RateLimit-Remaining: 97
202
+ X-RateLimit-Reset: 1710000000
203
+ ```
204
+
205
+ Returns `429 Too Many Requests` when exceeded.
206
+
207
+ ### SSE Event Stream
208
+
209
+ Subscribe to real-time mutation events:
210
+
211
+ ```bash
212
+ curl -N http://localhost:3001/api/events
213
+ ```
214
+
215
+ Events: `issue.created`, `issue.updated`, `issue.moved`, `issue.assigned`,
216
+ `issue.comment_added`, `issue.label_added`, `issues.batch_created`,
217
+ `issues.template_created`, and more.
218
+
219
+ ### Multi-Workspace
220
+
221
+ All tools/endpoints accept an optional workspace parameter. The connection pool caches clients by workspace slug:
222
+
223
+ ```bash
224
+ # MCP: pass workspace in tool arguments
225
+ {"tool": "list_projects", "arguments": {"workspace": "workspace-a"}}
226
+
227
+ # HTTP: via header or query param
228
+ curl -H "X-Huly-Workspace: workspace-a" http://localhost:3001/api/projects
229
+ curl "http://localhost:3001/api/projects?workspace=workspace-b"
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Testing
235
+
236
+ Uses Node.js built-in `node:test` and `node:assert` — no test framework dependencies.
237
+
238
+ ```bash
239
+ npm test
240
+ ```
241
+
242
+ | Section | Tests | Description |
243
+ | --- | --- | --- |
244
+ | **Unit** | 28 | Constants, ID parsing, route matching, rate limiting, auth |
245
+ | **Integration** | 43 | Full CRUD lifecycle against live Huly |
246
+ | **Account-Level** | 11 | Workspaces, profile, social IDs |
247
+ | **Mock** | 28 | Destructive ops, token auth via mocks |
248
+ | **HTTP Server** | 52 | Every REST endpoint via real HTTP requests |
249
+
250
+ **100% method coverage.** All test issues are prefixed with `[TEST]` and cleaned up after each run.
251
+
252
+ ---
253
+
254
+ ## Network Configurations
255
+
256
+ - **Local:** `HULY_URL=http://localhost:8087`
257
+ - **Remote:** `HULY_URL=https://huly.example.com`
258
+ - **Behind nginx proxy:** Point to the proxy port
259
+
260
+ ### Cloudflare Access / Tunnel
261
+
262
+ If Huly is behind Cloudflare Access with MFA, create a
263
+ bypass Application for `/_*` or these individual paths:
264
+
265
+ - `/_accounts`
266
+ - `/_transactor`
267
+ - `/_collaborator`
268
+ - `/_rekoni`
269
+ - `/config.json`
270
+
271
+ ---
272
+
273
+ ## Architecture
274
+
275
+ ```text
276
+ src/
277
+ client.mjs # HulyClient class - all business logic
278
+ pool.mjs # Connection pool - caches clients by workspace with TTL
279
+ mcp.mjs # MCP stdio entry point - MCP tools + resources
280
+ server.mjs # HTTP REST entry point - auth, rate limiting, SSE, OpenAPI
281
+ index.mjs # Backwards compat - re-exports mcp.mjs
282
+ ```
283
+
284
+ ```text
285
+ Claude Code -> stdio -> mcp.mjs -> pool.mjs -> client.mjs -> REST -> Huly
286
+ n8n / curl -> HTTP -> server.mjs -> pool.mjs -> client.mjs -> REST -> Huly
287
+ ```
288
+
289
+ ---
290
+
291
+ ## API Reference
292
+
293
+ Full list of all MCP tools and HTTP endpoints available through this server.
294
+
295
+ ### MCP Tools
296
+
297
+ #### Account & Workspace Management
298
+
299
+ | Tool | Description |
300
+ | --- | --- |
301
+ | `list_workspaces` | List all accessible workspaces |
302
+ | `get_workspace_info` | Get workspace details by slug |
303
+ | `create_workspace` | Create a new workspace |
304
+ | `update_workspace_name` | Rename a workspace |
305
+ | `delete_workspace` | Permanently delete a workspace |
306
+ | `get_workspace_members` | List workspace members and roles |
307
+ | `update_workspace_role` | Change a member's role |
308
+ | `get_account_info` | Get current user's account info |
309
+ | `get_user_profile` | Get current user's profile |
310
+ | `set_my_profile` | Update profile fields |
311
+ | `change_password` | Change password |
312
+ | `change_username` | Change username |
313
+
314
+ #### Invites
315
+
316
+ | Tool | Description |
317
+ | --- | --- |
318
+ | `send_invite` | Send workspace invite email |
319
+ | `resend_invite` | Resend pending invite |
320
+ | `create_invite_link` | Generate shareable invite link |
321
+
322
+ #### Integrations, Mailboxes, Social IDs, Subscriptions
323
+
324
+ | Tool | Description |
325
+ | --- | --- |
326
+ | `list_integrations` / `get_integration` / `create_integration` / `update_integration` / `delete_integration` | Full CRUD for integrations |
327
+ | `list_mailboxes` / `create_mailbox` / `delete_mailbox` | Mailbox management |
328
+ | `find_person_by_social_key` / `get_social_ids` / `add_email_social_id` | Person/social ID management |
329
+ | `list_subscriptions` | List account subscriptions |
330
+
331
+ #### Projects & Issues
332
+
333
+ | Tool | Description |
334
+ | --- | --- |
335
+ | `list_projects` / `get_project` | Browse projects |
336
+ | `list_issues` / `get_issue` / `create_issue` / `update_issue` | Full issue CRUD with filtering |
337
+ | `search_issues` | Full-text search across projects |
338
+ | `get_my_issues` | Issues assigned to current user |
339
+ | `batch_create_issues` | Create multiple issues at once |
340
+ | `move_issue` | Move issue between projects |
341
+ | `summarize_project` | Aggregated project metrics |
342
+ | `get_issue_history` | Activity timeline for an issue |
343
+ | `create_issues_from_template` | Create from feature/bug/sprint/release templates |
344
+
345
+ #### Labels, Relations, Milestones, Members, Comments, Time Tracking
346
+
347
+ | Tool | Description |
348
+ | --- | --- |
349
+ | `list_labels` / `create_label` / `add_label` / `remove_label` | Label management |
350
+ | `add_relation` / `add_blocked_by` / `set_parent` | Issue relationships |
351
+ | `list_milestones` / `get_milestone` / `create_milestone` / `set_milestone` | Milestone management |
352
+ | `list_members` / `assign_issue` | Member management |
353
+ | `add_comment` / `list_comments` | Comments |
354
+ | `set_due_date` / `set_estimation` / `log_time` | Time tracking |
355
+ | `list_task_types` / `list_statuses` | Metadata |
356
+
357
+ ### HTTP REST Endpoints
358
+
359
+ #### System Routes
360
+
361
+ | Method | Path | Description |
362
+ | --- | --- | --- |
363
+ | GET | `/health` | Health check |
364
+ | GET | `/api/openapi.json` | OpenAPI 3.0.3 spec |
365
+ | GET | `/api/events` | SSE event stream |
366
+
367
+ #### Account & Workspace Routes
368
+
369
+ | Method | Path | Description |
370
+ | --- | --- | --- |
371
+ | GET | `/api/workspaces` | List workspaces |
372
+ | GET | `/api/workspaces/:slug/info` | Get workspace info |
373
+ | POST | `/api/workspaces` | Create workspace |
374
+ | PATCH | `/api/workspaces/:slug/name` | Rename workspace |
375
+ | DELETE | `/api/workspaces/:slug` | Delete workspace |
376
+ | GET | `/api/workspaces/:slug/members` | List members |
377
+ | PATCH | `/api/workspaces/:slug/role` | Update member role |
378
+ | POST | `/api/workspaces/:slug/invites` | Send invite |
379
+ | POST | `/api/workspaces/:slug/invite-link` | Create invite link |
380
+ | GET | `/api/account` | Get account info |
381
+ | GET | `/api/profile` | Get user profile |
382
+ | PATCH | `/api/profile` | Update profile |
383
+ | GET | `/api/integrations` | List integrations |
384
+ | POST | `/api/integrations` | Create integration |
385
+ | DELETE | `/api/integrations/:id` | Delete integration |
386
+ | GET | `/api/mailboxes` | List mailboxes |
387
+ | GET | `/api/social-ids` | List social IDs |
388
+ | GET | `/api/subscriptions` | List subscriptions |
389
+
390
+ #### Project & Issue Routes
391
+
392
+ | Method | Path | Description |
393
+ | --- | --- | --- |
394
+ | GET | `/api/projects` | List all projects |
395
+ | GET | `/api/projects/:identifier` | Get project by identifier |
396
+ | GET | `/api/projects/:project/summary` | Project summary with metrics |
397
+ | GET | `/api/projects/:project/issues` | List issues (query: status, priority, label, milestone, limit) |
398
+ | GET | `/api/projects/:project/issues/:number` | Get issue |
399
+ | POST | `/api/projects/:project/issues` | Create issue |
400
+ | PATCH | `/api/issues/:issueId` | Update issue |
401
+ | POST | `/api/issues/:issueId/move` | Move to different project |
402
+ | GET | `/api/issues/:issueId/history` | Get activity history |
403
+ | GET | `/api/my-issues` | Issues assigned to current user |
404
+ | POST | `/api/projects/:project/batch-issues` | Batch create issues |
405
+ | POST | `/api/projects/:project/template` | Create from template |
406
+ | GET | `/api/search?query=...&project=...&limit=...` | Search issues |
407
+
408
+ #### Other Resource Routes
409
+
410
+ | Method | Path | Description |
411
+ | --- | --- | --- |
412
+ | GET | `/api/labels` | List all labels |
413
+ | POST | `/api/labels` | Create label |
414
+ | POST | `/api/issues/:issueId/labels` | Add label to issue |
415
+ | DELETE | `/api/issues/:issueId/labels/:label` | Remove label |
416
+ | POST | `/api/issues/:issueId/relations` | Add relation |
417
+ | POST | `/api/issues/:issueId/blocked-by` | Add blocked-by |
418
+ | POST | `/api/issues/:issueId/parent` | Set parent |
419
+ | GET | `/api/projects/:project/task-types` | List task types |
420
+ | GET | `/api/statuses` | List all statuses |
421
+ | GET | `/api/projects/:project/milestones` | List milestones |
422
+ | GET | `/api/projects/:project/milestones/:name` | Get milestone |
423
+ | POST | `/api/projects/:project/milestones` | Create milestone |
424
+ | PATCH | `/api/issues/:issueId/milestone` | Set/clear milestone |
425
+ | GET | `/api/members` | List workspace members |
426
+ | PATCH | `/api/issues/:issueId/assignee` | Assign/unassign issue |
427
+ | GET | `/api/issues/:issueId/comments` | List comments |
428
+ | POST | `/api/issues/:issueId/comments` | Add comment |
429
+ | PATCH | `/api/issues/:issueId/due-date` | Set/clear due date |
430
+ | PATCH | `/api/issues/:issueId/estimation` | Set estimation |
431
+ | POST | `/api/issues/:issueId/time-logs` | Log time |
432
+
433
+ ### Issue Templates
434
+
435
+ Use `create_issues_from_template` (MCP) or `POST /api/projects/:project/template` (HTTP):
436
+
437
+ | Template | Creates |
438
+ | --- | --- |
439
+ | `feature` | Epic + design/implement/test/docs/review sub-issues |
440
+ | `bug` | Bug + reproduce/root-cause/fix/regression-test sub-issues |
441
+ | `sprint` | Planning/standup/review/retro ceremony issues |
442
+ | `release` | Epic + freeze/QA/changelog/staging/prod/verify sub-issues |
443
+
444
+ ---
445
+
446
+ ## Security
447
+
448
+ `npm audit` reports moderate vulnerabilities in Svelte (SSR XSS).
449
+ These come from Huly SDK transitive dependencies — the SDK shares packages
450
+ with Huly's web frontend. This server never renders HTML or uses Svelte.
451
+ The vulnerabilities are **not exploitable** in this context.
452
+
453
+ ---
454
+
455
+ ## License
456
+
457
+ [MIT](LICENSE)
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@bgx4k3p/huly-mcp-server",
3
+ "version": "2.0.0",
4
+ "description": "MCP and HTTP REST server for Huly issue tracking with multi-workspace support",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "bgx4k3p",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/bgx4k3p/huly-mcp-server.git"
11
+ },
12
+ "homepage": "https://github.com/bgx4k3p/huly-mcp-server",
13
+ "keywords": [
14
+ "mcp",
15
+ "huly",
16
+ "model-context-protocol",
17
+ "issue-tracker",
18
+ "rest-api",
19
+ "claude",
20
+ "ai-agent"
21
+ ],
22
+ "engines": {
23
+ "node": ">=22"
24
+ },
25
+ "files": [
26
+ "src/",
27
+ "LICENSE",
28
+ "README.md"
29
+ ],
30
+ "bin": {
31
+ "huly-mcp-server": "src/index.mjs"
32
+ },
33
+ "scripts": {
34
+ "start": "node src/index.mjs",
35
+ "start:mcp": "node src/mcp.mjs",
36
+ "start:server": "node src/server.mjs",
37
+ "test": "node --test test/integration.test.mjs"
38
+ },
39
+ "dependencies": {
40
+ "@hcengineering/api-client": "^0.7.3",
41
+ "@hcengineering/chunter": "^0.7.0",
42
+ "@hcengineering/core": "^0.7.4",
43
+ "@hcengineering/tags": "^0.7.0",
44
+ "@hcengineering/task": "^0.7.0",
45
+ "@hcengineering/tracker": "^0.7.0",
46
+ "@modelcontextprotocol/sdk": "^1.27.1",
47
+ "jsdom": "^29.0.0"
48
+ }
49
+ }