MCP Server (AI Agent Integration)
Overview
Section titled “Overview”Fencer exposes a Model Context Protocol (MCP) server at
https://app.fencer.dev/mcp/ that lets AI agents and LLM-powered tools read your security data.
Through the MCP server, an AI assistant can list your organizations, set an active organization
context, look up assets and members, and list or read vulnerabilities. The server is read-only.
Clients authenticate with OAuth 2.0, a personal API key, or a service account token.
Prerequisites
Section titled “Prerequisites”- A Fencer account with access to at least one organization.
- An MCP-compatible AI client (e.g., Claude Desktop, Cursor, or any tool that supports MCP over HTTP).
- To use a token instead of OAuth: a personal API key, or a service account token with Enable MCP access turned on (see API Access).
Connect your AI client
Section titled “Connect your AI client”Most MCP clients handle the OAuth authentication flow automatically — on first connection you will be redirected to log in to Fencer in your browser and approve access.
Claude Desktop
Section titled “Claude Desktop”Add the Fencer server to your Claude Desktop configuration file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
You can also open it from Claude Desktop via Settings > Developer > Edit Config.
{ "mcpServers": { "Fencer": { "command": "npx", "args": ["-y", "mcp-remote", "https://app.fencer.dev/mcp/"] } }}Restart Claude Desktop after saving. Requires Node.js 20+.
Claude Code
Section titled “Claude Code”Run a single CLI command:
claude mcp add --transport http Fencer https://app.fencer.dev/mcp/Then type /mcp in chat to authenticate.
Cursor
Section titled “Cursor”Open Settings > Tools & MCP > New MCP Server, or add to .cursor/mcp.json in your project:
{ "mcpServers": { "Fencer": { "type": "http", "url": "https://app.fencer.dev/mcp/" } }}OpenAI Codex
Section titled “OpenAI Codex”Add to ~/.codex/config.toml (global) or .codex/config.toml (project-scoped):
[mcp_servers.fencer]url = "https://app.fencer.dev/mcp/"Opencode
Section titled “Opencode”Add to opencode.json in your project root:
{ "mcp": { "fencer": { "type": "remote", "url": "https://app.fencer.dev/mcp/" } }}How it works
Section titled “How it works”The MCP server implements the MCP 2025-03-26 protocol over
HTTP using JSON-RPC 2.0, and answers as server-sent events when the client asks for
text/event-stream. Clients authenticate using OAuth 2.0 with PKCE, which Fencer supports via
dynamic client registration — meaning your AI tool can register itself automatically without any
manual setup.
Instead of OAuth, a client can send a Fencer personal API key or service account token as the bearer token. A personal API key reaches the organizations it has been granted. A service account token works only when the service account has Enable MCP access turned on, and is always bound to its own organization.
Once authenticated, the client calls tools exposed by the server. The server enforces organization-level access control, the role’s permissions and OAuth scopes on every request, so an agent can only access data the authenticated user is permitted to see.
Usage / Configuration
Section titled “Usage / Configuration”1. Discover the OAuth endpoints
Section titled “1. Discover the OAuth endpoints”Your MCP client starts by fetching the server’s metadata endpoints:
GET /.well-known/oauth-authorization-serverGET /.well-known/oauth-protected-resourceThe authorization server metadata returns the endpoints needed for the OAuth flow:
authorization_endpoint—/oauth/authorize/token_endpoint—/oauth/token/revocation_endpoint—/oauth/revoke_token/registration_endpoint—/oauth/register/
The protected resource metadata identifies the MCP resource (/mcp/) and the supported scopes:
mcp:vulnerabilities:read, cli:api:read and cli:api:write.
2. Register your client dynamically
Section titled “2. Register your client dynamically”MCP clients register themselves via the dynamic client registration endpoint:
POST /oauth/register/Content-Type: application/json
{ "client_name": "My AI Agent", "redirect_uris": ["http://localhost:3000/callback"]}The response includes a client_id you use for the authorization code flow. Clients are registered
as public OAuth clients (no client secret) using the authorization code grant with PKCE (S256)
and refresh tokens.
3. Authorize and obtain a token
Section titled “3. Authorize and obtain a token”Complete the standard OAuth 2.0 authorization code flow with PKCE:
- Redirect the user to the
authorization_endpointwithcode_challenge_method=S256and themcp:vulnerabilities:readscope. - The user logs in and grants access.
- Exchange the authorization code at the
token_endpointfor an access token.
4. Call the MCP endpoint
Section titled “4. Call the MCP endpoint”Send JSON-RPC 2.0 requests to the MCP endpoint:
POST /mcp/Authorization: Bearer <access_token>Content-Type: application/jsonThe server supports these JSON-RPC methods:
| Method | Description |
|---|---|
initialize | Handshake — returns server info, protocol version, capabilities and the flow |
tools/list | Lists all available tools and their input schemas |
tools/call | Invokes a specific tool with arguments |
ping | Returns an empty result |
resources/list, resources/templates/list and prompts/list return empty lists. Notifications
(requests without an id) are acknowledged with HTTP 202.
The server exposes these tools:
| Tool | Description |
|---|---|
organizations.list | Lists the organizations you can access and the active one. Call this first. |
session.set_organization | Sets the active organization for later tool calls. |
vulnerabilities.list | Lists vulnerabilities, with filters, ordering and pagination. |
vulnerabilities.get | Gets one vulnerability by slug, including detail fields such as code and location. |
assets.list | Lists assets, to turn an asset name into the slug vulnerabilities.list filters by. |
members.list | Lists members, to turn a name or email into the id vulnerabilities.list filters by. |
5. Follow the organization contract
Section titled “5. Follow the organization contract”The server enforces a specific workflow for organization-scoped tools. You must:
- Call
organizations.listto discover accessible organizations. - Call
session.set_organizationto set your active organization context. - Then call data tools like
vulnerabilities.listandvulnerabilities.get.
Alternatively, you can pass organization_slug directly as an argument to any organization-scoped
tool to skip step 2. A service account token always uses its own organization.
Examples
Section titled “Examples”Initialize the connection
Section titled “Initialize the connection”{ "jsonrpc": "2.0", "id": 1, "method": "initialize"}List organizations
Section titled “List organizations”{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "organizations.list", "arguments": {} }}Set active organization
Section titled “Set active organization”{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "session.set_organization", "arguments": { "organization_slug": "my-org" } }}Query vulnerabilities
Section titled “Query vulnerabilities”{ "jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": { "name": "vulnerabilities.list", "arguments": { "page": 1, "page_size": 10, "q": "critical" } }}The vulnerabilities.list tool supports the following parameters:
| Parameter | Type | Description |
|---|---|---|
organization_slug | string (optional) | Override the active session organization |
page | integer | Page number (default: 1) |
page_size | integer | Results per page (default: 20) |
q | string (optional) | Search query |
severity | list of strings | Only these severities |
status | string (optional) | Only this status |
category | string (optional) | Only this category |
live | boolean (optional) | Only vulnerabilities that are live in production |
ignored | boolean (optional) | Only ignored (or only not ignored) vulnerabilities |
deferred | boolean (optional) | Only deferred (or only not deferred) vulnerabilities |
has_assignee | boolean (optional) | Only assigned (or only unassigned) vulnerabilities |
in_scope | boolean (optional) | Only in-scope (or only out-of-scope) vulnerabilities |
asset | list of strings | Asset slugs from assets.list, not asset names |
assignee | list of strings | Member ids from members.list; "null" matches unassigned |
priority_level | list of strings | Only these priority levels |
order_by | string (optional) | Sort order |
filters | string (optional) | Advanced filter expression (JSON), e.g. for date ranges |
filter_operator | string (optional) | Operator for combining filters |
Troubleshooting
Section titled “Troubleshooting”401 Unauthorized with resource_metadata header — Your access token is missing or expired. The
response includes a WWW-Authenticate header pointing to the
/.well-known/oauth-protected-resource endpoint. Re-authenticate using the OAuth flow.
“Organization not found” or “Forbidden” errors — The organization_slug you provided doesn’t
exist or your account doesn’t have access. Call organizations.list first to see which
organizations are available.
“Call organizations.list then session.set_organization, or pass organization_slug.” — No
organization is set for the session. Set one, or pass organization_slug.
“Missing required scope” error — Your OAuth token doesn’t include the required scope for the
tool. vulnerabilities.list, vulnerabilities.get, assets.list and members.list require the
mcp:vulnerabilities:read scope (cli:api:read or cli:api:write also work). Request the scope
during authorization.
“MCP access is not enabled for this service account.” — Turn on Enable MCP access for the service account, or use a different token.
Rate limiting — The MCP endpoint enforces per-user rate limits (120 requests per minute by default). If you receive a 429 response, reduce your request frequency.