Skip to main content

MCP Server Architecture and Tool Surface

This page is superseded by dev/mcp/api-reference, which is updated first and tracks the latest implementation details.

Source-backed reference for apps/backend/src/mcp/.

This page remains as historical context. For the canonical and continuously maintained source-backed details, use dev/mcp/api-reference.


What the MCP module does

The MCP module exposes FlowPOS use cases through Model Context Protocol with:

  • HTTP entrypoint (POST /mcp) for initialize and tool calls
  • optional SSE stream (GET /mcp) for MCP clients that request streaming
  • session cleanup (DELETE /mcp)
  • dual authentication (V1 API keys + V2 JWT access tokens)
  • per-principal tool filtering and call-time guard enforcement

Key entrypoints:

  • mcp.module.ts
  • interfaces/mcp.controller.ts
  • application/mcp-session.service.ts
  • registry/tool-registry.service.ts
  • application/mcp-tools-initializer.service.ts

Hexagonal mapping

LayerMCP filesResponsibility
Domaindomain/mcp-tool.interface.ts, domain/mcp-principal.interface.tsTool contract, principal contract, security errors
Applicationmcp-session.service.ts, mcp-token.service.ts, mcp-api-key.service.ts, mcp-tools-initializer.service.tsSession lifecycle, token issuance/refresh, key lifecycle, tool wiring
Infrastructuremcp-api-key.repository.ts, mcp-prompts.handler.ts, mcp-resources.handler.tsDB-backed key storage, prompt/resource registration
Interfacesmcp.controller.ts, mcp-token.controller.ts, mcp-api-key.controller.ts, .well-known controller, guardsHTTP transport and authentication adapters

The domain layer defines access semantics; infrastructure implements storage and MCP SDK integration.


Authentication flows

McpAuthGuard is an OR-guard:

  1. McpApiKeyGuard (Authorization: Bearer fp_mcp_...)
  2. fallback McpTokenGuard (Authorization: Bearer <FlowPOS MCP JWT>)

If both fail, MCP returns 401 Authentication required: provide a valid MCP API key or access token.

V1: API keys

Managed via authenticated REST endpoints:

  • POST /mcp/keys
  • GET /mcp/keys?businessId=...
  • PATCH /mcp/keys/:id/revoke
  • DELETE /mcp/keys/:id

rawKey is returned once on create and never recoverable.

V2: token exchange and refresh

  • POST /mcp/token: Firebase ID token -> MCP JWT
  • POST /mcp/token/refresh: refresh MCP JWT (valid and recently-expired tokens supported)

Important behavior from mcp-token.service.ts:

  • default TTL is 86400 seconds
  • refresh grace window is 7 days after expiry
  • memberships are reloaded from DB during refresh
  • default scopes in issued JWT:
    • pos:read
    • pos:write
    • reports:read
    • psa:read
    • pos:intents

Session workflow

The session flow is strict:

  1. First request to POST /mcp must be JSON-RPC initialize without mcp-session-id.
  2. Server returns mcp-session-id response header.
  3. All subsequent POST /mcp, GET /mcp, and DELETE /mcp requests must include:
    • Authorization
    • mcp-session-id

McpSessionService stores session transport + principal in memory.

For OAuth/JWT sessions, principal state is also stored in Redis under mcp:session:{sessionId} with token-aligned TTL. This lets context updates (for example set_active_business) persist across subsequent requests within the same session lifetime.


Tool visibility and enforcement model

Layer 1: session-time visibility (ToolRegistry.listFor)

Tools are hidden when:

  • operatorOnly and principal is not platform_operator
  • multiBusinessOnly and principal has <=1 authorized business
  • tool requires pos:intents and principal is tenant_developer
  • principal lacks required scopes

Layer 2: call-time enforcement (ToolRegistry.dispatch)

Before executing handler:

  • tool existence is validated
  • scopes are revalidated
  • active business is required unless tool is operator-only or marked skipTenantCheck

This dual check prevents accidental exposure from transport-level errors.


Tool catalog (current)

The initializer currently registers 35 tools.

Platform tools (5)

ToolScopesNotes
list_tenantsnoneoperator-only
set_active_tenantnoneoperator-only, skips tenant check
set_active_businessnonemulti-business only, skips tenant check
get_active_businesspos:readactive business context + locations
list_locationspos:readlocation IDs for active business

Domain tools (26)

AreaToolsScopes
Productsget_products, search_productspos:read
Inventoryget_inventory, get_low_stock_alertspos:read
Orderslist_orders, get_orderpos:read
Orders writecreate_order, void_transactionpos:write
Saleslist_sales, get_salepos:read
Purchaseslist_purchases, get_purchasepos:read
Reportsget_sales_report, get_top_products, get_sales_by_locationreports:read
FELget_taxpayer_info, list_credit_notes, get_credit_note, list_debit_notes, get_debit_note, list_cancellations, get_cancellationpos:read
Implementation portallist_implementations, get_implementation, log_hours, get_implementation_statuspsa:read

Intent tools (4)

ToolScopes
summarize_daypos:intents
summarize_periodpos:intents
get_inventory_healthpos:intents
get_client_healthpos:intents, psa:read

Prompts and resources

Session initialization also registers:

  • 8 prompt shortcuts (McpPromptsHandler)
  • 5 resources (McpResourcesHandler: 3 static + 2 templates)

These are capability-level guidance surfaces; they do not bypass tool scope rules.


Operational constraints and pitfalls

  • GET /mcp and DELETE /mcp are authenticated (same guard as POST).
  • Redis stores principal state, not transport state; unknown in-memory session IDs still require re-initialize.
  • Scope changes are reflected only in new sessions because tool visibility is calculated on initialize.
  • In production, MCP_TOKEN_SECRET must be set (module startup fails if default secret is used in production mode).

For production triage, use:

  • dev/runbooks/mcp-session-auth-troubleshooting