Skip to main content
Glama
Chansokheang

Bizplay MCP Server

by Chansokheang

Bizplay MCP Server (sample)

A working prototype of the "Bizplay MCP Server Gateway" from the strategy report, built with FastMCP. It lets AI agents such as Claude, Copilot Studio, or Agentforce use Bizplay corporate card and expense functions.

The existing Bizplay API is not modified. The MCP server is a separate program that calls the existing REST endpoints over HTTP, the same way the web or mobile app does.

AI agent (Claude, Copilot, Agentforce)
        |  MCP protocol (stdio or HTTP)
Bizplay MCP Gateway        <- NEW: this project
  - identity and role checks
  - Korean compliance rules
  - audit log
        |  plain HTTP / JSON
Existing Bizplay REST API  <- UNCHANGED (mocked here)

Two ways to connect the existing API

Option A: zero-code

Option B: curated gateway

File

src/bizplay_mcp/openapi_gateway.py

src/bizplay_mcp/server.py

How

FastMCP reads the API's OpenAPI/Swagger spec and generates one tool per endpoint

Hand-written, task-level tools that call several endpoints each

Code per endpoint

None

A few lines in adapters.py

Permissions, compliance, audit

No

Yes

Best for

Fast prototyping, internal use

The real product

Recommended path: start with Option A to see what agents can do with your API today, then move the useful tools into Option B and add the guardrails.

Related MCP server: paygentic

Onboarding portal (mockup)

A self-service web portal where an API provider registers its API with the gateway and controls who may call what. Mockup scope: auth, MCP registry, access control, security. No load balancing, scaling, or DevOps.

uv run bizplay-portal

Open http://127.0.0.1:18090 and sign in with admin@bizplay.co.kr / admin1234. Two demo employees can sign in as members and serve themselves: minji@bizplay.co.kr / minji1234 (emp001, finance) and junho@bizplay.co.kr / junho1234 (emp002, hr).

Page

What it does

Overview

Request-flow diagram, security checklist score, recent gateway calls

MCP Registry

Register a REST API from its OpenAPI spec or an existing MCP server by URL, publish/unpublish, Deploy as MCP server, Test connection

Access Control

Per tool: enabled, allowed roles, confirm-before-call. Applied by the gateway on the next call

Agent Tokens

Issue a bearer token bound to one Bizplay user and role; shown once; revoke takes effect immediately

Security

Bearer requirements, default token lifetime, identity mapping, upstream credential rotation

Audit Log

Every gateway call with user, how the user was identified (bearer or env), tool, outcome

Auth modes for a registered API

Mode

API change

Publish rule

What the gateway does

Bearer

API rejects anonymous calls

Service token required, connection test must see 401

Sends the service token

Network-isolated

None, firewall only

Allowlisted gateway address recorded

Sends nothing

Open

None

Allowed, flagged red as accepted risk

Sends nothing, enforces everything itself

OAuth per user

None; the backend has its own auth server

Authorization URL, token URL and client id known

Sends the calling user's own token, refreshed as needed

In every mode the gateway still requires agent tokens, applies tool policy, and scopes results to the caller's company: any argument named corpNo (or similar) must equal the token's company, and records belonging to other companies are removed from responses.

Registry gateway: two kinds of backend

bizplay_mcp/registry_gateway.py serves every published provider. A provider is one of:

Kind

You give the portal

What the gateway does

REST API (openapi)

Base URL plus the OpenAPI spec

FastMCP generates one tool per endpoint (OpenAPIProvider)

Existing MCP server (mcp)

The server's MCP URL

Reads its tool list once, then proxies every call (ProxyProvider)

Both get the same governance: agent tokens, per-tool policy, company scoping, audit. On the shared endpoint tool names are prefixed by the provider id (bizplay_classifier_getAllCorps, flow_list_tasks). Publishing adds the tools on the next request, with no restart. Unpublishing hides and refuses them, though they stay loaded until the next restart. Changing the agent token requirement does need one, because that is fixed when the server starts.

The tool names stored by the portal are the ones FastMCP generates, which differ from raw operationIds in some specs (FastAPI's download_url_api_v1_templates__template_id__download_get becomes download_url_api_v1_templates). Older registrations that stored raw ids are renamed on load, policy intact.

uv run --no-sync python -m bizplay_mcp.registry_gateway --transport http --port 8002

Claude Desktop entry bizplay-registry runs the same server over stdio with the identity taken from BIZPLAY_USER_ID, BIZPLAY_ROLE, and BIZPLAY_COMPANY.

Deploy one API as its own MCP server

Every gateway endpoint serves two shapes:

<endpoint>/mcp            every published provider, tools prefixed by provider id
<endpoint>/mcp/<id>       one provider on its own, plain tool names

Deploy as MCP server on a provider's page switches the second one on (and publishes the provider). An agent that adds /mcp/workflow sees only that product, with names like list_templates, the way a vendor's own MCP app appears in claude.ai or ChatGPT. Undeploy returns 404 there immediately; the shared endpoint is unaffected. It is the same process, so nothing new is started and the same tokens, policy, company scoping and audit apply. The detail page writes its setup instructions for whichever endpoint you pick.

The portal process serves this gateway too, at its own origin, so one host name covers the UI, /mcp and /mcp/<id>. That is the endpoint the register dialog prefills unless a public address is set on the Security page.

Per-user OAuth: the gateway as token broker

Backends A and B may each have their own auth server. The AI client never sees either: it authenticates to the gateway once, and the gateway exchanges that identity for the right backend token on every call.

Claude ──(one login: gateway)──► Gateway ──(user's token from auth server A)──► backend A
                                         ──(user's token from auth server B)──► backend B

Register the backend with auth mode OAuth. For MCP servers that publish their auth metadata (RFC 9728 / RFC 8414), Discover fills the endpoints in and registers the gateway as a client (RFC 7591); otherwise enter the authorization URL, token URL and client id, and whitelist the redirect URL shown (<portal origin>/oauth/callback). Then on the provider page:

  1. Connect account: pick the Bizplay user id the account belongs to (the identity on the agent token), sign in at the backend's auth server in the tab that opens (authorization code + PKCE), and land back in the portal.

  2. The gateway stores the access and refresh tokens per (user, backend), refreshes them before they expire, and attaches the access token to that user's calls. A caller who has not linked an account gets a tool error saying so; nobody else's token is ever used.

  3. Disconnect revokes access at once. Tools of an OAuth MCP backend are read with a linked user's token (Refresh tools), since listing them needs one.

scripts/mock_oauth_backend.py is a backend with its own auth server to try this against: uv run python scripts/mock_oauth_backend.py --port 18095, register http://127.0.0.1:18095 with the spec at /openapi.json in OAuth mode, Discover, publish, Connect account, then call items_api_listMyItems through the gateway and see the signed-in user's items.

A second demo backend: COOCON

coocon-mock is a project-based data-scraping API in the style of a financial data aggregator: a company owns projects, a project scrapes sources (bank accounts, corporate cards, Hometax tax invoices, the four insurances) through jobs, and a finished job yields records. It ships in the image as the coocon compose service and runs locally with uv run coocon-mock --port 18096.

Register it as a REST API with base URL http://127.0.0.1:18096 (or http://coocon:18096 inside compose), spec at /openapi.json, auth mode open, then publish. Reads (listProjects, getJobRecords, ...) are enabled at once; startJob and createProject are writes, so enable them on the backend page first. Set COOCON_API_TOKEN to make the API demand a bearer token and register it in bearer mode instead. A useful multi-step prompt: "For company 1078836129, scrape Hometax for the first half of September and total the VAT."

Helping the model find its way: instructions, filled-in parameters, tool names

A model facing sixty tools with names like get_2 and list_3 guesses, and asks the user for ids it could have looked up. Three settings fix that, all kept per backend so every gateway that serves the backend inherits them:

  • Usage notes on the backend page ("How to use this backend"): which tool to call first and which next. The gateway sends them as MCP instructions when a client connects (on initialize and on server/discover). An endpoint's text is its own opening lines from the gateway page, then the notes of each backend it serves that this caller is entitled to. The gateway page previews the exact text, as any directory user.

  • Parameters filled in from the caller. Bind a parameter such as corpNo or employeeId to the caller's company, user id or role. It disappears from the tools' schemas and the gateway fills it in on every call, replacing whatever the model sent. The portal suggests bindings by name and never applies one by itself. A caller with no token has no identity, so they keep seeing the parameter.

  • Tool names. Type a readable name over an operation id in the tool policy table (list_2 becomes listBots). Clients see and call the new name; the old one still works.

Order and defaults: parameter sources, origins, workflows

Three more per-backend settings, with per-gateway overrides:

  • Company parameters are filled in from the caller, with no setup: any parameter named like corpNo (the names in BIZPLAY_COMPANY_KEYS) leaves the tool schema and the caller's own company goes upstream, whatever the model sends. The API still accepts explicit sources per parameter (caller identity, fixed, default) and per-gateway overrides for teams that need them.

  • Which tool produces which id. askBot.botId comes from listBots, field id. The gateway writes "get it from listBots" into the tool's description and, when a call arrives without the value, tells the model what to call first. This is a fact about the API, so it holds on every gateway. Rows are stated by an admin or learned from real calls: within a session the gateway remembers the values each tool returned, and when a later call uses one of them, that is an observed link (counted, never applied until confirmed). It works across backends too.

  • Values for this gateway. A value depends on who is using the gateway, so it is set on the gateway page, per backend tool and parameter: a fixed value is sent on every call and hidden from the model; a default is shown to the model and used only when it leaves the parameter out. Each row has an on/off switch, and other gateways are not affected (PUT /api/endpoints/{key}/settings with values). The backend API still accepts a typed value in comes_from for a value shared by every gateway; the gateway page shows such rows as inherited.

  • Workflows. A fixed sequence published as one tool on a gateway: askCompanyBot(question) runs listBots, then askBot with {{steps.0.data.0.id}} and {{input.question}}. The gateway runs the steps in order through the same entitlement, tool policy and audit as direct calls, so the model cannot get the order wrong. scripts/guide_askdoc.py sets all of this up for the AskDoc chatbot.

Korean and English

The portal is written in English and translated to Korean in the browser: static/i18n.js holds the dictionary and a few patterns for strings with names and numbers in them, and applies them to every rendered page, dialog, toast and confirm. The language button in the sidebar (and on the sign-in page) stores the choice and reloads. A first visit follows the browser language. Data stays as it is: backend names, tool descriptions from specs, and audit details are shown as the server produced them.

Users: who tokens and linked accounts belong to

The Users page is the directory. An agent token is issued to a person picked from it and takes their role, company and access groups from there, so those are set once per person and cannot disagree between tokens. The same picker is used when linking an account on an OAuth backend. "Someone not listed" in either dialog lets an admin type a new id (with role and groups), which adds the person to the directory at the same time.

A user with a portal password signs in as a member: they see only My access, where they issue their own tokens, link their own accounts on backends that need a personal sign-in, and see the gateways they may use. Members cannot reach the admin pages, and the API refuses them anything but their own tokens and connections. Users without a password are managed by an admin. Removing a user revokes their tokens and drops their linked accounts.

MCP clients sign in with OAuth (claude.ai, ChatGPT)

claude.ai and ChatGPT connectors cannot send a static bearer header; they only know "no auth" or OAuth against the server itself. So the gateway is its own OAuth 2.1 authorization server, following the MCP authorization spec: a 401 from /mcp/<gateway> carries a resource_metadata link, the client reads /.well-known/oauth-authorization-server, registers itself at /register (RFC 7591), sends the person to /authorize (the portal's own sign-in page, employee accounts from the user directory), and trades the code at /token with PKCE. The access token it gets is an agent token bound to that person, so entitlement, tool policy, company scoping, per-user backend links and the audit log apply unchanged; it lasts an hour and is renewed with a refresh token that lasts the portal's token lifetime. These sessions appear on the Agent Tokens page and can be revoked there.

In practice: turn on "Require token" on a gateway, paste its HTTPS URL into claude.ai as a custom connector, and sign in as minji@bizplay.co.kr when the portal's page opens. Admin accounts are refused there on purpose: an agent acts as a person with a role and a company.

Login-endpoint backends (username and password)

Many internal APIs have no OAuth server, just POST /login that returns a token. Register those with auth mode Login endpoint and tell the gateway where to sign in (login URL), what to send (a JSON body template with {username} and {password}), where the token is in the answer (a dot path such as data.accessToken), how long it lives (an expiry field or a fixed lifetime), and how to present it on calls (header and scheme, Authorization: Bearer by default). The gateway signs in itself, caches the token, and signs in again when it expires or the API answers 401.

Credentials come in two shapes: a service account (one username and password kept with the backend's upstream credential; every caller shares that identity) or each user (people sign in with their own username and password on My access, an admin can do it for them on the backend page, and the API sees the real person). The COOCON mock demonstrates it: start it with COOCON_AUTH=login and it wants a token from POST /auth/login (users minji:minji1234, junho:junho1234, svc:svc1234); its whoami tool then tells you which account the gateway signed in with.

Bearer tokens everywhere

Three separate bearer tokens, none of them visible to the AI model:

AI agent --(agent token)--> MCP gateway --(service token)--> Bizplay API
                            ^ portal session token protects the portal's own /api/*
  • Bizplay API: every /api/* endpoint returns 401 without Authorization: Bearer <service token> (BIZPLAY_API_TOKENS, default demo-service-token). Only /health is public.

  • MCP gateway (HTTP): every request needs a portal-issued agent token. The token's user and role become the caller's identity, and the role must match Bizplay's user record. Over stdio (Claude Desktop) the identity falls back to BIZPLAY_USER_ID.

  • Portal: every /api/* call except login needs the session token from /api/login.

Shared state lives in data/portal_state.json (written by the portal, read by the gateway). Delete it to reset to the seed data.

Project layout

openapi/bizplay-existing-api.json   OpenAPI spec of the existing API (used by Option A)
src/legacy_bizplay_api/             MOCK of the existing Bizplay API. Treat as untouchable.
                                    (app.py enforces bearer auth on every /api/* endpoint)
src/bizplay_mcp/
  adapters.py       Only place that knows the existing endpoints (HTTP client, sends the service token)
  auth.py           Gateway bearer verification against portal-issued agent tokens
  policy_store.py   Shared control-plane state: registry, tokens, per-tool access policy
  specs.py          Tool tables: exact FastMCP names for a spec, tool list of an MCP server
  registry_gateway.py  Serves every published provider (OpenAPI or proxied MCP), /mcp and /mcp/<id>
  compliance.py     Korean corporate-card rules (simplified demo values)
  audit.py          JSON Lines audit log of every tool call -> logs/audit.jsonl
  server.py         Option B: curated MCP gateway (auth + policy + audit)
  openapi_gateway.py  Option A: auto-generated MCP gateway
src/portal/         Onboarding portal mockup (Starlette backend + vanilla JS UI)
scripts/smoke_http.py  Real-network end-to-end check
tests/              Automated tests (in-process, no network)

Setup

Requires Python 3.11+ and uv.

uv sync

Run

Start the mock existing API (in production, this is Bizplay's real API):

uv run legacy-bizplay-api --port 18080

Then start one of the gateways in another terminal.

uv run bizplay-mcp --transport http --port 8000
uv run bizplay-mcp-openapi --transport http --port 8001

The MCP endpoints are http://127.0.0.1:8000/mcp and http://127.0.0.1:8001/mcp. Point the gateway at another API with the BIZPLAY_API_BASE_URL environment variable.

Run the end-to-end check against the curated gateway:

uv run python scripts/smoke_http.py

Connect an AI client

Open the registry, click How to use it on an API, and the detail page shows copy-paste setup written for that API: its own endpoint, tool prefix, server name, and a sample call using one of its real tools. Summary of what each client needs:

Client

How it connects

Works today

Claude Desktop

Launches the gateway locally over stdio, identity from env vars

Yes

Claude Code

HTTP with an Authorization: Bearer header

Yes

Any MCP client, scripts, curl

Same HTTP endpoint and header

Yes

ChatGPT, Claude.ai

Remote connector, needs public HTTPS and OAuth

Not yet

Copilot Studio, Agentforce

Remote MCP server, needs public HTTPS and OAuth

Not yet

An HTTPS address for claude.ai and ChatGPT

Those clients refuse plain HTTP, so the gateway needs TLS. Two ways:

A throwaway URL, no DNS or certificate work. A Cloudflare quick tunnel:

docker compose --profile tunnel up -d tunnel && docker compose logs tunnel | grep trycloudflare.com

The printed https://...trycloudflare.com plus /mcp is the connector URL. It changes every restart and is open to anyone who has it.

A stable URL. Point a subdomain at the server and terminate TLS with nginx. The portal serves the gateway on its own origin, so one location / covers the UI, /mcp and every /mcp/<id>:

sudo certbot --nginx -d mcp-portal.example.com
location / {
    proxy_pass http://127.0.0.1:9015;      # the portal's published port
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Connection '';
    proxy_buffering off;      # MCP streams responses; buffering makes it hang
    proxy_read_timeout 3600s;
}

https://mcp-portal.example.com/mcp is then the connector URL, and a deployed provider is at https://mcp-portal.example.com/mcp/<id>. Put the HTTPS address in Public MCP endpoint on the Security page so new registrations inherit it, or set it per API with Edit connection, then add it in claude.ai under Settings, Connectors, Add custom connector.

The one-line shortcut

On an API's detail page, Connect command issues a token and prints commands with that token already in them: a claude mcp add line, a Claude Desktop config block using mcp-remote, and a curl call to check it. Copy, paste, done.

Connecting without a token

The gateways require a portal-issued agent token over HTTP. For a demo you can turn that off on the Security page, or with BIZPLAY_REQUIRE_AGENT_TOKEN=false, which takes precedence. The gateways read the setting at startup, so restart them afterwards. Then this is the whole setup:

claude mcp add --transport http bizplay-classifier http://HOST:9011/mcp

With the requirement off the gateway cannot tell callers apart: identity falls back to BIZPLAY_USER_ID, BIZPLAY_ROLE and BIZPLAY_COMPANY, so every caller is the same user and company scoping applies to that one identity. Demos only. The Security checklist marks it red while it is off.

Claude Code, after issuing a token on the Agent Tokens page:

claude mcp add --transport http bizplay-registry http://127.0.0.1:8002/mcp --header "Authorization: Bearer <YOUR_AGENT_TOKEN>"

The cloud products cannot reach 127.0.0.1 and have no field for a static bearer token, so they need the gateway exposed over HTTPS (a tunnel such as cloudflared or ngrok for a demo) and an OAuth provider configured on the gateway. FastMCP ships the providers, so that is configuration rather than a rewrite.

Use it from Claude Desktop

Add this entry under mcpServers in %APPDATA%\Claude\claude_desktop_config.json, then fully quit Claude Desktop from the system tray and reopen it:

"bizplay": {
  "command": "C:\\Users\\user\\.local\\bin\\uv.exe",
  "args": [
    "--directory",
    "C:\\Users\\user\\OneDrive\\Documents\\02. Master Degree\\03. Lab\\01. Project\\01. WebCash\\04. BizPlay\\07. Experiment\\01. Bizplay MCP Server",
    "run",
    "bizplay-mcp"
  ],
  "env": {
    "BIZPLAY_API_BASE_URL": "embedded",
    "BIZPLAY_USER_ID": "emp001",
    "FASTMCP_SHOW_SERVER_BANNER": "false"
  }
}
  • "embedded" runs the mock existing API inside the MCP server process, so no second terminal is needed. Demo data resets every time Claude Desktop restarts. To use a separately running API instead, set it to http://127.0.0.1:18080 and start uv run legacy-bizplay-api --port 18080 first.

  • Use the full path to uv.exe, because Claude Desktop may not see your terminal's PATH.

  • Change BIZPLAY_USER_ID to mgr001 to act as the manager.

  • If the server does not appear, check %LOCALAPPDATA%\Claude\Logs\mcp-server-bizplay.log. If that file does not exist, Claude Desktop has not restarted since the config changed.

Try asking: "Help me close my September 2026 card expenses."

What the curated gateway exposes

Tool

Who

What it does

get_card_balance

anyone

Limit, spent, and remaining per card for a month

list_card_transactions

anyone

The caller's own card transactions

find_missing_receipts

anyone

Transactions missing receipt images or attendee lists

validate_tax_compliance

anyone

Korean rule check plus deductible input VAT estimate

draft_expense_report

anyone

Creates a draft report with a compliance check

submit_for_approval

owner

Submits to the manager, refused while blockers remain

list_pending_approvals

manager

Reports waiting for the caller

decide_approval

manager

Approve or reject, comment required to reject

Resources: bizplay://me/profile, bizplay://budgets/{department}/{month}, bizplay://policies/korean-compliance. Prompt: month_end_closing.

Demo users: emp001 and emp002 are employees, mgr001 is their manager. Seed data is for September 2026.

Docker

One image, four services. Requires Docker Desktop running.

docker compose up --build

Service

Default published port

Container port

Notes

gateway

9010

8000

Curated tools, calls legacy-api by service name

registry-gateway

9011

8002

Serves every published API; new ones appear without a restart

portal

9012

18090

UI and API, plus the same registry gateway at /mcp and /mcp/<id> on its own origin

legacy-api

9013

18080

Mock of the existing Bizplay API; drop the mapping in production

coocon

9016

18096

COOCON mock, a second demo backend (project-based scraping); register it as http://coocon:18096

Changing ports

Copy .env.example to .env and edit it. Compose reads it automatically, so docker-compose.yml needs no changes.

PUBLIC_HOST=mcp.example.com
GATEWAY_PORT=9010
REGISTRY_PORT=9011
PORTAL_PORT=9012
LEGACY_API_PORT=9013

Containers always listen on their fixed internal ports. Only the published port changes, so any range works as long as the values differ.

PUBLIC_HOST and the two gateway ports also set BIZPLAY_PUBLIC_GATEWAY_URL and BIZPLAY_PUBLIC_REGISTRY_URL, which decide the endpoint the portal shows on each API detail page and stores with newly registered APIs. Without them agents would be handed a URL that only works inside the container.

If neither variable is set, the portal uses the host the browser opened it on, with BIZPLAY_GATEWAY_PORT and BIZPLAY_REGISTRY_PORT (defaults 8000 and 8002).

Existing entries keep the URL they were registered with. After changing the host or ports, open the API and use Edit connection, or reset the demo state with docker compose down -v.

Registering an API: the base URL

Give the host only, for example https://api.example.com. The paths come from the spec. A base URL ending in a path that the spec's paths also start with produces /api/v1/api/v1/... and a 404 on every call. The portal detects that case and trims the duplicate, both on registration and on edit.

Portal state and the audit log live in named volumes (state, logs) so they survive restarts. Set BIZPLAY_API_TOKENS in the environment to change the demo service token. The registry gateway reaches external APIs such as bizplay-api.aiconvergencelab.com directly, so the container needs outbound internet access.

Tests

uv run pytest

Not production-ready yet

  • Tokens are static lookups, not OAuth. Agent tokens are random strings stored in a JSON file and matched by value. Production needs OAuth 2.1 with signed JWTs from Bizplay's identity provider, verified by key (FastMCP's JWTVerifier / RemoteAuthProvider), and hashed storage for any API keys.

  • Portal accounts are demo accounts with plain-text passwords and in-file sessions.

  • Linked-account tokens live in the JSON state file, next to the other secrets. Production keeps per-user OAuth tokens in a vault and lets end users link accounts from their own login, not only from the admin portal.

  • Over stdio there is no bearer check. Claude Desktop launches the server as a local process, so the identity comes from BIZPLAY_USER_ID.

  • Compliance rules are simplified demo values. They are not tax advice and need review by a Korean tax professional.

  • The existing API is a mock. Replace openapi/bizplay-existing-api.json with the real Swagger export and point BIZPLAY_API_BASE_URL at the real API.

  • Option A has no guardrails. Do not expose it to customers as is.

Available Tools

8 tools
decide_approvalDecide ApprovalA
Destructive

Managers only: approve or reject a report assigned to the caller. Confirm with the user first.

ParametersJSON Schema
NameRequiredDescriptionDefault
commentNo
decisionYes
report_idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, readOnlyHint=false, and non-idempotent. The description adds material context beyond them: the authorization constraint ('Managers only'), the scope constraint ('assigned to the caller'), and a required confirmation step before committing an irreversible decision. It does not spell out that the decision is final/irreversible or the effect of the optional comment, so it is useful but not exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single compact sentence pair with no waste; the authorization constraint and confirmation requirement are front-loaded and actionable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive, single-target mutation with an output schema already present, the description covers actor, scope, and the confirmation safeguard. It is nearly complete, missing only parameter meaning and any note on reversibility, which the destructiveHint partly signals.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden of explaining the three parameters, yet it mentions none of them. 'report_id' and the 'decision' enum are largely self-describing, but the 'comment' parameter (default '', maxLength 500) has no semantics offered anywhere about what it is for or whether it is required for rejection.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb pair (approve/reject) and resource (a report assigned to the caller), which is clearly distinguishable from sibling tools like submit_for_approval and list_pending_approvals. An agent can identify the action without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides clear context: restricted to managers, applies only to reports assigned to the caller, and requires user confirmation first. It stops short of naming when NOT to use it or pointing to alternatives (e.g., list_pending_approvals to find a report_id), so it is strong but not complete.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

draft_expense_reportDraft Expense ReportA

Create a DRAFT expense report and return it with a compliance check. Nothing is submitted.

ParametersJSON Schema
NameRequiredDescriptionDefault
monthNoMonth as YYYY-MM. Defaults to the current month.
titleYes
txn_idsNoTransactions to include. Omit to include all of the month's transactions not already in a report.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare non-destructive, non-readonly, non-idempotent write semantics. The description adds meaningful context the annotations do not: the result is a DRAFT that is not submitted, and it includes a compliance check. This is exactly the kind of write-side clarification that helps. Not a 5 because it does not mention persistence/visibility of the draft or whether it can be edited/regenerated afterward.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One tight sentence front-loads the action ('Create a DRAFT'), then states the return ('return it with a compliance check'), then the crucial non-effect ('Nothing is submitted'). No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists, so return-value details needn't be spelled out, and annotations cover the safety profile. The description covers the essential non-obvious behavior (draft-only, compliance check). A small gap remains around lifecycle — what happens to the draft afterward, and whether re-calling duplicates it (idempotentHint=false).

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 67%; the schema already documents 'month' (default = current month) and 'txn_ids' (omit to include all). The description adds nothing about parameters, so it merely repeats the tool purpose. Baseline 3 given moderate schema coverage with the key semantics documented in the schema itself.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb (Create) + resource (draft expense report) + scope ('Nothing is submitted') clearly distinguishes it from the submit_for_approval sibling. 'DRAFT' and 'return it with a compliance check' tell an agent exactly what this does versus the other approval-related tools in the list.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'Nothing is submitted' implicitly routes the agent here when draft-only behavior is needed, versus submit_for_approval. But no explicit 'use X instead when ready to submit' statement is provided, so an agent must infer the boundary from the sibling name.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_missing_receiptsFind Missing ReceiptsA
Read-only

Find the caller's transactions that are missing receipt images or entertainment attendee lists.

ParametersJSON Schema
NameRequiredDescriptionDefault
monthNoMonth as YYYY-MM. Defaults to the current month.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, so the safe-read profile is covered. The description contributes one useful behavioral fact — results are scoped to the caller's own transactions — but says nothing about result volume, whether it spans card and out-of-pocket activity, or how incomplete data is handled.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single tight sentence with the audience ("caller's transactions") front-loaded and the filtering condition following immediately. Nothing is wasted and nothing is buried.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described, and the lone parameter is fully documented in the schema. The description covers what is found and for whom; only the meaning of the domain-specific "entertainment attendee lists" and edge-case behavior remain unexplained.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With a single parameter at 100% schema description coverage, the schema already documents the YYYY-MM format and the current-month default. The description adds no extra semantics about the month filter, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb ("Find") and resource ("the caller's transactions") plus the exact filter condition (missing receipt images or entertainment attendee lists). This clearly separates it from list_card_transactions and validate_tax_compliance, though it never names a sibling explicitly as the scoring rubric's top tier asks.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit when-to-use statement, no prerequisites, and no named alternative. The use case (preparing an expense report, satisfying receipt requirements) is only implied by the description's filter condition, leaving the agent to infer routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_card_balanceGet Card BalanceA
Read-only

Show the caller's corporate cards with monthly limit, amount spent, and remaining balance.

ParametersJSON Schema
NameRequiredDescriptionDefault
monthNoMonth as YYYY-MM. Defaults to the current month.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is clear without the description. The description adds the fact that it returns all of the caller's cards with three computed figures, but says nothing about card count, pagination, or whether the month affects the spend calculations. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One sentence, front-loaded with the verb and resource, with the returned fields listed last. No filler and nothing wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained, and annotations cover the safety profile; the one optional parameter is documented in the schema. The definition is adequate for a simple read tool, though it omits any routing guidance against the seven sibling tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single 'month' parameter is fully documented in the schema, including the YYYY-MM format and current-month default. The description never mentions the month parameter, so it adds no meaning beyond the schema — the baseline 3 for high coverage is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Show) and resource (caller's corporate cards) plus the exact fields returned — monthly limit, amount spent, remaining balance — which separates it conceptually from the transaction-listing and approval siblings. However, it does not name or reference any sibling tool, so the differentiation is implicit rather than explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit when-to-use statement, no prerequisites, and no mention of alternatives such as list_card_transactions. Usage is only inferable from the described return fields (a balance check), which meets the 'implied usage' bar but leaves the agent to guess.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_card_transactionsList Card TransactionsB
Read-only

List the caller's corporate card transactions for a month.

ParametersJSON Schema
NameRequiredDescriptionDefault
monthNoMonth as YYYY-MM. Defaults to the current month.
card_idNoOne of the caller's card ids. Omit for all cards.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds the useful constraint that results are limited to the caller's own card transactions (an auth-scoping detail beyond the annotations), but says nothing about pagination, result volume, or ordering.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with no filler; the scope (caller, card transactions, monthly window) is stated immediately.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need no explanation, and both parameters are documented in the schema. However, for a listing tool the description omits any mention of result size, pagination, or ordering, which are exactly the gaps a list tool should close.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% – both month (YYYY-MM, defaults to current month) and card_id (omit for all cards) are fully documented in the schema. The description's 'for a month' and 'caller's' phrasing mirrors rather than extends that, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (list) and resource (corporate card transactions) with the caller scope and month granularity. It does not distinguish itself from siblings like get_card_balance or find_missing_receipts, but the resource is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No when-to-use guidance, no mention of alternatives, no exclusions. The 'for a month' phrasing implies a per-month query window but tells the agent nothing about when this tool is preferable to its siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_pending_approvalsList Pending ApprovalsA
Read-only

Managers only: list expense reports waiting for the caller's approval.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds non-redundant context: an authorization constraint (managers only) and the fact that results are scoped to the caller's own approval queue, which the annotations do not convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with the access gate and the result scope, zero filler. Every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return values need no explanation, and the description covers the access constraint and result scope. Adequate for a no-param read tool, though it could note what happens when the caller has no approvals pending.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline is 4. There is nothing for the description to clarify beyond what the empty schema already shows.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('list expense reports') plus a precise scope ('waiting for the caller's approval'), which separates it from siblings like decide_approval and submit_for_approval. It stops short of explicitly naming the alternative tools, but the resource is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The 'Managers only' gate and 'caller's approval' scope give real context for when to call it, but no explicit when-not condition or named alternative (e.g. use decide_approval to act on these items) is provided. Usage is implied rather than spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

submit_for_approvalSubmit For ApprovalA

Submit one of the caller's draft reports to their manager. Refused if compliance blockers remain. Only call this after the user has explicitly confirmed submission.

ParametersJSON Schema
NameRequiredDescriptionDefault
report_idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare non-read-only, non-idempotent, and non-destructive. The description adds important behavioral context beyond annotations: a refusal condition and a user-confirmation requirement. It omits what happens after submission or reversibility, but the added value is real.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the core purpose and then the critical usage constraints. Every sentence earns its place with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given an output schema exists and annotations cover the safety profile, the description provides adequate context for a mutation tool. It lacks parameter details and next-step guidance, but the critical safety constraints are present.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% and the single required parameter report_id has no description. The description only implies it must be a caller-owned draft report, with no format or sourcing guidance. This is minimally adequate for a required parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb ('Submit') and resource ('draft reports to their manager'), clearly distinct from siblings like draft_expense_report or decide_approval. The approval workflow aspect is implied rather than stated, but the core purpose is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

States an explicit prerequisite ('Only call this after the user has explicitly confirmed submission') and a refusal condition ('Refused if compliance blockers remain'). No alternatives are named, but the when-to-use context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

validate_tax_complianceValidate Tax ComplianceA
Read-only

Check transactions against Korean corporate-card rules (receipts, clean-card merchants, entertainment evidence, late-night/weekend use) and estimate deductible input VAT.

ParametersJSON Schema
NameRequiredDescriptionDefault
txn_idsYesTransaction ids to check.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds useful scope context by naming the rule categories checked and disclosing that it also estimates deductible input VAT, but says nothing about batching behavior, failure modes, or performance limits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single dense sentence with no filler, front-loaded on the core action and immediately qualified by the rule set. Every clause carries information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With annotations covering the safety profile and an output schema covering return values, the description only needs to convey what is checked and roughly what comes back, which it does. Minor gap: it does not indicate the scope of validation (per-transaction results, aggregate, or both).

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is a single parameter (txn_ids) with 100% schema description coverage, so the schema already explains it. The description adds no format or scoping detail (e.g., max number of ids, mixed-vendor ids), so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a concrete verb ('Check') and resource ('transactions') and enumerates the specific rule families it applies (receipts, clean-card merchants, entertainment evidence, late-night/weekend use), plus a second function (estimating deductible input VAT). This is far more specific than the name alone. It does not explicitly differentiate from the overlapping sibling find_missing_receipts, which is the only thing keeping it from a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied rather than stated: an agent can infer this is the compliance/validation step, but there is no guidance on when to call it versus find_missing_receipts or list_card_transactions, and no stated prerequisites. The closest sibling (find_missing_receipts) overlaps on the 'receipts' dimension and is never mentioned.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 8 tool updatesv0.1.0
    • First observeddecide_approval
    • First observeddraft_expense_report
    • First observedfind_missing_receipts
    • First observedget_card_balance
    • First observedlist_card_transactions
    • First observedlist_pending_approvals
    • First observedsubmit_for_approval
    • First observedvalidate_tax_compliance

TDQS

A4/5.0

Scored across 8 tools

Disambiguation5/5

Each tool targets a distinct action-resource pair: balance viewing, transaction listing, receipt gap detection, tax validation, report drafting, submission, and manager-side listing/decision. The only mild overlap (missing-receipt checks vs. tax compliance receipts) is clearly delineated by descriptions emphasizing VAT estimation versus receipt discovery.

Naming Consistency5/5

All eight tools follow a consistent snake_case verb_noun convention (get_card_balance, list_card_transactions, validate_tax_compliance, draft_expense_report, etc.). No mixing of casing or verb styles.

Tool Count5/5

Eight tools is well-scoped for a corporate card expense workflow, covering both employee and manager roles without redundancy. Each tool earns its place in the lifecycle.

Completeness4/5

The surface covers balance, transactions, compliance, drafting, submission, and approval decision — a solid full lifecycle. Minor gaps: no tool to fetch or edit an existing draft report's contents, which an agent would likely need after a rejection.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    D
    maintenance
    Enables AI agents to manage financial operations including balance checks, money transfers, invoicing, spending policies, and audit logs through the PayGent API.
    13
    -
  • A
    license
    A
    quality
    D
    maintenance
    Enables AI assistants to perform financial analysis, budget forecasting, compliance checks, expense categorization, and risk assessment, returning structured JSON with audit-ready governance receipts.
    5
    44 npm
    1
    Business Source 1.1
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to safely call enterprise tools through a governed MCP gateway with permission enforcement, blast-radius controls, input validation, and a full audit trail for every invocation.
    MIT