SESSION TOOL SCOPING

Same server, different customers

One MCP gateway, one tool backend, N isolated views. Per-session allow and deny rules with wildcards — enforced at the gateway layer and updatable mid-conversation.

WHAT IT LOOKS LIKE

One server. Three sessions. Three different worlds.

Shared backend

VIVI knowledge-base server

500 knowledge-base tools · one deployment · one set of credentials

SESSION AAcme Corp

3

visible tools

  • VIVI__kb_finance
  • VIVI__kb_hr
  • VIVI__kb_legal
SESSION BBeta Industries

12

visible tools

  • VIVI__kb_pricing
  • VIVI__kb_catalog
  • VIVI__kb_support_*
  • …9 more
SESSION CGamma Labs

497

visible tools

  • VIVI__*
  • denied: VIVI__internal_debug
  • denied: VIVI__beta_*

All three sessions hit the same server. The gateway filters tools/list, SEARCH_TOOLS, and tools/call per session — the agent never sees tools that aren't in scope.

HOW IT WORKS

Four primitives. One line of Python.

Allow and deny lists with wildcards

Two optional fields on every session: allowed_tool_names (allowlist) and denied_tool_names (denylist). Both accept exact names or whole-server wildcards like HUBSPOT__*. Deny always wins, so you can allow an entire server and carve out a single internal tool in one line.

mcpgateway-sdk
from mcpgateway_sdk import MCPGateway
 
async with MCPGateway(
url="http://localhost:8000",
api_key="mgw_usr_live_...",
) as gw:
session = await gw.sessions.create(
allowed_tool_names=[
"VIVI__kb_finance",
"HUBSPOT__*",
"GMAIL__*",
],
denied_tool_names=["HUBSPOT__internal_debug"],
)
wildcards · exact match · deny always wins

Three scope levels

Every session is scoped to the authenticated user. Beyond that, you can narrow to a specific server or a specific bundle. The allow and deny filters then apply within whichever universe you chose. Passing both server_id and bundle_id is rejected — they are mutually exclusive.

server_idbundle_idScopeTool universe
nullnullGateway-wideAll tools across all connected servers
uuidnullServer-scopedOnly tools from that one server
nulluuidBundle-scopedOnly tools from that one bundle

Change scope mid-conversation

PATCH the session at any time to tighten or loosen the rules. Only the fields you provide are changed — omit allowed_tool_names to leave it unchanged, or pass null to clear it. Useful for progressive disclosure: start lean, unlock specialized tools as the user's intent reveals itself, revoke access the moment the conversation ends.

mcpgateway-sdk
# Unlock the next set of tools — mid-conversation
session = await gw.sessions.update(
session.id,
allowed_tool_names=[
"VIVI__kb_finance",
"VIVI__kb_legal", # newly unlocked
"GMAIL__*",
],
)
 
# Or clear everything
await gw.sessions.update(session.id,
allowed_tool_names=None,
denied_tool_names=None,
)
PATCH /api/v1/sessions/{session_id} · only provided fields change

Filtered at every tool surface

Session scope applies uniformly to tools/list in LIST mode, SEARCH_TOOLS and EXECUTE_TOOL in SEARCH_EXECUTE mode, and direct tools/call. Three documented exceptions: gateway SYSTEM__* builtins are always visible, direct connections to a single server via /mcp/servers/{server_id} bypass scoping by design, and the stateless REST endpoint /api/v1/tools/search has no session context.

SurfaceScoped?Behavior
tools/list (LIST mode)YesOnly allowed tools appear
SEARCH_TOOLSYesVector search results filtered after scoring
EXECUTE_TOOLYesRejects calls outside scope
tools/call (direct)YesSame enforcement path
SYSTEM__* builtinsNoGateway meta-tools — always visible
/mcp/servers/{server_id}NoDirect server connection — bypasses by design
POST /api/v1/tools/searchNoStateless REST — no session context

RESOLUTION ORDER

Three rules. Evaluated in order.

01

Deny always wins

If a tool matches any deny pattern, it's blocked — even if it also matches an allow pattern.

02

No allow list = allow all

If allowed_tool_names is null, every tool that survives the deny list is visible.

03

With an allow list, match is required

If allowed_tool_names is set, a tool must match at least one entry (exact or wildcard) to be visible.

CUSTOMER: VIVI

One server, hundreds of tenants, zero cross-contamination. Session scoping is how we go from "AI assistant in a demo" to "AI assistant each customer actually trusts with their data."

Read how VIVI runs multi-tenant knowledge graphs on one MCP backendCase study

INDUSTRY CONTEXT

Every platform filters tools. The level matters.

The MCP spec does not define per-session tool filtering — SEP-1300 was rejected and closed. So every major platform implements it at its own layer. MCP Gateway is session-level, which means the scope can change during a conversation with a single PATCH.

PlatformApproachLevel
MCP Gatewayallowed/denied_tool_names per sessionGateway
OpenAIallowed_tools per API requestClient API
AWS BedrockCedar policies per principalGateway
Kong AI GatewayConsumer Group ACLsGateway
LiteLLMAllowlists per API key / teamProxy
CrewAIget_mcp_tools() per agentFramework

WHERE IT FITS

Use cases

Multi-tenant SaaS

One MCP server, 500 knowledge-base tools, N customers. Each customer's agent is scoped to only their data — no cross-contamination, no separate deployments.

Role-based access

Support agents see customer-facing tools. Admin agents see internal tools. Same backend, different session scopes — no RBAC rewiring required.

Progressive disclosure

Start the session with a lean toolset. As the user's intent reveals itself, PATCH in more specific tools. Keeps the tool list tight and the context window lean.

Context optimization

Fewer tools = better tool selection accuracy. Anthropic's research shows tool accuracy degrades past 30–50 tools; session scoping is how you stay under the cliff.

What's next

Ship multi-tenant agents this week

Session scoping ships with every MCP Gateway deployment. Create a scoped session in one call, change it mid-conversation, or strip it back to unrestricted — all through the same Python SDK.