Skip to content
Charming Docs
Esc
navigateopen⌘Jpreview
On this page

Connect one app over MCP

Give a single app its own MCP URL. The client sees the app's declared routes as tools and calls them by name.

What you can do

Point an MCP client at one app. Every app you can open has an address on the MCP host: https://mcp.charm.ing/<owner>/<app-name>. A client connected there sees that app alone, with each declared route as a tool named after its operation. The client calls addPlant({ bed: "north", plant: "basil" }) with no app id and no operation selector.

Keep the shared server for everything else. https://charm.ing/mcp still reaches every app you own, creates apps, and edits code. The per-app URL is the same access at one app’s address, for a client that should work with that app and nothing more.

Turn it on for yourself. Turn Apps as MCP servers on under Features in your account settings; the MCP section of each app’s settings then shows the app’s URL with a copy button.

Technical

The MCP section of an app’s settings shows the URL to paste into a client. The client authenticates with Charming’s own sign-in, the same flow the shared server uses.

Copy this prompt for your agent

Connect to my Charming app over MCP. Read
https://charm.ing/docs/guides/connect-an-app-over-mcp.md first. I will
give you the app's URL on mcp.charm.ing; add it as an MCP server, sign
in when asked, then list its tools and tell me what the app can do.

What the client needs

The host serves MCP protocol revision 2026-07-28 and later. A client that sends the 2025 handshake receives an error naming the supported revisions; the official TypeScript client negotiates the newer revision with versionNegotiation: { mode: 'auto' }.

Sign-in follows the standard MCP authorization flow. An unauthenticated request receives a 401 whose WWW-Authenticate header names the app’s protected-resource document at https://mcp.charm.ing/.well-known/oauth-protected-resource/<owner>/<app-name>; that document names https://charm.ing as the authorization server.

What the client receives

A connected client acts with your account’s access to Charming, the same access the shared server grants. The per-app URL narrows what the client sees, not what your account can do. Connect only clients you trust with your account.

Within the app, your access decides what the tools do. Read tools run for anyone who can open the app. Write tools need run access; a viewer who calls one receives a forbidden error and nothing changes.

Which routes become tools

Every route the app’s active revision declares in export const routes becomes a tool, except a route with public: false. Operations an app handles only inside default.fetch never appear; an app with no declared routes connects and lists no tools, and its author can add routes to change that.

  • The tool keeps the operation’s name when that name is a valid MCP tool name (A-Z, a-z, 0-9, _, ., -, up to 128 characters). Any other name is encoded the same way every time. If an encoded name would clash with another tool, the app publishes no tools until its author renames one.
  • The tool’s input schema, output schema, title, description, and examples come from the route. A route whose input is not an object takes its input as { "value": ... }.
  • The route’s annotations become the tool’s readOnlyHint, destructiveHint, idempotentHint, and openWorldHint.

Renames and old URLs

After you rename an app or change your handle, the old URL keeps answering for the same app, and a client connected there keeps working. Settings show the current URL; hand that one to new clients.

Errors

Error Meaning
401 with WWW-Authenticate Sign in first. The header names the app’s resource document.
403 experiment_disabled Apps as MCP servers is off for your account. Turn it on under Features.
404 not_found No app answers at that address for your account.
forbidden (tool result) The tool changes the app’s state and your access to the app is read-only.
invalid_input / invalid_output (tool result) The arguments or the handler’s result did not match the route’s schema.
revision_mismatch (tool result) The app changed between listing its tools and calling one. List the tools again.

Was this page helpful?