wflow-mcp
Official# wflow-mcp — experimental MCP server for the wflow.com public API
> ⚠️ **Experimental / proof of concept.** Not an official wflow product. Built to explore
> what Claude can do over the wflow public API. Use against a test/playground organization.
>
> 📚 A full offline reference of the wflow public API (all 173 operations + schemas) is
> bundled at [`docs/wflow-public-api.md`](docs/wflow-public-api.md).
A [Model Context Protocol](https://modelcontextprotocol.io) server that lets Claude
(Desktop, Code, or any MCP client) operate the [wflow.com](https://wflow.com) document
platform through its [public API](https://developers.wflow.com) — read and change
documents, registers (cost centres, contracts, …), users, roles and teams, **across one
or many organizations**.
It is built to cover the use cases from the "Využití Claude s wflow API" deck plus the
long tail of the API, and it does auth, organization routing, pagination and rate-limit
handling for you.
---
## What it can do (mapped to the deck)
| Slide | Example ask | Tools that serve it |
|---|---|---|
| **Users / roles / teams across orgs** | "Which users can pay across organizations?" | `wflow_find_users_with_right` (cross-org, defaults to payment rights) |
| | "Replace user Y with X in all organizations" | `wflow_list_users` (allOrganizations) → `wflow_upsert_user` / `wflow_delete_user` per org |
| | "Create team X and add these users" | `wflow_list_users` → `wflow_upsert_team` |
| | "Prepare re-invoicing docs for client access" | `wflow_list_users` + `wflow_get_user` (roles/teams/rights) across orgs |
| **Bulk document changes** | "Set all documents with order Z01 to cost centre Centrála" | `wflow_get_register` (resolve the code) → `wflow_bulk_update_documents` |
| | "Import line items from this table into this invoice" | `wflow_update_document` (`lines: [...]`) |
| | "Find documents for these transactions across all orgs" | `wflow_search_documents` (allOrganizations, `query`) |
| **Controlling / reporting** | "Find inconsistencies in posting" | `wflow_search_documents` + `wflow_get_document` / `wflow_get_document_events` |
| | "Is any cost centre inactive this quarter?" | `wflow_get_register` + `wflow_search_documents` |
| | "Find deviations vs. the ledger export" | `wflow_export_documents` (datev/excel/…) |
| | "Where is the order for this invoice?" | `wflow_search_documents` (`query=orderNo = "…"`) |
Anything not covered by a typed tool is reachable via `wflow_request` (browse the full
surface with `wflow_api_catalog`) — so the server exposes **all 173 API operations**.
---
## Tools (26)
**Discovery & generic**
- `wflow_list_organizations` — orgs the principal can access (foundation for cross-org work)
- `wflow_whoami` — current account/identity for an org
- `wflow_api_catalog` — browse/filter the full endpoint catalog
- `wflow_request` — call **any** endpoint (method, path, org, query, body)
**Documents**
- `wflow_search_documents` — filter/sort/auto-paginate, single or cross-org
- `wflow_get_document` · `wflow_get_document_events`
- `wflow_update_document` — create/update; set accounting registers by code, add line items
- `wflow_bulk_update_documents` — query → patch every match (**dry-run by default**)
- `wflow_export_documents` — export to ERP/accounting formats (datev, excel, isdoc, money…)
- `wflow_upload_document_file` · `wflow_create_document_with_files`
**Registers** (cost centres, contracts, activities, chart of accounts, partners, projects, …)
- `wflow_get_register` · `wflow_upsert_register` (PUT = replace set / PATCH = partial)
**Users / roles / teams**
- `wflow_list_users` · `wflow_get_user` · `wflow_upsert_user` · `wflow_delete_user`
- `wflow_find_users_with_right` — cross-org "who can do X" (e.g. who can pay)
- `wflow_list_roles` · `wflow_upsert_role` · `wflow_delete_role`
- `wflow_list_teams` · `wflow_get_team` · `wflow_upsert_team` · `wflow_delete_team`
---
## Setup
```bash
cd wflow-mcp
npm install
npm run build
cp .env.example .env # then edit .env
```
### Configure `.env`
```ini
WFLOW_AUTH_MODE=client_credentials
WFLOW_ORG=your-org-slug
WFLOW_CLIENT_ID=your_client_id
WFLOW_CLIENT_SECRET=…
WFLOW_READONLY=false # set true to block all writes
WFLOW_MAX_ITEMS=2000 # auto-pagination safety cap per org
```
### Verify it works (live smoke test)
```bash
npm run smoke
```
This spawns the server over stdio exactly like a Claude client, lists the tools, does a
set of reads, then **creates → bulk-updates → verifies → deletes** a throwaway document.
---
## Authentication
Two OAuth 2.0 flows, selected by `WFLOW_AUTH_MODE`:
### 1. `client_credentials` (default, machine-to-machine)
Uses `WFLOW_CLIENT_ID` + `WFLOW_CLIENT_SECRET`, scope `uccl_common_api`. The client must be
granted access to each organization in **wflow → Settings → Integrations → API accesses**.
This is what the provided `_docasny` client uses and it works out of the box.
### 2. `interactive` (opens the wflow login page — you type your username/password)
Authorization Code + PKCE. Set `WFLOW_AUTH_MODE=interactive` and `WFLOW_CLIENT_ID`, then:
```bash
npm run login # opens https://account.wflow.com in your browser
```
The refresh token is cached in `~/.wflow-mcp/tokens.json`, so the server starts without
prompting afterwards. With interactive auth, `wflow_list_organizations` returns **every org
your account can see** — which is what makes the cross-organization use cases shine.
> Requirement: the client must be registered in wflow with grant type **Authorization code**
> and must whitelist the redirect URI `http://localhost:53682/callback`. Interactive clients
> are set up via wflow technical support. The provided `_docasny` client is client-credentials
> only, so interactive mode needs a separate interactive client id.
---
## Add to Claude
### Claude Desktop / Claude Code (`.mcp.json` or `claude_desktop_config.json`)
```json
{
"mcpServers": {
"wflow": {
"command": "node",
"args": ["/absolute/path/to/wflow-mcp/dist/index.js"],
"env": {
"WFLOW_AUTH_MODE": "client_credentials",
"WFLOW_ORG": "your-org-slug",
"WFLOW_CLIENT_ID": "your_client_id",
"WFLOW_CLIENT_SECRET": "PUT-SECRET-HERE",
"WFLOW_READONLY": "false"
}
}
}
}
```
(Environment variables set here override `.env`, and don't depend on the working directory.)
### Claude Code (CLI, one-liner)
```bash
claude mcp add wflow \
-e WFLOW_AUTH_MODE=client_credentials \
-e WFLOW_ORG=your-org-slug \
-e WFLOW_CLIENT_ID=your_client_id \
-e WFLOW_CLIENT_SECRET=PUT-SECRET-HERE \
-- node /absolute/path/to/wflow-mcp/dist/index.js
```
Then in Claude: *"List my wflow organizations"*, *"Which users can pay?"*, *"Set every
document with order Z01 to cost centre 123 (dry run first)"*.
---
## Design notes & safety
- **Hybrid tool surface.** High-value typed tools for the deck's workflows + a generic
`wflow_request` for the entire API. The endpoint catalog (`src/endpoints.json`, 173 ops)
is bundled so `wflow_api_catalog` works offline.
- **Cross-org is first-class.** `search`, `list_users`, `list_teams`, `find_users_with_right`
and `bulk_update` accept `organizations: [...]` or `allOrganizations: true` and fan out.
- **Registers by code.** Set `accounting.costCenter = {code:"123"}` (or `externalId`/`id`);
wflow resolves the register on write — no id lookup needed. (Assigning an *invalid* register
code is silently ignored by wflow.)
- **Dry-run by default.** `wflow_bulk_update_documents` previews matches unless `apply=true`.
- **Read-only mode.** `WFLOW_READONLY=true` blocks every non-GET request.
- **Pagination.** List/search auto-paginate up to `WFLOW_MAX_ITEMS` per org and report
`truncated`.
- **Rate limits.** 429s are retried once, respecting `x-rate-limit-period`.
## Project layout
```
src/
config.ts env + .env loader
auth.ts token manager (client_credentials + interactive PKCE)
client.ts HTTP client, org resolution, pagination
util.ts result helpers + endpoint catalog loader
endpoints.json bundled catalog of all 173 API operations
tools/
discovery.ts list_organizations, whoami, api_catalog, request
documents.ts search, get, update, bulk_update, events, export, upload, create_with_files
registers.ts get_register, upsert_register
people.ts users, roles, teams, find_users_with_right
index.ts server bootstrap (stdio)
login.ts standalone interactive login (npm run login)
scripts/smoke.ts end-to-end test against the live playground
```
TDQS
Scored across 26 tools
Each tool targets a distinct resource and action: roles, teams, documents, registers, users, orgs, plus discovery/escape-hatch tools. Even create_document_with_files vs update_document are clearly separated by file attachment vs inline line items, and api_catalog/request serve unique discovery/execution roles.
Tools consistently follow a verb_noun pattern (list_*, get_*, upsert_*, delete_*, create_*, search_*, update_*). Minor exceptions like wflow_whoami, wflow_api_catalog, and wflow_request break the strict verb-first scheme but remain predictable and readable.
26 tools is above the typical 15-tool comfort zone, but the server covers a broad enterprise domain (documents, users, teams, roles, registers, organizations) plus low-level discovery/request capabilities. Most tools earn their place, though a unified document creation tool could reduce redundancy.
Documents, users, teams, roles, and registers have solid CRUD/lifecycle coverage, and bulk/export/event operations are a plus. However, there is no typed get_role, no document delete, and no approval/workflow action tools; these are deferred to the generic wflow_api_catalog/wflow_request escape hatch, which mitigates but does not fully cover the gaps.