Skip to main content
Glama
BelAnas

business-tools

by BelAnas

MCP server for business tools

A Model Context Protocol server that lets Claude and other AI assistants work with a company's order system: look up customers, orders and stock, and take real actions such as creating an order or requesting a refund.

It is for teams that want an assistant to do routine operations work but cannot hand a language model a database password. Every API key has a role, the business rules are enforced in code, and every call is written to an audit log that shows if anyone edits it afterwards.

What it does

  • Offers 13 tools over MCP: six that read (customers, products and stock, orders, sales figures, the audit log) and seven that act (customer note, create order, change order status, request and decide refunds, adjust stock).

  • Works over both MCP transports: stdio for desktop assistants, and HTTP with a bearer token for remote or shared use.

  • Gives each API key one of three roles. A key sees and can call only the tools of its role.

  • Masks customer email and phone for roles that may not read them.

  • Enforces the business rules in code: an order moves only along allowed steps, refunds never exceed what was paid, large refunds wait for a manager, nobody decides their own refund.

  • Writes one audit record per call, allowed or refused. Records are chained by hash, so an edited or deleted record is detected.

  • Accepts an idempotency key on every action, so a retried call does not act twice.

  • Returns errors as structured results with a code and a plain message, so the assistant can correct itself.

  • Includes a demo agent and an evaluation script. Everything runs with no API key.

Related MCP server: ParcelPilot MCP Server

How it works

flowchart TD
    A[AI assistant<br/>Claude Desktop, Claude Code,<br/>or the demo agent] -->|MCP over stdio<br/>key in the environment| S
    A2[Remote assistant or service] -->|MCP over HTTP<br/>Authorization: Bearer key| B[Bearer check<br/>401 without a valid key]
    B --> S[MCP server<br/>server.py]
    S -->|tools/list| F[Tool list filtered by role]
    S -->|tools/call| G

    subgraph G[Gateway: the single path for every call]
        direction TB
        G1[1. Rate limit per key] --> G2[2. Permission of the role]
        G2 --> G3[3. Arguments validated<br/>with Pydantic]
        G3 --> G4[4. Idempotency key<br/>already used?]
        G4 --> G5[5. Tool runs in one transaction<br/>business rules first]
    end

    G5 --> D[(SQLite<br/>customers, products,<br/>orders, refunds)]
    G -->|every call: ok, denied or error| L[(Audit log<br/>hash-chained)]
    G -->|result, or error with code and message| A

The MCP layer (server.py) only translates protocol messages. All rules live in the gateway (gateway.py) and the tools, which is why they are tested both directly and through MCP.

Tools and roles

Output of python -m business_mcp tools:

tool                  kind    permission        viewer   agent    manager
search_customers      read    customers:read    yes      yes      yes
get_customer          read    customers:read    yes      yes      yes
search_products       read    products:read     yes      yes      yes
get_order             read    orders:read       yes      yes      yes
list_orders           read    orders:read       yes      yes      yes
sales_summary         read    reports:read      yes      yes      yes
read_audit_log        read    audit:read        -        -        yes
add_customer_note     action  customers:write   -        yes      yes
create_order          action  orders:write      -        yes      yes
update_order_status   action  orders:write      -        yes      yes
request_refund        action  refunds:request   -        yes      yes
decide_refund         action  refunds:approve   -        -        yes
adjust_stock          action  inventory:write   -        -        yes

The server also publishes two resources (business://policies, generated from the same tables the gateway enforces, and business://schema) and two prompts (daily_briefing, review_refund).

Real output

The demo agent asks for a refund above the agent's limit. The server stores it as pending and tells the assistant what to say:

$ python -m business_mcp demo "Refund \$300 on order O00012 because the wrong items were sent"
Connected to business-tools over stdio. 10 tools offered to this key. Provider: mock.
Task: Refund $300 on order O00012 because the wrong items were sent
  1. get_order({"order_id": "O00012"})
     -> {"id": "O00012", "customer_id": "C0011", "status": "delivered", "total": 453.1, "created_at": "2026-04-10T13:14:00Z", "customer_name": "Jonas Petrov", "company": "Granite Bookshop", "lines": [{"product_id": "P029", "name...
  2. request_refund({"order_id": "O00012", "amount": 300.0, "reason": "the wrong items were sent"})
     -> {"refund_id": "R0001", "order_id": "O00012", "amount": 300.0, "status": "pending", "message": "Waiting for a manager: the amount is above your limit of 100. Tell the customer the refund is under review, not that it is do...
Answer: Refund R0001 of $300.00 on order O00012 is pending. Waiting for a manager: the amount is above your limit of 100. Tell the customer the refund is under review, not that it is done.

The same kind of request with a read-only key. The action tool is not even offered:

$ python -m business_mcp demo "Mark order O00300 as shipped" --key demo-viewer-key
Connected to business-tools over stdio. 6 tools offered to this key. Provider: mock.
Task: Mark order O00300 as shipped
Answer: I cannot do that with my access: the tool update_order_status is not available to this API key.

The audit log afterwards:

$ python -m business_mcp audit tail -n 5
    1 2026-10-12T12:07:04Z demo-agent     stdio  get_order            ok  order O00012
    2 2026-10-12T12:07:04Z demo-agent     stdio  request_refund       ok  refund R0001 pending, 300.0
$ python -m business_mcp audit verify
Audit log intact: 2 records. Head hash: 495d91a08606c80c436d0eddf4ce6e646fbb0dc9aed88685074285639d4eb4c2

Results

All numbers below come from commands run for this repository on one 8-core CPU machine.

What was measured

Result

Command

Automated tests, offline, no key

46 passed

pytest -q

Role and tool combinations behaving as the written policy says (3 roles x 13 tools, over stdio)

39 of 39

python -m evals.run_eval

Tool list offered to each role matches the policy

3 of 3 roles

python -m evals.run_eval

Guardrail cases handled correctly (forbidden moves, bad arguments, masking, retry, rate limit)

29 of 29

python -m evals.run_eval

Refused calls that changed any business data

0 of 25

python -m evals.run_eval

HTTP authentication cases (no key, unknown key, valid key, revoked key)

4 of 4

python -m evals.run_eval

Audit records compared with calls counted by the client

893 records for 893 calls

python -m evals.run_eval

Tampering detected (edited record, deleted record, edited record with its hash recomputed)

3 of 3

python -m evals.run_eval

Read call over stdio, median / 95th percentile (300 calls)

6.0 ms / 8.8 ms

python -m evals.run_eval

Action call over stdio, median / 95th percentile (100 calls)

5.1 ms / 7.0 ms

python -m evals.run_eval

Read call over HTTP, median / 95th percentile (300 calls)

13.0 ms / 17.1 ms

python -m evals.run_eval

Action call over HTTP, median / 95th percentile (100 calls)

12.0 ms / 14.7 ms

python -m evals.run_eval

Demo agent tasks on the offline scripted provider

10 of 10

python -m evals.run_eval

The offline provider is a script that recognises a fixed set of phrasings. Its 10 of 10 shows that the client, the protocol and the server work together; it says nothing about how well a language model chooses tools.

Real-model check: pending. No OpenAI key was available when this was built, and the local model that was tried did not answer in time, so the demo agent has not yet been run with a real language model and nothing was spent. What is covered instead: a test runs the real OpenAI client library against a local stub server and confirms that the tool definitions and the token cap are sent and that a tool call and a final answer are read back correctly. python -m evals.live_check is ready to run the eight live tasks as soon as a key or a local model is set.

The evaluation fails when it should: with the orders:write permission deliberately added to the viewer role, the same command reported 37 of 39 and exited with an error.

Quick start

No API key is needed. Python 3.12.

git clone https://github.com/BelAnas/mcp-business-tools-server.git
cd mcp-business-tools-server
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

pytest -q                                   # the test suite
python -m business_mcp demo "What is the status of order O00012?"
python -m evals.run_eval                    # the measurements in the table above

The first command that needs the database creates data/business.db: a fictional office-supplies wholesaler with 60 customers, 40 products and 300 orders, generated from a fixed seed, plus three demo keys (demo-viewer-key, demo-agent-key, demo-manager-key).

Other requests the offline demo understands:

python -m business_mcp demo "Which products are running low?"
python -m business_mcp demo "Show sales from 2026-09-01 to 2026-09-30 by category"
python -m business_mcp demo "Add a note to customer C0005: asked for a paper quote"
python -m business_mcp demo "Mark order O00012 as cancelled"     # refused: a delivered order is final

Connect an assistant

Claude Desktop (claude_desktop_config.json), with the paths of your clone:

{
  "mcpServers": {
    "business-tools": {
      "command": "/path/to/mcp-business-tools-server/.venv/bin/python",
      "args": ["-m", "business_mcp", "serve"],
      "cwd": "/path/to/mcp-business-tools-server",
      "env": { "BUSINESS_MCP_API_KEY": "demo-agent-key" }
    }
  }
}

Claude Code:

claude mcp add business-tools --env BUSINESS_MCP_API_KEY=demo-agent-key \
  -- /path/to/mcp-business-tools-server/.venv/bin/python -m business_mcp --db /path/to/mcp-business-tools-server/data/business.db serve

The role of the key decides what the assistant can do. Start with demo-viewer-key to try it read-only.

Run over HTTP

python -m business_mcp serve --transport http        # http://127.0.0.1:8931/mcp
curl -s -X POST http://127.0.0.1:8931/mcp -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# {"error": "A valid API key is required: Authorization: Bearer <key>"}

Use a real model in the demo agent

export LLM_PROVIDER=openai OPENAI_API_KEY=...         # or OPENAI_BASE_URL for a local model
python -m business_mcp demo "Cancel order O00298 and tell me what happened to the stock"
python -m evals.live_check                            # 8 tasks judged by the database

Real keys

python -m business_mcp keys create --label support-bot --role agent   # printed once, stored as a hash
python -m business_mcp keys list
python -m business_mcp keys revoke --label support-bot

Before any real use, delete the demo keys (keys revoke --label demo-agent, and so on).

Configuration

Copy .env.example to .env. Environment variables take precedence over the file.

Setting

Default

Meaning

BUSINESS_MCP_API_KEY

empty

The key the server runs as over stdio. It refuses to start without a valid one.

BUSINESS_MCP_DB

data/business.db

Database file.

BUSINESS_MCP_HOST, BUSINESS_MCP_PORT

127.0.0.1, 8931

Address of the HTTP transport.

RATE_LIMIT_PER_MINUTE

120

Calls allowed per key per minute.

AGENT_REFUND_LIMIT

100

Largest refund an agent key gets approved at once.

MANAGER_REFUND_LIMIT

1000

Largest refund a manager key gets approved at once.

LLM_PROVIDER

mock

Demo agent only: mock (offline script) or openai.

OPENAI_API_KEY, OPENAI_MODEL, OPENAI_BASE_URL

empty, gpt-4o-mini, empty

Demo agent only. Set the base URL for Ollama or another compatible server.

MAX_OUTPUT_TOKENS

300

Demo agent only: cap on each model reply.

Project structure

business_mcp/
  config.py        settings read from the environment
  db.py            SQLite schema and connections (read tools get a read-only connection)
  seed.py          the synthetic wholesaler, generated from a fixed seed
  auth.py          API keys (hashed), roles and their permissions
  errors.py        the error codes tools return
  ratelimit.py     per-key call limit
  audit.py         hash-chained audit log and its verification
  gateway.py       the single path for every call: checks, transaction, audit
  policy.py        the policy text served as an MCP resource, generated from the code
  server.py        the MCP server: stdio, HTTP with bearer check, resources, prompts
  cli.py           serve, seed, keys, audit, tools, demo
  tools/
    registry.py    one entry per tool: argument model, permission, handler
    read.py        the six read tools
    write.py       the seven action tools and the business rules
agent/
  providers.py     offline scripted provider and OpenAI-compatible provider
  loop.py          the agent loop over an MCP client
  run.py           the demo command
evals/
  run_eval.py      permission matrix, guardrails, audit, HTTP auth, latency, agent tasks
  live_check.py    the demo agent with a real model, judged by the database
  report.json      the full report of the last evaluation run
tests/             pytest suite: gateway rules, MCP over three transports, agent

Limits

  • The business system is a demo SQLite database with synthetic data. A real deployment replaces the SQL in the tools with calls to the company's own system; the gateway stays the same.

  • Authentication is by static API key. There is no OAuth sign-in and no per-user identity behind a key: the audit log names the key, not the person using the assistant.

  • The audit chain detects edits and deletions inside the log. It cannot detect the removal of the most recent records unless the head hash (printed by audit verify) is also stored somewhere the server cannot write to. That export is not automated here.

  • The audit log stores the arguments of each call, capped at 1,000 characters. Free text such as a note or a search term is therefore copied into the log.

  • The rate limit is kept in memory per server process. Several processes behind a load balancer would each count separately.

  • SQLite allows one writer at a time. The response times were measured with one client making calls one after another, not under concurrent load.

  • Amounts have no currency and no tax. A refund is a record with a status; no payment provider is called.

  • The offline provider of the demo agent is a script for a fixed set of phrasings, not a model.

  • Claude Desktop and Claude Code were not driven by hand in this build. Compatibility was tested with the official MCP client library, with both the current protocol and the older handshake.

  • The HTTP transport has no TLS of its own; it is meant to sit behind a reverse proxy.

License

MIT. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to interact with a commercial management system through controlled capabilities for querying orders, analyzing invoices, summarizing customer activity, and creating orders, with role-based access control and audit trails.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to retrieve customer, order, ticket, policy, and agreement information, and to prepare or execute state-changing support actions like escalations and follow-ups with confirmation and access control.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to securely call enterprise MCP tools with tenant-scoped RBAC, human approvals, audit logging, and multi-tool workflows across customer, order, document, and ticket data.
    -