qonto-assistant/tests/test_mcp_auth.py
tegwick d2ffd372b5 QONTO-WP-0003-T03: MCP client auth + shared multi-harness config snippet
Gate /mcp with a shared-secret bearer token (QONTO_ASSISTANT_MCP_TOKEN,
mcp_auth.py::BearerTokenAuthMiddleware, constant-time compare, REST
untouched) since no OIDC issuer exists in this fleet yet -- pointing
FastMCP's OAuth Protected Resource flow at a non-existent issuer would be
worse than not having it. This token is a service credential, never a bank
credential; per-actor identity stays the existing X-Actor-* convention.

Add docs/mcp-integration.md: tool catalog, the two-layer auth model (workload
auth today vs. deferred OIDC target), and one shared {"mcpServers": {...}}
client config snippet (url + headers) usable across Claude Code, Claude
Desktop, Cursor, and Codex/Grok-style harnesses.

Verified live using only that snippet: unauthenticated and wrong-token
requests get 401 before reaching any tool; a request built from the
snippet's URL + headers lists tools and calls qonto_org_summary
successfully against the fixture-backed server.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 09:41:25 +02:00

35 lines
1.1 KiB
Python

from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from starlette.testclient import TestClient
from qonto_assistant.mcp_auth import BearerTokenAuthMiddleware
def _protected_app() -> Starlette:
async def ok(request):
return PlainTextResponse("ok")
app = Starlette(routes=[Route("/", ok)])
app.add_middleware(BearerTokenAuthMiddleware, token="secret-token")
return app
def test_bearer_auth_rejects_missing_header() -> None:
client = TestClient(_protected_app())
response = client.get("/")
assert response.status_code == 401
assert response.json()["error_code"] == "unauthorized"
def test_bearer_auth_rejects_wrong_token() -> None:
client = TestClient(_protected_app())
response = client.get("/", headers={"Authorization": "Bearer wrong-token"})
assert response.status_code == 401
def test_bearer_auth_accepts_matching_token() -> None:
client = TestClient(_protected_app())
response = client.get("/", headers={"Authorization": "Bearer secret-token"})
assert response.status_code == 200
assert response.text == "ok"