Bizplay MCP Server
This server is a Bizplay MCP gateway for corporate card and expense management, exposing tools to view card data, check compliance, draft/submit reports, and handle approvals.
View card balance: Get monthly limit, spent, and remaining per card.
List card transactions: Retrieve the caller's card transactions for a month, optionally filtered by card.
Find missing receipts: Identify transactions lacking receipt images or attendee lists.
Validate tax compliance: Check transactions against Korean corporate-card rules and estimate deductible VAT.
Draft expense report: Create a draft report (nothing submitted) with a compliance check.
Submit for approval: Submit a draft report to the manager, refused if compliance blockers remain.
List pending approvals: (Manager only) See reports awaiting the caller's approval.
Decide approval: (Manager only) Approve or reject a report, with optional comment.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Bizplay MCP Serverwhat's my corporate card balance and recent transactions?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 |
|
|
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 |
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-portalOpen 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 ( |
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 ( | Base URL plus the OpenAPI spec | FastMCP generates one tool per endpoint ( |
Existing MCP server ( | The server's MCP URL | Reads its tool list once, then proxies every call ( |
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 8002Claude 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 namesDeploy 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 BRegister 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:
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.
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.
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
instructionswhen a client connects (oninitializeand onserver/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
corpNooremployeeIdto 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_2becomeslistBots). 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 inBIZPLAY_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.botIdcomes fromlistBots, fieldid. 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}/settingswithvalues). The backend API still accepts a typed value incomes_fromfor 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)runslistBots, thenaskBotwith{{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.pysets 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 withoutAuthorization: Bearer <service token>(BIZPLAY_API_TOKENS, defaultdemo-service-token). Only/healthis 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 syncRun
Start the mock existing API (in production, this is Bizplay's real API):
uv run legacy-bizplay-api --port 18080Then start one of the gateways in another terminal.
uv run bizplay-mcp --transport http --port 8000uv run bizplay-mcp-openapi --transport http --port 8001The 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.pyConnect 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 | 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.comThe 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.comlocation / {
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/mcpWith 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 tohttp://127.0.0.1:18080and startuv run legacy-bizplay-api --port 18080first.Use the full path to
uv.exe, because Claude Desktop may not see your terminal's PATH.Change
BIZPLAY_USER_IDtomgr001to 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 |
| anyone | Limit, spent, and remaining per card for a month |
| anyone | The caller's own card transactions |
| anyone | Transactions missing receipt images or attendee lists |
| anyone | Korean rule check plus deductible input VAT estimate |
| anyone | Creates a draft report with a compliance check |
| owner | Submits to the manager, refused while blockers remain |
| manager | Reports waiting for the caller |
| 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 --buildService | Default published port | Container port | Notes |
gateway | 9010 | 8000 | Curated tools, calls |
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 |
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 |
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=9013Containers 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 pytestNot 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.jsonwith the real Swagger export and pointBIZPLAY_API_BASE_URLat the real API.Option A has no guardrails. Do not expose it to customers as is.
Available Tools
8 toolsdecide_approvalDecide ApprovalADestructive
Managers only: approve or reject a report assigned to the caller. Confirm with the user first.
| Name | Required | Description | Default |
|---|---|---|---|
| comment | No | ||
| decision | Yes | ||
| report_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| month | No | Month as YYYY-MM. Defaults to the current month. | |
| title | Yes | ||
| txn_ids | No | Transactions to include. Omit to include all of the month's transactions not already in a report. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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 ReceiptsARead-only
Find the caller's transactions that are missing receipt images or entertainment attendee lists.
| Name | Required | Description | Default |
|---|---|---|---|
| month | No | Month as YYYY-MM. Defaults to the current month. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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 BalanceARead-only
Show the caller's corporate cards with monthly limit, amount spent, and remaining balance.
| Name | Required | Description | Default |
|---|---|---|---|
| month | No | Month as YYYY-MM. Defaults to the current month. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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 TransactionsBRead-only
List the caller's corporate card transactions for a month.
| Name | Required | Description | Default |
|---|---|---|---|
| month | No | Month as YYYY-MM. Defaults to the current month. | |
| card_id | No | One of the caller's card ids. Omit for all cards. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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 ApprovalsARead-only
Managers only: list expense reports waiting for the caller's approval.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| report_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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 ComplianceARead-only
Check transactions against Korean corporate-card rules (receipts, clean-card merchants, entertainment evidence, late-night/weekend use) and estimate deductible input VAT.
| Name | Required | Description | Default |
|---|---|---|---|
| txn_ids | Yes | Transaction ids to check. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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.
8 tool updates
v0.1.0- First observed
decide_approval - First observed
draft_expense_report - First observed
find_missing_receipts - First observed
get_card_balance - First observed
list_card_transactions - First observed
list_pending_approvals - First observed
submit_for_approval - First observed
validate_tax_compliance
TDQS
Scored across 8 tools
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.
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.
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.
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
Related MCP Connectors
Pre-spend firewall for AI agents. Approves, blocks, flags transactions against policy rules.
Connect AI agents to 580+ APIs with OAuth or API-key auth. Hosted gateway; API pricing varies.
- StackOneOAuthcom.stackone
Give AI agents 30,000+ safe, token-optimized actions across Workday, SAP, Oracle + hundreds more.
Give your AI agent a spending limit: approval controls and single-use virtual cards.
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceEnables AI agents to manage personal expenses through natural language conversations. Supports adding, searching, and analyzing transactions with automatic categorization and financial insights.3MIT
- FlicenseAqualityDmaintenanceEnables AI agents to manage financial operations including balance checks, money transfers, invoicing, spending policies, and audit logs through the PayGent API.13-
- AlicenseAqualityDmaintenanceEnables AI assistants to perform financial analysis, budget forecasting, compliance checks, expense categorization, and risk assessment, returning structured JSON with audit-ready governance receipts.544 npm1Business Source 1.1
- AlicenseNot gradedqualityBmaintenanceEnables 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