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.3.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.3-py3-none-any.whl (42.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: fca_mcp-0.0.3.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.3.tar.gz
Algorithm Hash digest
SHA256 4fc8d9b2fdec946c5f696a387d5103d35d237c73844fae1471e39d1e15db544f
MD5 dc54600d38419cddac461d272ec1049f
BLAKE2b-256 004d0c3d2f4d42291cfe2f845ba325d93e0a1d990cd63ff4956d708da45c32b6

See more details on using hashes here.

File details

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

File metadata

  • Download URL: fca_mcp-0.0.3-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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 b07197c764ed0c4e7a60afc92f954831dd2a446002a02dbd2d28a00cb2c1e5fe
MD5 7892553e68a73f428a387d974fb83298
BLAKE2b-256 f912e183ac2727b796242d9494f50f3e3324b6eaa698a4652d8c3a4dd27e7af0

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