Session Tool Scoping
Per-session allow and deny lists with wildcards — multi-tenant tool isolation, role-based access, and progressive disclosure at the gateway layer.
Overview
By default, every MCP session can see and execute all tools from all connected servers. Per-session tool scoping adds two optional fields to a session:
allowed_tool_names(allowlist) — when set, only matching tools are visibledenied_tool_names(denylist) — when set, matching tools are blocked
Both fields support wildcards (PREFIX__*) to target all tools from a server without enumerating each one.
Session scoping filters all tool surfaces: tools/list in LIST mode, SEARCH_TOOLS, EXECUTE_TOOL, and direct tools/call. An agent literally cannot discover or call tools outside its session scope.
Consider a gateway with 500 knowledge base tools from VIVI, 10 tools from HUBSPOT, and 8 tools from GMAIL:
| Session | allowed | denied | Result |
|---|---|---|---|
| A | VIVI__kb_finance, VIVI__kb_hr, HUBSPOT__*, GMAIL__* | HUBSPOT__internal_debug | 2 VIVI + 9 HUBSPOT + 8 GMAIL = 19 tools |
| B | null | VIVI__secret_tool | Everything except VIVI__secret_tool |
| C | null | null | Everything (default, no restrictions) |
Session Scope Levels
Every session is scoped to the authenticated user. Beyond that, you can optionally narrow the session to a specific server or bundle:
server_id | bundle_id | Scope | Tool universe |
|---|---|---|---|
null | null | Gateway-wide | All tools across all connected servers |
uuid | null | Server-scoped | Only tools from that one server |
null | uuid | Bundle-scoped | Only tools from that one bundle |
Setting both server_id and bundle_id is rejected (400 error). They are mutually exclusive.
The allowed_tool_names and denied_tool_names filters then apply within whichever scope level you chose. For example, a gateway-wide session with an allowlist filters across all servers; a server-scoped session with an allowlist filters within that server's tools only.
Resolution Precedence
For each tool, the gateway evaluates three rules in order:
- Deny always wins — if the tool matches any deny pattern, it's blocked
- No allow list = allow all — if
allowed_tool_namesisnull, the tool is visible - Must match allow — if
allowed_tool_namesis set, the tool must match an entry
# Pseudocode
if tool matches any denied pattern → BLOCKED
if allowed is null → VISIBLE
if tool matches any allowed pattern → VISIBLE
else → BLOCKEDHow It Works
Create a scoped session
Pass allowed_tool_names and/or denied_tool_names when creating a session. The gateway enforces the scope across all tool operations.
POST /api/v1/sessions
{
"allowed_tool_names": [
"MY_KBS__kb_finance",
"MY_KBS__kb_hr",
"HUBSPOT__*",
"GMAIL__*"
],
"denied_tool_names": [
"HUBSPOT__internal_debug"
]
}No server_id or bundle_id — session spans all servers. Allow/deny filters across the entire gateway.
POST /api/v1/sessions
{
"server_id": "a1b2c3d4-...",
"denied_tool_names": [
"VIVI__secret_tool",
"VIVI__admin_reset"
]
}Session is tied to one server. Agent sees only that server's tools, minus the two denied.
POST /api/v1/sessions
{
"bundle_id": "e5f6g7h8-...",
"allowed_tool_names": [
"MY_KBS__kb_pricing",
"MY_KBS__kb_product_docs"
]
}Session is tied to one bundle. Agent sees only the two allowed tools from that bundle.
Omit both fields (or set to null) for unrestricted access — fully backwards compatible.
Agent uses tools normally
Pass the session ID in the Mcp-Session-Id header on every request to /mcp/gateway. The agent has no awareness that scoping is applied — it simply sees fewer tools in tools/list and SEARCH_TOOLS results.
# tools/list — only returns allowed tools, minus denied
curl -X POST http://localhost:8000/mcp/gateway \
-H "Authorization: Bearer mgw_usr_live_xxx" \
-H "Mcp-Session-Id: a1b2c3d4-..." \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'If the agent attempts to call a denied tool, the gateway rejects it:
{
"content": [{"type": "text", "text": "Tool 'HUBSPOT__internal_debug' is not in session scope"}],
"isError": true
}Update scope at runtime (optional)
Need to grant or revoke tool access mid-conversation? Patch the session. Only provided fields are changed — omit a field to leave it unchanged.
PATCH /api/v1/sessions/{session_id}
{
"denied_tool_names": ["HUBSPOT__debug", "HUBSPOT__admin"]
}PATCH /api/v1/sessions/{session_id}
{
"allowed_tool_names": ["VIVI__kb_finance", "GMAIL__*"]
}PATCH /api/v1/sessions/{session_id}
{
"allowed_tool_names": null,
"denied_tool_names": null
}Wildcards
Both fields support the whole-server wildcard PREFIX__*:
| Pattern | Meaning |
|---|---|
HUBSPOT__search | Exact match — only this one tool |
HUBSPOT__* | All tools from the HUBSPOT server |
Partial wildcards (e.g., HUBSPOT__search_*) are not supported and will be rejected.
Tool Name Format
Tool names must use the prefixed format: SERVER_PREFIX__tool_name or SERVER_PREFIX__*.
The prefix is derived from the server name (uppercase, spaces/hyphens replaced with underscores):
| Server Name | Tool Name | Prefixed Name |
|---|---|---|
my-knowledge-bases | search_kb_elizabeth | MY_KNOWLEDGE_BASES__search_kb_elizabeth |
github-api | create_issue | GITHUB_API__create_issue |
slack | send_message | SLACK__send_message |
my-knowledge-bases | (all tools) | MY_KNOWLEDGE_BASES__* |
To find the correct prefixed names, call tools/list without scoping first, or use POST /api/v1/tools/search.
Scoping Behavior Reference
allowed | denied | Result |
|---|---|---|
null | null | All tools visible (default) |
["A__x", "B__y"] | null | Only tools A__x and B__y |
null | ["A__secret"] | Everything except A__secret |
["A__*"] | ["A__debug"] | All A tools except A__debug |
["A__x", "B__*"] | ["B__admin"] | A__x + all B except B__admin |
[] (empty) | null | Block all tools |
What Gets Filtered
| Gateway Path | Scoped? | Details |
|---|---|---|
tools/list (LIST mode) | Yes | Only allowed tools appear in the tool list |
SEARCH_TOOLS | Yes | Vector search results filtered after search (over-fetches 3x when scoped) |
EXECUTE_TOOL | Yes | Calls to tools outside scope are rejected |
tools/call (direct) | Yes | Direct MCP tool calls also respect session scope |
SYSTEM__ builtins | Always visible | Gateway meta-tools are never filtered |
/mcp/servers/{id} (direct server) | Not scoped | Direct server connections bypass session scoping by design |
POST /api/v1/tools/search (REST API) | Not scoped | Stateless REST endpoint, no session context |
Input Validation
Tool names are validated on creation and update:
- Non-empty — empty strings are rejected
- Must contain
__— thePREFIX__tool_nameseparator is required - Wildcard must be whole-server —
PREFIX__*is allowed,PREFIX__search_*is rejected - No wildcards in prefix —
*__toolis rejected SYSTEMprefix reserved —SYSTEM__anythingis rejected (builtins can't be scoped)
Invalid names are rejected with a 422 Validation Error.
SDK Integration
Using the MCP Gateway Python SDK:
from mcpgateway_sdk import MCPGateway
async with MCPGateway(base_url="http://localhost:8000", api_key="mgw_usr_live_xxx") as gw:
# Create a scoped session
session = await gw.sessions.create(
allowed_tool_names=[
"VIVI__kb_finance",
"VIVI__kb_hr",
"VIVI__kb_legal",
"HUBSPOT__*",
"GMAIL__*",
],
denied_tool_names=[
"HUBSPOT__internal_debug",
],
)
# Update only the deny list (allowed list unchanged)
session = await gw.sessions.update(
session.id,
denied_tool_names=["HUBSPOT__internal_debug", "HUBSPOT__admin_reset"],
)
# Clear all restrictions
session = await gw.sessions.update(
session.id,
allowed_tool_names=None,
denied_tool_names=None,
)Use Cases
Multi-tenant SaaS — One MCP server exposes 500 knowledge base tools. Each customer's agent gets a session scoped to only their knowledge bases. Customer A sees 3 tools, Customer B sees 12 — from the same server.
Role-based access — A support agent sees customer-facing tools. An admin agent sees internal tools. Same server, different session scopes.
Progressive disclosure — Start with basic tools. As the conversation reveals the user's intent, patch the session to unlock more specific tools. Keeps the initial tool list lean.
Context optimization — Reduce the tool list to only relevant tools, saving tokens and improving model accuracy.
Security isolation — Prevent agents from discovering or calling tools they shouldn't know about.
Industry Context
The MCP specification does not define per-session tool filtering. Every major platform implements this at the gateway or orchestration layer:
| Platform | Approach | Level |
|---|---|---|
| MCP Gateway | allowed/denied_tool_names per session | Gateway |
| OpenAI | allowed_tools per API request | Client API |
| AWS Bedrock | Cedar policies per principal | Gateway |
| Kong AI Gateway | Consumer Group ACLs | Gateway |
| LiteLLM | Allowlists per API key/team | Proxy |
| CrewAI | get_mcp_tools() per agent | Framework |
MCP Gateway's approach is session-level (not request-level or key-level), which means scope can change during a conversation without creating a new connection. There is an open proposal (SEP-1300) to add tool filtering to the MCP spec, but it is not yet standardized.
