@noeva/crm-mcp
v1.10.0
Published
MCP server for the Noeva CRM — protocol adapter over the noeva-server-api CRM REST API. Ships an HTTP service (central deploy) and a stdio package (client-distributable).
Readme
@my-ginkgo/noeva-crm-mcp
An MCP (Model Context Protocol) server for the Noeva CRM. It is a thin protocol
adapter: it translates MCP tools/call requests into HTTP calls against the existing
noeva-server-api CRM REST API, applying a policy/confirmation layer in between. It
holds no domain logic, no database access, and no Noeva secrets.
Public install (npm)
Installazione pubblica senza credenziali GitHub:
npx -y @noeva/crm-mcpConfig MCP (es. Claude Desktop):
{
"mcpServers": {
"noeva-crm": {
"command": "npx",
"args": ["-y", "@noeva/crm-mcp"],
"env": {
"NOEVA_API_BASE_URL": "https://api.noeva.ai",
"NOEVA_API_KEY": "noeva_sk_..."
}
}
}
}Il canale
@my-ginkgo/noeva-crm-mcpsu GitHub Packages resta disponibile per uso interno (richiede un PATread:packages).
Two artifacts are built from this one codebase:
| Transport | Who runs it | Who consumes it | Auth source |
| ------------------------------------------ | ---------------------------- | ----------------------------------------- | ---------------------------------------- |
| HTTP (StreamableHTTPServerTransport) | Noeva (central deploy) | the Mastra agent platform | per-request bearer token |
| stdio (StdioServerTransport) | the customer (local process) | their MCP client (Claude Desktop, IDE, …) | NOEVA_API_KEY (a noeva_sk_… API key) |
Both share the exact same MCP core, the same 18 tools, and the same CRM client.
Security model (read this)
noeva-server-apiis the only trust boundary. It verifies the token, the workspace, and membership. This server forwardsAuthorization: Bearer <token>+x-noeva-workspace: <id>and propagates whatever it gets back.- The token is opaque. It is never decoded or verified locally. A 401/403 from the API is surfaced as an authorization error.
- No Noeva secrets in the build. No
SUPABASE_JWT_SECRET, no service key — not even in the HTTP image. The stdio package ships to customer machines, so anything in it is considered public. - No escalation. Because the server is pure pass-through, a customer inspecting or modifying it gains nothing beyond what their own token already permits.
- Writes require confirmation. Either via MCP
elicitation(primary) or a dry-run + signedconfirm_token(fallback). The token is bound to the exact arguments, so a preview cannot be turned into a different write.
Tools
One consolidated tool per CRM domain; pick the operation with the action field. Every
tool requires workspace_id (UUID).
crm_leads, crm_companies, crm_offers, crm_activities, crm_templates,
crm_quotes, crm_quote_templates, crm_entity_templates, crm_lookups,
crm_locations, crm_assets, crm_vocab, crm_punches, crm_notifications,
crm_dashboard, crm_explore, crm_search, crm_members.
Read actions run immediately. Write actions go through the confirmation layer.
notifications.mark_read / mark_all_read are soft-writes (no confirmation by
default; set MCP_CONFIRM_SOFT_WRITES=true to require it).
Two things do not behave like plain CRUD:
crm_quotes.generatestarts an async document build and returns aprocessId. Progress lives outside the/crmroute category, so this server cannot report completion — poll it through the Noeva core tools.- Template-driven records.
crm_leadsandcrm_companiesaccepttemplate_idpluscustom_fieldsindata; the available models come fromcrm_entity_templates.
What this server deliberately does not expose, and why, is listed in
docs/SCOPE.md.
Customer installation guide (stdio)
For non-technical users. You will paste a small block of config into your MCP client.
1. Generate a NOEVA API key
In the Noeva app, go to Profile → API Keys and create a key. Copy it — you will not
be able to see it again. The key looks like noeva_sk_…, is personal and revocable, acts
with your own permissions, and only grants access to the workspaces you are a member of.
An API key is required because a normal login session expires after ~1 hour, which would stop the local server from working. The key must start with
noeva_sk_.
2. Add it to your MCP client
Open your client's MCP server configuration (e.g. Claude Desktop's config file) and add:
{
"mcpServers": {
"noeva-crm": {
"command": "npx",
"args": ["-y", "@my-ginkgo/noeva-crm-mcp"],
"env": {
"NOEVA_API_KEY": "noeva_sk_…",
"NOEVA_API_BASE_URL": "https://api.noeva.ai"
}
}
}
}Package registry. Published to GitHub Packages under the private
@my-ginkgoscope, sonpxneeds read access. Add to your~/.npmrc:@my-ginkgo:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=<GitHub PAT with read:packages>The PAT must belong to an account granted access to the
my-ginkgoorg.
3. Verify it works
Restart your MCP client. Ask it to "list my Noeva CRM workspace members" — it should call
crm_members. If you see results, you are connected.
4. Revoke access
Revoke the API key in the Noeva app (Profile → API Keys). The local server stops
working immediately on its next call. You can also just remove the noeva-crm block from
your config.
Central HTTP deployment
Container image built from the included Dockerfile (node:20-slim). Stateless, so it
scales horizontally. Configure via env (see .env.example):
NOEVA_API_BASE_URL(https) — the CRM API.MCP_CONFIRMATION_SECRET— 32+ random chars, shared across instances (dry-run and execute may hit different pods). If unset, a per-process secret is generated and a warning is logged (only safe for a single instance).MCP_RESOURCE_URL,MCP_OAUTH_AUTHORIZATION_SERVER— advertised at/.well-known/oauth-protected-resource.SENTRY_DSN— optional; error tracking is only wired in the HTTP build.
Health check: GET /health. MCP endpoint: POST /mcp.
Development
pnpm install
pnpm dev:http # HTTP transport with reload
pnpm dev:stdio # stdio transport
pnpm build # tsc -> dist/
pnpm test # vitest (unit + integration)
pnpm lintTry the tools with MCP Inspector
The Inspector gives a browser UI to list and call the tools. It inherits the process env, so pass the credentials inline:
# source (tsx, no build needed)
NOEVA_API_KEY="noeva_sk_…" NOEVA_API_BASE_URL="http://localhost:3002" pnpm inspect
# built dist/ (the artifact Claude Desktop runs)
NOEVA_API_KEY="noeva_sk_…" NOEVA_API_BASE_URL="http://localhost:3002" pnpm inspect:distThen Connect → pick a tool (e.g. crm_members, set workspace_id + action: list)
→ Run. You can also set the env vars in the Inspector's Environment Variables
fields instead of inline.
The Inspector does not support
elicitation, so writes use the fallback: the first call returns a dry-run preview +confirm_token; call again with the identical arguments plus that token to execute. (Claude Desktop, which supports elicitation, shows a confirmation popup instead.)
For the HTTP transport, start the server and point the Inspector at the URL:
# terminal 1
NOEVA_API_BASE_URL=http://localhost:3002 MCP_CONFIRMATION_SECRET=$(openssl rand -hex 32) pnpm start:http
# terminal 2
npx -y @modelcontextprotocol/inspector
# UI: Transport = "Streamable HTTP", URL = http://localhost:8080/mcp,
# header Authorization: Bearer <token>Prerequisites & open questions
- P1 — durable customer credential — resolved.
noeva-server-apinow accepts long-lived personal API keys (noeva_sk_…): the auth middleware detects thenoeva_sk_prefix, looks the key up inuser_api_keys, and authenticates as the owning user. The stdio build uses such a key viaNOEVA_API_KEY. - P2 — server-side rate limiting. The in-process limit here is a courtesy guard, not
a security control. Authoritative per-token rate limiting must live in
noeva-server-api. - Mastra token acquisition for the HTTP transport, and whether Mastra's MCP client
supports
elicitation(if not, the HTTP transport always uses the token fallback). - OAuth audience. This server uses token pass-through (it cannot do a token exchange without a secret). A full OAuth Resource Server form with its own audience remains a possible evolution for the HTTP transport only.
crm/timbrature. A separate router exists alongsidecrm/punches; whether to expose both is open.
