Skip to main content

Auto-generation of MCP tools

When you create a function with define(), it is automatically exposed as an MCP (Model Context Protocol) tool. Your AI agents can discover and invoke your function as a tool without additional configuration.

Example

For this function:
index.ts
import { define, z } from "@jelou/functions";

export default define({
  name: "query-balance",
  description: "Queries a customer's balance by phone number",
  input: z.object({
    phone: z.string().min(10).describe("Phone number with country code"),
  }),
  output: z.object({
    name: z.string(),
    balance: z.number(),
    currency: z.string(),
  }),
  handler: async (input, ctx) => {
    ctx.log("Querying balance", { phone: input.phone });
    return { name: "Maria Garcia", balance: 150.00, currency: "USD" };
  },
});
The /mcp endpoint exposes this schema:
{
  "name": "query-balance",
  "description": "Queries a customer's balance by phone number",
  "inputSchema": {
    "type": "object",
    "properties": {
      "phone": {
        "type": "string",
        "minLength": 10,
        "description": "Phone number with country code"
      }
    },
    "required": ["phone"]
  }
}

The description field: the most important thing for MCP

The description field of your define() is what the AI agent reads to decide when to invoke your tool. If the description is vague, the agent will not know when to use it — or worse, will use it at the wrong time.
❌ Vague✅ SpecificWhy it matters
"Handles users""Looks up a user by email or phone and returns their profile with balance"The agent knows exactly what data it can get
"Sends message""Sends a WhatsApp message to a number with country code (e.g.: 593…)"The agent knows the channel and expected format
"Queries API""Queries the status of a shipment by tracking number in the Servientrega API"The agent knows the provider and what data it needs

.describe() annotations on fields

Zod .describe() annotations become parameter descriptions in the MCP tool. This is what AI agents see when they discover your function:
input: z.object({
  phone: z.string().min(10).describe("Number with country code, e.g.: 593987654321"),
  type: z.enum(["prepaid", "postpaid"]).describe("Customer's plan type"),
  includeHistory: z.boolean().default(false).describe("Whether to include the last 5 transactions"),
})
Write clear and specific descriptions in .describe(). AI agents use these descriptions to decide what values to pass to your function.

Testing the MCP endpoint

Start the local server with jelou dev and query the MCP schema:
curl http://localhost:3000/mcp
Invoke the function directly:
curl -X POST http://localhost:3000 \
  -H "Content-Type: application/json" \
  -d '{"phone": "593987654321"}'
In production:
curl https://query-balance.fn.jelou.ai/mcp

Disabling MCP

If your function should not be discoverable as a tool (for example, a webhook that only receives callbacks), disable MCP:
config: { mcp: false }
When MCP is disabled, the /mcp endpoint returns 404.

How agents use it

When you configure an AI agent in Jelou Brain Studio and assign functions as tools, the agent:
  1. Discovers available tools via the /mcp endpoint
  2. Reads the name, description, and input schema
  3. Decides when to invoke the tool based on the user’s conversation
  4. Sends validated parameters to your function
  5. Receives the response and incorporates it into the conversation
All of this happens automatically — you only need to write the function with define() and assign it to the agent.

Multi-tool MCP

When you use app(), a single MCP server at /mcp automatically registers all tools. Each tool appears as an independent tool with its name, description, and schema. To exclude a specific tool from the MCP registry, use mcp: false in its config:
import { app, define, z } from "@jelou/functions";

export default app({
  tools: {
    queryBalance: define({
      description: "Queries a customer's balance",
      input: z.object({ phone: z.string().min(10) }),
      handler: async (input) => ({ balance: 150.00 }),
    }),
    paymentWebhook: define({
      description: "Receives payment callbacks",
      input: z.object({ transactionId: z.string() }),
      config: { mcp: false },
      handler: async (input) => ({ received: true }),
    }),
  },
});
In this example, AI agents discover queryBalance via MCP but paymentWebhook is only accessible via direct HTTP at /payment-webhook.
See the full multi-tool guide for more details about auto-generated routes and config combination.