feat(mcp): authorise tool-call parameters, not just the tool name

The ACL answers 'may this identity call create-product'. Nothing answered 'may
it call it with these arguments' — write authorises writing, not this
particular record. And the read cap counts calls, so one call asking for a
hundred thousand rows cost the same as one asking for ten.

The OPA plugin sends the decoded JSON-RPC body, so the decision point sees the
tool name and its arguments. The policy lives once in k8s-platform/opa and every
domain asks the same server.

Enforced at the gateway rather than in an agent, because an agent that has been
talked into something is the case this exists for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Stéphane Tailland
2026-08-29 18:09:20 +02:00
parent c0799877ad
commit a51867b161
3 changed files with 46 additions and 0 deletions
+1
View File
@@ -18,6 +18,7 @@ resources:
- mcp/ai-mcp-proxy.yaml
- mcp/openid-connect.yaml
- mcp/rate-limiting.yaml
- mcp/opa.yaml
- mcp/audit-log.yaml
- mcp/audit-log-body-capture.yaml
- mcp/audit-log-body-capture-bindings.yaml
+27
View File
@@ -0,0 +1,27 @@
# Authorisation on tool-call parameters, not just the tool name.
#
# The ACL answers "may this identity call create-product?". Nothing answered
# "may it call it with *these* arguments" — and `write` authorises writing, not
# this particular record. The read cap counts calls, so one call asking for a
# hundred thousand rows costs the same as one asking for ten.
#
# Enforced here rather than in an agent, because an agent that has been talked
# into something is exactly the case this exists for. The policy lives once, in
# k8s-platform/opa/policy.rego, and every domain asks the same server.
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: ${NAME}-sales-mcp-opa
namespace: ${NAMESPACE}
plugin: opa
config:
opa_protocol: http
opa_host: opa.opa.svc.cluster.local
opa_port: 8181
opa_path: /v1/data/kong
# The decision is about the arguments, so the arguments have to reach the
# decision point. Without this OPA sees a method and a path and nothing it
# can rule on.
include_parsed_json_body_in_opa_input: true
include_body_in_opa_input: false
include_route_in_opa_input: false
+18
View File
@@ -69,3 +69,21 @@ spec:
group: configuration.konghq.com
kind: KongRoute
name: ${NAME}-sales-mcp
---
apiVersion: configuration.konghq.com/v1alpha1
kind: KongPluginBinding
metadata:
name: ${NAME}-sales-mcp-opa-binding
namespace: ${NAMESPACE}
spec:
controlPlaneRef:
type: konnectNamespacedRef
konnectNamespacedRef:
name: ${CONTROL_PLANE_REF_NAME}
pluginRef:
name: ${NAME}-sales-mcp-opa
targets:
routeRef:
group: configuration.konghq.com
kind: KongRoute
name: ${NAME}-sales-mcp