MCP Server Endpoints

Register and manage Model Context Protocol servers, verify agent access to tools, and expose OAuth 2.0 Protected Resource Metadata.


Register MCP Server

POST /api/v1/mcp/servers

Register an MCP server with Bulwark so agents can be authorized to call its tools.

Headers

  • Authorization: Bearer <apiKey>
  • X-Bulwark-Tenant: <tenant-id>

Body

{
  "name": "file-ops-server",
  "slug": "file-ops-server",
  "description": "File system operations for the workspace agent",
  "resource_uri": "https://mcp.example.com",
  "tools": [
    {
      "name": "read_file",
      "description": "Read a file by path",
      "scopes_required": ["files:read"]
    },
    {
      "name": "write_file",
      "description": "Write or overwrite a file",
      "scopes_required": ["files:write"]
    }
  ],
  "allowed_agents": ["agent_01j..."]
}

| Field | Required | Description | |-------|----------|-------------| | name | Yes | Unique server name within the tenant | | resource_uri | Yes | Base URI of the MCP server (used as the OAuth 2.0 resource identifier) | | tools | No | Tool definitions with required scopes for authorization | | slug | No | URL-safe slug; auto-generated from name if omitted | | description | No | Human-readable description | | allowed_agents | No | Agent IDs that may access this server; empty means all agents |

Response 201

{
  "id": "01j...",
  "tenant_id": "01j...",
  "name": "file-ops-server",
  "slug": "file-ops-server",
  "description": "File system operations for the workspace agent",
  "resource_uri": "https://mcp.example.com",
  "tools": [
    { "name": "read_file", "scopes_required": ["files:read"] },
    { "name": "write_file", "scopes_required": ["files:write"] }
  ],
  "status": "active",
  "allowed_agents": ["agent_01j..."],
  "config": {},
  "created_at": "2026-03-30T00:00:00Z",
  "updated_at": "2026-03-30T00:00:00Z"
}

List MCP Servers

GET /api/v1/mcp/servers

Headers

  • Authorization: Bearer <apiKey>
  • X-Bulwark-Tenant: <tenant-id>

Response 200

{
  "servers": [
    {
      "id": "01j...",
      "tenant_id": "01j...",
      "name": "file-ops-server",
      "slug": "file-ops-server",
      "resource_uri": "https://mcp.example.com",
      "tools": [
        { "name": "read_file", "scopes_required": ["files:read"] },
        { "name": "write_file", "scopes_required": ["files:write"] }
      ],
      "status": "active",
      "allowed_agents": [],
      "config": {},
      "created_at": "2026-03-30T00:00:00Z",
      "updated_at": "2026-03-30T00:00:00Z"
    }
  ],
  "count": 1
}

Get MCP Server

GET /api/v1/mcp/servers/{id}

Response 200

{
  "id": "01j...",
  "tenant_id": "01j...",
  "name": "file-ops-server",
  "slug": "file-ops-server",
  "description": "File system operations for the workspace agent",
  "resource_uri": "https://mcp.example.com",
  "tools": [
    { "name": "read_file", "scopes_required": ["files:read"] },
    { "name": "write_file", "scopes_required": ["files:write"] }
  ],
  "status": "active",
  "allowed_agents": [],
  "config": {},
  "created_at": "2026-03-30T00:00:00Z",
  "updated_at": "2026-03-30T00:00:00Z"
}

Update MCP Server

PUT /api/v1/mcp/servers/{id}

Replace the mutable fields of a registered MCP server. All body fields are applied as a full replacement of the current values — omit tools or allowed_agents to clear them.

Headers

  • Authorization: Bearer <apiKey>
  • X-Bulwark-Tenant: <tenant-id>

Body

{
  "name": "file-ops-server",
  "description": "Updated description",
  "resource_uri": "https://mcp.example.com",
  "tools": [
    {
      "name": "read_file",
      "description": "Read a file by path",
      "scopes_required": ["files:read"]
    },
    {
      "name": "write_file",
      "description": "Write or overwrite a file",
      "scopes_required": ["files:write"]
    }
  ],
  "allowed_agents": ["agent_01j..."]
}

| Field | Required | Description | |-------|----------|-------------| | name | Yes | Server name | | resource_uri | Yes | Base URI of the MCP server | | description | No | Human-readable description | | tools | No | Full replacement of tool definitions; omit to clear | | allowed_agents | No | Agent IDs allowed to access this server; omit to allow all |

slug is not updatable — it is derived from name at registration time and used as a stable identifier in Biscuit scope strings.

Response 200

{
  "status": "updated"
}

Response 500

{ "error": "failed to update MCP server" }

Delete MCP Server

DELETE /api/v1/mcp/servers/{id}

Removes the server registration. Agents with existing Biscuit tokens scoped to this server's tools will be denied on next introspection.

Response 200

{
  "status": "deleted"
}

Introspect Agent Token

POST /api/v1/mcp/introspect

Public endpoint — no admin credentials required. Called by MCP servers to verify that an incoming agent Biscuit token is valid and grants access to a specific tool.

Bulwark resolves the registered server by server_id, builds the canonical resource string mcp:<server-slug>:<tool> from the stored slug, and validates the agent session against the database — it must belong to the claiming agent, be active, and be unexpired. A cryptographically valid token is denied once its session has been revoked, completed, or expired. Tokens must be minted with rights in the canonical mcp:<server-slug>:<tool> form or introspection denies them.

Body

{
  "token": "<biscuit-token>",
  "tool": "write_file",
  "server_id": "mcp_01j...",
  "session_id": "sess_01j...",
  "agent_id": "agent_01j..."
}

| Field | Required | Description | |-------|----------|-------------| | token | Yes | Biscuit token (base64url) | | tool | Yes | MCP tool name being invoked | | server_id | Yes | Registered MCP server ID | | session_id | Yes | Agent session ID (from token) | | agent_id | Yes | Agent ID (from token) |

Response 200 — Authorized

{
  "active": true,
  "agent_id": "agent_01j...",
  "tenant_id": "00000000-0000-7000-8000-...",
  "session_id": "sess_01j...",
  "tool": "write_file",
  "expires_at": 1765432100
}

expires_at is the Unix timestamp at which the agent session expires.

Response 200 — Denied

{
  "active": false,
  "tool": "write_file",
  "reason": "token verification failed"
}

Denial reasons: unknown server (server ID not registered or inactive), agent not allowed for this server (the server restricts allowed_agents), session invalid (session missing, owned by another agent, revoked, completed, or expired), token verification failed, or a Biscuit policy reason. Requests missing any required field return 400.


Protected Resource Metadata

GET /api/v1/mcp/servers/{id}/metadata

Returns OAuth 2.0 Protected Resource Metadata per RFC 9728. MCP clients use this discovery document to determine which authorization server issues tokens for this resource.

Response 200

{
  "resource": "https://mcp.example.com",
  "authorization_servers": [
    "https://api.bulwarkauth.com"
  ],
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["files:read", "files:write"],
  "introspection_endpoint": "https://api.bulwarkauth.com/api/v1/mcp/introspect"
}

This endpoint is unauthenticated and intended for public discovery.