mlld speaks MCP in both directions: serve your exe functions as tools for any MCP client, or import tools from external servers as callable functions. Tool collections control what agents see. Reshaping customizes tool interfaces.
MCP
mlld speaks MCP in both directions: serve your functions as tools, or import tools from external servers.
Export — serve functions as MCP tools:
exe @greet(name: string) = js { return "Hello " + name; }
export { @greet }
mlld mcp tools.mld
Any MCP client can now call greet. See mcp-export.
Import — use external MCP tools as functions:
import tools { @echo } from mcp "npx -y @modelcontextprotocol/server-everything"
show @echo("hello")
Imported tool outputs carry src:mcp taint automatically. See mcp-import.
Reading order:
| Want to... | Read |
|---|---|
| Serve functions as tools | mcp-export |
| Import external MCP tools | mcp-import |
| Control what agents see | mcp-tool-gateway, tool-reshaping |
| Secure MCP data flows | mcp-security, mcp-policy, mcp-guards |
| Track tool usage | tool-call-tracking |
| End-to-end example | pattern-guarded-tool-export |
Exporting MCP Tools
Export exe functions, run mlld mcp. Every exported function becomes an MCP tool.
exe @status() = js { return "ok"; }
exe @greet(name: string) = js { return "Hello " + name; }
export { @status, @greet }
mlld mcp tools.mld
Clients see two tools: status (no params) and greet (one string param). Name conversion is automatic — @greetUser becomes greet_user over MCP.
Type annotations generate JSON Schema:
exe @search(query: string, limit: number) = cmd {
gh issue list --search "@query" -L @limit --json number,title
} with { description: "Search issues" }
export { @search }
The with { description } clause populates the tool description. Type annotations (string, number, boolean, object, array) generate the input schema. See exe-metadata.
Serve a directory:
mlld mcp llm/mcp/
mlld mcp "llm/mcp/*.mld.md"
If llm/mcp/ exists, mlld mcp with no arguments serves every module in it.
Environment overrides:
mlld mcp tools.mld --env MLLD_GITHUB_TOKEN=ghp_xxx
Keys must start with MLLD_. Modules read them with import { @MLLD_GITHUB_TOKEN } from @input.
Filter tools:
mlld mcp tools.mld --tools status,greet
Serve reshaped tool collections:
mlld mcp tools.mld --tools-collection @agentTools
Uses bind/expose definitions from a var tools collection instead of raw exports. See mcp-tool-gateway and tool-reshaping.
Client configuration (Claude Code):
{
"mcpServers": {
"my-tools": {
"command": "npx",
"args": ["mlld", "mcp", "tools.mld"]
}
}
}
Security: When tools are called via MCP, inputs carry src:mcp taint. See mcp-security.
Tool Collections
var tools defines a named collection of tools with metadata. Use it to control what an agent sees and attach labels for guards.
exe @readData() = js { return "ok"; }
exe @deleteData() = js { return "deleted"; }
var tools @agentTools = {
safeRead: { mlld: @readData },
dangerousDelete: {
mlld: @deleteData,
labels: ["destructive"],
description: "Deletes records"
}
}
Tool definition fields:
mlld— executable referencelabels— guard/policy signals (destructive,net:w)bind— pre-fill parameters (seetool-reshaping)expose— limit visible parameters (seetool-reshaping)description— override tool description
Scope tools to an agent with env:
env @agent with { tools: @agentTools } [
run cmd { claude -p @task }
]
The agent only sees tools in @agentTools. Guards check @mx.op.labels on each call.
Serve a collection over MCP:
mlld mcp tools.mld --tools-collection @agentTools
The --tools-collection flag serves the reshaped collection instead of raw exports. Bound parameters are hidden; only exposed parameters appear in the tool schema. See mcp-export for basic serving, pattern-guarded-tool-export for a complete example.
Guard on labels:
guard @blockDestructive before op:exe = when [
@mx.op.labels.includes("destructive") => deny "Blocked"
* => allow
]
Labels from the tool definition flow to @mx.op.labels in guard context. See mcp-guards.
Tool Reshaping
Reshape tool interfaces using bind and expose to control what parameters agents see. Used in var tools collections (see mcp-tool-gateway).
bind - Pre-fill parameters:
exe @createIssue(owner: string, repo: string, title: string, body: string) = cmd {
gh issue create -R @owner/@repo -t "@title" -b "@body"
}
var tools @agentTools = {
createIssue: {
mlld: @createIssue,
bind: { owner: "mlld", repo: "infra" }
}
}
The agent sees only title and body. The bound parameters owner and repo are fixed.
expose - Limit visible parameters:
var tools @agentTools = {
createIssue: {
mlld: @createIssue,
bind: { owner: "mlld", repo: "infra" },
expose: ["title", "body"]
}
}
Explicitly list which parameters appear in the tool schema. Parameters not in expose are hidden from the agent.
Default behavior:
Without expose, all parameters except those in bind are visible. Adding expose overrides this - only listed parameters appear.
Variable binding:
Bound values can reference variables:
var @org = "mlld"
var @defaultRepo = "main"
var tools @agentTools = {
createIssue: {
mlld: @createIssue,
bind: { owner: @org, repo: @defaultRepo }
}
}
Variables are resolved when the tool collection is defined, not when called.
Nested objects in bind:
var tools @agentTools = {
configure: {
mlld: @configure,
bind: { config: { timeout: 30, retries: 3 } }
}
}
Complete example:
exe @searchDocs(index: string, query: string, limit: number, format: string) = cmd {
search-tool --index @index -q "@query" -n @limit --format @format
}
var tools @agentTools = {
searchDocs: {
mlld: @searchDocs,
bind: { index: "production", format: "json" },
expose: ["query", "limit"],
description: "Search documentation"
}
}
The agent sees:
query(string, required)limit(number, required)
Hidden from agent:
index(always "production")format(always "json")
Importing MCP Tools
Import tools from an MCP server as callable exe functions. The server spec is a shell command that launches the server.
Selected import:
import tools { @echo } from mcp "npx -y @modelcontextprotocol/server-everything"
show @echo("hello")
Namespace import:
import tools from mcp "npx -y @modelcontextprotocol/server-filesystem /workspace" as @fs
show @fs.listDirectory("/workspace")
Namespace import requires as @alias.
Name conversion is automatic. MCP's list_directory becomes mlld's @listDirectory. The mapping works in both directions.
Security: All MCP tool outputs carry src:mcp taint automatically. See mcp-security for propagation details, mcp-guards for filtering, mcp-policy for flow restrictions.