@kivimedia/kmhub
v2.11.0
Published
KM Hub Terminal Mode. Installs the KM Hub MCP connector into your own Claude Code on your own machine, and ships the kmhub CLI that registers, updates and diagnoses it.
Maintainers
Readme
kmhub-mcp
A standalone MCP server for KM Hub. It exposes your
workspace to any MCP client (Claude Desktop, Claude Code, IDEs, agents) using a KM Hub
API key, by wrapping the public REST API (kmhub-api).
It ships in two transports that share the exact same tool set (tools.mjs plus the
family modules in tools/):
- Local stdio (
index.mjs) - the client spawns the process; the org key comes from theKMHUB_API_KEYenv var. One process = one workspace. Best for a single power user on their own machine. - Remote Streamable HTTP (
remote.mjs) - one long-running server (hosted on the VPS) serves many orgs with zero local install. Each request authenticates with its own KM Hub key sent as aAuthorization: Bearer kmh_live_...header, and is scoped to that org only. Deploy runbook: DEPLOY-VPS.md.
Tools
Tools are grouped into families, one file per family in tools/.
tools.mjs discovers them at load time, so a new family is a new file and nothing else.
core - orientation and the CRM essentials
km_me- the connected workspace (id, name, plan) + the key's scopeskm_waiting- what needs you (approvals, enquiries, tasks, invoices, contracts)km_list_clients- recent clients / leads (optionallimit)km_create_lead- create a lead (first_name,last_name,company,email,phone,notes)km_list_deals- pipeline deals (optionallimit,stage,status)km_list_events- calendar events (optionallimit,from/tostart-date range)km_list_bookings- recent bookings (optionallimit,status,from/toevent-date range)km_create_inquiry- log an enquiry + its client (client_namerequired;event_type,event_date,budget_cents,notes, ...). Opens a pipeline deal automatically. Audited + undoable in the AI activity log.km_create_booking- create a real booking + its client (client_nameandevent_daterequired; optionalstatus:pendingdefault orconfirmed). Opens a pipeline deal at booked/won. Audited + undoable.km_create_outreach_draft- queue an outreach message as a DRAFT (bodyrequired; optionalsubject,channelemail/linkedin/ig/sms_whatsapp,deal_id,contact_id). It only QUEUES: a human approves the draft in the Approval Queue before anything can send. Audited.
meta - keeping the connection current
km_check_updates- asks KM Hub what it publishes today (GET /versionreturnsapi_version,rules_version,tools_version,min_client,changelog_url) and answers in plain language. Two judgements: is this connector still abovemin_client(the build KM Hub refuses to serve below), and is the rules file on disk still the publishedrules_version? Pass the local one in asrules_version. The rules pack carries a content hash after a+, and the two endpoints do not always both publish it, so the semver base and the hash are judged separately rather than looping on a suffix that proves nothing.km_fetch_rules- downloads the current KM Hub operating rules (GET /rules) and returns{ version, markdown, sha256, bytes, instructions }. The model is instructed to write that markdown verbatim over the user's global rules file (for Claude Code, theirCLAUDE.md), replacing any older KM Hub block, and then to tell the user it did so.
Both meta tools degrade gracefully: if your KM Hub has not shipped /version or /rules yet
they say "your KM Hub does not support update checks yet" and report success, rather than
failing. The checksum is computed locally from the bytes that actually arrived; if KM Hub also
sends one and the two differ, the response carries a warning instead of a silent bad write.
Profiles
Every tool schema is loaded into every turn, so a big tool set is a real context tax. A profile loads only the families you want:
| Profile | Loads |
|---|---|
| core | orientation + CRM essentials, plus meta |
| outreach | core + the outreach families |
| money | core + the billing families |
| content | core + the content families |
| coach | Fully Booked Coach operations, client delivery, acquisition, and content requests |
| full | everything (default) |
full is the default, so nothing changes for an existing connection. An unknown profile name
falls back to full rather than erroring: a typo in a config file should never cost someone
their tools.
- Remote: per request,
POST /mcp?profile=moneyor anX-KMHub-Profile: moneyheader. The query string wins if both are present. The profile is read from the request and passed down by argument, so the server stays stateless and two tenants can hold two different profiles at the same moment. - Local stdio: the
KMHUB_PROFILEenv var.
GET /health reports the families that loaded and the tool count for each profile.
Setup - local stdio (index.mjs)
In KM Hub: Settings > Developer & API -> create an API key (
kmh_live_...). Read tools need thereadscope, create tools needwrite(a read-only key gets 403 on writes).Install deps in this folder:
npm installAdd to your MCP client.
Claude Code (one command):
claude mcp add kmhub -e KMHUB_API_KEY=kmh_live_xxxxxxxx -- node /absolute/path/to/kmhub/mcp-server/index.mjsClaude Desktop
claude_desktop_config.json:{ "mcpServers": { "kmhub": { "command": "node", "args": ["/absolute/path/to/kmhub/mcp-server/index.mjs"], "env": { "KMHUB_API_KEY": "kmh_live_xxxxxxxx", "KMHUB_PROFILE": "full" } } } }(Override the API base with
KMHUB_API_BASEif self-hosting.KMHUB_PROFILEis optional and defaults tofull.)
Setup - remote Streamable HTTP (remote.mjs)
No local install for the user - they point their client at the hosted VPS endpoint and
authenticate with their own KM Hub key as a header. One server process serves every org;
each request is scoped to the token it carries. Operators: see
DEPLOY-VPS.md (pm2 kmhub-mcp, port 8830,
https://mcp.kivimedia.co/mcp).
Claude Code (one command):
claude mcp add --transport http kmhub https://mcp.kivimedia.co/mcp \
--header "Authorization: Bearer kmh_live_xxxxxxxx"Add ?profile=core to the URL to load a smaller tool set:
claude mcp add --transport http kmhub "https://mcp.kivimedia.co/mcp?profile=core" \
--header "Authorization: Bearer kmh_live_xxxxxxxx"Claude Desktop / config-file clients:
{
"mcpServers": {
"kmhub": {
"type": "streamable-http",
"url": "https://mcp.kivimedia.co/mcp",
"headers": {
"Authorization": "Bearer kmh_live_xxxxxxxx",
"X-KMHub-Profile": "full"
}
}
}
}Health check (no auth): GET https://mcp.kivimedia.co/health.
Adding tools
See tools/README.md for the family contract. Short version: drop a new
tools/<family>.mjs exporting FAMILY, TOOLS, register(server, call, helpers) and an
optional PROFILES, and it loads itself. You never edit tools.mjs, so parallel work on
different families cannot collide. A family that fails to load, or claims a tool name another
family already took, is skipped with a warning on stderr; the rest of the server carries on.
Notes
This is the same org-scoped, key-authed substrate the Telegram bot and Zapier use - one
auth surface for all machine access. Everything is scoped to the key's workspace; a key
can never reach another tenant's data. The API rate-limits each key to 60 reads and 20
writes per minute (429 + retry-after beyond that). Writes land in the AI activity log;
outreach drafts additionally wait for human approval in the Approval Queue before any send.
Lapsed subscription (402). If the API answers 402 subscription_inactive, every tool in
every family returns a plain-English message instead of a raw JSON blob: the subscription is
not active, nothing in the workspace changed, and here is where to restart it. That is handled
once in the shared out() helper in tools.mjs, so it covers families that do not exist yet.
Other statuses keep the raw JSON body, which is what the model needs to reason about validation
errors.
