Skip to content
Console →
Website →
Asking an AI? Paste this URL https://keelson.dev/llms.txt

Use your app from AI clients (MCP)

You can make your app an MCP server (use your app from AI clients). MCP (Model Context Protocol) is a protocol that lets AI clients such as Claude and ChatGPT list and call external “tools”. For example, if you give an inventory app a “search items” tool and a “record an arrival” tool, users can operate the app from a conversation with their AI client, within their own permissions.

Your app only provides the tool declarations in the mcp: section of keelson.yaml and one JSON endpoint (handler) per tool. Keelson handles login, the consent screen, and the MCP exchange with the AI client.

  1. The app author declares tools in the mcp: section of keelson.yaml and deploys
  2. A user registers the connection URL (the app URL + /mcp) in their AI client
  3. The first time, the Keelson login and consent screens appear. When the user approves, the AI client is connected as that user
  4. When the AI client calls a tool, Keelson checks membership, app permissions, and IP restrictions, and validates the arguments against the declaration
  5. Keelson calls POST /api/mcp/<tool name> on your app and returns the JSON your app sent back to the AI client as the tool result

Only apps that run server-side code (apps with a command and no assets) can use this. Static sites, SPAs, and hybrid apps cannot declare it. It is available on every plan, with no differences between plans.

Add an mcp: section to keelson.yaml and deploy. The example below declares a read tool, search_items, and a write tool, create_arrival.

slug: stock-desk
runtime: python-slim
command: "python app.py"
db:
mode: libsql
mcp:
enabled: true
instructions: "Look up stock levels and record incoming deliveries."
tools:
- name: search_items
description: "Search stock by item name."
access: read
input:
type: object
properties:
query:
type: string
required: [query]
- name: create_arrival
description: "Record one incoming delivery."
access: write
permission: manage
input:
type: object
properties:
item_id:
type: string
quantity:
type: integer
minimum: 1
required: [item_id, quantity]
KeyMeaning
enabledtrue publishes the tools. false keeps the declaration but stops calls
instructionsA description of the tools as a whole, passed to the AI client (optional)
tools[].nameTool name. Starts with a lowercase letter and uses only lowercase letters, digits, and _. Your app’s endpoint is POST /api/mcp/<name>
tools[].descriptionThe tool description shown to the AI client. The AI reads it to decide when to call the tool
tools[].accessread or write (changes data). See Read and write tools, permissions
tools[].permissionview (default) or manage. A manage tool can be used only by people with manage permission on the app
tools[].inputThe shape of the arguments (JSON Schema 2020-12). The top level is type: object

For the types, limits, and validation of each key, see the keelson.yaml reference.

Put many tools in a separate file (mcp.file)

Section titled “Put many tools in a separate file (mcp.file)”

With mcp.file, you can put instructions and tools in a separate YAML file. keelson.yaml then contains only enabled and file.

slug: stock-desk
runtime: python-slim
command: "python app.py"
db:
mode: libsql
mcp:
enabled: true
file: mcp.yaml

mcp.yaml in the same folder as keelson.yaml:

instructions: "Look up stock levels and record incoming deliveries."
tools:
- name: search_items
description: "Search stock by item name."
access: read
input:
type: object
properties:
query:
type: string
required: [query]
- name: create_arrival
description: "Record one incoming delivery."
access: write
permission: manage
input:
type: object
properties:
item_id:
type: string
quantity:
type: integer
minimum: 1
required: [item_id, quantity]
  • When you deploy, the Keelson CLI inserts the contents of mcp.yaml into keelson.yaml before validating and uploading it. Your local files are not rewritten
  • Only instructions and tools can go in the separate file. mcp.file cannot be combined with mcp.instructions / mcp.tools
  • Only newer versions of the Keelson CLI read mcp.file. Older versions reject the deploy because of an unsupported key. If that happens, run keelson upgrade to update the CLI to the latest version

When a tool is called, Keelson sends POST /api/mcp/<tool name> to your app. The path is determined by the tool name and cannot be changed.

The body is the JSON object of arguments the AI client passed ({} when there are none). Only arguments that passed validation against the declared input reach your app.

Only headers that Keelson builds reach your app. No header sent by the AI client is passed through, so the AI client’s access token and forged X-Keelson-* headers never reach your app.

HeaderContent
X-Keelson-User-IdUser ID of the connected user. Use this to identify the user
X-Keelson-User-EmailThe same user’s email address
X-Keelson-User-NameThe same user’s display name
X-Keelson-User-App-PermsThe user’s permissions on the app (view or view,manage)
X-Keelson-Auth-Sourcemcp (indicates a call through MCP)
X-Keelson-Mcp-ToolName of the called tool
X-Keelson-Mcp-ClientIdentifier (URL) of the AI client
X-Request-IdRequest ID. Also shown as request id: in error results

The Authorization header carries a value used inside Keelson. It is not the AI client’s token, and your app does not use it. Use the user information headers the same way as in Use signed-in user information.

Return a response of 2xx with JSON (up to 1 MiB). Returning a JSON object makes the fields easier for the AI client to read.

Your app’s responseResult returned to the AI client
2xx with a JSON bodyThe JSON body as the tool result
2xx with an empty body, a non-JSON body, or a body over 1 MiBError (invalid tool response)
4xxError. The first 4 KiB of the body is shown as is to the user and the AI client
3xx or 5xxError (tool execution failed). The body is not shown
No response within 60 secondsError (unknown whether it ran)
  • The body of a 4xx response is visible to the user and the AI client. Write only explanations that may be shown (for example, “Item A-100 was not found”)
  • Each call has 60 seconds in total. Enqueue slow work as a background task and respond right away
  • Keelson does not retry failed calls
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
@app.post("/api/mcp/search_items")
async def search_items(request: Request):
user_id = request.headers["X-Keelson-User-Id"]
args = await request.json() # e.g. {"query": "bolt"}
items = find_items(owner=user_id, query=args["query"]) # your app's search
return {"items": items}
@app.post("/api/mcp/create_arrival")
async def create_arrival(request: Request):
perms = request.headers.get("X-Keelson-User-App-Perms", "").split(",")
if "manage" not in perms:
return JSONResponse({"error": "You do not have permission to record arrivals"}, status_code=403)
args = await request.json()
if not item_exists(args["item_id"]):
# The body of a 4xx response is visible to the user
return JSONResponse({"error": f"Item {args['item_id']} was not found"}, status_code=404)
arrival_id = record_arrival(args["item_id"], args["quantity"])
return {"arrival_id": arrival_id}
app.use(express.json());
app.post("/api/mcp/search_items", (req, res) => {
const userId = req.headers["x-keelson-user-id"];
const items = findItems({ owner: userId, query: req.body.query }); // your app's search
res.json({ items });
});
app.post("/api/mcp/create_arrival", (req, res) => {
const perms = (req.headers["x-keelson-user-app-perms"] ?? "").split(",");
if (!perms.includes("manage")) {
return res.status(403).json({ error: "You do not have permission to record arrivals" });
}
const { item_id, quantity } = req.body;
if (!itemExists(item_id)) {
// The body of a 4xx response is visible to the user
return res.status(404).json({ error: `Item ${item_id} was not found` });
}
res.json({ arrival_id: recordArrival(item_id, quantity) });
});

Keelson does not pass calls to a permission: manage tool from people without manage permission. Checking X-Keelson-User-App-Perms in your app as well, as in the examples above, keeps the tool safe if the declaration changes.

  • Tools with access: write are shown separately on the consent screen as “Write tools (can change data)”. Users can choose “Approve read tools only”. In that case, write tools cannot be used. To use them later, the user connects again and approves them
  • Tools with permission: manage can be used only by people with manage permission on the app. For assigning permissions, see Members and roles
  • Tools a user cannot use do not appear in the list. Tools that need permissions the user lacks, and write tools the user did not approve, are removed from the AI client’s tool list
  • Every call is made as the connected user, within that user’s permissions. Calls from people removed from the workspace or who lost view permission on the app are usually rejected within 2 minutes

Users register the connection URL in their AI client. The connection URL is the app URL followed by /mcp (for example, https://acme--stock-desk.keelson.run/mcp), and it is also shown on the app’s MCP tab in the console. For how to register the connection URL, see the MCP settings documentation of each AI client.

  1. Register the connection URL in the AI client
  2. The first time, the Keelson login screen opens. Log in with an account that can use the app
  3. On the consent screen, check the tools the AI client will use and select Approve
  • Only members who can view the app can connect
  • Only AI clients that identify themselves as claude.ai or chatgpt.com are accepted. Other clients stop at the login step even if the connection URL is registered
  • Access tokens are valid for 1 hour, and the AI client renews them automatically. The renewal token expires after 60 days without use, or at most 180 days after connecting. When it expires, connect again
  • Connecting the same AI client to the same app again disconnects the previous connection
ActionWhoEffect
Stop MCP for the workspace (Settings → Security → MCP (AI clients))Owner / AdminStops calls to every app in the workspace. Connections remain and work again once MCP is allowed
Deploy with mcp.enabled: falsePeople who can deploy the appStops calls to that app. Connections remain and work again after you set it back to true and deploy
Revoke under “Connected AI clients” on the app’s MCP tabOwner / Admin, people with manage permission on the appDisconnects that connection. The AI client immediately loses access, including its renewal token
Revoke under “Your connected AI clients”The user who connectedDisconnects the user’s own connection

Stopping MCP and mcp.enabled: false only stop calls. Only disconnecting (revoking) ends a connection. “Your connected AI clients” is linked from Settings → Security and from the consent screen.

  • For an app with mcp.enabled: true, Keelson responds to /mcp, /.well-known/oauth-protected-resource, and /.well-known/oauth-protected-resource/mcp, and they never reach your app
  • For apps that run server-side code (including the server side of a hybrid app), paths /api/mcp and paths starting with /api/mcp/ return 404 except through MCP, even without an mcp: declaration. Do not put routes other than tool handlers on these paths. Statically served parts are not affected

For the list of reserved paths, see Reserved URL paths in the Keelson Deploy Spec.

ItemLimit
Number of tools40 per app
instructions4096 characters
Tool description1024 characters
Tool input64 KB as JSON, 32 levels of nesting
One call60 seconds
Response body (2xx)1 MiB
Calls60 per minute per connection
Concurrent calls10 per app (per Keelson gateway instance; when the gateway scales out, your app can receive more concurrent calls than this in total)

Calls over the rate or concurrency limit are returned to the AI client with a request to retry after a short wait. Limits do not change by plan. The concurrency limit of 10 is not a ceiling on the concurrent calls your app receives, so do not size database connection pools or similar resources on the assumption of 10.

SymptomWhat to check
A tool does not appear in the listWhether the user has permission on the app (permission: manage tools need manage permission). For a write tool, whether the user approved it on the consent screen. Whether mcp.enabled is true. Whether MCP is stopped for the workspace (shown in the status on the app’s MCP tab)
Calls are rejected in a workspace with IP restrictionsIP restrictions also apply to calls through MCP. Calls from claude.ai or ChatGPT come from each provider’s servers, not from the user’s computer, so they are not allowed as is. Review the access sources in IP restrictions
”Invalid tool response” is shownWhether the handler returns 2xx with a JSON body, and the body is not empty, plain text, or over 1 MiB
Cannot connect after logging inWhether the AI client identifies itself as claude.ai or chatgpt.com. Whether the account you logged in with is a member who can view the app
Deploy rejects file under mcp as an unsupported keyThe Keelson CLI is old. Run keelson upgrade to update to the latest version, then deploy again