governed-mcp
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., "@governed-mcpComment on ticket T-1001 that the refund was processed, and hold it if approval is needed."
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.
governed-mcp
Agents propose, a policy layer enforces. An agent that reads tickets, emails or documents is reading text an attacker can write, and a well-written system prompt is a request, not a control. governed-mcp is an MCP server that sits in front of any other MCP server and decides, outside the model, what each tool call may do: go straight through, wait for a named person to approve it, or be refused. Approvals are claimed once, retries of the same step are recognised as retries, writes carry a step-derived idempotency key downstream, writes can be read back and checked, and every decision lands in a hash-chained audit log.
It is a small, readable reference (about 1,300 lines of Python in the package), extracted from ideas in Liyagent, a governed agent platform. It is not a product and has no production users.
flowchart LR
subgraph app["Claude Agent SDK app"]
C[coordinator] -->|Agent tool| T[triage sub-agent<br/>read tools only]
C -->|Agent tool| E[executor sub-agent<br/>write tools]
end
T -- MCP stdio --> G
E -- MCP stdio --> G
subgraph G["governed-mcp (role: agent)"]
P{policy<br/>allow / require_approval / deny}
K[step key]
V[read-after-write check]
end
P -- allow --> D[(downstream MCP server<br/>fake CRM)]
P -- require_approval --> Q[(approvals<br/>SQLite)]
P -- deny --> X[refused]
H[person: CLI or MCP client<br/>role: approver] -- approve / reject --> Q
Q -- claimed once --> D
D --> V
G -.-> A[(hash-chained<br/>audit log)]Quickstart (2 minutes, no API key)
git clone https://github.com/pavansky/governed-mcp && cd governed-mcp
uv sync
uv run python -m examples.demo # coordinator -> triage -> executor, held write, CLI approval, verified result
uv run python -m evals.run # 6 governance scenarios against the real stack, stub model
uv run pytest -q # unit + integration testsWhat the demo shows: the triage agent reads ticket T-1001, the executor's comment goes straight through (allow), its update_ticket comes back pending_approval and the ticket is untouched, the agent's retry finds the same request instead of making a new one, a person approves with the CLI, the gateway runs the call with the held arguments, reads the ticket back to confirm the change, and the audit chain verifies.
Use it yourself:
uv run governed-mcp -c examples/governed.yaml tools # every downstream tool and its posture
uv run governed-mcp -c examples/governed.yaml serve # agent-facing MCP server on stdio
uv run governed-mcp -c examples/governed.yaml pending
uv run governed-mcp -c examples/governed.yaml approve apr_... --as you --note "looks right"
uv run governed-mcp -c examples/governed.yaml reject apr_... --as you --note "already refunded"
uv run governed-mcp -c examples/governed.yaml audit verify [--anchor <head hash>]With Claude (needs ANTHROPIC_API_KEY):
uv sync --extra agent
uv run python -m examples.agent_app "Triage ticket T-1002 and handle whatever it needs."
uv run python -m evals.run --live # live scenarios; skipped cleanly without a keyThe app uses claude-opus-5-5 with adaptive thinking; --model claude-sonnet-5-5 is the cheaper choice for routine triage.
To put a person's MCP client (Claude Desktop, Claude Code) on the approver side, register governed-mcp -c governed.yaml serve --role approver --as <name> there. That role adds approve and reject; the agent role never has them.
Related MCP server: AgentGuard MCP Server
What is in the repo
Path | What it is |
| YAML policy: glob rules, argument conditions ( |
| SQLite approvals: |
| Idempotency key derived from run, step, tool and canonical arguments |
| Hash-chained audit log; |
| The governor: schema check, policy, hold or execute, key injection, read-after-write check, audit; role-scoped governance tools |
| Stdio MCP server (MCP Python SDK 2.x low-level |
| Fake ticketing/CRM MCP server with read and write tools; honours idempotency keys; can be told to drop writes silently |
| Claude Agent SDK app: coordinator plus |
| The same shape driven by a deterministic stub model, for offline runs |
| Agent Skills: |
| Scenario evals with deterministic outcome checks; offline by default, |
The evals
Every scenario runs the real processes (agent-role gateway, fake CRM, approval CLI) and checks outcomes: what the CRM's records say, what the approvals table and audit chain say. The stub model only chooses tool calls, and in the injection scenario it is deliberately gullible, obeying every instruction it reads in the ticket, so the result depends on the gateway alone.
Scenario | What it proves |
| allowed write goes through; held write changes nothing until approved; approval executes, verifies, and the agent sees the stored result |
| ticket text tells the agent approval is pre-granted; the stub tries |
| four identical calls make one approval; two approvers click at once and one wins; the write is applied once downstream; a retry after approval returns the stored result |
| destructive tool refused, nothing queued, posture visible in the tool listing |
| downstream acknowledges an update it dropped; the read-back check marks the approval |
| a person's rejection, with their reason, is what the agent's retry gets back |
The suite is checked against itself: switching off the role check and the argument check makes prompt_injection fail 4 of its 11 checks.
Live mode runs the same CRM through examples/agent_app.py with Claude and asserts the outcomes that must hold whatever the model does: no unapproved ticket, refund or delete write landed, and the happy path left an update_ticket waiting for a person.
How this maps to an enterprise deployment
Here | In a customer deployment |
| Policy-as-code reviewed like any other change; per-tenant or per-agent policies; owners for each rule |
| Approvals in the tools people already use (Slack, Teams, ServiceNow, Jira), tied to SSO identity, separation of duties (the person who started the agent does not approve its writes) |
SQLite approvals + audit | PostgreSQL with row locks, or a workflow engine; audit head anchored to WORM storage or a SIEM so truncation and rewrites are detectable |
Step keys | The orchestrator's own run and step ids, passed as MCP |
| Reconciliation against the system of record, alerting on drift |
Fake CRM | Jira, ServiceNow, Salesforce, an internal ERP: anything with an MCP server, unchanged |
| Team-specific procedures shipped with the agent so its behaviour matches the policy it runs under |
| Red-team scenarios per customer workflow, run in CI on every policy or prompt change |
Limitations, stated plainly
Single node. SQLite serialises writers, which is what makes claims safe here. Several gateway replicas need a shared database with real row locks.
At-least-once at the edge. If the gateway dies after sending an approved call and before recording the result, the row stays
executing, and resending it is a second delivery. The idempotency key makes that harmless only if the downstream honours it. Many APIs do not, and then it is not harmless.No step id means identical calls collapse. Without a step in
_meta, two deliberately identical writes in one run share a key and the second is treated as a retry of the first. That is the safe direction to be wrong in, but it is a limitation. The live Agent SDK app does not pass a step id, so it relies on this.Tamper-evident, not tamper-proof. Anyone who can write the database can rebuild the chain from a tampered row onward. Anchor
audit headsomewhere they cannot reach.Read-only is the downstream's claim. A tool annotated
readOnlyHint: truegetsread_default. Pin or override tools whose annotations you have not checked (policy-authoringcovers this).Approver authentication is out of scope.
--asrecords a name; it does not prove one. Real deployments put identity in front of the approver channel.Stdio only. The gateway serves stdio. The MCP SDK's streamable HTTP transport would be the next step for a shared deployment.
The offline evals test the enforcement layer, not the model. They show the gateway holds against a model that does the wrong thing. Whether Claude does the right thing (reports the approval id, does not route around a denial) is what
--livemeasures, and that has not been run in CI because it needs a key.
Licence
MIT. All data in the examples is synthetic.
Available Tools
9 toolsadd_commentBIdempotent
[policy: allow] Add an internal comment to a ticket.
| Name | Required | Description | Default |
|---|---|---|---|
| body | Yes | ||
| ticket_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 readOnlyHint=false, idempotentHint=true and destructiveHint=false, so the safety profile is covered. The description's real contribution is "internal", disclosing visibility scope that the annotations do not express. It says nothing about side effects such as notifications to the ticket requester or whether the comment is editable afterwards.
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 redundant wording. It is tight, though the brevity is partly the source of the tool's specification gaps rather than a virtue in itself.
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, but the tool writes data and both of its required parameters are entirely undocumented. For a mutation with 0% schema coverage, one sentence plus a policy tag leaves an agent guessing about input format and side effects.
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%: both parameters appear only as bare titles ("Body", "Ticket Id"). The description must compensate and does not — no format for ticket_id, no hint about markdown, length limits, or whether the body is escaped or sanitised.
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 and resource ("Add an ... comment to a ticket") and adds the qualifier "internal", which tells an agent the audience is staff-only rather than the customer. It stops short of differentiating itself from siblings such as update_ticket, which could also plausibly carry commentary.
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 bracketed "[policy: allow]" tag is a governance marker, not guidance about when to call the tool. The verb "Add" implies use on an existing ticket, but there is no statement of preconditions (ticket must exist, whether the caller is the requester) and no reference to any alternative tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
approval_statusARead-only
Check a held call by its approval_id. Returns its status (pending, executing, done, failed, rejected, expired) and, once done, the downstream result.
| Name | Required | Description | Default |
|---|---|---|---|
| approval_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already establishes this is a safe read, and the description goes beyond that by disclosing the state machine (pending, executing, done, failed, rejected, expired) and that the downstream result appears only once done. It does not say whether expired/rejected calls can be retried or how long pending states persist, so it stops short of a 5.
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 tight sentences with the identifier-based lookup front-loaded and the return shape second. Nothing is redundant or 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?
With no output schema, the description usefully enumerates the return values and the conditional downstream result, which is the main thing an agent needs. It is slightly incomplete on where approval_id is obtained and whether repeated polling is expected for pending states.
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% for the single required parameter, so the description must carry the weight. It ties approval_id to the held call being checked, but adds no format, origin (where the id comes from), or validation detail beyond the parameter name 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?
Description gives a specific verb and resource: 'Check a held call by its approval_id', and states what is returned (status plus downstream result). This distinguishes it from listing tools like list_pending, though it never names a sibling explicitly, keeping it short of 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 only implied: an agent infers it should call this when it holds an approval_id and wants progress. There is no explicit statement of when to use this versus list_pending, nor any guidance on polling cadence or when the check is unnecessary.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete_customerBDestructive
[policy: deny] Permanently delete a customer and their tickets.
| Name | Required | Description | Default |
|---|---|---|---|
| customer_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 readOnlyHint=false and destructiveHint=true, so the safety profile is known from structured data. The description adds genuinely new information: the delete is permanent and cascades to the customer's tickets. Beyond that it says nothing about permission requirements, confirmation, or whether other records (invoices, refunds) are affected, which matters for an irreversible operation.
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 destructive scope is stated immediately. The cryptic policy tag consumes the first tokens without explaining itself, which is the only structural weakness.
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 one-parameter surface is simple. However, for an irreversible destructive tool the description omits confirmation behavior, permission requirements, and the full scope of collateral deletion, leaving real gaps an agent would want covered.
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% on the single parameter, but the required parameter is a plain identifier whose meaning is effectively conveyed by the tool's framing, and there are no hidden format or enum concerns. The description adds no explicit detail about customer_id format or provenance, so it neither helps nor misleads.
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?
"Permanently delete a customer and their tickets" gives a specific verb (delete), a specific resource (customer), and the cascade scope (their tickets). It is unambiguous and distinguishable from every sibling, none of which are deletion operations, though it does not explicitly name a sibling to contrast with.
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 leading "[policy: deny]" tag hints the call is disallowed but never explains what policy, under what conditions, or what an agent should do instead. There is no statement of prerequisites, confirmation requirements, or alternative tools for deactivating rather than deleting a customer.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_customerCRead-only
[policy: allow] Get a customer record.
| Name | Required | Description | Default |
|---|---|---|---|
| customer_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 readOnlyHint=true, so the safety profile is covered. The description adds nothing beyond that: no not-found behavior, no permission or scope notes, and the "[policy: allow]" prefix is unexplained boilerplate rather than usable behavioral context.
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?
It is a single short sentence with no padding, but the "[policy: allow]" prefix is noise and the sentence is front-loaded with policy metadata rather than the actionable detail an agent needs.
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?
The tool is simple (one string parameter, read-only, output schema present so return values need no explanation), which lowers the bar. Still, the single required parameter is entirely unexplained and no usage context is given, leaving the definition thinner than a lookup tool should be.
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 one required parameter with 0% schema description coverage, so the description carries the full burden and fails: it never says what a customer_id is (internal ID, email, external key) or its format. This is the kind of detail that determines whether the call succeeds.
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?
"Get a customer record" names a verb and a resource, but it is close to a restatement of the tool name get_customer and adds only the notion of a single record. It does nothing to distinguish this from siblings such as list_pending, search_tickets, or delete_customer.
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 guidance on when to call this versus alternatives, despite nine siblings including search_tickets (finding customers/tickets by criteria) and delete_customer. The agent must infer that a known customer_id is required before this tool is appropriate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_ticketARead-only
[policy: allow] Get one ticket with its full body and comments.
| Name | Required | Description | Default |
|---|---|---|---|
| ticket_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 readOnlyHint=true, so the safety profile is covered. The description does add that the full body and comments are returned, which is useful payload context beyond the annotations, but it says nothing about error behavior for invalid or deleted tickets or any access constraints.
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 that answers what is returned. The '[policy: allow]' prefix is metadata noise that does not help an agent select or call the tool.
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?
The tool is simple: one required parameter, an output schema that documents the return shape, and annotations covering safety. Return values need not be explained, so the remaining gap is only the lack of failure-mode guidance for a nonexistent ticket_id.
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 burden, yet it adds no format or source information for ticket_id (prefix, numeric vs slug, where to obtain it). The parameter name is self-explanatory, which keeps this at a minimum-viable 3 rather than lower.
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 ('Get one ticket') plus the payload scope ('full body and comments'), which reads clearly as single-record retrieval versus the sibling search_tickets. It never names the sibling explicitly, so differentiation relies on the word 'one'.
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 only implied: 'one ticket' suggests fetching a known ticket_id rather than searching. There is no explicit when-to-use versus search_tickets, no mention of what happens if the ticket is missing, and no mention of related siblings like add_comment or update_ticket.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
issue_refundBIdempotent
[policy: require_approval] Refund an amount to a customer's account balance.
| Name | Required | Description | Default |
|---|---|---|---|
| amount | Yes | ||
| reason | Yes | ||
| customer_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 the write profile (readOnlyHint=false, idempotentHint=true, destructiveHint=false), so the safety bar is lower. The description adds genuinely new behavior — that funds are credited to an account balance rather than reversing a payment, and that approval is required — but omits what happens if approval is denied, whether values are capped, and how the pending state is surfaced.
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 plus a bracketed policy tag — no filler and the operational caveat comes first. Its brevity is efficient, though the same compactness is what leaves the parameters under-specified.
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 idempotency. But for a financial mutation gated by approval with three undocumented required parameters, the description should explain the approval mechanics and the meaning of 'reason' and 'amount'; neither is 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 description coverage is 0% for all three required parameters. The description only loosely implies 'amount' and 'customer_id' by mentioning 'an amount to a customer's account balance'; it says nothing about currency, sign, valid range, customer identifier format, or what 'reason' is used for.
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 ('Refund'), the resource being moved ('an amount'), and the destination ('a customer's account balance'), which is more precise than a bare name restatement. It does not name any sibling or scope boundary, but the sibling set (tickets, customers, approvals) is unrelated enough that confusion is unlikely.
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 '[policy: require_approval]' tag implies this call enters an approval flow rather than completing immediately, which is a meaningful usage cue pointing at list_pending/approval_status. However, it never says explicitly when to use this versus another path, nor what to do after the approval requirement is triggered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_pendingARead-only
List tool calls waiting for a human decision.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already establishes that this is a safe read operation, so the description does not need to restate that. It adds useful context that the results are tool calls awaiting human decisions, but it says nothing about ordering, pagination, scope across users or workspaces, or what happens when nothing is pending.
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?
The definition is a single, front-loaded sentence with no filler or redundant wording. It states the core behavior immediately and does not bury it in unnecessary qualifications.
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 simple zero-parameter read tool this is minimally adequate, especially because the readOnlyHint covers the safety profile. But with no output schema, the description should say more about what the returned list contains, how it is ordered, and how it relates to approval_status; those omissions leave real ambiguity for an agent choosing between the two.
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?
This tool has zero parameters, so there are no parameter semantics for the description to clarify. The baseline for a parameterless tool applies, and the description does not introduce any misleading parameter expectations.
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 specific verb and resource: it lists tool calls awaiting a human decision. That is clear enough to separate it from ticket and customer siblings, but it does not distinguish it from approval_status, which appears to cover overlapping approval territory.
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 intended use case is implied by 'waiting for a human decision,' so an agent can infer this is for finding pending approvals. However, there is no explicit when-to-use guidance, no mention of when not to use it, and no reference to approval_status as an alternative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_ticketsARead-only
[policy: allow] Search tickets by text in title/body, optionally filtered by status.
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | ||
| status | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and the description is consistent with a pure read. It adds that matching happens against title and body text, which is useful beyond the annotations, but says nothing about result caps, pagination, ranking, or what an empty query returns.
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 core action and the filter caveat; nothing is wasted. The '[policy: allow]' prefix is metadata noise rather than helpful description content.
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 both parameters get at least prose coverage. The remaining gap is status value semantics and result-set size behavior, which is moderate but not fatal for a two-parameter search.
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 burden; it does explain that query matches title/body text and that status is an optional filter. However, it gives no valid status values (the schema has no enum) and does not explain that the empty-string defaults likely mean 'match all'/'no filter'.
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 (search) and resource (tickets) plus the searched fields (title/body), which distinguishes it from get_ticket, which presumably fetches one ticket. It never names a sibling explicitly, so the differentiation is inferred rather than stated.
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 'optionally filtered by status' implies a retrieval workflow, but there is no guidance on when to choose this over list_pending or get_ticket, and no exclusions or prerequisites. Usage is only implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
update_ticketAIdempotent
[policy: require_approval] Change a ticket's status (open|in_progress|closed), priority (P1-P4) and/or assignee.
| Name | Required | Description | Default |
|---|---|---|---|
| status | No | ||
| assignee | No | ||
| priority | No | ||
| ticket_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 readOnlyHint=false, idempotentHint=true and destructiveHint=false, so the safety profile is covered. The description adds genuinely new behavioral context with the "[policy: require_approval]" gate, which is not in any structured field. However, it never explains what happens to fields left unset (empty-string defaults suggest 'no change' but this is unstated) or how the approval gate manifests in the response.
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 policy tag leading and the mutable fields plus their value domains packed in. No filler, no restatement of the title.
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 the annotations cover the safety profile. What remains missing for a mutation gated by policy is the operational meaning of require_approval and whether omitted fields preserve or clear existing values. Adequate but with real gaps for a state-changing tool.
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 0% schema description coverage and no enums in the schema, the description carries the full parameter burden and does real work: it supplies the legal values open|in_progress|closed and P1-P4, and 'and/or' signals that fields are optional partial updates. It stops short on assignee (user ID vs email vs name) and gives no meaning to ticket_id.
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 plus resource ("Change a ticket's status... priority... assignee") with the allowed values enumerated inline, so an agent immediately knows what the tool mutates. It is clearly not a read tool like get_ticket or search_tickets, though it never explicitly names those siblings as alternatives.
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 description implies when to use it (when a ticket's status, priority, or assignee must change) but offers no explicit comparison to siblings such as add_comment or approval_status. The require_approval policy tag hints at a prerequisite, yet the description never states whether the agent must call approval_status first or what to do if approval is pending.
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.
9 tool updates
v0.1.0- First observed
add_comment - First observed
approval_status - First observed
delete_customer - First observed
get_customer - First observed
get_ticket - First observed
issue_refund - First observed
list_pending - First observed
search_tickets - First observed
update_ticket
TDQS
Scored across 9 tools
Each tool maps to a distinct resource+action: searching vs. fetching tickets, commenting vs. updating status, refunding, and deleting a customer are all clearly separated. The only mild overlap is between list_pending (all held calls) and approval_status (one call by id), but the descriptions make the boundary readable.
Strong, predictable snake_case verb_noun pattern (search_tickets, get_ticket, add_comment, update_ticket, issue_refund, delete_customer). The two approval tools deviate slightly toward noun phrasing (list_pending, approval_status), with approval_status notably lacking a verb.
Nine tools is well within the ideal 3-15 range and each one earns its place across the ticket, customer, and approval-gate surfaces. No tool looks redundant or vestigial.
Read/update/comment/refund/delete are covered, but there is no create_ticket and no customer creation or update, so the lifecycle is not closed. The approval flow also lacks any approve/deny operation, leaving agents to rely on an out-of-band human decision.
Related MCP Connectors
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
Zero-secret MCP gateway for AI agents: risk-scored, audited calls with human-in-the-loop approval.
Supervised API-write gateway for AI agents with policy, human approval and execution receipts.
Host any HTTP API as MCP tools with a tamper-evident cross-agent settlement ledger.
Related MCP Servers
- AlicenseAqualityBmaintenanceSecurity-enforcing MCP proxy that sits between an AI agent and any number of downstream MCP servers, intercepting every tool call through a capability-token policy gateway that can allow, deny, or escalate to human approval before the call reaches any real tool. It also exposes built-in operator tools for approval workflows, audit trail queries, token management, voice/HUD output, and hierarchical2115Apache 2.0
- FlicenseNot gradedqualityCmaintenanceProvides a secure MCP boundary for AI agents, intercepting and validating tool calls, redacting secrets, and requiring human approval for sensitive actions with a tamper-evident audit trail.-
- AlicenseNot gradedqualityCmaintenanceProvides a human-in-the-loop approval gateway for AI agents, enforcing policies and audit logging for MCP-compatible tool calls.Apache 2.0
- AlicenseNot gradedqualityBmaintenanceSits in front of any MCP server to enforce allow/ask/block policies on tool calls, paths, shell commands, and network domains, with local approval prompts and an audit log.MIT