Summary: A team connecting an MCP server needs to prevent an agent from invoking unapproved tools even when the server connection succeeds. MCP security needs both access controls and a decision about the proposed tool call. Keep MCP transport authorization and server-side permissions. Add an APort check at a supported runtime's tool boundary when you need an agent passport to limit which server or tool can run.
The MCP authorization specification defines access to restricted HTTP MCP servers. It does not install a policy hook in your agent runtime. This guide uses Claude Code's PreToolUse hook as the concrete integration; MCP is not a separate APort installer target.
Tested setup: restrict MCP servers in a Claude Code hook
Prerequisites: macOS or Linux, Bash, Node.js 18 or newer, npm/npx and jq, plus network access for the package download. Tested with @aporthq/[email protected] on September 30, 2026. No MCP server, hosted account or API key is required for this local hook test.
The generated local passport permits all MCP servers by default. This example replaces that wildcard with docs and allows only search, preserving the generated rate and parameter-size limits. It passes proposed calls to the installed hook without contacting a server or deleting data.
set -euo pipefail
export APORT_CLAUDE_CODE_CONFIG_DIR="$(mktemp -d)"
test -d "$APORT_CLAUDE_CODE_CONFIG_DIR"
export APORT_CONFIG_DIR="$APORT_CLAUDE_CODE_CONFIG_DIR"
export APORT_OWNER_EMAIL="[email protected]"
npx --yes @aporthq/[email protected] claude-code \
--mode=local --non-interactive --enforcement=enforce
jq -e '.hooks.PreToolUse | length > 0' \
"$APORT_CONFIG_DIR/settings.json"
aport_passport="$APORT_CONFIG_DIR/aport/passport.json"
jq '.limits["mcp.tool.execute"] += {
"allowed_servers": ["docs"], "allowed_tools": ["search"]
}' "$aport_passport" > "$aport_passport.tmp"
mv "$aport_passport.tmp" "$aport_passport"
aport_hook="$APORT_CONFIG_DIR/aport/runtime/bin/aport-claude-code-hook.sh"
aport_allow=$(printf '%s\n' \
'{"tool_name":"mcp__docs__search","tool_input":{"query":"setup"}}' \
| bash "$aport_hook")
test -z "$aport_allow"
printf '%s\n' \
'{"tool_name":"mcp__unapproved__delete_records","tool_input":{"table":"sandbox"}}' \
| bash "$aport_hook" > "$APORT_CONFIG_DIR/denied.json"
jq -e '.hookSpecificOutput.permissionDecision == "deny" and
(.hookSpecificOutput.permissionDecisionReason | contains("oap.mcp_server_not_allowed"))' \
"$APORT_CONFIG_DIR/denied.json"Expected result: both jq assertions print true and the shell exits successfully. The proposed docs/search call produces no blocking output. The unapproved server receives permissionDecision: deny with reason oap.mcp_server_not_allowed. Inspect denied.json and the local aport/audit.log in the temporary config directory.
Move from the local test to your runtime
Use the Claude Code setup guide to install into your normal configuration and confirm /hooks shows APort after a restart. Apply the server and tool names from your own MCP configuration to that passport. Start a team rollout in warn mode, review the findings, then enable enforcement and verify a denied call cannot reach the server.
The direct hook test above verifies the installed adapter and local policy path. It does not test a live MCP transport, OAuth exchange, remote server or hosted signing. Repeat it through your actual host with a harmless test tool before relying on enforcement.
An allowlisted tool still needs scoped arguments
Server and tool allowlists are one control. A permitted export tool can still send too much data; an allowed file tool can still target a sensitive path. For each tool, identify which control validates its arguments and test that control with realistic payloads.
| Tool type | Check before execution |
|---|---|
| File access | Allowed root, sensitive paths and maximum size |
| Database query | Database, tenant, read/write authority and export limit |
| External API | Destination, account ownership and permitted operation |
| Payments | Amount, currency, recipient and approval requirements |
The tested setup demonstrates server and tool scoping only. It does not establish that arbitrary MCP arguments receive all the checks above. Map those arguments into an appropriate policy or enforce them in the server itself. Protect credentials and keep server-side authorization even when a runtime hook is installed.
For the protocol design, read MCP authorization versus MCP security. For a URL-bearing tool, use the web-fetch DNS and SSRF model to distinguish policy checks from control of the outbound connection.
Install my guardrail through the runtime that calls your MCP tools. Keep one allowed and one denied fixture in the rollout check.
Frequently Asked Questions
Common questions about this topic.