Skip to content

MCP gateway

POST /v1/gateway/mcp decides an MCP tool call. The tool name is mapped to an action by substring. Vulnify does not execute arguments.

The response is the same decision body as POST /v1/events, plus gateway. Unlike the HTTP gateway, this object has no upstream. gateway.upstream is not in the schema. mappedAction is the action the tool name mapped to. allowed is whether that decision allows the call.

The Node.js guardMcpHandler adapter is a different path. It guards a handler inside your own MCP server and calls POST /v1/events. Use that when the server runs in your process. Use this endpoint when the caller sends the tool call to Vulnify.

Terminal window
curl -X POST https://api.vulnify.io/v1/gateway/mcp \
-H "Authorization: Bearer $VULNIFY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"routeId": "ROUTE_ID",
"tool": "crm.export_contacts",
"resource": "Customer Database",
"arguments": { "limit": 500 }
}'
OpenAPI 0.3.0 (https://api.vulnify.io/openapi.json). Parameters.
NameInRequiredTypeDescription
Idempotency-KeyheadernostringRetries with the same key (per organization and endpoint, 24 hours) return the stored decision instead of deciding again. The replay always includes finalDecision, derived from the stored decision and the current review.
OpenAPI 0.3.0 (https://api.vulnify.io/openapi.json). Request body.
FieldTypeRequiredDescription
agentstring (max length 100)noAgent name as registered in Vulnify.
agentIdstring (uuid)noAgent id. Use this or `agent`.
resourcestring (max length 100)noResource name as registered in Vulnify.
resourceIdstring (uuid)noResource id. Use this or `resource`.
destinationINTERNAL | EXTERNAL_EMAIL | EXTERNAL_APIno
recordsAffectedinteger (0–100000000)no
contentstring (max length 100000)noOptional text scanned for sensitive data. Scanned in memory and never stored.
routeIdstring (uuid)noRegistered MCP route (supplies a default agent).
toolstring (min length 1, max length 200)yesMCP tool name. Mapped to an action by substring.
argumentsobjectnoTool arguments. Not executed by Vulnify.

tool is required. A registered MCP route supplies a default agent. The 400 response still describes a validation error when agent and resource are not identified. arguments is an object of any keys. It is not executed by Vulnify.

Decision: { id, decision, finalDecision, evaluatedDecision, monitored, riskLevel, riskScore, reasons, policy, dlpFindings, lgpdCategories, review, quotaExceeded, sandbox }. decision is the stored outcome and does not change when a review is resolved. finalDecision is the effective outcome (ALLOW after approval, BLOCK after denial or expiry, REVIEW while pending) and is always present, including an idempotent replay. quotaExceeded flags a plan overrun without blocking; sandbox is true for TEST keys.

OpenAPI 0.3.0 (https://api.vulnify.io/openapi.json). Response body.
FieldTypeRequiredDescription
idstring (uuid)yes
decisionALLOW | REVIEW | BLOCKyesDecision recorded on the event. It does not change when a review is resolved.
finalDecisionALLOW | REVIEW | BLOCKyesEffective outcome. Equals `decision` when there is no review. REVIEW while a review is pending. ALLOW after approval, BLOCK after denial or expiry. Always present, including an idempotent replay of a response stored before this field existed: the replay derives it from the stored decision and the current review.
evaluatedDecisionALLOW | REVIEW | BLOCKyesWhat full enforcement would have decided. Differs from `decision` in monitor mode.
monitoredbooleanyes
riskLevelLOW | MEDIUM | HIGH | CRITICALyes
riskScoreinteger (0–100)yes
reasonsarray of stringyes
policyobject or nullyes
policy.idstring (uuid)yes
policy.namestringyes
dlpFindingsarray of stringyesSensitive-data types found in `content`.
lgpdCategoriesarray of IDENTIFICATION | CONTACT | LOCATION | FINANCIAL | HEALTH | COMPANY | CREDENTIALSyesLGPD categories derived from `dlpFindings`.
reviewobject or nullyesNull when the event has no human review.
review.statusPENDING | APPROVED | DENIED | EXPIREDyes
review.expiresAtstring or null (date-time)yes
review.decidedAtstring or null (date-time)yes
review.notestring or nullyes
quotaExceededbooleanyesPlan quota is over the limit. The decision is still made.
sandboxbooleanyesTrue when the event was created with a TEST key.
gatewayobjectno
gateway.protocolMCPyes
gateway.routeIdstring or null (uuid)no
gateway.toolstringyes
gateway.mappedActionREAD_DATA | WRITE_DATA | DELETE_DATA | EXPORT_DATA | SEND_EMAILyes
gateway.allowedbooleanyes
OpenAPI 0.3.0 (https://api.vulnify.io/openapi.json). Response headers.
HeaderDescription
X-Request-IdRequest id for support and log correlation (a caller-sent plain id is kept).
x-api-versionMachine API contract (v1).
x-vulnify-versionAPI build (SemVer, see /changelog).
Idempotent-Replay"true" when the body is a stored replay
OpenAPI 0.3.0 (https://api.vulnify.io/openapi.json). Error responses.
StatusMeaning
400Validation error (agent/agentId and resource/resourceId are required)
401Missing, invalid, revoked or expired API key
403Key bound to another agent, or caller IP not in the key allowlist
404Unknown agent, resource or gateway route
413JSON body larger than 200 KB
429Rate limit exceeded

A 413 means the JSON body is larger than 200 KB. It is a client error, including when a caller hoped to fail open. See Errors.

The call is listed in the gateway call log with protocol MCP.