Repository navigation
Development
git clone https://github.com/JoKeks2023/huly-mcp.git
cd huly-mcp
npm install
cp .env.example .env # fill in credentials (only needed for live runs and scripts)| Script | Command | Purpose |
|---|---|---|
npm run build |
tsc |
Compile src/ → dist/
|
npm test |
node --import tsx --test test/*.test.ts |
Run the test suite (no Huly server needed) |
npm run dev |
tsx watch src/index.ts |
Run from source with reload |
npm start |
node dist/index.js |
Run the built server (stdio) |
npm run setup |
node scripts/setup.js |
Token wizard + workspace discovery test (Setup wizard) |
The server itself does not load .env. It reads the process environment, which the MCP client, docker --env-file or your shell provide. scripts/demo.js and scripts/cleanup-issues.js load .env themselves.
src/
index.ts stdio entry point, shutdown handling
server.ts tool registration + server instructions
schemas.ts zod schemas (WorkspaceArg / ReadWorkspaceArg)
connection.ts WorkspaceConnectionManager (auth, discovery, per-workspace connections)
tools/*.ts tool handlers, one file per area (workspaces.ts, issues.ts, …)
utils/ errors (wrapToolHandler), format, markdown → ProseMirror, storage (/files, datalake)
test/
helpers.ts in-memory fakes: account service, per-workspace TxOperations
connection.test.ts auth, discovery, caching, isolation, legacy filter, shutdown
tools.test.ts tool routing, mutations, same IDs across workspaces, schema rules, token regression tests
scripts/ setup wizard and helper scripts
Tests use Node's built-in node:test runner with tsx. There are no extra test dependencies, and no Huly server or network access is needed.
-
test/helpers.ts→createFakeEnv({ workspaces, data, token, tokenInfo, restrictTo, … })builds a realWorkspaceConnectionManagerwith injected fakes:- an account client that records every
login/getUserWorkspaces/selectWorkspacecall and the token it was called with - an
openConnectionthat returns an in-memoryFakeTxClientper workspace, recording all mutations
- an account client that records every
- Tool tests install the fake manager with
setConnectionManager()and call the real tool handlers. -
globalThis.fetchis mocked for/filestests.
Invariants enforced by tests (keep them green when changing things):
- every mutation schema requires
workspace, and every registered tool exceptlist_workspacesaccepts it - no "last used" fallback; blank workspace is rejected
- one connection per workspace UUID, concurrent first use opens one connection
-
getUserWorkspaces/selectWorkspaceuse the account token,getWorkspaceMembersthe workspace token - no
/filesrequest URL and no tool result contains a token
Typecheck the tests (they are not part of tsc's src/ build):
npx tsc --noEmit --strict --esModuleInterop --skipLibCheck --module node16 --moduleResolution node16 --target es2022 --lib es2022,dom test/*.tsThere is no linter configured in the repository.
-
Schema in
src/schemas.ts, with the correct workspace field:Useexport const ArchiveProjectSchema = z.object({ workspace: WorkspaceArg, // changes data → required identifier: z.string().describe('Project identifier, e.g. "PROJ"') })
ReadWorkspaceArgonly for tools that never change anything. -
Handler in
src/tools/<area>.ts, resolving its connection from its own argument:Never cache a workspace or client in module state.export const archiveProject = wrapToolHandler<z.infer<typeof ArchiveProjectSchema>>(async (args) => { const client = await getConnection(args.workspace) // … use getWorkspaceInfo(args.workspace) for /files; send the token only as a Bearer header return '✅ …' })
-
Register in
src/server.tswithserver.tool(name, description, Schema.shape, handler). -
Tests: a writing tool must be added to
MUTATION_SCHEMASintest/tools.test.ts. The registration test identifies writing tools by name prefix (create_,update_,delete_,add_,remove_,set_,log_,link_,send_,attach_,start_), so stick to these prefixes or extend the list. - Update the tool tables in the README and in Tools.
All three need the compiled dist/ (npm run build) and credentials in the environment. Because they write data, they need an explicit workspace: --workspace=<slug|name|id>, falling back to HULY_WORKSPACE. Without either they stop with "No workspace given."
| Script | Usage |
|---|---|
scripts/import-csv.js |
node scripts/import-csv.js tasks.csv PROJ --workspace=acmeteam. Imports issues from CSV (title required; priority, status, dueDate optional) |
scripts/cleanup-issues.js |
node scripts/cleanup-issues.js PROJ 59 68 --workspace=acmeteam [--confirm]. Deletes an issue range; dry run without --confirm
|
scripts/demo.js |
node scripts/demo.js --workspace=acmeteam. Live demo: creates, updates and deletes a test issue |
-
Docker: automatic on push to
mainandv*tags (Docker Deployment). -
npm: published manually (
prepublishOnlyruns the build).npm-publish-ghp.ymlmirrors releases to GitHub Packages as@jokeks2023/huly-mcp-selfhost. - The
server.jsonin the repository is still upstream's MCP registry entry and does not describe this fork.
- No squash merges; merge commits or rebase merges only.
- Behavior changes are called out explicitly in commit messages and PR descriptions.