Spendesk aims to give finance teams a secure and trusted way to access and use their spend data through AI. Spendesk brings together every aspect of company spend - from cards, expenses, and invoices to procurement, budgets, and travel - and captures the financial context needed to make strategic business decisions.
Its MCP makes that data instantly usable through the AI assistant of their choice: ask questions, find details, build reports, automate recurring workflows, and take action, all with permissioned access that keeps finance data protected. The result is a new kind of finance workflow, built for speed, trust, and everyday decision-making.
This was not Spendesk’s first bet on Platformatic. The engineering team had already shipped the alpha of Spendesk’s Public API using Platformatic’s client generator, now known as Massimo. When building the MCP server, they extended a proven relationship with production-grade tooling, this time with @platformatic/mcp. Along the way, they contributed improvements directly to the open-source packages.
Building without starting over
Financial data integrations demand zero tolerance for error. Spendesk ruled out generic MCP implementations from the start. AI clients must authenticate through the same OAuth system as all other clients, with no parallel authentication stack.
Every tool call enforces the same scopes, roles, and company boundaries as the rest of Spendesk’s platform. The initial release is retrieval-only by design. Assistants can answer questions about payables or suppliers, but cannot approve or pay until the write path earns production trust.
Platformatic’s Fastify-native MCP implementation enabled this without a rebuild. MCP requests connect directly to the same request context as the rest of Spendesk’s stack. The team reused existing OAuth tokens, roles, scopes, and operational tooling. No second authorization system was needed for AI traffic.
This made the MCP server straightforward to build because it fit directly into our existing Fastify architecture. We could reuse Spendesk’s OAuth tokens, request context, scopes, roles, tenant boundaries, and observability instead of building a separate integration layer for AI clients.
Roberto BianchiStaff Software EngineerThe result is a single, consistent pipeline running from API contract to AI tool call:
Tools like get_payables, get_suppliers, and create_purchase_order are registered with a name, description, input schema, and handler, and Platformatic exposes them automatically through the MCP protocol’s tools/list and tools/call.
Because the handlers call straight into Spendesk’s existing services, there’s no shadow version of the business logic living alongside the real one.
Security that doesn’t cut corners
Every tool call must pass strict checks before reaching a handler. Only a valid OAuth token issued to an MCP-configured client is accepted. The token must carry the required scope. For example, payable:read can access get_payables, but anything needing payable:write is not visible.
Spendesk independently checks the authenticated user’s role, so even with the right scope, access can be denied if the role does not permit the action. Company or organization context is derived from the token, never from an argument passed by the AI. All input must pass JSON Schema validation before any handler runs.
That role check was a real gap Platformatic’s authorization hook had to close, it needs to inspect the full authenticated request context, not just the MCP payload, and the hook that makes it possible, canAccessTool, didn’t exist in the package until Spendesk needed it.
It’s one of a small set of features the team built against their own production requirements and then contributed upstream rather than keeping in a private fork: AJV-based JSON Schema validation for strict argument checking, transformRouteSchema for attaching Spendesk’s own OAuth requirements and scopes to the schemas MCP routes generate, and a trio of functions: mcpCallTool, mcpHasTool, mcpListToolNames, that let trusted internal workflows reuse the exact same handler, validation, and authorization pipeline as an external MCP call, without a loopback HTTP request in between.
Every one of those is now available to any team building a production MCP on Platformatic, not just Spendesk.
Type-safe, end-to-end, with Massimo
Each MCP tool is built on a Massimo-generated TypeScript client, produced directly from Spendesk’s OpenAPI specs. Any change to an operation’s name, parameters, or response shape triggers a TypeScript error in development or CI, not a broken tool call in production.
Massimo makes the OpenAPI specification the source of truth for our clients. It generates the TypeScript methods and definitions from the contract, so changes to an operation, parameter, or response shape are detected during development or CI rather than through a failed MCP call in production.
Developers can iterate against a local specification and regenerate from the canonical service-catalogue contract before shipping.
Roberto BianchiStaff Software EngineerMassimo generates clients from a checked-in spec, a local development spec, or the authenticated canonical spec in Spendesk’s service catalogue. Developers can iterate locally and then regenerate against the source of truth before shipping. Spendesk wraps the generated clients in an inter-service client layer, adding request authentication, caching, tracing, and error handling.
This is critical for MCP, since Spendesk’s APIs have large schemas, many optional filters, and varying response shapes. An AI assistant calling a tool does not have a human to catch silent response changes.
The client implementation and its TypeScript definitions are generated together instead of being maintained independently. That reduces duplication and makes changes to large API contracts easier to review and maintain. Massimo gives us a consistent and repeatable way to maintain type-safe clients as our APIs evolve.
Roberto BianchiStaff Software EngineerPerformance under AI-shaped traffic
AI assistants generate bursty, exploratory API traffic, with no human to notice subtle issues. Internal execution through mcpCallTool skips the network hop for trusted workflows. Redis-backed sessions allow a session started on one instance to continue on another, with a controlled memory fallback if Redis is unavailable. This is essential for distributed production setups like Spendesk’s.
The important performance characteristic for us is that MCP calls run within our Fastify architecture, while trusted internal workflows can reuse the same handlers through mcpCallTool without making a loopback HTTP request. Redis-backed sessions also allow the service to run across multiple production instances.
Roberto BianchiStaff Software EngineerBased on real production logs/metrics over a week, performance is typically fast, with long-tailed outliers only for very complex aggregated responses that aren't cached.
For the fastest results, when we hit the Redis cache (that we also use for the MCP sessions), tool calls take a few ms. The fastest tools are simple lookups:
- get_chart_of_accounts
- (~32ms)
- list_companies
- (~37ms)
- get_analytical_field_values
- (~36ms)
- get_analytical_fields
- (~61ms)
- get_cost_centers
- (~63ms)
All well under 100ms.
The verdict
The best thing Platformatic gave us was the ability to introduce MCP without creating a second platform.
We kept Spendesk’s existing authentication, authorization, tenant isolation, service integrations, and operational tooling, while extending the framework where our production requirements exposed gaps. We gained an MCP implementation that fits our architecture rather than forcing us to work around it.
Roberto BianchiStaff Software EngineerWrite-action tools, such as creating or archiving a supplier, are already built and gated behind a security review before production. Once approved, Spendesk’s platform moves from answering questions to enabling agents to take action.