Skip to content

Repository files navigation

enty-mcp

An MCP server for Enty.io — invoices, bank transactions, accounting deadlines, and contracts, exposed as tools your AI agent can call.

Enty has no public API. This server drives the same GraphQL endpoint the Enty web app uses, authenticated with your browser session token. That comes with caveats you should read before using it:

Unofficial. This project is not affiliated with or endorsed by Enty. It talks to an undocumented internal API that can change or break at any time, and using it may not be covered by Enty's terms of service. It reads your real company data — there is no sandbox. Two tools also write to it (catalog items only); nothing deletes.

Tools

Tool What it returns
enty_get_me Logged-in user + active company
enty_list_companies Companies on the account
enty_list_invoices Invoices with status filter, totals, per-status counts
enty_list_counterparties Clients/providers with tax details
enty_list_items Catalog items (invoice line items)
enty_list_item_units Units of measure an item may use
enty_list_transaction_documents Documents already attached to a transaction
enty_list_invoice_payment_candidates Transactions that could be an invoice's payment
enty_list_bank_accounts Bank accounts with IBAN and connection status
enty_list_transactions Transactions with account/date/direction/text filters
enty_get_transaction One transaction with note and category
enty_get_balance Combined balance of chosen accounts
enty_get_transactions_statistics Document-matching stats for a date range
enty_list_categories Transaction categorization tree
enty_list_accounting_periods Accounting periods
enty_get_period_deadlines Next tax/reporting deadline for a period
enty_get_potential_tax Estimated tax for a period, by rate (EUR)
enty_get_period_issues Open bookkeeping issues for a period
enty_list_contracts Contracts with e-sign lifecycle status
enty_list_deals Deals with status and counterparty
enty_get_deals_stats Expected vs received deal amounts

Write tools

Tool What it does
enty_create_item Create a catalog item (product/service)
enty_update_item Update a catalog item's name, price, currency, unit, or description
enty_create_deal Create a deal for a counterparty
enty_create_invoice Create a draft invoice with line items
enty_mark_invoice_paid Mark an invoice paid
enty_link_transaction_to_invoice Record a bank transaction as an invoice's payment
enty_attach_document_to_transaction Upload a local file and attach it to a transaction

These write to your live company. Two risk levels are worth separating.

Catalog items and deals are reference data. Creating or editing one does not alter invoices that already reference it.

The last three touch bookkeeping. Linking a transaction to an invoice and attaching documents change records your accountant works from, and a wrong link reads as a reconciliation error. None of them can be undone here, so check ids before calling.

enty_create_invoice leaves a draft. Nothing is sent to the client and no PDF is generated; review and issue it in the Enty web app. It takes four calls, because Enty builds an invoice that way: create a blank draft, patch the header, add the lines, store the totals. If a later step fails the draft survives, and the error carries its id so you can finish or discard it by hand.

enty_link_transaction_to_invoice needs the invoice to have a generated document, since the link is between that document and the transaction. Enty has no separate payment record. Open a fresh invoice once in the web app if the tool reports it has none.

enty_attach_document_to_transaction is the one path that is not GraphQL. Bytes go to Enty's file-storage service as multipart form data, the returned file is registered as an accounting document, and only then is it linked to the transaction.

Nothing in this server deletes. Enty's API has 233 mutations, 19 of them deletions (deleteItemsAtOrganization, DeleteTransactions, DeleteUserCompanies, and so on). None are exposed, and the GraphQL client refuses to send any mutation whose operation name is not in an explicit allowlist (ALLOWED_MUTATIONS in src/enty_mcp/graphql.py), so a blocked write never leaves the process. Deleting anything is a manual action in the Enty web app, on purpose.

enty_update_item merges: pass only the fields you want changed. Enty's update replaces the whole item, so the tool reads the item's current values first and sends them back alongside your changes. Without that, updating one field would blank the rest.

The rest of the mutation surface is mapped (spec/graphql/bundle-operations/) but unexposed. Adding a write means adding its name to the allowlist, which is the review step.

Authenticating

Two options. Pick one.

Email and password (ENTY_EMAIL, ENTY_PASSWORD). The server logs in for you, keeps the session token in memory, and logs in again by itself when the session expires. Nothing to do when a token dies, which is what you want for a server meant to run unattended. Enty issues no refresh token, so renewing really is a fresh login.

A session token (ENTY_AUTH_TOKEN). Log into app.enty.io, open DevTools → Network, click any request to /api/, and copy the value of the enty-auth request header. This expires, and without credentials the server cannot renew it, so you repeat the steps whenever tools start failing.

Set both and the token is used first, falling back to the credentials once it expires. That skips a login on startup while still surviving expiry.

Either way the secret grants full access to your Enty account, so treat it like the password it effectively is. Credentials and tokens are held as pydantic SecretStr, kept out of the client's shared headers, sent only to your configured ENTY_BASE_URL, and never written to logs. The account email is logged at INFO when a login happens, so you can tell which account a running server is using.

Accounts with multi-factor authentication cannot use email and password. Enty answers those logins with no session id, and the server says so rather than failing obscurely. Use a token for those.

Running

With uv

git clone https://github.com/appsome/enty-mcp-server
cd enty-mcp-server
cp .env.example .env      # paste your token
uv sync
uv run enty-mcp                       # streamable HTTP on :8001/mcp
uv run enty-mcp --transport stdio     # for stdio clients

With Docker Compose

cp .env.example .env      # fill in ENTY_EMAIL and ENTY_PASSWORD
docker compose up -d
# server: http://localhost:8001/mcp   health: http://localhost:8001/health

The compose file builds from source. To run a published image instead:

docker run -d -p 8001:8001 \
  -e ENTY_EMAIL=you@example.com -e ENTY_PASSWORD=... \
  ghcr.io/appsome/enty-mcp-server:latest

Images are built for linux/amd64 and linux/arm64 and pushed to GitHub Container Registry on every push to main, tagged latest and with the commit sha. Pushing a v* tag adds the matching semver tags. The container exits immediately with a clear error if neither credentials nor a token are set.

Configuration

Env var Default
ENTY_EMAIL / ENTY_PASSWORD credentials; server logs in and renews itself
ENTY_AUTH_TOKEN session token; alternative to the above
ENTY_BASE_URL https://app.enty.io
ENTY_LOCALE en
ENTY_TIMEOUT_S 30
MCP_HOST / MCP_PORT 0.0.0.0 / 8001 HTTP transports
LOG_LEVEL INFO

Connecting a client

OpenHands (docker compose): use docker-compose.override.example.yml to add the service to your stack, then point OpenHands at http://enty-mcp:8001/mcp — see openhands/mcp.json and openhands/config.toml.snippet.

Claude Code:

claude mcp add --transport http enty http://localhost:8001/mcp

Claude Desktop (stdio):

{
  "mcpServers": {
    "enty": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/enty-mcp-server", "enty-mcp", "--transport", "stdio"],
      "env": { "ENTY_EMAIL": "you@example.com", "ENTY_PASSWORD": "..." }
    }
  }
}

How the API mapping was done

Enty's GraphQL endpoint has introspection disabled, so the schema was reconstructed from two sources: a HAR capture of the web app (56 operations with response shapes, spec/graphql/operations/) and the app's JS bundles, which contain every GraphQL document the frontend can send (434 operations, spec/graphql/bundle-operations/). spec/graphql/NOTES.md documents the conventions and the remaining gaps. The extraction scripts in scripts/ are repeatable when the app changes.

Development

uv sync
uv run pytest          # respx-mocked, no network
uv run ruff check src tests scripts
uv run pyright

Tests never touch the real API. Fixture data is synthetic, shaped like recorded responses.

License

MIT

About

MCP Server for Enty.io

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages