App MCP Tools

What It Is

An app can offer some of its routes as tools to MCP clients — Claude, ChatGPT, IDE assistants — and to the tenant's own AI agents. A tool is a route with a description a model can read and a JSON Schema of its arguments; it appears next to the tenant's built-in tools.

"routes": {
  "GET /cases/search": { "handler": "server/cases/search.php", "auth": "role:case_reader,case_writer" }
},
"mcp": {
  "tools": [{
    "name": "find_case",
    "title": "Find a case",
    "description": "Find cases by client name or reference. Use it before opening a new case.",
    "route": "GET /cases/search",
    "inputSchema": {
      "type": "object",
      "properties": { "query": { "type": "string", "minLength": 2 } },
      "required": ["query"],
      "additionalProperties": false
    },
    "annotations": { "readOnlyHint": true }
  }]
}

Why It Matters

The built-in tools let a model read and write records generically. An app tool carries the app's own knowledge — how to find a case, what "escalate" means, which checks come first — in one operation the model can call safely, with the same rules as the app's screens.

How It Works

  • Who sees a tool. A tool is listed only to callers who may open the app and call its route: a role: route shows its tool to those roles only. Superusers see every tool.
  • Who it runs as. The route runs as the caller, with the caller's permissions, exactly as when the app's front-end calls it. Tools cannot use internal routes.
  • Arguments. They are checked against inputSchema before the route runs; a call that does not match fails with the problems listed. The handler receives them as its JSON body ($body), scalar ones also as $query, path parameters of the route as $params, and the call as $tool (name, arguments).
  • The result. The handler's JSON becomes the tool result (structuredContent; a list is wrapped as {"result": […]}). An answer of 400 or more, or a handler error, is a failed call with the message or error id.
  • Names. On the wire a tool is <tenant prefix>_<app>_<tool>, e.g. acme_case_portal_find_case, shortened with a hash beyond 64 characters. A deploy is refused when a name is taken — by a built-in tool or another app's tool.

Writing Good Tools

  • Describe when to use the tool, not only what it does — models choose tools by their descriptions.
  • Keep arguments few and constrained: enum, minLength, additionalProperties: false.
  • Return compact JSON with the fields a model needs to continue; link records by id.
  • Mark read-only tools with "readOnlyHint": true, and tools that delete or send with "destructiveHint": true.

Tool Options

KeyMeaning
nameLowercase letters, digits and underscores (at most 40), unique within the app.
titleA short human-readable name.
descriptionWhat the tool does and when to use it (at most 2000 characters).
routeA route of the app that is not internal. Its path parameters must be required arguments.
inputSchemaA JSON Schema of type object for the arguments.
outputSchemaOptional JSON Schema of the result, for clients that use it.
annotationsHints for clients: readOnlyHint, destructiveHint, idempotentHint, openWorldHint.

Tenant Settings

These keys are not on the Application settings screen; set them in the tenant configuration.

customAppsMcpTools

What it controls:

  • whether apps' mcp.tools are offered by the tenant's MCP server and to its AI agents

Default:

  • true

How to configure it:

  • set it to false to keep MCP clients and agents to the built-in tools

What it affects:

  • app tools are neither listed nor callable while it is off; the app's routes still work for its front-end
  • turning the whole MCP server off is mcpServer