npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@drewling/twenty-mcp

v0.7.1

Published

MCP server for Twenty CRM — full REST + GraphQL surface, self-healing schema, retries, and structured logging.

Readme

@drewling/twenty-mcp

npm Docker CI

A Model Context Protocol server for Twenty CRM, generated from Twenty's REST and Metadata OpenAPI specs.

Exposes ~59 tools across four APIs so any MCP client (Claude Desktop, Claude Code, Cursor, etc.) can read, write, and manage your CRM:

| API | Tools exposed | Operations covered | What you can do | |---|---|---|---| | Core REST | ~35 entity tools | 392 | Companies, people, opportunities, notes, tasks, attachments, workflows … | | Metadata REST | 13 grouped metadata_* tools | 65 | Custom objects, fields, webhooks, API keys, views … | | GraphQL | 3 | — | Deep nesting, aggregations, full schema introspection | | File uploads | 1 | — | Upload files to Twenty storage, then attach them to any CRM record | | Helper tools | 6 | — | Filter builder, auto-pagination wrappers for major entities | | Observability | 1 (twenty_health) | — | Spec source, age, tool count, last drift event |

Core REST operations are grouped by entity (e.g. one companies tool with an action parameter) rather than one tool per endpoint. This keeps the tool list at ~35 instead of 392, reducing context overhead by ~90% while covering the full API surface. Call list_twenty_capabilities to discover all entities and their available actions.

v0.3 added agent-ergonomics tooling: filter DSL helper, auto-pagination, structured errors, MCP resources, and guided workflow prompts.

v0.35 adds self-healing schema: the server fetches the live OpenAPI spec from Twenty at boot, polls every 5 minutes for changes, and automatically rebuilds the tool list when your data model changes — no restart or regen needed.

v0.36 collapses the 65 flat metadata_* tools into 13 grouped tools using the same entity+action pattern as core REST, reducing the tool list by ~52 entries.

v0.4 adds production reliability: retries with jitter, request timeouts, client-side idempotency, pino structured logging, optional OTel tracing, and a 260-test suite.

v0.5 adds distribution: published to npm as @drewling/twenty-mcp, Docker image on GHCR, automated daily spec regen CI, and copy-paste examples for all major MCP clients.

v0.6 adds metadata ergonomics: get_object_by_name tool (look up objectMetadataId by name), find_all_custom_fields and find_all_webhooks convenience tools, valid JSON truncation with exposed pagination cursors, and improved webhook/field tool descriptions.


Install

# One-shot via npx (no install needed)
npx @drewling/twenty-mcp

# Or install globally
npm install -g @drewling/twenty-mcp
twenty-mcp

# Docker
docker run --rm -i \
  -e TWENTY_API_KEY=your_key \
  -e TWENTY_API_URL=https://api.twenty.com/rest \
  ghcr.io/drewling/twenty-mcp:latest

Quick start

You need:

  • Node.js ≥ 20 (or Docker)
  • A Twenty API key — Settings → Developers → New API Key in your workspace
  • The base URL of your Twenty instance (Twenty Cloud or self-hosted)

1. Get an API key

In Twenty: Settings → Developers → API Keys → Create. Copy the token.

2. Wire it into your MCP client

| Variable | Example | |---|---| | TWENTY_API_KEY | eyJhbGciOi… | | TWENTY_API_URL | https://api.twenty.com/rest (must end in /rest) |

Copy-paste configs for each client are in the examples/ directory.

Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "twenty": {
      "command": "npx",
      "args": ["-y", "@drewling/twenty-mcp"],
      "env": {
        "TWENTY_API_KEY": "your_twenty_api_key",
        "TWENTY_API_URL": "https://api.twenty.com/rest"
      }
    }
  }
}

Restart Claude Desktop. The tools appear under the 🔌 menu.

Security note. MCP client config files store the token in plaintext on disk. chmod 600 the file, and prefer the CLI install path below where available.

Claude Code

claude mcp add twenty \
  --env TWENTY_API_KEY=your_twenty_api_key \
  --env TWENTY_API_URL=https://api.twenty.com/rest \
  -- npx -y @drewling/twenty-mcp

Cursor

~/.cursor/mcp.json:

{
  "mcpServers": {
    "twenty": {
      "command": "npx",
      "args": ["-y", "@drewling/twenty-mcp"],
      "env": {
        "TWENTY_API_KEY": "your_twenty_api_key",
        "TWENTY_API_URL": "https://api.twenty.com/rest"
      }
    }
  }
}

Docker

docker run --rm -i \
  -e TWENTY_API_KEY=your_key \
  -e TWENTY_API_URL=https://api.twenty.com/rest \
  ghcr.io/drewling/twenty-mcp:latest

For Claude Desktop with Docker, set command to docker and args to ["run", "--rm", "-i", "-e", "TWENTY_API_KEY=...", "-e", "TWENTY_API_URL=...", "ghcr.io/drewling/twenty-mcp:latest"].


Run locally (development)

git clone https://github.com/drewling/twenty-mcp
cd twenty-mcp
npm install
cp .env.example .env   # add your key and URL
npm start              # builds and runs on stdio

Watch mode:

npm run dev

Point a local MCP client at the build:

{
  "mcpServers": {
    "twenty": {
      "command": "node",
      "args": ["/absolute/path/to/twenty-mcp/build/index.js"],
      "env": {
        "TWENTY_API_KEY": "...",
        "TWENTY_API_URL": "https://api.twenty.com/rest"
      }
    }
  }
}

What's exposed

Core REST tools

Operations from /rest/open-api/core are grouped into one tool per entity. Examples:

  • companies(action: "findMany", filter: "name[ILIKE]:%acme%")
  • people(action: "createOne", requestBody: { ... })
  • opportunities(action: "findOne", id: "<uuid>")
  • notes(action: "deleteOne", id: "<uuid>")

Call list_twenty_capabilities first to see all available entities and which actions each supports. Actions include findMany, findOne, createOne, createMany, updateOne, updateMany, deleteOne, deleteMany, restoreOne, groupBy, findDuplicates, and entity-specific variants.

Filter syntax: Use build_twenty_filter to construct filter strings without trial-and-error, or write them manually: field[COMPARATOR]:value — e.g. name[ILIKE]:%acme%, createdAt[GTE]:"2024-01-01". Combine with commas (AND). Use and(…), or(…), not(…) for complex conditions. Operators: EQ, NEQ, GT, GTE, LT, LTE, IN, LIKE, ILIKE, IS, STARTS_WITH.

Pagination: Pass limit (default 60, max 60) and starting_after = pageInfo.endCursor for the next page. Or use find_all_* to auto-paginate.

Depth: depth=1 (default) includes direct relations; depth=0 returns the bare object.

Metadata tools (metadata_ prefix)

The 65 metadata operations are grouped into 13 entity tools using the same action pattern as core REST tools:

  • metadata_objects(action: "findMany") — list custom object types; action: "createOne" / "updateOne" / "deleteOne" to manage them
  • metadata_fields(action: "findMany") — custom fields; createOne / updateOne / deleteOne to manage them
  • metadata_webhooks(action: "findMany") — webhook subscriptions
  • metadata_apiKeys(action: "findMany") — API key management
  • metadata_views(action: "findMany") — saved views; also metadata_viewFields, metadata_viewFilters, metadata_viewSorts, metadata_viewGroups, metadata_viewFilterGroups
  • metadata_pageLayouts(action: "findMany") — page layouts; also metadata_pageLayoutTabs, metadata_pageLayoutWidgets

All 13 grouped tools support: findMany, findOne, createOne, updateOne, deleteOne.

GraphQL escape-hatch tools

| Tool | Purpose | |---|---| | graphql_query | Arbitrary query or mutation against /graphql | | graphql_introspect | Full schema introspection; pass type_name to filter | | graphql_metadata | Query/mutation against the metadata GraphQL endpoint |

Use these when REST doesn't reach: deep nested fetches, aggregations, batched mutations.

File upload tool

Twenty's file upload endpoint isn't part of the REST OpenAPI spec, so it's hand-written in src/file-tools.ts.

| Tool | Purpose | |---|---| | upload_file | Upload a file to Twenty storage; returns the path string |

Typical workflow:

  1. Call upload_file with a local file_path (or file_content + file_name for in-memory data).
  2. The tool returns a JSON object containing a path field.
  3. Pass that path as fullPath when calling createOneAttachment, linking the file to a company, person, note, etc.
upload_file(file_path="/tmp/report.pdf")
→ { "path": "workspace-id/report-uuid.pdf" }

attachments(action="createOne", requestBody={
  name: "Q4 Report",
  fullPath: "workspace-id/report-uuid.pdf",
  fileCategory: "PRESENTATION",
  targetCompanyId: "<company-uuid>"
})

Helper tools (v0.3+)

build_twenty_filter

Construct a valid Twenty filter string from a structured input — no syntax trial-and-error:

build_twenty_filter({ field: "name", operator: "ILIKE", value: "acme" })
→ { "filter": "name[ILIKE]:\"%acme%\"" }

build_twenty_filter({ field: "revenue", operator: "GT", value: 1000000 })
→ { "filter": "revenue[GT]:1000000" }

build_twenty_filter({ and: [
  { field: "name", operator: "ILIKE", value: "acme" },
  { field: "revenue", operator: "GT", value: 0 }
]})
→ { "filter": "and(name[ILIKE]:\"%acme%\",revenue[GT]:0)" }

Supported operators: EQ, NEQ, GT, GTE, LT, LTE, IN, IS, LIKE, ILIKE, STARTS_WITH. Strings and dates are automatically quoted; numbers and booleans are not. ILIKE/LIKE wraps values in % wildcards if none are present.

find_all_* (auto-pagination)

Fetch all records without a manual pagination loop:

find_all_companies(filter: "name[ILIKE]:\"%acme%\"", max: 500)
→ { records: [...], totalFetched: 42, wasCapped: false, endCursor: "..." }

Available wrappers: find_all_companies, find_all_people, find_all_opportunities, find_all_notes, find_all_tasks. All accept the same filter, order_by, depth, and max parameters. Default max is 10000.

MCP Resources (v0.3+)

Fetch a record by URI without calling a tool:

| Resource URI | Description | |---|---| | twenty://company/{id} | Fetch company by ID | | twenty://person/{id} | Fetch person by ID | | twenty://opportunity/{id} | Fetch opportunity by ID | | twenty://note/{id} | Fetch note by ID | | twenty://task/{id} | Fetch task by ID |

MCP Prompts (v0.3+)

Guided multi-step workflows accessible via ListPrompts / GetPrompt:

| Prompt | Description | |---|---| | log-a-call | Log a call activity linked to a company and contact | | create-lead-from-email | Create a lead from an inbound email (company, contact, note, task) | | weekly-pipeline-review | Summarize pipeline by stage and list tasks due this week |

Error responses (v0.3+)

All errors return a structured JSON object instead of raw axios details:

{ "error": { "code": "DUPLICATE", "message": "Company 'Acme' already exists", "hint": "Use updateOne to modify the existing company." } }

Error codes: VALIDATION, AUTH_FAILED, NOT_FOUND, DUPLICATE, CONFLICT, RATE_LIMIT, NETWORK_ERROR, SERVER_ERROR.

Self-healing schema (v0.35+)

The server fetches Twenty's live OpenAPI spec at boot and polls for changes every 5 minutes (configurable via TWENTY_SPEC_REFRESH_MS). When a spec change is detected, the tool list is rebuilt and connected MCP clients receive a notifications/tools/list_changed event so they re-discover without reconnecting.

What this means in practice:

  • Add a custom field to Person in Twenty's UI → within 5 minutes, people(action: "updateOne", ...) accepts the new field.
  • Delete a custom object → tools for it disappear from the list automatically.
  • No restart, no npm run regen, no redeploy.

Drift recovery: If a tool call returns a NOT_FOUND error that looks like schema drift (e.g. "Route not found", "Field X does not exist"), the server immediately forces a spec refresh and retries the call once. If the retry succeeds, you get the result. If not, you get a structured error with the drift signal.

Monitoring: Call twenty_health to see the current spec state:

{
  "specSource": "live",
  "specEtag": "\"abc123\"",
  "specAgeSeconds": 42,
  "lastRefreshAt": "2026-01-15T10:30:00.000Z",
  "toolCount": 111,
  "lastDriftEventAt": null
}

Offline / airgapped: Set TWENTY_OFFLINE=true to skip all live fetching and serve tools from the bundled openapi/twenty-core.json.


Reliability & Observability (v0.4+)

Retries and timeouts

Failed requests are retried up to 3 times with exponential backoff + jitter. Retries trigger on 5xx responses, 429 rate limits, and network errors (ECONNABORTED, ETIMEDOUT). 4xx errors (except 429) are not retried. The Retry-After header is respected.

| Env var | Default | Description | |---|---|---| | TWENTY_TIMEOUT_MS | 30000 | Per-request timeout in milliseconds | | TWENTY_RETRY_BASE_MS | 100 | Exponential backoff base delay | | TWENTY_RETRY_MAX_MS | 10000 | Maximum backoff delay cap | | TWENTY_RETRY_JITTER_MS | 100 | Random jitter added to each backoff interval |

Client-side idempotency

Create operations (createOne, createMany) support an idempotency_key parameter. When the same key is seen twice within 24 hours, the cached response is returned without hitting the API — preventing duplicate records on network retries.

companies(action: "createOne", idempotency_key: "onboard-acme-2026", requestBody: { name: "Acme" })
# Second call with same key → returns cached result, no duplicate created

| Env var | Default | Description | |---|---|---| | TWENTY_IDEMPOTENCY_MAX_ENTRIES | 1000 | LRU cache capacity (entries evicted LRU when full) | | TWENTY_IDEMPOTENCY_TTL_MS | 86400000 | Cache TTL in milliseconds (default 24h) |

Structured logging

All tool calls emit structured JSON logs to stderr (never stdout — keeps the MCP stdio transport clean). Sensitive fields (apiKey, token, password, secret, authorization) are redacted automatically.

LOG_LEVEL=debug  # trace | debug | info | warn | error (default: info)

Each log line includes tool, status, latency_ms, and retryCount for observability tooling.

OpenTelemetry tracing (optional)

No-op by default. Set OTEL_EXPORTER_OTLP_ENDPOINT to enable tracing — the server will dynamically import @opentelemetry/sdk-node and emit spans for every tool call. If the packages aren't installed, it falls back to no-op gracefully.

OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318

Install the OTel packages separately if you want traces:

npm install @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http

OpenAPI spec drift CI

Three GitHub Actions workflows handle ongoing maintenance:

| Workflow | Trigger | What it does | |---|---|---| | ci.yml | Every push + PR | Build, typecheck, full test suite | | check-spec-drift.yml | Daily + manual | SHA-256 diff of bundled spec vs live | | spec-regen.yml | Daily 06:00 UTC | Fetches fresh specs, reruns regen, opens PR if changed | | npm-publish.yml | Push to master (when package.json changes) or manual | Publishes to npm | | docker-publish.yml | Push to master or v*.*.* tag | Builds + pushes multi-arch image to GHCR |

Required GitHub secrets: NPM_TOKEN, TWENTY_SPEC_API_KEY (a Twenty Cloud API key for spec regen).

Run the drift check locally:

TWENTY_API_URL=https://api.twenty.com/rest TWENTY_API_KEY=your_key npm run check:spec

Regenerating against a newer Twenty version

# Fetch both specs (requires TWENTY_API_URL and TWENTY_API_KEY in env)
npm run fetch:specs

# Regenerate core tools (regenerates src/index.ts — see "Local patches" below)
npm run regen

# Regenerate metadata tools (always safe to re-run, no patches needed)
npm run regen:metadata

npm run build

Local patches on src/index.ts

After every npm run regen, re-apply these patches on top of the generated src/index.ts:

  1. dotenv import at the top.
  2. Friendly env aliases + fail-fast + error handlers mapping TWENTY_API_KEYBEARER_TOKEN_BEARERAUTH and TWENTY_API_URLAPI_BASE_URL.
  3. TypeScript build fix — coerce response.headers['content-type'] with String(...).
  4. MCP instructions string documenting filter DSL, pagination, and API groups.
  5. Metadata + GraphQL + file-upload imports and registration — the import statements for metadata-tools.js / graphql-tools.js / file-tools.js, snapshot of restToolDefinitions, the registration loops, executeGraphqlTool, and the multipart FormData branch in executeApiTool.
  6. Entity-grouped tool layerrestToolDefinitions snapshot, inferAction, entityGroups, buildEntitySchema, list_twenty_capabilities, and the grouped ListTools/CallTool handlers.

src/metadata-tools.ts, src/graphql-tools.ts, src/file-tools.ts, src/filter-tools.ts, src/pagination-tools.ts, src/error-handler.ts, src/resources.ts, and src/prompts.ts survive regen untouched — only src/index.ts needs patching. Phase 4 of the roadmap automates this via a patches/ directory.


Configuration reference

| Env var | Required | Default | Description | |---|---|---|---| | TWENTY_API_KEY | yes | — | Workspace API key from Twenty Settings → Developers. Server refuses to start if unset. | | TWENTY_API_URL | yes | — | Twenty REST base URL, must end in /rest. Metadata and GraphQL paths are derived from this. | | BEARER_TOKEN_BEARERAUTH | — | — | Raw internal form; set TWENTY_API_KEY instead. | | API_BASE_URL | — | — | Raw internal form; set TWENTY_API_URL instead. | | TWENTY_TOOLS | — | all entities | Comma-separated entity names to expose (e.g. companies,people,notes). Reduces registered tools further. | | TWENTY_MAX_RESPONSE | — | 102400 | Max response size in bytes before truncation. Increase if large payloads are being cut off. | | TWENTY_OBJECTS_ALLOWLIST | — | all objects | Comma-separated object names to expose from the live spec (e.g. companies,people,deals). Applied on every refresh cycle. | | TWENTY_SPEC_REFRESH_MS | — | 300000 (5 min) | How often to poll for spec changes. Set to 0 to disable polling. | | TWENTY_OFFLINE | — | unset | Set to true to skip live spec fetching and always use the bundled openapi/twenty-core.json. Useful for airgapped environments. | | TWENTY_TIMEOUT_MS | — | 30000 | Per-request timeout in milliseconds. | | TWENTY_RETRY_BASE_MS | — | 100 | Exponential backoff base delay (ms). | | TWENTY_RETRY_MAX_MS | — | 10000 | Maximum backoff delay cap (ms). | | TWENTY_RETRY_JITTER_MS | — | 100 | Random jitter added to each retry delay (ms). | | TWENTY_IDEMPOTENCY_MAX_ENTRIES | — | 1000 | LRU cache size for idempotency deduplication. | | TWENTY_IDEMPOTENCY_TTL_MS | — | 86400000 | Idempotency cache TTL (ms, default 24h). | | LOG_LEVEL | — | info | pino log level: trace, debug, info, warn, error. Logs go to stderr. | | OTEL_EXPORTER_OTLP_ENDPOINT | — | unset | Set to enable OpenTelemetry tracing. Requires @opentelemetry/sdk-node installed. |


Project layout

.
├── examples/
│   ├── claude-desktop.json    # Copy-paste config for Claude Desktop
│   ├── cursor.json            # Copy-paste config for Cursor (~/.cursor/mcp.json)
│   ├── zed.json               # Copy-paste config for Zed (.zed/settings.json)
│   └── n8n.json               # Reference snippet for n8n MCP Tool node
├── openapi/
│   ├── twenty-core.json       # Cached core REST spec (fetch:spec)
│   └── twenty-metadata.json   # Cached metadata REST spec (fetch:spec:metadata)
├── scripts/
│   ├── gen-metadata-tools.js  # Generator for src/metadata-tools.ts
│   └── check-spec-drift.js    # SHA256 spec comparison for CI drift detection (v0.4)
├── src/
│   ├── index.ts               # Core REST tools + patches + request handlers
│   ├── spec-loader.ts         # ETag-aware spec fetch with disk fallback (v0.35)
│   ├── tool-registry.ts       # Runtime tool registry with diff + health (v0.35)
│   ├── request-executor.ts    # Axios request executor with retries + idempotency (v0.35/v0.4)
│   ├── retry-strategy.ts      # Exponential backoff, isRetryable, Retry-After (v0.4)
│   ├── idempotency-cache.ts   # LRU idempotency cache for create operations (v0.4)
│   ├── logger.ts              # pino structured logger to stderr (v0.4)
│   ├── telemetry.ts           # Optional OTel tracer hook (v0.4)
│   ├── metadata-tools.ts      # Generated metadata tools (metadata_* prefix)
│   ├── graphql-tools.ts       # Hand-written GraphQL escape-hatch tools
│   ├── file-tools.ts          # Hand-written file upload tool (upload_file)
│   ├── filter-tools.ts        # build_twenty_filter DSL helper (v0.3)
│   ├── pagination-tools.ts    # find_all_* auto-pagination wrappers (v0.3)
│   ├── error-handler.ts       # Structured error formatting + drift detection (v0.3/v0.35)
│   ├── resources.ts           # MCP resource templates (v0.3)
│   └── prompts.ts             # MCP guided workflow prompts (v0.3)
├── tests/                     # Vitest test suite (260 tests)
├── build/                     # Compiled JS output (gitignored)
├── Dockerfile                 # Multi-stage build for ghcr.io/drewling/twenty-mcp (v0.5)
├── smithery.yaml              # Smithery registry manifest (v0.5)
├── .mcp-so.json               # mcp.so registry manifest (v0.5)
├── .env.example               # Template for local development
├── ROADMAP.md                 # Phase plan
└── package.json

License

MIT.