Skip to main content
Glama
README.md
# 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

A3.7/5.0

Scored across 26 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count4/5

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.

Completeness3/5

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.

Maintenance

ActivityMaintained
ResponsivenessSyncing