Gating X's MCP Server with Per-Tool ZKP Authorization

June 30, 2026 · Viswanadha Pratap Kondoju

X shipped an MCP server. Agents can now search posts, manage bookmarks, and publish articles on behalf of users. OAuth tells X who the user is. Nothing in the MCP protocol tells X what the agent is allowed to do.

We built a demo wrapping X's xurl mcp stdio bridge with Bolyra Shield and ran three scenarios against a mock server that simulates X's MCP tool surface. The demo uses dev mode (mock ZKP proofs) to show the authorization flow without requiring circuit artifacts or X API credentials.

The gap

X's MCP server uses OAuth 2.0 PKCE for user authentication. Once an agent has a user's OAuth token, it can call any tool the token's scopes allow. There is no per-tool authorization layer. A social media reading agent and a publishing agent present the same bearer token. From X's perspective, they are identical.

This matters because X's MCP tool surface includes both read operations (search, user lookup, bookmarks) and write operations (publish articles, modify bookmarks). An agent that should only read your timeline can also publish articles under your name if it has the same OAuth token.

What we built

@bolyra/shield is a stdio proxy that sits between the MCP client and the MCP server. It intercepts every tools/call request, verifies a ZKP proof bundle, checks per-tool permission policies, and either forwards the call or rejects it with a JSON-RPC error.

Agent ←stdin/stdout→ @bolyra/shield ←stdin/stdout→ xurl mcp

verifyBundle() · checkToolPolicy() · rejectReplay() · defaultDeny()

Shield does not replace OAuth. It adds a cryptographic authorization layer on top. OAuth says "this agent acts as you." Bolyra says "this agent can only do these specific things as you, and here is the proof it stayed in bounds."

Three scenarios

ScenarioAgentToolResult
1. Attacker No proof search_recent_posts Blocked (-32000)
1. Attacker No proof add_bookmark Blocked (-32000)
2. Legit agent Full permissions search_recent_posts Allowed
2. Legit agent Full permissions add_bookmark Allowed
2. Legit agent Full permissions create_article Allowed
3. Delegated READ_DATA only search_recent_posts Allowed
3. Delegated READ_DATA only add_bookmark Blocked (-32001)
3. Delegated READ_DATA only create_article Blocked (-32001)

Tool names in this demo are mock placeholders for X MCP capability categories. X's docs describe capability areas but do not publish exact tool names. Real xurl tool names may differ.

Scenario 3 is the interesting one. A human delegates to an agent with READ_DATA only (permission bit 0). In production, the delegation circuit enforces one-way scope narrowing in a real ZK proof and the proof bundle cryptographically binds the narrowed scope. In this demo (dev mode), Shield walks the delegation chain shape and checks the permission bitmask without real cryptographic verification. The agent can search posts but cannot modify bookmarks (WRITE_DATA, bit 1) or publish articles (SIGN_ON_BEHALF, bit 5).

Tool permission mapping

PermissionBitTools
READ_DATA0search_recent_posts, get_user_by_username, get_me, get_bookmarks
WRITE_DATA1add_bookmark, remove_bookmark
WRITE_DATA + SIGN_ON_BEHALF1+5create_article

Each tool requires a specific combination of permission bits. SIGN_ON_BEHALF (bit 5) does not imply WRITE_DATA (bit 1). Publishing articles requires both bits set (bitmask 34). An agent with only WRITE_DATA can manage bookmarks but cannot publish articles. The policy is defined in shield.yaml, not hardcoded.

Default deny

We shipped a new Shield feature for this demo: defaultDeny. When enabled, any tools/call for a tool name not listed in the policy map is rejected, even if the agent has a valid proof. This closes the gap where X adds a new tool (say, delete_post) and existing Shield configs would silently allow it through because no policy entry exists.

# shield.yaml
devMode: true
defaultDeny: true    # Reject tools not listed below

tools:
  search_recent_posts:
    requireBitmask: 1    # READ_DATA
  add_bookmark:
    requireBitmask: 2    # WRITE_DATA
  create_article:
    requireBitmask: 34   # WRITE_DATA | SIGN_ON_BEHALF

Run it yourself

git clone https://github.com/bolyra/bolyra.git
cd bolyra/examples/x-mcp-demo
bash run-demo.sh

No X API credentials needed. The demo uses a mock server that simulates X's MCP tools and runs in dev mode with mock ZKP proofs (no circuit artifacts or trusted setup required). In production, Shield verifies real Groth16 proof bundles from the @bolyra/sdk. To use the real X API, install xurl and run:

bolyra-shield --server "xurl mcp" --config shield.yaml

What this means

X's MCP server is one of the first major platform APIs to adopt MCP natively. As more platforms ship MCP endpoints, the agent authorization gap becomes a product category. OAuth handles user identity. MCP handles tool discovery and invocation. Nothing handles per-tool agent authorization.

Bolyra Shield fills that gap with a single process that sits in front of any stdio MCP server. No changes to X's server. No changes to the MCP protocol. The authorization policy is a YAML file. The proofs are cryptographic.

← Previous: How Bolyra Agent Credentials Are Issued, Registered, Revoked, and Rotated

Clone the demo and see per-tool ZKP authorization on X's MCP tools.

View Demo on GitHub

This project is not affiliated with, endorsed by, or sponsored by X Corp. The demo uses a mock server with no connection to X's production systems.