# Model Context Protocol (MCP) Deft can expose any of its functions as tools callable by LLM clients — Claude Desktop, VS Code Copilot, Goose, and any other client that speaks the [Model Context Protocol](https://modelcontextprotocol.io/). The integration is the `mcp/*` namespace: build a tool server, add Deft functions to it, and serve over stdio or HTTP. The protocol is JSON-RPC 2.0; you don't have to think about that — `mcp/*` handles it. ## `mcp/*` Stdlib functions | Function | Returns | Purpose | |---|---|---| | `[mcp/create :name]` | `:ok` | Create an empty tool server | | `[mcp/add :name $fn ?description]` | `:ok` | Add a Deft function as a tool | | `[mcp/serve :name]` | blocks | Start stdio server | | `[mcp/http-serve :name $port]` | blocks | Start HTTP server | | `[mcp/dispatch :name $json-string]` | json-string | One-shot dispatch (testing) | ## Minimal Example
 def hello {|name^string|
    @doc "Greet someone by name."
    [str "Hello, $name!"]
}

def add {|a^number b^number|
    @doc "Add two numbers."
    ($a + $b)
}

mcp/create :my-tools
mcp/add :my-tools $hello
mcp/add :my-tools $add

mcp/serve :my-tools                          # blocks, serving over stdio 
Save as `my-tools.dft`. When an MCP client invokes `hello` with `{:name "Alice"}`, the function is called and the result returned to the LLM as JSON. ## How Tools Are Described `mcp/add` reads: - The function's **name** (the `def` name). - The **description** from `@doc` if you don't pass one explicitly. - The **parameter list** from the function signature — names and type hints become the JSON schema the LLM sees. So a well-typed function with a clear `@doc` becomes a well-described MCP tool with no extra boilerplate:
 def search-codebase {|query^string max-results^number|
    @doc "Search the codebase for a text query. Returns up to max-results matches."
    # ... implementation ...
    @{}
}

mcp/add :my-tools $search-codebase 
The LLM sees a tool named `search-codebase` with parameters `query:string` and `max-results:number` and your description as the prompt hint. ## Stdio Transport `mcp/serve` runs the server over stdin/stdout, blocking forever. This is what Claude Desktop and most editor integrations expect:
 mcp/serve :my-tools 
### Claude Desktop config In `claude_desktop_config.json`: ```json { "mcpServers": { "my-tools": { "command": "/path/to/deft", "args": ["/abs/path/to/my-tools.dft"] } } } ``` ### VS Code config In `.vscode/mcp.json`: ```json { "servers": { "my-tools": { "type": "stdio", "command": "/path/to/deft", "args": ["my-tools.dft"] } } } ``` ## HTTP Transport `mcp/http-serve` runs an HTTP listener on the given port. Useful when you want multiple concurrent clients or a long-running service:
 mcp/http-serve :my-tools 3001 
In `.vscode/mcp.json`: ```json { "servers": { "my-tools": { "type": "http", "url": "http://localhost:3001/mcp" } } } ``` ### Stdio vs HTTP — when to use which | Aspect | Stdio | HTTP | |---|---|---| | Clients per process | One | Many | | Lifecycle | Spawned by client | Long-running service | | Setup complexity | Simplest | Slightly more | | Latency | Fresh process each start | Warm process | For local editor integration, **stdio is the default** — it's the lowest-friction option and matches what every MCP client supports. Reach for HTTP when you want a shared service across multiple clients or a long-lived background process. ## Tool Namespacing For tools that share a prefix, name functions with `::` to get dotted namespace separators in the tool name:
 def file-ops::read   {|path^string| ... }
def file-ops::write  {|path^string content^string| ... }
def file-ops::list   {|dir^string| ... }

mcp/add :fs-tools $file-ops::read
mcp/add :fs-tools $file-ops::write
mcp/add :fs-tools $file-ops::list 
The LLM sees tools named `file-ops.read`, `file-ops.write`, `file-ops.list`. ## Best Practices 1. **Document every tool.** The LLM only knows what the description tells it. A function without `@doc` shows up with no hint about its purpose. 2. **Type your parameters.** Type hints become the JSON schema. An untyped parameter shows up as `any`, which the LLM may fill with garbage. 3. **Prefer small, composable tools.** Each tool should do one thing. An LLM can chain small tools more reliably than reason about a single monolithic one. 4. **Handle errors gracefully.** Return structured `%Err` maps rather than throwing — thrown errors abort the call and confuse the LLM. 5. **Return JSON-friendly data.** Maps, lists, and scalars serialise cleanly. Tagged maps, sets, and closures don't. ## A Realistic Tool
 def project-stats {|project-path^string|
    @doc {
        :description "Compute statistics for a Deft project: file count, total lines, dependencies."
        :params %{
            :project-path "Absolute path to the project root"
        |
        }
        :returns "Map with :files :lines :deps keys"
    }

    set files [fs/glob "$project-path/**/*.dft"]
    set total 0
    for f in $files {
        set text [fs/read $f]
        set lines $text(lines)
        set total ($total + $lines)
    |
    }

    set deps @{}
    for f in $files {
        set text [fs/read $f]
        for line [split $text "\n"] {
            if [starts-with? [trim $line] "import "] {
                set parts [split $line "\""]
                if ($parts(len) >= 2) { $deps << $parts(nth 1) }
            |
        |
    }
    |

    %{
        :files $files(len)
        :lines $total
        :deps $deps(unique)
    |
    }
}

mcp/create :dev-tools
mcp/add :dev-tools $project-stats
mcp/serve :dev-tools 
## Dispatch For Testing `mcp/dispatch` is a one-shot entry point useful for tests and scripting. Pass a JSON-RPC request string, get back a JSON-RPC response string — without spinning up a server:
 set req [json/stringify %{
    :jsonrpc "2.0"
    :id      1
    :method  "tools/call"
    :params  %{ :name "hello" :arguments %{ :name "Alice" } }
}]

set resp [mcp/dispatch :my-tools $req]
echo $resp 
Useful for unit-testing your tool surface in a `defsuite` without needing a live LLM client. ## What's Already Shipped The TUI's built-in chat app exposes a rich MCP tool server itself — see `packages/stdext/src/mcp_tools.dft` for examples including: - `help.lookup` — fetch docs by name - `list-stdext` — enumerate the stdext package (legacy alias: `list-stdlib`) - `grep-codebase` / `search-codebase` — search source files - `git-log` / `git-diff` — git operations - `find-files` — locate files by pattern - `find-references` — symbol cross-reference - `run-tests` — invoke the test runner - `eval-dft` — evaluate a Deft snippet in a sandbox - `web-fetch` — fetch a URL - `write-patch` — apply code changes These are good references for what a useful MCP tool looks like. ## Quick Reference | Form | Purpose | |---|---| | `[mcp/create :name]` | Create a tool server | | `[mcp/add :name $fn ?desc]` | Register a function | | `[mcp/serve :name]` | Stdio server (blocks) | | `[mcp/http-serve :name $port]` | HTTP server (blocks) | | `[mcp/dispatch :name $json]` | One-shot dispatch |