Skip to content

ll-lang MCP Server

lllc mcp starts a stdio Model Context Protocol server for ll-lang tooling.

Current implementation is self-hosted: the F# CLI command forwards to lllcself/src/Main.lll (mcp subcommand), and the MCP handlers live in lllcself/src/Mcp.lll.

lllc mcp

The process blocks until stdin closes.


Setup

Add to your MCP config (for example ~/.config/claude/mcp.json or project .mcp.json):

{
  "mcpServers": {
    "ll-lang": {
      "command": "lllc",
      "args": ["mcp"]
    }
  }
}

If lllc is not on $PATH, use an absolute path to the binary.


Protocol calls

The server handles these JSON-RPC methods:

  • initialize
  • tools/list
  • tools/call

initialize response:

{
  "protocolVersion": "2024-11-05",
  "capabilities": { "tools": {} },
  "serverInfo": { "name": "lllcself", "version": "1.0.0" }
}

Available tools (30)

Core compile/check: - compile_source, check_source, compile_file, check_file

Diagnostics and repair: - diagnose_source, diagnose_file, explain_error, fix_suggest, apply_fix_preview

Formatting and AST/introspection: - format_source, format_file, parse_source, typed_ast

Project-level: - project_graph, check_project, build_project

Symbol navigation: - symbols, definition, references

Dependency helpers: - mod_add, mod_tidy, mod_why

FFI helpers: - ffi_inspect, ffi_validate

Test helpers: - test_list, test_run (structured self-host suite over tools/check-selfhost-ci.sh)

Catalog/meta: - stdlib_search, list_errors, lookup_error, list_targets


Tool reference

compile_source

Input:

{ "source": "module M\nmain = 1" }

Current response shape:

{ "ok": true, "errors": [], "target": "fsharp", "fs": "module M\n..." }

Note: on compile failure, this tool still returns "ok": true; diagnostic text currently appears in "fs" (current self-hosted behavior).

check_source

Input:

{ "source": "module M\nmain = 1" }

Current response shape:

{ "ok": true, "result": "{\"ok\":true,\"stage\":\"ok\",\"primary_error\":\"\",\"secondary_count\":0}" }

result is a JSON string payload (not a nested object).

compile_file

Input:

{ "path": "/absolute/path/to/file.lll" }

Current response shape:

{ "ok": true, "errors": [], "target": "fsharp", "fs": "module ..." }

Missing-file response:

{ "ok": false, "errors": [{ "code": "E000", "message": "File not found: ..." }] }

Use absolute paths. Relative paths are resolved from the temporary self-host run directory and usually fail.

check_file

Input:

{ "path": "/absolute/path/to/file.lll" }

Current response shape:

{ "ok": true, "result": "{\"ok\":true,\"stage\":\"ok\",\"primary_error\":\"\",\"secondary_count\":0}" }

Missing-file response matches compile_file (E000).

Input:

{ "query": "list" }

Current response shape:

{
  "tools": [
    { "name": "listMap", "signature": "(A -> B) -> List[A] -> List[B]", "module": "Std.List", "scope": "stdlib" }
  ]
}

list_errors

Input:

{}

Current response shape:

[
  { "code": "E001", "name": "TypeMismatch", "description": "Expected type A got type B" }
]

lookup_error

Input:

{ "code": "E003" }

Current response shape:

{ "found": true, "code": "E003", "name": "NonExhaustiveMatch", "description": "Pattern match missing cases" }

list_targets

Input:

{}

Current response shape:

[
  { "id": "fs", "name": "FSharp", "status": "stable", "extension": ".fs", "description": "F# source files; default target" },
  { "id": "llvm", "name": "LLVM", "status": "experimental", "extension": ".ll", "description": "LLVM IR; subset backend — ADT support partial" }
]

diagnose_source / diagnose_file

Structured diagnostics for repair loops:

{
  "ok": false,
  "stage": "elaborator",
  "diagnostics": [
    {
      "code": "E002",
      "message": "Unbound variable: undefined",
      "line": 0,
      "col": 0,
      "endLine": 0,
      "endCol": 0,
      "severity": "error",
      "hint": ""
    }
  ]
}

format_source / format_file

Canonical formatting (newline normalization + trailing-space cleanup):

{
  "ok": true,
  "changed": true,
  "check_only": true,
  "formatted": "module M\nmain = 1\n"
}

parse_source / typed_ast

parse_source returns AST summary (module, decl_count, decls[]). typed_ast returns typed summary baseline (typed_decls[]) and diagnostics.

project_graph / check_project / build_project

  • project_graph: modules, import edges, topo order, loader errors.
  • check_project: per-file check diagnostics.
  • build_project: per-file build summary and output previews.

symbols / definition / references

Top-level symbol discovery and textual reference lookups over source/file input. When root is provided, these tools can resolve over project files (src/**/*.lll) and return path-aware locations.

explain_error / fix_suggest / apply_fix_preview

  • explain_error: code + cause + hints.
  • fix_suggest: candidate fix IDs.
  • apply_fix_preview: returns patched source + preview diff.

mod_add / mod_tidy / mod_why

  • mod_add: writes dependency line into lll.toml.
  • mod_tidy: self-hosted baseline no-op (structured response).
  • mod_why: reports manifest presence + direct importers.

ffi_inspect / ffi_validate

ffi_inspect returns current FFI surface (external / opaque) for source, file, or project root.

ffi_validate runs FFI-focused checks and returns:

  • compiler_diagnostics (from checkCompact)
  • ffi_diagnostics (FFI-only warnings like duplicates/unused externals)

Example call:

{
  "name": "ffi_inspect",
  "arguments": {
    "path": "/abs/path/spec/examples/valid/23-external-opaque.lll"
  }
}

FFI on LLL path only

Canonical policy:

  • FFI feature logic must live in .lll modules (stdlib/, lllcself/).
  • Archived stage0 under obsolete/stage0 is bootstrap-diagnostics-only.
  • New FFI behavior should be exposed/verified through self-hosted MCP and self-hosted CLI flows.

test_list / test_run

test_list returns self-host check-suite entries:

{
  "ok": true,
  "supported": true,
  "execution_mode": "selfhost",
  "total": 45,
  "tests": [
    { "name": "lllcself/src/Main.lll" }
  ],
  "timed_out": false,
  "exit_code": 0
}

test_run returns structured run summary:

{
  "ok": true,
  "supported": true,
  "execution_mode": "selfhost",
  "total": 45,
  "passed": 45,
  "failed": 0,
  "skipped": 0,
  "tests": [
    { "name": "lllcself/src/Main.lll" }
  ],
  "timed_out": false,
  "exit_code": 0
}

test_run delegates to tools/check-selfhost-ci.sh in the provided root directory and uses that script's exit code as pass/fail truth.


Smoke test

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
  '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"check_source","arguments":{"source":"module M\nmain = 1"}}}' \
  | lllc mcp