huly-mcp-selfhost 1.0.0 → 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.
Files changed (65) hide show
  1. package/.env.example +17 -9
  2. package/LICENSE +80 -0
  3. package/README.md +195 -111
  4. package/dist/connection.d.ts +65 -4
  5. package/dist/connection.js +230 -93
  6. package/dist/connection.js.map +1 -1
  7. package/dist/index.js +0 -0
  8. package/dist/schemas.d.ts +162 -2
  9. package/dist/schemas.js +65 -3
  10. package/dist/schemas.js.map +1 -1
  11. package/dist/server.js +52 -6
  12. package/dist/server.js.map +1 -1
  13. package/dist/tools/attachments.d.ts +3 -0
  14. package/dist/tools/attachments.js +5 -5
  15. package/dist/tools/attachments.js.map +1 -1
  16. package/dist/tools/channels.d.ts +5 -0
  17. package/dist/tools/channels.js +5 -5
  18. package/dist/tools/channels.js.map +1 -1
  19. package/dist/tools/comments.d.ts +3 -0
  20. package/dist/tools/comments.js +3 -3
  21. package/dist/tools/comments.js.map +1 -1
  22. package/dist/tools/components.d.ts +2 -0
  23. package/dist/tools/components.js +2 -2
  24. package/dist/tools/components.js.map +1 -1
  25. package/dist/tools/documents.d.ts +10 -1
  26. package/dist/tools/documents.js +16 -14
  27. package/dist/tools/documents.js.map +1 -1
  28. package/dist/tools/issues.d.ts +5 -0
  29. package/dist/tools/issues.js +11 -10
  30. package/dist/tools/issues.js.map +1 -1
  31. package/dist/tools/labels.d.ts +4 -0
  32. package/dist/tools/labels.js +4 -4
  33. package/dist/tools/labels.js.map +1 -1
  34. package/dist/tools/log-time.d.ts +1 -0
  35. package/dist/tools/log-time.js +1 -1
  36. package/dist/tools/log-time.js.map +1 -1
  37. package/dist/tools/members.d.ts +3 -1
  38. package/dist/tools/members.js +2 -2
  39. package/dist/tools/members.js.map +1 -1
  40. package/dist/tools/milestones.d.ts +2 -0
  41. package/dist/tools/milestones.js +2 -2
  42. package/dist/tools/milestones.js.map +1 -1
  43. package/dist/tools/organizations.d.ts +6 -1
  44. package/dist/tools/organizations.js +10 -9
  45. package/dist/tools/organizations.js.map +1 -1
  46. package/dist/tools/projects.d.ts +5 -1
  47. package/dist/tools/projects.js +4 -4
  48. package/dist/tools/projects.js.map +1 -1
  49. package/dist/tools/relations.d.ts +3 -0
  50. package/dist/tools/relations.js +3 -3
  51. package/dist/tools/relations.js.map +1 -1
  52. package/dist/tools/search.d.ts +1 -0
  53. package/dist/tools/search.js +1 -1
  54. package/dist/tools/search.js.map +1 -1
  55. package/dist/tools/statuses.d.ts +4 -1
  56. package/dist/tools/statuses.js +3 -3
  57. package/dist/tools/statuses.js.map +1 -1
  58. package/dist/tools/workspaces.d.ts +6 -0
  59. package/dist/tools/workspaces.js +19 -0
  60. package/dist/tools/workspaces.js.map +1 -0
  61. package/package.json +4 -2
  62. package/scripts/cleanup-issues.js +15 -6
  63. package/scripts/demo.js +10 -2
  64. package/scripts/import-csv.js +13 -5
  65. package/scripts/setup.js +22 -20
package/README.md CHANGED
@@ -1,18 +1,27 @@
1
- # huly-mcp-sdk (self-hosted fork)
1
+ # huly-mcp-selfhost
2
+
3
+ [![npm](https://img.shields.io/npm/v/huly-mcp-selfhost?label=npm)](https://www.npmjs.com/package/huly-mcp-selfhost)
4
+ [![Docker Pulls](https://img.shields.io/docker/pulls/jokeks2023/huly-mcp)](https://hub.docker.com/r/jokeks2023/huly-mcp)
5
+ [![Docker Image Size](https://img.shields.io/docker/image-size/jokeks2023/huly-mcp/latest)](https://hub.docker.com/r/jokeks2023/huly-mcp)
6
+ [![Publish Docker image](https://github.com/JoKeks2023/huly-mcp/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/JoKeks2023/huly-mcp/actions/workflows/docker-publish.yml)
7
+ [![License: EPL-2.0](https://img.shields.io/badge/license-EPL--2.0-blue)](LICENSE)
2
8
 
3
9
  > The most complete MCP server for [Huly](https://huly.app) — the open-source project management platform.
4
10
 
5
- Connects **Claude Desktop** (and any [MCP](https://modelcontextprotocol.io)-compatible client) directly to your Huly workspace. Manage projects, issues, milestones, components, documents, labels, chat, attachments, organizations, and more — all via natural language.
11
+ Connects **Claude Desktop** (and any [MCP](https://modelcontextprotocol.io)-compatible client) directly to your Huly workspaces — one account, as many workspaces as it can access. Manage projects, issues, milestones, components, documents, labels, chat, attachments, organizations, and more — all via natural language.
6
12
 
7
13
  ## About this fork
8
14
 
9
- This is a fork of [varaprasadreddy9676/huly-mcp](https://github.com/varaprasadreddy9676/huly-mcp), built out for a **self-hosted** (`huly-selfhost`) deployment and extended well past the upstream tool set. **Not published to npm** — see [Installing this fork](#installing-this-fork) below.
15
+ This is a fork of [varaprasadreddy9676/huly-mcp](https://github.com/varaprasadreddy9676/huly-mcp), built out for a **self-hosted** (`huly-selfhost`) deployment and extended well past the upstream tool set. Published separately as **[`huly-mcp-selfhost`](https://www.npmjs.com/package/huly-mcp-selfhost)** on npm and as a Docker image — `huly-mcp-sdk` on npm is the *original* upstream package, not this one. See [Installing this fork](#installing-this-fork) below.
10
16
 
11
17
  **Fixed vs. upstream:**
12
18
  - **Self-hosted document/description writes.** Upstream's `update_document` (and, by extension, any issue description) is hardcoded against Huly Cloud's "datalake" microservice at `dl-eu.huly.app`, which `huly-selfhost` doesn't run. This fork auto-detects self-hosted deployments via `HULY_FRONT_URL` and uploads through `front`'s own `/files` contract instead — falls back to the original Cloud behavior when `HULY_FRONT_URL` is unset. See [`src/utils/storage.ts`](src/utils/storage.ts).
13
19
  - **`description` on `create_issue`/`update_issue`.** Missing entirely upstream — issue descriptions are `MarkupBlobRef`s, same storage mechanism as document content, so this needed the same fix.
14
20
 
15
- **New tool categories** (upstream had 36 tools across Projects/Issues/Comments/Time/Labels/Relations/Members/Milestones/Components/Documents/Search; this fork adds 14 more):
21
+ **Multi-workspace:** one login, all workspaces of the account — see [Workspaces](#workspaces-one-account-several-workspaces).
22
+
23
+ **New tool categories** (upstream had 33 tools across Projects/Issues/Comments/Time/Labels/Relations/Members/Milestones/Components/Documents/Search; this fork adds 15 more):
24
+ - **Workspaces** — `list_workspaces`
16
25
  - **Chat** — `list_channels`, `create_channel`, `start_direct_message`, `send_message`, `list_messages`
17
26
  - **Attachments** — `attach_file`, `list_attachments`, `delete_attachment` (generic files on issues, any content type)
18
27
  - **Issue Statuses** — `list_issue_statuses`, `create_issue_status` (custom workflow states)
@@ -22,11 +31,14 @@ This is a fork of [varaprasadreddy9676/huly-mcp](https://github.com/varaprasadre
22
31
 
23
32
  ---
24
33
 
25
- ## Tools (50 total)
34
+ ## Tools (48 total)
35
+
36
+ All tools except `list_workspaces` take a `workspace` argument — required for every change, optional for reads when only one workspace is available.
26
37
 
27
38
  | Category | Tool | Description |
28
39
  |----------|------|-------------|
29
- | **Projects** | `list_projects` | List all projects in the workspace |
40
+ | **Workspaces** | `list_workspaces` | List the workspaces this account can access (name, slug, id) |
41
+ | **Projects** | `list_projects` | List all projects in a workspace |
30
42
  | | `get_project` | Get project details + available statuses |
31
43
  | | `create_project` | Create a new tracker project with a unique identifier |
32
44
  | **Issues** | `list_issues` | List issues with optional status / priority filters |
@@ -58,7 +70,7 @@ This is a fork of [varaprasadreddy9676/huly-mcp](https://github.com/varaprasadre
58
70
  | | `create_document` | Create a new document in a teamspace |
59
71
  | | `update_document` | Write Markdown content to a document — Mermaid diagrams render natively |
60
72
  | | `link_document` | Link a document to an issue — appears in the Relations panel |
61
- | **Search** | `search_issues` | Full-text search across all issues |
73
+ | **Search** | `search_issues` | Full-text search across all issues of a workspace |
62
74
  | **Chat** | `list_channels` | List all channels in the workspace |
63
75
  | | `create_channel` | Create a new channel |
64
76
  | | `start_direct_message` | Start (or find) a 1:1 direct message with a workspace member |
@@ -85,7 +97,18 @@ This is a fork of [varaprasadreddy9676/huly-mcp](https://github.com/varaprasadre
85
97
 
86
98
  ## Installing this fork
87
99
 
88
- This fork isn't published to npm — `npx huly-mcp-sdk` (the upstream package) will **not** include anything in this README past "About this fork". Clone and build instead:
100
+ This fork is published separately as **[`huly-mcp-selfhost`](https://www.npmjs.com/package/huly-mcp-selfhost)** (`huly-mcp-sdk` is the upstream package). Three ways to run it:
101
+
102
+ ### npm / npx
103
+
104
+ ```json
105
+ "command": "npx",
106
+ "args": ["-y", "huly-mcp-selfhost"]
107
+ ```
108
+
109
+ Every client example below uses this form.
110
+
111
+ ### Clone and build
89
112
 
90
113
  ```bash
91
114
  git clone https://github.com/JoKeks2023/huly-mcp.git
@@ -94,28 +117,65 @@ npm install
94
117
  npm run build
95
118
  ```
96
119
 
97
- Then in any client config below, replace `"command": "npx", "args": ["huly-mcp-sdk"]` with:
98
-
99
120
  ```json
100
121
  "command": "node",
101
122
  "args": ["/absolute/path/to/huly-mcp/dist/index.js"]
102
123
  ```
103
124
 
104
- **Your workspace slug** is the part of your Huly URL after the domain: `huly.app/`**`myteam`** → slug is `myteam` (self-hosted: the `url` field of your workspace, e.g. `https://your-instance.com/workbench/`**`myteam`**).
125
+ ### Docker (network-reachable server, not local stdio)
126
+
127
+ For deployments where the MCP server needs to be reachable over the network (behind a reverse proxy, remote MCP clients, etc.) rather than launched locally per-client, a prebuilt image is published to GHCR on every push to `main`:
128
+
129
+ ```bash
130
+ docker pull jokeks2023/huly-mcp:latest
131
+ # also mirrored at ghcr.io/jokeks2023/huly-mcp:latest
132
+ ```
133
+
134
+ Or with Compose — copy `.env.example` to `.env`, fill in your credentials (no workspace needed), then:
135
+
136
+ ```bash
137
+ docker compose up -d
138
+ ```
139
+
140
+ The container wraps the server with [`mcp-proxy`](https://github.com/sparfenyuk/mcp-proxy) to expose it over HTTP/SSE at `:8000/sse`, since standard MCP stdio transport can't be reached over a network directly. See [`Dockerfile`](Dockerfile) / [`docker-compose.yml`](docker-compose.yml).
141
+
142
+ ---
143
+
144
+ ## Workspaces (one account, several workspaces)
145
+
146
+ A single Huly account can belong to several workspaces — e.g. a personal one and a team one. This server logs in **once** with your account credentials and discovers every workspace that account can access via the account service (`getUserWorkspaces()`). You **don't configure a workspace** in the environment.
147
+
148
+ - **`list_workspaces`** returns name, slug and id of every available workspace.
149
+ - **Every other tool takes a `workspace` argument** — the slug, the name or the id (case-insensitive):
150
+ `list_projects(workspace="jokeks2023")`, `get_issue(workspace="avms", identifier="PROJ-1")`, `log_time(workspace="jokeks2023", identifier="WEB-12", hours=2)`.
151
+ - **Changes always require `workspace`.** Every tool that creates, updates, deletes, links, sends, attaches or logs something has `workspace` as a required argument — the server never falls back to an implicit, default or "last used" workspace for a write.
152
+ - **Reads may omit `workspace` only if exactly one workspace is available.** With several, the call fails and lists the options.
153
+ - **IDs are per workspace.** `PROJ-1`, a document id or a comment id identifies something only together with its workspace — the same id can exist in two workspaces. Pass the same `workspace` you got the id from.
154
+ - Each workspace gets its own lazily opened, cached WebSocket connection with its own workspace token (via `selectWorkspace()`), so workspaces never share state.
155
+
156
+ **Workspace slug:** the part of the Huly URL after the domain: `huly.app/workbench/`**`myteam`** → `myteam`. You don't need to look it up — `list_workspaces` (or `npm run setup`) prints it.
157
+
158
+ **Legacy `HULY_WORKSPACE`:** still honoured, but only as an optional *restriction*: if set, this server exposes just that one workspace (as before). Leave it unset to use all workspaces.
159
+
160
+ ---
161
+
162
+ ## Configuration
163
+
164
+ | Variable | Required | Purpose |
165
+ |----------|----------|---------|
166
+ | `HULY_EMAIL` + `HULY_PASSWORD` | one auth option | Account login (needs a password set on your Huly account) |
167
+ | `HULY_TOKEN` | one auth option | Account token for SSO accounts (Google/GitHub) — get it with `npm run setup` |
168
+ | `HULY_ACCOUNTS_URL` | self-hosted only | Account service — login and workspace discovery. Default `https://account.huly.app`. Self-hosted: your instance's accounts URL, e.g. `https://huly.example.com/_accounts` |
169
+ | `HULY_FRONT_URL` | self-hosted (optional on Cloud) | Front service — document/description content and file uploads. Not involved in login or workspace discovery. Self-hosted: e.g. `https://huly.example.com` |
170
+ | `HULY_WORKSPACE` | no (legacy) | Restrict the server to a single workspace |
105
171
 
106
172
  ---
107
173
 
108
174
  ## Compatible Clients
109
175
 
110
- The same MCP server works across all major AI coding tools. Pick your client, then swap the `npx`/`args` for the local build per [Installing this fork](#installing-this-fork) above.
176
+ The same MCP server works across all major AI coding tools. All examples use **email + password** and the npm package via `npx`; for SSO accounts replace the two credentials with `"HULY_TOKEN": "your-token"` (see [Authentication](#authentication)). To run a local build instead, replace `npx` + args with `node` + `["/absolute/path/to/huly-mcp/dist/index.js"]`.
111
177
 
112
- > **Auth note:** All config examples below use `HULY_TOKEN`. If you have issues with token expiry, use email + password instead — just replace the `env` block with:
113
- > ```json
114
- > "HULY_EMAIL": "your@email.com",
115
- > "HULY_PASSWORD": "yourpassword",
116
- > "HULY_WORKSPACE": "your-workspace-slug"
117
- > ```
118
- > See [Manual Auth](#manual-auth) for details on both options.
178
+ **Self-hosted:** add `HULY_ACCOUNTS_URL` and `HULY_FRONT_URL` to every `env` block below.
119
179
 
120
180
  ---
121
181
 
@@ -129,10 +189,10 @@ The same MCP server works across all major AI coding tools. Pick your client, th
129
189
  "mcpServers": {
130
190
  "huly": {
131
191
  "command": "npx",
132
- "args": ["huly-mcp-sdk"],
192
+ "args": ["-y", "huly-mcp-selfhost"],
133
193
  "env": {
134
- "HULY_TOKEN": "your-token",
135
- "HULY_WORKSPACE": "your-workspace-slug"
194
+ "HULY_EMAIL": "you@example.com",
195
+ "HULY_PASSWORD": "your-password"
136
196
  }
137
197
  }
138
198
  }
@@ -146,43 +206,40 @@ Restart Claude Desktop after saving.
146
206
  ### Claude Code (CLI)
147
207
 
148
208
  ```bash
149
- claude mcp add huly -e HULY_TOKEN=your-token -e HULY_WORKSPACE=your-slug -- npx huly-mcp-sdk
209
+ claude mcp add huly \
210
+ -e HULY_EMAIL=you@example.com \
211
+ -e HULY_PASSWORD=your-password \
212
+ -- npx -y huly-mcp-selfhost
150
213
  ```
151
214
 
152
- Or scope it to a single project only:
153
-
154
- ```bash
155
- claude mcp add huly --scope project -e HULY_TOKEN=your-token -e HULY_WORKSPACE=your-slug -- npx huly-mcp-sdk
156
- ```
157
-
158
- Verify it's connected: `claude mcp list`
215
+ Add `--scope project` (writes `.mcp.json`, shared with the repo — don't commit secrets) or `--scope user` (all your projects) after `add huly`. Verify with `claude mcp list`.
159
216
 
160
217
  ---
161
218
 
162
219
  ### Cursor
163
220
 
164
- Create or edit `~/.cursor/mcp.json`:
221
+ Create or edit `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project):
165
222
 
166
223
  ```json
167
224
  {
168
225
  "mcpServers": {
169
226
  "huly": {
170
227
  "command": "npx",
171
- "args": ["huly-mcp-sdk"],
228
+ "args": ["-y", "huly-mcp-selfhost"],
172
229
  "env": {
173
- "HULY_TOKEN": "your-token",
174
- "HULY_WORKSPACE": "your-workspace-slug"
230
+ "HULY_EMAIL": "you@example.com",
231
+ "HULY_PASSWORD": "your-password"
175
232
  }
176
233
  }
177
234
  }
178
235
  }
179
236
  ```
180
237
 
181
- Restart Cursor. The tools appear in the Agent panel under MCP.
238
+ The tools appear in the Agent panel under MCP.
182
239
 
183
240
  ---
184
241
 
185
- ### Windsurf (Codeium)
242
+ ### Windsurf
186
243
 
187
244
  Create or edit `~/.codeium/windsurf/mcp_config.json`:
188
245
 
@@ -191,34 +248,35 @@ Create or edit `~/.codeium/windsurf/mcp_config.json`:
191
248
  "mcpServers": {
192
249
  "huly": {
193
250
  "command": "npx",
194
- "args": ["huly-mcp-sdk"],
251
+ "args": ["-y", "huly-mcp-selfhost"],
195
252
  "env": {
196
- "HULY_TOKEN": "your-token",
197
- "HULY_WORKSPACE": "your-workspace-slug"
253
+ "HULY_EMAIL": "you@example.com",
254
+ "HULY_PASSWORD": "your-password"
198
255
  }
199
256
  }
200
257
  }
201
258
  }
202
259
  ```
203
260
 
204
- Restart Windsurf. MCP tools are available to the Cascade AI panel.
261
+ Refresh the MCP servers in the Cascade panel.
205
262
 
206
263
  ---
207
264
 
208
- ### VS Code — Cline extension
265
+ ### VS Code — Cline
209
266
 
210
- 1. Install the [Cline extension](https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev)
211
- 2. Open Cline settings → **MCP Servers** → **Edit MCP Settings**
212
- 3. Add:
267
+ Cline → **MCP Servers** → **Installed** → **Configure MCP Servers** (opens `cline_mcp_settings.json`):
213
268
 
214
269
  ```json
215
270
  {
216
- "huly": {
217
- "command": "npx",
218
- "args": ["huly-mcp-sdk"],
219
- "env": {
220
- "HULY_TOKEN": "your-token",
221
- "HULY_WORKSPACE": "your-workspace-slug"
271
+ "mcpServers": {
272
+ "huly": {
273
+ "command": "npx",
274
+ "args": ["-y", "huly-mcp-selfhost"],
275
+ "env": {
276
+ "HULY_EMAIL": "you@example.com",
277
+ "HULY_PASSWORD": "your-password"
278
+ },
279
+ "disabled": false
222
280
  }
223
281
  }
224
282
  }
@@ -226,44 +284,36 @@ Restart Windsurf. MCP tools are available to the Cascade AI panel.
226
284
 
227
285
  ---
228
286
 
229
- ### VS Code — Continue extension
287
+ ### VS Code / JetBrains — Continue
230
288
 
231
- 1. Install the [Continue extension](https://marketplace.visualstudio.com/items?itemName=Continue.continue)
232
- 2. Edit `~/.continue/config.json` and add to the `mcpServers` array:
289
+ Add to `~/.continue/config.yaml` (MCP tools are available in Agent mode):
233
290
 
234
- ```json
235
- {
236
- "mcpServers": [
237
- {
238
- "name": "huly",
239
- "command": "npx",
240
- "args": ["huly-mcp-sdk"],
241
- "env": {
242
- "HULY_TOKEN": "your-token",
243
- "HULY_WORKSPACE": "your-workspace-slug"
244
- }
245
- }
246
- ]
247
- }
291
+ ```yaml
292
+ mcpServers:
293
+ - name: huly
294
+ command: npx
295
+ args: ["-y", "huly-mcp-selfhost"]
296
+ env:
297
+ HULY_EMAIL: you@example.com
298
+ HULY_PASSWORD: your-password
248
299
  ```
249
300
 
250
301
  ---
251
302
 
252
303
  ### Zed
253
304
 
254
- Edit `~/.config/zed/settings.json` and add a `context_servers` entry:
305
+ Add to `~/.config/zed/settings.json`:
255
306
 
256
307
  ```json
257
308
  {
258
309
  "context_servers": {
259
310
  "huly": {
260
- "command": {
261
- "path": "npx",
262
- "args": ["huly-mcp-sdk"],
263
- "env": {
264
- "HULY_TOKEN": "your-token",
265
- "HULY_WORKSPACE": "your-workspace-slug"
266
- }
311
+ "source": "custom",
312
+ "command": "npx",
313
+ "args": ["-y", "huly-mcp-selfhost"],
314
+ "env": {
315
+ "HULY_EMAIL": "you@example.com",
316
+ "HULY_PASSWORD": "your-password"
267
317
  }
268
318
  }
269
319
  }
@@ -274,35 +324,27 @@ Edit `~/.config/zed/settings.json` and add a `context_servers` entry:
274
324
 
275
325
  ### OpenAI Codex CLI
276
326
 
277
- Edit `~/.codex/config.json` and add to `mcpServers`:
327
+ Add to `~/.codex/config.toml`:
278
328
 
279
- ```json
280
- {
281
- "mcpServers": {
282
- "huly": {
283
- "type": "stdio",
284
- "command": "npx",
285
- "args": ["huly-mcp-sdk"],
286
- "env": {
287
- "HULY_TOKEN": "your-token",
288
- "HULY_WORKSPACE": "your-workspace-slug"
289
- }
290
- }
291
- }
292
- }
329
+ ```toml
330
+ [mcp_servers.huly]
331
+ command = "npx"
332
+ args = ["-y", "huly-mcp-selfhost"]
333
+ env = { HULY_EMAIL = "you@example.com", HULY_PASSWORD = "your-password" }
293
334
  ```
294
335
 
336
+ or: `codex mcp add huly --env HULY_EMAIL=you@example.com --env HULY_PASSWORD=your-password -- npx -y huly-mcp-selfhost`
337
+
295
338
  ---
296
339
 
297
340
  ### Any other MCP-compatible client
298
341
 
299
- The server uses standard **stdio transport**. If your tool supports MCP, the config pattern is always the same:
342
+ The server uses standard **stdio transport**:
300
343
 
301
- - **command:** `node`
302
- - **args:** `["/absolute/path/to/huly-mcp/dist/index.js"]` (see [Installing this fork](#installing-this-fork))
303
- - **env:** `HULY_TOKEN` + `HULY_WORKSPACE` (or `HULY_EMAIL`/`HULY_PASSWORD`; self-hosted also needs `HULY_ACCOUNTS_URL` + `HULY_FRONT_URL`, see [Manual Auth](#manual-auth))
344
+ - **command:** `npx` with args `["-y", "huly-mcp-selfhost"]`, or `node` with `["/absolute/path/to/huly-mcp/dist/index.js"]`
345
+ - **env:** `HULY_EMAIL` + `HULY_PASSWORD` (or `HULY_TOKEN`); self-hosted also `HULY_ACCOUNTS_URL` + `HULY_FRONT_URL` — see [Configuration](#configuration)
304
346
 
305
- Consult your tool's MCP documentation for the exact config file location.
347
+ For network access (HTTP/SSE) use the [Docker image](#docker-network-reachable-server-not-local-stdio).
306
348
 
307
349
  ---
308
350
 
@@ -310,6 +352,11 @@ Consult your tool's MCP documentation for the exact config file location.
310
352
 
311
353
  ## Example Prompts
312
354
 
355
+ **Workspaces:**
356
+ - *"Which Huly workspaces do I have?"*
357
+ - *"List the projects in my AVMS workspace"*
358
+ - *"In the jokeks2023 workspace, log 2 hours on WEB-12"*
359
+
313
360
  **Projects & issues:**
314
361
  - *"Create a new project called 'Mobile App' with identifier MOBILE"*
315
362
  - *"List all in-progress issues in the PROJ project"*
@@ -352,12 +399,14 @@ Consult your tool's MCP documentation for the exact config file location.
352
399
 
353
400
  ```json
354
401
  "env": {
355
- "HULY_TOKEN": "...",
356
- "HULY_WORKSPACE": "myteam",
402
+ "HULY_EMAIL": "...",
403
+ "HULY_PASSWORD": "...",
357
404
  "HULY_FRONT_URL": "https://front.huly.app"
358
405
  }
359
406
  ```
360
407
 
408
+ `HULY_FRONT_URL` only serves document/description content and file uploads — each request uses the token of the workspace named in the tool call. It plays no part in login or workspace discovery (that is `HULY_ACCOUNTS_URL`).
409
+
361
410
  For **self-hosted** Huly, set `HULY_FRONT_URL` to your own front service URL (e.g. `http://localhost:8083`).
362
411
 
363
412
  ### Writing: `update_document`
@@ -413,9 +462,12 @@ The Mermaid block renders as a live interactive diagram in Huly's document edito
413
462
  Import many issues at once from a CSV file — useful for migrating from other tools:
414
463
 
415
464
  ```bash
416
- node scripts/import-csv.js tasks.csv PROJ
465
+ npm run build
466
+ node scripts/import-csv.js tasks.csv PROJ --workspace=myteam
417
467
  ```
418
468
 
469
+ The target workspace is required (`--workspace=<slug>`, or `HULY_WORKSPACE` as a fallback), since the script writes data. Credentials come from the environment as for the server.
470
+
419
471
  **CSV format:**
420
472
 
421
473
  ```csv
@@ -429,9 +481,9 @@ Required column: `title`. Optional: `priority` (Urgent/High/Medium/Low), `status
429
481
 
430
482
  ---
431
483
 
432
- ## Manual Auth
484
+ ## Authentication
433
485
 
434
- Create a `.env` file in the project root (or pass via `env` in your client config):
486
+ Create a `.env` file in the project root (or pass via `env` in your client config). No workspace is needed in either option.
435
487
 
436
488
  **Option A — Email + password (recommended):**
437
489
 
@@ -440,40 +492,72 @@ Works if you have a password set on your Huly account (Profile → Security →
440
492
  ```bash
441
493
  HULY_EMAIL=your@email.com
442
494
  HULY_PASSWORD=yourpassword
443
- HULY_WORKSPACE=your-workspace-slug
444
495
  ```
445
496
 
446
- **Option B — Token:**
497
+ **Option B — Token (SSO accounts: Google/GitHub):**
498
+
499
+ ```bash
500
+ npm run setup
501
+ ```
502
+
503
+ The wizard sends a one-time code to your email, saves the resulting account token as `HULY_TOKEN` in `.env`, and lists the workspaces the account can access (a discovery test).
447
504
 
448
505
  ```bash
449
- HULY_WORKSPACE=your-workspace-slug
450
506
  HULY_TOKEN=your-token-here
451
507
  ```
452
508
 
453
- To get a token: go to [huly.app](https://huly.app) → open browser DevTools → Application → Local Storage → `https://huly.app` → copy the `token` value.
509
+ A token copied from the browser (DevTools → Application → Local Storage → `token`) also works, but may be scoped to the single workspace open in that tab. If the account service refuses to list workspaces for such a token, the server falls back to that one workspace. Use `npm run setup` for full multi-workspace discovery.
454
510
 
455
- > Tokens expire after some time. If you get an auth error, switch to email + password auth or refresh the token from DevTools.
511
+ > Tokens expire after some time. If you get an auth error, run `npm run setup` again or switch to email + password.
456
512
 
457
513
  **Self-hosted Huly:**
458
514
 
459
515
  ```bash
460
- HULY_ACCOUNTS_URL=https://your-huly-instance.com/account
461
- HULY_FRONT_URL=https://your-huly-instance.com
516
+ HULY_ACCOUNTS_URL=https://your-huly-instance.com/_accounts # login + workspace discovery
517
+ HULY_FRONT_URL=https://your-huly-instance.com # document content + file uploads
462
518
  ```
463
519
 
464
520
  ---
465
521
 
466
522
  ## Architecture
467
523
 
468
- - **Single long-lived WebSocket connection** — connects once per process via `@hcengineering/server-client`, not per tool call (model load takes 1–3 s, so this keeps tools fast)
469
- - **Lazy init** — connects on the first tool call so auth errors surface clearly in Claude
470
- - **Dual auth** — OTP token (works for Google/GitHub SSO) or email + password
471
- - **Stdio transport** — standard MCP transport compatible with Claude Desktop and any MCP client
524
+ ```text
525
+ HULY_EMAIL + HULY_PASSWORD (or HULY_TOKEN)
526
+ ↓
527
+ account login (once) account-level client: getUserWorkspaces(), selectWorkspace()
528
+ ↓
529
+ getUserWorkspaces() → workspace discovery (cached)
530
+ ↓
531
+ WorkspaceConnectionManager
532
+ ├── workspace A: selectWorkspace(A) → endpoint + token → WebSocket (cached)
533
+ ├── workspace B: selectWorkspace(B) → endpoint + token → WebSocket (cached)
534
+ └── … workspace-level client (workspace token): getWorkspaceMembers()
535
+ ↓
536
+ MCP tools (each call names its workspace)
537
+ ```
538
+
539
+ - **One login, many workspaces** — the account session is shared; each workspace gets its own long-lived connection via `@hcengineering/server-client` (model load takes 1–3 s, so connections are reused, not opened per call)
540
+ - **Lazy** — nothing connects until the first tool call, and a workspace connects only when first used, so auth errors surface clearly in the client
541
+ - **No current workspace** — there is no global or "last used" workspace; see [Workspaces](#workspaces-one-account-several-workspaces)
542
+ - **Stdio transport** — standard MCP transport; the Docker image adds HTTP/SSE via `mcp-proxy`
543
+
544
+ See [`src/connection.ts`](src/connection.ts).
472
545
 
473
546
  ---
474
547
 
475
548
  ## Changelog
476
549
 
550
+ ### 2.0.0 — multi-workspace
551
+
552
+ Breaking: tools that change data require `workspace`; `HULY_WORKSPACE` only restricts the server to one workspace.
553
+
554
+ - **New: multi-workspace support** — one account login, workspaces discovered via `getUserWorkspaces()`, one cached connection per workspace; `HULY_WORKSPACE` is no longer required (still honoured as an optional single-workspace restriction)
555
+ - **New: `list_workspaces`**
556
+ - **Changed: every tool takes `workspace`** — required for all tools that change data; reads may omit it only when exactly one workspace is available
557
+ - **New: `huly-mcp-selfhost` bin alias** — `npx huly-mcp-selfhost` now starts the server directly
558
+ - **Changed: `npm run setup`** no longer asks for a workspace; it lists the discovered workspaces instead
559
+ - **Changed: `scripts/import-csv.js`, `cleanup-issues.js`, `demo.js`** need `--workspace=<slug>` (or `HULY_WORKSPACE`)
560
+
477
561
  ### Fork — self-hosted support, chat, attachments, statuses, organizations
478
562
  - **Fix: self-hosted document/description writes** — auto-detects `huly-selfhost` via `HULY_FRONT_URL` and uploads through `front`'s `/files` contract instead of Huly Cloud's datalake, which self-hosted doesn't run
479
563
  - **New: `description` on `create_issue`/`update_issue`** — was missing entirely upstream
@@ -1,9 +1,70 @@
1
1
  import { type AccountClient } from '@hcengineering/account-client';
2
- import { TxOperations } from '@hcengineering/core';
3
- export declare function getConnection(): Promise<TxOperations>;
4
- export declare function getAccountClient(): Promise<AccountClient>;
5
- export declare function getWorkspaceInfo(): Promise<{
2
+ import { TxOperations, type PersonId } from '@hcengineering/core';
3
+ export interface WorkspaceRef {
4
+ uuid: string;
5
+ /** Workspace slug, as in huly.app/<slug>. Passed to selectWorkspace(). */
6
+ url: string;
7
+ name: string;
8
+ mode?: string;
9
+ isDisabled?: boolean;
10
+ }
11
+ export interface WorkspaceConnection {
12
+ workspace: WorkspaceRef;
13
+ client: TxOperations;
14
+ /** Workspace-scoped account client (token carries the workspace) — e.g. getWorkspaceMembers(). */
15
+ accountClient: AccountClient;
16
+ wsToken: string;
17
+ workspaceUuid: string;
18
+ }
19
+ interface OpenConnection {
20
+ client: TxOperations;
21
+ close: () => Promise<void>;
22
+ }
23
+ export interface ConnectionManagerOptions {
24
+ accountsUrl: string;
25
+ email?: string;
26
+ password?: string;
27
+ token?: string;
28
+ /** Legacy HULY_WORKSPACE: restricts this server to that single workspace. */
29
+ restrictTo?: string;
30
+ createAccountClient?: (accountsUrl: string, token?: string) => AccountClient;
31
+ openConnection?: (endpoint: string, token: string, socialId: PersonId) => Promise<OpenConnection>;
32
+ }
33
+ export declare class WorkspaceConnectionManager {
34
+ private readonly opts;
35
+ private readonly createAccountClient;
36
+ private readonly openConnection;
37
+ private session;
38
+ private workspaces;
39
+ private generation;
40
+ private readonly connections;
41
+ constructor(opts: ConnectionManagerOptions);
42
+ /** All workspaces this account can use (after the optional legacy HULY_WORKSPACE restriction). */
43
+ listWorkspaces(): Promise<WorkspaceRef[]>;
44
+ /**
45
+ * Resolve user input (slug, name or UUID; case-insensitive) to a workspace.
46
+ * Without input, succeeds only if exactly one workspace is available.
47
+ */
48
+ resolveWorkspace(input?: string): Promise<WorkspaceRef>;
49
+ /** Cached connection for the given workspace (see resolveWorkspace for the input rules). */
50
+ getWorkspace(input?: string): Promise<WorkspaceConnection>;
51
+ closeAll(): Promise<void>;
52
+ private match;
53
+ private getSession;
54
+ private login;
55
+ private discover;
56
+ private connect;
57
+ }
58
+ export declare function getConnectionManager(): WorkspaceConnectionManager;
59
+ /** Replace the process-wide manager (tests). */
60
+ export declare function setConnectionManager(next: WorkspaceConnectionManager | null): void;
61
+ export declare function listWorkspaces(): Promise<WorkspaceRef[]>;
62
+ export declare function getWorkspace(workspace?: string): Promise<WorkspaceConnection>;
63
+ export declare function getConnection(workspace?: string): Promise<TxOperations>;
64
+ export declare function getAccountClient(workspace?: string): Promise<AccountClient>;
65
+ export declare function getWorkspaceInfo(workspace?: string): Promise<{
6
66
  wsToken: string;
7
67
  workspaceUuid: string;
8
68
  }>;
9
69
  export declare function closeConnection(): Promise<void>;
70
+ export {};