AFAS MCP Server

Version Python License Documentation: Diátaxis Build Coverage pyscn quality

An open-source Model Context Protocol server for AFAS Profit. It lets AI assistants such as Claude Desktop, Claude Code, Cursor or any other MCP client read data through your GetConnectors, inspect connector schemas, and, only when you switch it on, write through UpdateConnectors.

  • Read-only by default. Insert, update and delete tools exist only when AFAS_ALLOW_WRITES=true.
  • Your connectors, your rules. The server sees exactly the GetConnectors and UpdateConnectors that the AFAS app connector behind the token allows. Nothing is bundled or hard-coded.
  • Schema-aware. Field definitions come from AFAS metainfo, and write payloads are validated against the UpdateConnector schema before anything is sent.
  • Readable filters. Filters are expressed by field id and operator name (contains, greater_than, ...) and rendered into the AFAS query syntax, including OR groups and the JSON filter fallback.
  • Runs anywhere an MCP client does. stdio for desktop clients, streamable HTTP for shared deployments.

How it fits together

flowchart LR client["<b>MCP client</b><br/>Claude Desktop · Claude Code · Cursor · ..."] subgraph server["afas-mcp-server &nbsp;·&nbsp; one AFAS token &nbsp;·&nbsp; read-only by default"] direction TB read["<b>Read tools</b> (always on)<br/>afas_connection_info · afas_list_connectors<br/>afas_describe_get_connector · afas_describe_update_connector<br/>afas_get_rows · afas_validate_payload"] filters["Filters by field id and operator name,<br/>rendered to the AFAS query syntax"] write["<b>Write tools</b> (only with AFAS_ALLOW_WRITES=true)<br/>afas_insert · afas_update · afas_delete"] validate["Offline validation against<br/>the UpdateConnector schema"] read --> filters write --> validate end subgraph afas["AFAS Profit REST services"] direction TB meta["metainfo"] get["GetConnectors"] upd["UpdateConnectors"] end client <-- "MCP over stdio<br/>or streamable HTTP" --> server read -- "GET" --> meta filters -- "GET · skip, take, orderby" --> get validate -- "POST · PUT · DELETE" --> upd classDef writes fill:#fff4e5,stroke:#e08a00,color:#5c3d00 class write,validate writes

The assistant discovers connectors and fields through AFAS metainfo, reads rows through GetConnectors with filters and paging, and can only reach UpdateConnectors when the operator has enabled writes, and then only after the payload has been checked against the connector's schema.

Quick start

  1. In AFAS Profit, create an app connector (Algemeen > Beheer > App connector), add the GetConnectors and UpdateConnectors the assistant may use, and create a token for a user. Note the environment number from your AFAS Online URL, e.g. 12345 in https://12345.afasonline.com.

  2. Install uv and verify the connection:

export AFAS_MEMBER_ID=12345
export AFAS_TOKEN='<token><version>1</version><data>YOUR_TOKEN_DATA</data></token>'
uvx afas-mcp-server --check

You should see the base URL the server will talk to and the Profit version AFAS reports.

  1. Register the server with your MCP client. For Claude Desktop, add this to claude_desktop_config.json:
{
  "mcpServers": {
    "afas": {
      "command": "uvx",
      "args": ["afas-mcp-server"],
      "env": {
        "AFAS_MEMBER_ID": "12345",
        "AFAS_TOKEN": "<token><version>1</version><data>YOUR_TOKEN_DATA</data></token>"
      }
    }
  }
}

For Claude Code:

claude mcp add afas -e AFAS_MEMBER_ID=12345 -e AFAS_TOKEN='<token>...</token>' -- uvx afas-mcp-server
  1. Ask your assistant something like "Which AFAS connectors can you use?" or "Show the ten most recently changed employees." It will discover connectors, read their field definitions and page through rows.

Until the package is published on PyPI, run it from a checkout instead: uv run afas-mcp-server, or from git with uvx --from git+https://github.com/schubergphilis/afas_mcp_server afas-mcp-server.

Tools

Tool Changes data Purpose
afas_connection_info no Which environment the server talks to; proves the token works.
afas_list_connectors no GetConnectors and UpdateConnectors available to the token, with keyword search.
afas_describe_get_connector no Field ids, labels, types and lengths of a GetConnector.
afas_describe_update_connector no Fields, mandatory flags, allowed values and nested objects of an UpdateConnector.
afas_get_rows no One page of rows with filters, sort order and paging metadata.
afas_validate_payload no Check an UpdateConnector payload against its schema and show the body that would be sent.
afas_insert yes Create records (HTTP POST). Only with AFAS_ALLOW_WRITES=true.
afas_update yes Change records (HTTP PUT). Only with AFAS_ALLOW_WRITES=true.
afas_delete yes Delete one record (HTTP DELETE). Only with AFAS_ALLOW_WRITES=true.

The full parameter reference is in docs/reference/tools.md.

Configuration

Settings come from AFAS_* environment variables or a .env file in the working directory (see .env.example).

Variable Default Effect
AFAS_TOKEN required App connector token: the <token> XML, only its <data> value, or the base64 form.
AFAS_MEMBER_ID AFAS Online environment number. Required unless AFAS_BASE_URL is set.
AFAS_ENVIRONMENT production production, test or accept; selects rest, resttest or restaccept.
AFAS_BASE_URL Full REST base URL; overrides member id and environment.
AFAS_ALLOW_WRITES false Register the insert, update and delete tools.
AFAS_DEFAULT_TAKE 100 Rows per page when a call gives no take.
AFAS_MAX_TAKE 1000 Largest take a call may request.
AFAS_TIMEOUT_SECONDS 60 HTTP timeout per AFAS call.
AFAS_LANGUAGE nl-nl Language of AFAS messages: nl-nl, nl-be, fr-fr, de-de or en-us.
AFAS_LOG_LEVEL INFO Log level; logs go to stderr so stdio stays clean.

Command line: afas-mcp-server [--transport stdio|streamable-http] [--host H] [--port P] [--path /mcp] [--check].

Writing to AFAS

Writes are off until you set AFAS_ALLOW_WRITES=true. With writes on, the assistant is instructed to describe the UpdateConnector, validate the payload and show the change before sending it, and every write tool validates the payload against the schema first (switch off per call with validate: false). Recommended practice:

  • Start against your AFAS test environment (AFAS_ENVIRONMENT=test).
  • Give the token an app connector with only the connectors the assistant needs.
  • Keep separate tokens, and separate server instances, for read-only and writing use.

Security notes

  • The token is a credential for your ERP and HR data. Keep it in environment variables or a .env file that is not committed; never paste it into a prompt.
  • The server forwards calls to AFAS and nothing else. It stores no data and calls no other service.
  • The streamable HTTP transport has no authentication of its own. Bind it to localhost or put it behind a reverse proxy that authenticates clients.

How this repository is maintained

An AI agent, running as the Claude Code GitHub Action, maintains this repository under human supervision. Its standing instructions are in AGENTS.md: what it must never do (make writing possible by default, touch credentials, add a non-permissive dependency, push to main, publish), how it runs the checks, and the weekly checklist it works through.

  • Dependencies. Dependabot proposes updates every Monday after a seven-day release cooldown. A workflow merges GitHub Actions and non-major tooling updates once CI is green; the agent reviews runtime dependencies and major bumps, fixes the code when an update breaks it, and approves or asks a human.
  • Health. Every week the agent checks failing builds, security audit findings, expiring audit overrides, untriaged issues and a stale lockfile, opens pull requests for what it can fix, and posts a summary on the Maintenance log issue.
  • Requests. Maintainers ask for changes with @claude in any issue or pull request. Text from people without write access is treated as data, never as instructions.
  • Humans decide releases, publishing, anything verified against a real AFAS environment, license questions and security disclosures.

Contributors follow the same rules; see CONTRIBUTING.md. The wiring and the repository settings it relies on are described in Maintain the repository with an agent.

Documentation

Full documentation lives in docs/: build and open it with ./workflow.cmd document, or read it online at https://schubergphilis.github.io/afas_mcp_server/ once GitHub Pages is enabled for the repository.

Developing

Development flow as Paleofuturistic Python

Prerequisite: uv. Every development action runs through ./workflow.cmd <task>: the first run bootstraps the environment automatically. ./workflow.cmd lint and ./workflow.cmd test are the two you will use most.

The scaffold manual lives in the docs' Developer section: start with First-run setup; the full command list is in the Invoke task catalog.

License

Apache-2.0. AFAS and AFAS Profit are trademarks of AFAS Software B.V.; this project is not affiliated with AFAS.

Where to go next

  • New here? Getting started takes you from a token to a first answer.
  • Setting up a client? Connect an MCP client covers Claude Desktop, Claude Code, Cursor and streamable HTTP; Enable writes safely covers the write side.
  • Looking something up? Tools lists every tool, parameter and filter operator; Configuration lists every variable, flag and exit code; API is the Python API generated from the docstrings.
  • Wondering why? About AFAS MCP Server explains the design decisions and what is deliberately out of scope.

How this site is organized

The four Diátaxis sections in the navigation bar, Tutorials, How-to, Reference and Explanation, document the server for its users.

The Developer section is the manual for the scaffold this project inherited from the Paleofuturistic Python template: the daily workflow, the task runner, testing, security, releasing, and the design rationale behind it all. The scaffold gives the quick tour.