Skip to main content

MCP server for the UK Financial Services (FCA) Register RESTful API

Project description

FCA Register MCP Server

A Model Context Protocol server that exposes the UK FCA Financial Services Register to LLM clients as a set of read-only tools. Built on FastMCP v3 and the fca-api async client.

Overview

  • 25 read-only tools across five domains: firms, individuals, funds, regulated markets, and identifier search.
  • Two transports: HTTP (FastAPI/uvicorn) for remote MCP clients, and stdio for local integrations.
  • OAuth2 via Auth0, with two modes:
    • remote — JWT verification only (the MCP server trusts an upstream Auth0 tenant).
    • proxy — full OAuth proxy with dynamic client registration; tokens are persisted to Azure Blob Storage, encrypted with Fernet.
  • Scope-based authorization: tools tagged fca_api:read require the fca-api:read scope in the access token. Enforcement is per-tool, so initialize and tools/list remain reachable by unauthenticated clients.
  • Structured responses: Pydantic models generated by reflection from fca-api types, stripping internal fields.

Tools

Module Tools
search.py search_frn, search_irn, search_prn
firms.py get_firm, get_firm_names, get_firm_addresses, get_firm_controlled_functions, get_firm_individuals, get_firm_permissions, get_firm_requirements, get_firm_requirement_investment_types, get_firm_regulators, get_firm_passports, get_firm_passport_permissions, get_firm_waivers, get_firm_exclusions, get_firm_disciplinary_history, get_firm_appointed_representatives
individuals.py get_individual, get_individual_controlled_functions, get_individual_disciplinary_history
funds.py get_fund, get_fund_names, get_fund_subfunds
markets.py get_regulated_markets

All tools are decorated with ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True).

Quick start

Prerequisites

  • Python 3.13+
  • PDM for dependency management
  • FCA Register API credentials (FCA_API_USERNAME, FCA_API_KEY)
  • An Auth0 tenant (only the remote mode needs a tenant at minimum; proxy mode additionally needs client credentials and Azure Blob Storage)

Install

pdm install
cp .env.example .env
# edit .env with your credentials

Run the HTTP server

pdm run python -m fca_mcp serve                 # production-like
pdm run python -m fca_mcp serve --reload        # with autoreload

By default the server binds 0.0.0.0:8000. SERVER_BASE_URL must be set (used for OAuth metadata and resource URLs).

Run over stdio

pdm run python -m fca_mcp stdio

stdio mode skips the AuthMiddleware entirely — see server/__init__.py.

Docker

docker-compose up --build

docker-compose.yml launches the server alongside an Azurite emulator. Set FCA_API_USERNAME, FCA_API_KEY, AUTH0_DOMAIN, AUTH0_AUDIENCE, and SERVER_BASE_URL in the environment of the host running docker-compose.

Architecture

LLM client
  │  MCP over HTTP (OAuth2 Bearer) or stdio
  ▼
┌─────────────────────────────────────────────┐
│ FastMCP server (fca_mcp.server.get_server)  │
│ ─────────────────────────────────────────── │
│  Middleware stack (outer → inner):          │
│    ErrorHandlingMiddleware                  │
│    RateLimitingMiddleware                   │
│    LoggingMiddleware                        │
│    AuthMiddleware (restrict_tag FCA_API_RO) │
│ ─────────────────────────────────────────── │
│  Sub-servers (mounted):                     │
│    search · firms · funds ·                 │
│    individuals · markets                    │
│ ─────────────────────────────────────────── │
│  Auth provider:                             │
│    RemoteAuthProvider  (mode=remote)        │
│    Auth0Provider       (mode=proxy)         │
└───────────────────────┬─────────────────────┘
                        ▼
            fca_api.async_api.Client
                        ▼
               FCA Register REST API

Dependency injection

Tools receive the shared fca_api client through FastMCP's Depends chain defined in server/deps.py:

async def get_firm(
    frn: FrnParam,
    fca_client: fca_api.async_api.Client = deps.FcaApiDep,
) -> types.firm.FirmDetails:
    result = await fca_client.get_firm(frn)
    return types.firm.FirmDetails.from_api_t(result)

The client is constructed once in the server lifespan and reused across requests.

Type reflection

Response types are synthesised from fca-api types by reflect_fca_api_t() in server/types/base.py. Fields marked with InternalUrl are stripped. Tools convert raw results with Model.from_api_t(api_result).

Auth: scopes vs tags

Note the hyphen-vs-underscore distinction:

  • Scope (auth/scopes.py): FCA_API_RO = "fca-api:read" — the OAuth scope claimed in access tokens.
  • Tag (auth/tags.py): FCA_API_RO = "fca_api:read" — the tag applied to tool decorators.

AuthMiddleware calls restrict_tag(FCA_API_RO, scopes=[FCA_API_RO]) — any tool tagged with fca_api:read requires the fca-api:read scope to execute. Untagged tools are allowed through without scope checks. On stdio, the middleware short-circuits.

Configuration

All settings are loaded from environment variables via Pydantic-Settings. See SETTINGS.md for the full reference and .env.example for a working template.

The most important variables:

Variable Required Purpose
FCA_API_USERNAME / FCA_API_KEY Yes FCA Register API credentials
AUTH0_MODE No (remote default) remote for JWT-only, proxy for OAuth proxy
AUTH0_DOMAIN / AUTH0_AUDIENCE Yes Auth0 tenant identifiers
AUTH0_CLIENT_ID / AUTH0_CLIENT_SECRET / AUTH0_JWT_SIGNING_KEY / AUTH0_STORAGE_ENCRYPTION_KEY proxy mode only OAuth proxy secrets
AUTH0_INTERACTIVE_CLIENT_ID No When set, exposes the interactive /interactive web UI
AZURE_CREDENTIAL proxy mode none (connection string / Azurite) or default (DefaultAzureCredential)
AZURE_STORAGE_CONNECTION_STRING When AZURE_CREDENTIAL=none Connection string for Azurite or an Azure Storage account
AZURE_STORAGE_ACCOUNT When AZURE_CREDENTIAL=default Storage account name
SERVER_BASE_URL Yes Public base URL used in OAuth resource metadata

Health check

The HTTP app exposes GET /.container/health, returning service name, version, uptime, and timestamp. The Dockerfile HEALTHCHECK polls this endpoint.

Development

pdm run pytest                                      # all tests with coverage
pdm run pytest tests/server/test_firm_simple.py     # single file
pdm run pytest tests/server/test_firm_simple.py::test_get_firm -v

pdm run ruff check                                  # lint
pdm run ruff check --fix                            # auto-fix
pdm run ruff format                                 # format

Tests use FastMCP's in-memory FastMCPTransport — no HTTP server or live Auth0/Azure is required. Fixtures in tests/server/conftest.py replace the fca-api client with a record/replay mock and substitute a synthetic AccessToken so the auth middleware runs without live tokens. To test scope denial, override the oauth_scopes fixture to return [].

Code style

  • Line length: 120
  • Ruff rules: A, B, C, E, F, I, W, N, C4, T20, PTH
  • Python 3.13+

Project layout

src/fca_mcp/
├── __main__.py              # python -m fca_mcp → CLI
├── cli.py                   # typer CLI (serve / stdio)
├── settings.py              # Pydantic-Settings config
├── logging.py               # dictConfig-based logging setup
├── uvcorn_app.py            # Starlette/uvicorn HTTP app factory
├── azure/                   # Azure Blob key-value store + client factory
├── http/                    # Interactive OAuth UI routes + static assets
└── server/
    ├── __init__.py          # get_server(): mounts sub-servers + middleware
    ├── app.py               # FcaApp lifespan container
    ├── deps.py              # FastMCP Depends wiring
    ├── search.py            # FRN/IRN/PRN identifier search
    ├── firms.py             # 15 firm tools
    ├── funds.py             # 3 fund tools
    ├── individuals.py       # 3 individual tools
    ├── markets.py           # get_regulated_markets
    ├── auth/                # Auth provider, scopes, tags
    └── types/               # Reflected Pydantic models

License

MIT — see LICENSE.

Related

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fca_mcp-0.0.2.tar.gz (34.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fca_mcp-0.0.2-py3-none-any.whl (42.5 kB view details)

Uploaded Python 3

File details

Details for the file fca_mcp-0.0.2.tar.gz.

File metadata

  • Download URL: fca_mcp-0.0.2.tar.gz
  • Upload date:
  • Size: 34.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: pdm/2.26.8 CPython/3.13.13 Linux/6.17.0-1010-azure

File hashes

Hashes for fca_mcp-0.0.2.tar.gz
Algorithm Hash digest
SHA256 d614554085eb7420215f77a11cdca5eb71218be702cbb62a6c161d9618fe3464
MD5 db36c36baf3e7414b8b24c1599983a07
BLAKE2b-256 9bc445681df3489c6bc5f582764d6451388b9c4d00faaf195b447964f48bf9b3

See more details on using hashes here.

File details

Details for the file fca_mcp-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: fca_mcp-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 42.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: pdm/2.26.8 CPython/3.13.13 Linux/6.17.0-1010-azure

File hashes

Hashes for fca_mcp-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 1aa9ccfc66051202f40c802d4aad9424a38bf93be5fb716fa9260b6c1374ea5f
MD5 90b596b87e07da673836dbc3971e0df1
BLAKE2b-256 f40e506843f7e1ffd8864a659b843bc4d19871fe8c28d6102ee9689b69eac362

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page