GenerateSaaS

External Agent (MCP)

Expose your app's AI capabilities to an external agent (the user's own Claude Code or Codex) over an OAuth-authenticated MCP server, with the same capability set the in-app assistant has.

The external agent server turns your app's capability layer into an authenticated Model Context Protocol (MCP) server. An always-on external agent connects to it, signs in once with OAuth, and calls the same capabilities your in-app assistant uses - on that user's behalf, with no app UI open.

The server is off by default: it mounts only when config.mcpServer.enabled is true. With it off, the MCP endpoint and its discovery routes do not exist and no OAuth provider is registered.

Connect an agent

The agent needs one thing - your backend origin plus /api/mcp:

https://your-app.com/api/mcp
Point the agent at the MCP URL. Add it as a remote MCP server in the agent's configuration.
The user signs in to authorize. On first connect the agent runs an OAuth flow: the user approves on your app's sign-in page and the agent receives an access token scoped to them. Your app is the OAuth provider - no third party is involved.
The agent calls your capabilities. Each call runs server-side, scoped to the authenticated user, and is audited like every other capability invocation.

Authentication

Access is gated by OAuth 2.1: your app issues the token, validates it on every request, and ties each call to a user. An unauthenticated, invalid, or expired token is rejected before any tool runs.

Discovery documentPathWhat it tells the agent
Protected-resource metadata/.well-known/oauth-protected-resourceThat the MCP endpoint requires a token, and which authorization server issues it
Authorization-server metadata/.well-known/oauth-authorization-serverWhere to run the OAuth flow (authorize, token, and registration endpoints)

A compliant agent reads these itself and registers dynamically, so you hand-configure no client IDs, endpoints, or app registrations. A fullstack project forwards those two root paths to the co-hosted Hono app; with a standalone backend the agent resolves them against the backend origin directly, so no forwarder routes ship.

What the agent can do

The agent acts on the user's behalf, so it gets the same full capability set the in-app assistant does, automations included.

  • Discover and call. It lists the tools available to it with each input schema, calls any of them, and receives the result as text. It has no shell, file, or network access of its own, and no secret is returned to it.
  • Nothing MCP-specific to write. The agent is served the same capability registry the rest of your AI surface uses, so adding a capability once reaches the in-app assistant, the external agent, and automations at the same time.
  • Web research too. web_search and web_extract appear whenever a vendor key is set and config.ai.webSearch is not false. There is no per-request toggle here, so those two are the whole gate.

Those calls cost money, billed to the token's user. Each search or page read is a credits line item under the ai.mcp feature tag, priced through your pricingConfig.credits markup like every other metered call. An empty balance refuses the call before the vendor is reached, and a project with credits disabled meters nothing. An identical call repeated within 15 minutes bills once, so an agent that retries a call it timed out on cannot charge the user twice. The endpoint's AI rate limiter bounds how fast an agent can drive any of this.

The external agent runs at the chat surface, like the in-app assistant, because both are USER-DRIVEN. Restricting a capability to chat (via surfaces) therefore keeps it available to both while withholding it from unattended automation runs - the loop-guard that stops an automated run from managing automations.

Under the hood

  • Engine: Mastra's MCPServer. It takes your capability toolset (AI SDK tool() objects) as-is and does the tool-to-MCP conversion - tools/list schemas and tools/call argument validation - via @mastra/schema-compat.
  • Transport: the MCP SDK's web-standard streamable HTTP, stateless JSON-response mode. Mastra's own HTTP serving is Node-bound, so the route connects this transport to Mastra's underlying MCP server instead, keeping @repo/api deployable to Node, serverless, and edge-style runtimes alike.
  • The other direction - your agent consuming external MCP servers - uses MCPClient from the same @mastra/mcp package; see Agents.

Enabling it

Set config.mcpServer.enabled to true. That mounts /api/mcp and its OAuth discovery routes, registers your app as the OAuth provider, and shows the MCP settings tab. config.ai.enabled gates none of it.
Apply the database changes for the OAuth provider (the access-token and client tables), then give the agent your MCP URL and let the user authorize it.

config.ai.enabled still matters for part of the surface, because the registry is shared: a capability that runs a model on your server - an automation the agent creates, say - never runs while the AI product is off. Account capabilities work either way; integration capabilities run too, but integrations are connected from the AI-gated Integrations tab.

On this page