Skip to content

Reconmap MCP Server

Reconmap includes an experimental Model Context Protocol (MCP) server. The MCP server allows AI agents, LLMs, and development environments that support MCP to natively interact with Reconmap using tools and resources.

Purpose

The MCP server acts as an extension (or subset) of the core Reconmap API, providing standard MCP tooling that exposes Reconmap's features to language models.

Currently, the server exposes limited tools such as:

  • ListProjects: Retrieves a list of active Reconmap projects.
  • GetProjectDetails: Gets the detailed metadata and description of a specific project by its ID.

The server also exposes these Prompts (reusable templates a client can offer to the user). Both take a projectId, require read access to that project, and point the model at the project's resources:

  • triage_findings: prioritise a project's findings, spot duplicates and propose a remediation order.
  • executive_summary: draft a short, non-technical summary of a project's security posture.

Prompts live in Mcp/Prompts/, tools in Mcp/Tools/ and resources in Mcp/Resources/, with one class per entity (ProjectTools, ProjectResources, TaskResources, FindingResources, RunnerResources).

In addition, the server exposes the following Resources (data sources):

  • reconmap://projects
  • reconmap://tasks
  • reconmap://findings
  • reconmap://runners

And these parameterized resource templates ({id} is the UUID of the project or finding):

  • reconmap://projects/{id}: one project.
  • reconmap://projects/{id}/findings: the findings of one project.
  • reconmap://findings/{id}: one finding, with its full details.

Templates check access to the project they belong to, so a user outside the project gets Access denied.. A missing ID fails with Project not found. or Finding not found..

Shared services

The MCP tools and resources do not query the database themselves. They call the same application services as the REST controllers (IProjectService, ITaskService, IFindingService and IRunnerService, in src/server/Api/Application/Services). Filtering and lookup logic lives in those services. When you add a tool or resource, add or extend a service and call it from both the controller and the MCP class. The MCP layer only applies access scoping (McpAccessGuard) and shapes the output.

Requirements

The MCP server is a standard .NET 10 Console application located in the src/server/Mcp directory.

Running with Docker

The MCP server has its own image, ghcr.io/reconmap/mcp, built from src/server/Mcp/docker/Dockerfile (the build context is src/server, because the project references the API project) and pushed by the mcp-build-push GitHub workflow. In Docker Compose it is the mcp service of the app profile:

docker compose --profile app up -d mcp

It listens on port 5520 (http://localhost:5520/mcp) and reuses the API's appsettings.json and the same database, Redis, Keycloak and OPA settings as the API. Settings:

  • MCP_PUBLIC_URL (.env): public URL of the endpoint, passed as Mcp:PublicUrl. Set it when the server is behind a proxy or another hostname. Defaults to http://localhost:5520/mcp.
  • MCP_HEALTH_URL (.env): URL the REST API probes to report MCP status on the System health page and dashboard widget, passed to the API as Mcp:HealthUrl. Defaults to http://mcp:5520/health.
  • Keycloak:RequireHttpsMetadata: the JWT handler requires HTTPS Keycloak metadata outside the Development environment. The Compose service sets this to false because the bundled Keycloak uses plain HTTP. Remove it when Keycloak is served over HTTPS. The REST API honours the same setting.

The server exposes an anonymous GET /health endpoint (ASP.NET Core health checks for the database and Redis) that returns { "status": "Healthy", "checks": [...] }, with HTTP 503 when a check fails.

The container runs as a non-root user. Build or push it by hand with make build-mcp push-mcp in src/server.

Authentication and authorization

Every request to /mcp must carry Authorization: Bearer <token>. Two types of token are accepted, the same ones as the REST API:

  • A Reconmap API token (created from the user's profile). Tokens with the read-only scope are fine here because the server only exposes read operations.
  • A Keycloak access token (JWT) for the Reconmap realm, normally obtained through the OAuth flow described below.

Requests without a valid token get 401 Unauthorized.

OAuth login for MCP clients

The server implements the OAuth discovery part of the MCP authorization specification, with Keycloak as the authorization server:

  1. A client calls /mcp without a token and gets 401 with WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/mcp".
  2. The client reads that document (served by the MCP server), which lists the Keycloak realm (Keycloak:ValidIssuer) as the authorization server.
  3. The client reads the Keycloak metadata, uses its client ID (mcp-client, see below), sends the user to the Keycloak login page (authorization code with PKCE), and receives an access token.
  4. The client calls /mcp again with Authorization: Bearer <access token>.

The MCP Inspector and other OAuth-capable clients do all of this by themselves: just connect without any header and sign in when the browser opens. Users sign in with their normal Reconmap account and get that account's permissions.

Configuration:

  • Keycloak:ValidIssuer must be an issuer URL the client can reach, for example http://localhost:8080/realms/reconmap.
  • Mcp:PublicUrl (optional) is the public URL of the MCP endpoint, for example https://mcp.example.com/mcp. By default it is derived from the request, which is wrong behind a reverse proxy that changes the host or scheme.
  • Mcp:AllowedOrigins lists the browser origins allowed to call the MCP server (CORS). Browser-based clients run the OAuth discovery from the browser, so their origin must be listed. Cors:AllowedOrigins and the MCP Inspector defaults (http://localhost:6274, http://127.0.0.1:6274) are always included.
  • Keycloak rejects anonymous dynamic client registration by default (the Trusted Hosts policy answers Host not trusted), so clients cannot register themselves. Use the pre-registered public client mcp-client instead: in the MCP Inspector open the OAuth settings and set Client ID to mcp-client (no secret). It requires PKCE and allows the redirect URIs of the MCP Inspector (http://localhost:6274 and http://127.0.0.1:6274) VS Code and OpenCode. Add the redirect URI of any other client to it in Keycloak (Clients → mcp-client). The client is part of infra/keycloak/realm.json, which is only imported into a new realm: on an existing installation, create an equivalent public client by hand (standard flow on, PKCE method S256, the redirect URIs above, default client scopes roles, profile and email; some clients such as OpenCode also request offline_access, so keep that scope on the client and give users the offline_access realm role, otherwise the login fails with Offline tokens not allowed for the user or client).

Access follows the same rules as the REST API:

  • Administrators and superusers can read everything.
  • Users and clients only see data in projects they are a member of. Lists of projects, tasks and findings are filtered accordingly, and reading a single project is checked against the OPA policy (policy/authz.rego).
  • Runners are infrastructure data and are only available to administrators and superusers.
  • A denied call fails with Access denied.

The server needs the same configuration as the API (Keycloak, Opa, Redis and database settings in appsettings.json), and browser access is limited to the origins in Cors:AllowedOrigins.

For OpenCode, add to opencode.json, then run opencode mcp auth reconmap (or just start OpenCode and use the server; it opens the browser for the Keycloak login):

{
  "mcp": {
    "reconmap": {
      "type": "remote",
      "url": "http://localhost:5000/mcp",
      "oauth": { "clientId": "mcp-client" }
    }
  }
}

If your client does not support OAuth, send an API token instead. With the MCP Inspector, add a custom Authorization header with the value Bearer <your-api-token>. AI clients usually accept a header, for example:

{
  "mcpServers": {
    "reconmap": {
      "url": "http://localhost:5000/mcp",
      "headers": { "Authorization": "Bearer <your-api-token>" }
    }
  }
}

How to use it

The MCP Server has been configured to use the Streamable HTTP (SSE) transport instead of stdio to completely bypass any MSBuild or console output issues. It runs as a standalone ASP.NET Core web application.

Running locally for Development

  1. Ensure you have the .NET 10 SDK installed.
  2. Build and run the project:
cd src/server
dotnet run --project Mcp/Mcp.csproj

By default, the server will start on port 5000 (e.g. http://localhost:5000).

Using the MCP Inspector

The MCP Inspector is an official interactive developer tool that allows you to test and debug your MCP server locally without needing to connect it to a full AI client.

To launch the MCP Inspector and connect it to your running Reconmap MCP Server, execute this command from a separate terminal:

npx @modelcontextprotocol/inspector http://localhost:5000/mcp

The Inspector detects the transport from the URL (/mcp means Streamable HTTP). Do not put a transport name such as sse before the URL: the Inspector treats it as a command to spawn, which fails with spawn sse ENOENT. If you prefer the web UI, run npx @modelcontextprotocol/inspector with no arguments, choose Streamable HTTP as the transport, and enter http://localhost:5000/mcp.

This will start a local web interface where you can: - View all available Resources (like reconmap://projects). - See all registered Tools (like ListProjects). - Execute tools interactively and see their JSON responses.

Connecting an AI Client

To connect an AI assistant (such as Claude Desktop or VS Code Copilot) to the Reconmap MCP Server, you need to configure it to connect via Server-Sent Events (SSE) to the HTTP endpoint.

Example mcp.json (or claude_desktop_config.json) configuration:

{
  "mcpServers": {
    "ReconmapMCP": {
      "command": "node",
      "args": [
        "path/to/some-sse-proxy.js", 
        "http://localhost:5000/mcp"
      ]
    }
  }
}

Note: Some clients natively support SSE URLs directly. If your client requires a command, you might need a simple proxy script. Please check your specific AI client's documentation on how to configure SSE endpoints.