@lelemon-ai/mcp
An MCP server for the Lelemon API v2. It turns every operation of the API into a tool, so Claude, Cursor or any MCP client can manage agents, contacts, cases, messages and more with your API key.
The tools are generated from the API's own OpenAPI contract each time the server starts, so they always match the API you point it at.
Install
You need Node.js 20 or newer and a Lelemon API key. Start with a test key (lel_test_...) until you trust the client.
The server talks MCP over stdio, so you do not run it by hand. Add it to your client with npx -y @lelemon-ai/mcp as the command.
Claude Desktop
Edit claude_desktop_config.json (Settings, Developer, Edit Config):
{
"mcpServers": {
"lelemon": {
"command": "npx",
"args": ["-y", "@lelemon-ai/mcp"],
"env": {
"LELEMON_API_KEY": "lel_test_...",
"LELEMON_API_URL": "https://api.lelemon.cl"
}
}
}
}
Cursor
Edit ~/.cursor/mcp.json (or .cursor/mcp.json in a project) with the same mcpServers block as above.
Claude Code
claude mcp add lelemon \
-e LELEMON_API_KEY=lel_test_... \
-e LELEMON_API_URL=https://api.lelemon.cl \
-- npx -y @lelemon-ai/mcp
Settings
All settings are environment variables of the MCP client entry.
| Variable | Description |
|---|---|
LELEMON_API_KEY | Required. The API key the tools act with. |
LELEMON_API_URL | The API to talk to. Defaults to https://api.lelemon.cl. Set it explicitly when you use a key from another environment. It must be https; http is accepted only for localhost, 127.0.0.1 and [::1]. |
LELEMON_SUBACCOUNT | A sub_... id. Every call acts on that subaccount unless a call passes its own subaccount argument. Only for platform keys. |
LELEMON_MCP_ALLOW | Turns on hidden groups, comma separated. See below. |
LELEMON_MCP_TOOLS | Keeps only these tools, comma separated. See below. |
LELEMON_OPENAPI_FILE | Reads the contract from a file instead of downloading it. The key's scopes are still read from the API. |
What it exposes
Only tools. There are no resources and no prompts: everything the API can do is an operation, and an operation is a tool.
Tool names are the API's operation ids, like agents.list, cases.takeControl or whatsapp.messages.create. Each tool takes the path and query values of its operation as arguments, the JSON request body as body, and an optional subaccount.
Each tool also declares an outputSchema, the top-level properties of the answer the API gives when the call works (type, enum and short description; nested objects are just object, so the list stays small), and returns that answer as structuredContent next to the same JSON as text. Tools whose answer has no body (a delete) have neither. A failed call returns an error, with no structuredContent.
Annotations tell the client what each tool does: readOnlyHint for reads, destructiveHint for deletes and for anything that sends a message or costs money, and openWorldHint for tools that reach people or systems outside Lelemon. Tools marked destructive or money should be confirmed with the person before they run.
Only what your key can do
At start the server asks the API which scopes your key has (GET /v2/me) and lists only the tools your key covers. A key with agents:read and cases:read sees the agent and case reads, not the whole API, which also saves model context.
Filter the list yourself
LELEMON_MCP_TOOLS narrows the list further. Each entry is an operation id or a prefix ending in .*:
LELEMON_MCP_TOOLS=cases.*,agents.list,agents.retrieve
An entry that matches no operation stops the server at start, so typos do not go unnoticed. The filter is a setting of this client, not of the API.
Hidden groups
Some tools are off until you turn them on with LELEMON_MCP_ALLOW. A group hides the operations that change something in it or return a secret. Harmless reads, like apiKeys.list, approvals.list or suppressions.retrieve, are always visible.
| Group | What it hides |
|---|---|
keys | Creating, changing, rotating and deleting API keys, and every tool that returns a secret (signing secrets, webhook secrets, run identities). The model never sees a secret unless you turn this on. |
approvals | Approving, rejecting or asking for review of what an agent wants to do, and the approval floor of actions. |
suppression | Deleting a contact, changing opt-outs and redacting messages. |
LELEMON_MCP_ALLOW=keys,approvals
Uploads and downloads
Operations that only accept multipart/form-data (file uploads) or answer with a binary file cannot be a tool, so they are left out. Operations that accept both multipart and JSON, like creating a knowledge document with text, are exposed through JSON. At start the server prints the left-out operations to stderr with the reason.
Retrying without doing things twice
Every tool that creates something or has a side effect takes an idempotencyKey. Tools that send messages or cost money (effect money) require it: the model chooses a key and remembers it. For the others a key is generated when you leave it out.
Every result and every error of those tools returns the key it used, as its own idempotencyKey: <key> block and inside the error. When a call fails or times out, retry with the same key: if the first call went through, the API answers with the original result instead of doing it again.
Errors
Errors name tools and arguments, never routes or headers.
- Mistakes found before calling the API come back as tool errors with a
code(unknown_argument,missing_argument,invalid_argument), the argument's name, and the list of valid arguments. api_unreachableandtimeout(calls wait 50 seconds, less than the 60 seconds an MCP client waits by default, so the client gets this error and not its own-32001) say whether to retry with the sameidempotencyKey. A client that waits less than 50 seconds gives up first.invalid_responsemeans the API answered with success but the answer is not the JSON object the tool documents. The call went through.- Errors from the API keep its
code,messageandfix, withfix.endpointas a tool name andfix.paramas an argument name.
What it downloads at start
On every start the server downloads GET <LELEMON_API_URL>/v2/openapi.json (15 second limit) and GET <LELEMON_API_URL>/v2/me with your key. If either fails, it exits with code 1 and a message on stderr that says which URL it tried and what to check. When it starts, it prints one line to stderr: name and version, the number of tools exposed, and the subaccount.
It sends your key only to LELEMON_API_URL.
Develop
pnpm --filter @lelemon-ai/mcp typecheck
pnpm --filter @lelemon-ai/mcp test
pnpm --filter @lelemon-ai/mcp build
Tests that read the real contract use apps/docs/api/openapi.v2.json. Point LELEMON_OPENAPI_FILE at another file to test against it.
License
MIT, Lelemon SpA.