API reference
Endpoints, headers, auth and error codes for the tool-call hook.
API reference
The endpoints your agent uses to get tool calls governed. All calls authenticate with an agent key.
Authentication
Send your agent key (it starts with sk-mcp-) as a bearer token on every request.
Authorization: Bearer sk-mcp-...Headers
| Header | Purpose |
|---|---|
| Authorization | Bearer sk-mcp-... agent key. Required. |
| x-vw-agent-run-id | Groups model calls into the same run. Aliases: x-session-id, helicone-session-id. |
POST /v1/mcp/hook
Ask for a decision on a native tool call. The gateway never runs the tool; it only returns a decision.
Checks run in this order: the agent key's allowed and blocked tool lists (only when the key's apply_tool_lists_to_hook setting is on), then the guardrail scan, then approval rules, then the rate limit. A tool the key's lists don't permit gets a deny with the reason "Tool '<name>' is not allowed for this agent key".
Request body:
| Field | Description |
|---|---|
| tool_name | Name of the tool, e.g. "Bash". |
| arguments | Object with the tool arguments, e.g. { "command": "ls" }. |
| session_id | The run id for this turn. Used to group the call into a run. |
| tool_use_id | A unique id for this specific call. |
curl -X POST "$VW_GATEWAY_URL/v1/mcp/hook" \
-H "Authorization: Bearer $VW_AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{"tool_name":"Bash","arguments":{"command":"ls"},"session_id":"run-123","tool_use_id":"call-1"}'Responses, one per decision:
{ "decision": "allow" }
{ "decision": "deny", "reason": "policy violation", "detections": [] }
{ "decision": "approval_required", "approval_id": 42,
"poll_endpoint": "/v1/mcp/approvals/42/status",
"expires_at": "2026-04-20T15:30:00+00:00" }
{ "decision": "rate_limited", "reason": "rate limit exceeded" }For a monitor only agent key the decision is always allow (or rate_limited). When a rule matched, the response adds an optional monitored object. Treat the call as allowed either way; the field is for logging.
| Field | Description |
|---|---|
| monitored.would | What an enforcing key would have got: "deny" or "approval_required". The most severe match. |
| monitored.reason | Why, for that match. |
| monitored.source | Which check produced that match: "guardrail", "approval_rule" (a require approval rule), "tool_acl" (the key's allowed or blocked tool lists: on the MCP proxy, and on native calls when the key's apply_tool_lists_to_hook is on) or "tool_requires_approval" (a tool marked as requiring approval, MCP proxy only). |
| monitored.rule | { "id", "name" } of the rule behind that match, or null when it came from the key's tool lists or a tool marked as requiring approval. |
| monitored.outcomes | Every match, each with would, reason, source and rule. |
{ "decision": "allow",
"monitored": { "would": "approval_required",
"reason": "rule 'Approve rm' requires approval",
"source": "approval_rule",
"rule": { "id": 21, "name": "Approve rm" },
"outcomes": [ { "would": "approval_required",
"reason": "rule 'Approve rm' requires approval",
"source": "approval_rule",
"rule": { "id": 21, "name": "Approve rm" } } ] } }POST /v1/mcp/hook/result
Report the result of a tool that has already run, so the run shows what happened. Best-effort; it never blocks.
| Field | Description |
|---|---|
| tool_name | Name of the tool that ran. |
| tool_response | Object with the tool result. An object with a non-null "error" key is recorded as a failed call, whatever the tool. Results over 10240 bytes (as serialized JSON) are cut. |
| session_id | Same run id used on the hook call. |
| tool_use_id | Same call id used on the hook call. |
import httpx, os
httpx.post(
f"{os.environ['VW_GATEWAY_URL']}/v1/mcp/hook/result",
headers={"Authorization": f"Bearer {os.environ['VW_AGENT_KEY']}"},
json={
"tool_name": "Bash",
"tool_response": {"stdout": "file1\nfile2"},
"session_id": "run-123",
"tool_use_id": "call-1",
},
)GET /v1/mcp/approvals/{id}/status
Check whether a pending approval has been decided. Returns a status of pending, approved, denied or expired.
curl "$VW_GATEWAY_URL/v1/mcp/approvals/42/status" \
-H "Authorization: Bearer $VW_AGENT_KEY"JSON-RPC error codes
On the MCP proxy path (POST /v1/mcp), an approval requirement comes back as a JSON-RPC error.
| Code | Meaning |
|---|---|
| -32001 | Tool requires approval. error.data has approval_id, poll_endpoint and expires_at. |
With a monitor only agent key the proxy forwards the call instead of returning an error. When something matched, the JSON-RPC result carries the same monitored object as the hook in result._meta["verifywise.ai/monitored"].
Agent key mode
Each agent key has an enforcement_mode: "enforce" (the default) blocks and pauses calls by policy, and "monitor" only records what would have happened. It is set on the Agent keys page. Agent key responses include these fields:
| Field | Description |
|---|---|
| enforcement_mode | "enforce" or "monitor". Accepted when creating or updating a key (Admin only); any other value is rejected with a 400. |
| enforcement_mode_changed_at | When the mode last changed (ISO 8601), or null if it was never changed from enforce. |
| enforcement_mode_changed_by | Id of the user who last changed the mode, or null (also when that user has since been deleted). |
| enforcement_mode_changed_by_name | Name of that user, or null. Returned when listing keys. |
| apply_tool_lists_to_hook | true or false (default false). When true, the key's allowed and blocked tool lists also apply to native tool calls through POST /v1/mcp/hook; when false they apply to the MCP proxy only. Accepted when creating or updating a key (Admin only); a value that is not a boolean is rejected with a 400. |
What gets scanned
For file-write tools, the gateway scans the content being written, not the file path or the text being removed. For other tools it scans the full arguments. This means writing sensitive data can be blocked, while deleting it is allowed.