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 reference
  • labels — guard/policy signals (destructive, net:w)
  • bind — pre-fill parameters (see tool-reshaping)
  • expose — limit visible parameters (see tool-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.