FsLangMCP — semantic F# for your AI coding agent
Stop your agent grepping F#. Give it the real compiler: cross-project find, a trustworthy check verdict, types, rename, dead-code — over MCP.
Why
Text search misses the semantics F# depends on — partial application, aliased opens, shadowed bindings, cross-project uses, record-field set-sites, CE custom-operations — and over-matches comments and strings. FsLangMCP resolves symbols via the real compiler (FCS in-process + FSAC LSP), so your agent gets trustworthy answers instead of grep noise.
In a live-execution A/B, shaping the tool surface to agent intent cut agents' rg/grep-fallback from 56% → 28% and steps-to-completion from 10.4 → 5.8 (success rate 89% → 100%). Full story: docs/why-agents-grep-fsharp.md.
Quickstart
Prerequisites: .NET SDK 10+ on PATH.
dotnet tool install -g FsLangMcp
fslangmcp --bootstrap-tools # one-time: fetches fsautocomplete + ionide.projinfo.tool
Add to your MCP client config:
{
"mcpServers": {
"fslangmcp": { "command": "fslangmcp" }
}
}
Then call set_project with your .fsproj or .sln path, and start with find or check.
Full setup: docs/getting-started.md · Per-client configs: docs/configuration.md · Runnable examples: examples/.
Changelog
What You Get
35 tools in 8 groups. Full reference: docs/tools-reference.md.
Headline tools
| Tool | What it does |
|---|---|
find |
Multi-project semantic search: definitions, references, record-field set sites, member call-sites, all solution .fsprojs. Beats textual rg on partial application, aliased opens, and cross-project sites. |
check |
One trustworthy verdict (clean/errors/unknown) from a fresh in-process type-check. Never reports a stale-cache false-clean; bare check() suffices. |
Navigate / understand
| Tool | What it does |
|---|---|
set_project |
Initialize or switch FSAC/LSP context. Accepts .fsproj, .sln, .slnx, or directory. Required before LSP-proxy tools. |
project_health |
Fast read-only preflight: options readiness, source files, analyzer setup, test project discovery. No build, no tests. |
fcs_project_outline |
Compact project-wide outline over all compile files. |
fcs_file_outline |
Per-file outline; summaryOnly=true (default) keeps token cost low. |
fsharp_project_inspect |
Read-only .fsproj inspection: compile order, references, signature/implementation pairing. |
fcs_symbol_at_word |
Tolerant symbol lookup by line + word — no exact cursor column needed. |
fcs_get_project_options |
Get compiler OtherOptions for a .fsproj via proj-info. Diagnostic helper. |
Diagnose / fix
| Tool | What it does |
|---|---|
fcs_explain_diagnostic |
Plain-language explanation + repair hints for a compiler diagnostic code (e.g. FS0039). |
fcs_diagnostic_fixes |
Fetch a file's diagnostics and request code-action fixes, grouped per diagnostic. |
fcs_check_compile_order |
Detect when FS0039 is a compile-order problem, not a missing open. |
fcs_suggest_open |
Given an unresolved symbol name, return ranked open directive candidates. |
Refactor preview
| Tool | What it does |
|---|---|
fcs_rename_preview |
Preview a semantic rename's full impact (edits grouped by file, with before/after text) without writing. |
fcs_refactor_impact |
Blast-radius preview: uses, tests, compile order, public API — all orchestrated in one call. |
fcs_make_internal_visible |
Drop private from a declaration at a position; returns a workspace edit, writes nothing. |
fcs_tests_for_symbol |
List the tests that likely cover a symbol (test-file uses + enclosing test name). |
Review / cleanup
| Tool | What it does |
|---|---|
fcs_dead_code |
List likely-unused private/internal bindings as cleanup candidates. |
fcs_review_scan |
Scan source for AST-level review candidates: match wildcards, mutable, blocking calls, casts, reflection. |
fcs_public_api |
Emit the project's full public API surface in stable order — useful for API-diff snapshots. |
fcs_signature_status |
Report .fsi-vs-impl drift: members hidden from signature or stale in signature. |
fcs_create_file_plan |
Plan where a new .fs file belongs in compile order; writes nothing. |
Analyzers
| Tool | What it does |
|---|---|
fcs_analyzer_diagnostics |
Report F# analyzer diagnostics (SARIF-backed, grouped by analyzer and severity). |
fcs_analyzer_setup_preview |
Plan what to add to enable analyzers in a .fsproj; writes nothing. |
NuGet / types
| Tool | What it does |
|---|---|
fcs_nuget_types |
Enumerate types in one referenced assembly (exact SimpleName match, case-insensitive). |
fcs_nuget_members |
Enumerate members of one type from a referenced assembly. |
fcs_referenced_symbols |
Substring search across all referenced assemblies (NuGet + framework). |
Raw LSP proxies
Require set_project. Prefer the semantic tools above for agent flows; these are useful for exact-position editor operations and FSAC debugging.
| Tool | What it does |
|---|---|
textDocument_completion |
Raw LSP completion at an exact position. |
textDocument_formatting |
Raw LSP formatting via Fantomas; returns edits, does not write to disk. |
textDocument_codeAction |
Raw LSP code-action proxy (with empty diagnostic context). |
textDocument_rename |
Raw LSP semantic rename; returns WorkspaceEdit. |
fcs_signature_help |
Exact-position FCS signature help around a call site. |
fsharp_signature_data |
Structured FSAC fsharp/signatureData at an exact call-site position. |
Meta
| Tool | What it does |
|---|---|
fslangmcp_version |
Returns installed version. Zero-arg. Use when filing UX feedback. |
fsharp_runtime_status |
Read-only runtime snapshot: heap sizes, GC counts, FCS cache size, FSAC working set. |
Example Agent Session
1. set_project {"projectPath": "/abs/path/MyApp.sln"}
→ readiness.lsp=true, loadedProjects=[...], fslangmcpVersion="0.12.1"
2. check {}
→ verdict="clean"
3. find {"query": "OrderId", "kind": "definition"}
→ [Domain/Order.fs:12 definition `type OrderId = ...`, ...]
4. fcs_file_outline {"path": "/abs/path/Domain/Order.fs"}
→ module/type headers, memberCounts by kind
5. fcs_rename_preview {"path": "/abs/path/Domain/Order.fs", "line": 12, "character": 5, "newName": "OrderIdentifier"}
→ edits grouped by file, totalEdits=7, crossProject=false
Runtime Options
Command-line args:
--project <path>/-p <path>— pre-load a project on startup--fsac-command <cmd>— override thefsautocompleteexecutable--fsac-args "<args>"— pass extra args to FSAC--bootstrap-tools— install/updatefsautocomplete+ionide.projinfo.tool
Environment fallbacks: FSAC_COMMAND, FSAC_ARGS, FSA_PROJECT_PATH.
Garbage collector
FsLangMCP ships with Workstation GC + Concurrent GC baked in via runtimeconfig.template.json. This is the recommended profile for stdio MCP servers per FsMcp's runtime tuning guide: FCS workloads are bursty (load → idle → next request), and Server GC's "don't release until OS pressure" produces alarming RSS growth on dev laptops. Override if needed:
DOTNET_gcServer=1— opt into Server GC (higher throughput, holds memory longer)DOTNET_GCHeapHardLimitPercent=0xA— cap heap at 10% of RAM (either GC mode)
MCP Stdio Config
Installed tool:
{
"mcpServers": {
"fsharp": {
"command": "fslangmcp",
"args": ["--project", "/absolute/path/to/App.fsproj"]
}
}
}
Local dev (without install):
{
"mcpServers": {
"fsharp": {
"command": "dotnet",
"args": [
"run", "--project", "/path/to/FsLangMcp.fsproj",
"--", "--project", "/absolute/path/to/App.fsproj"
]
}
}
}
Parallel Agent Usage
Pass projectPath explicitly on every FCS tool call — FCS tools cache project-wide results per resolved .fsproj, so agents targeting different projects share no stale caches.
{ "path": "/absolute/path/to/File.fs", "projectPath": "/absolute/path/to/App.fsproj" }
Concurrency limits:
FSLANGMCP_MAX_CONCURRENT_FCS=2- LSP tools are always serialized. FSAC owns one mutable workspace, so LSP concurrency is intentionally not configurable.
Process and RPC timeouts (milliseconds):
FSLANGMCP_LSP_STARTUP_TIMEOUT_MS=60000FSLANGMCP_LSP_REQUEST_TIMEOUT_MS=30000FSLANGMCP_PROJ_INFO_TIMEOUT_MS=120000FSLANGMCP_BOOTSTRAP_TIMEOUT_MS=300000
Response Shape
- Success:
{"status": "ok", ...fields}or{"status": "ok", "result": <payload>} - Not ready:
{"status": "not_ready", "message": "..."}— FSAC workspace still loading - Invalid args:
{"status": "invalid_args", "message": "..."}— required arg blank/missing - Error: MCP protocol error with
{"errorKind": "...", "message": "..."}payload
LSP positions (line, character) are 0-based.
Development Commands
just restore
just check # build + tests
just analyze # F# analyzers via FSharp.Analyzers.Build
Wired analyzers: Ionide.Analyzers, G-Research.FSharp.Analyzers, FSharp.Analyzers.Build.
For local development, restore the local analyzer tool:
dotnet tool restore
Known Issues
- LSP proxy tools return
{"status": "not_ready"}if called beforefsautocompletefinishes loading.set_projectwaits up to 30 seconds for workspace readiness; the preceding RPC handshake has its own configurable 60-second timeout and kills/reset FSAC on expiry. - FCS does not expose cancellation once synchronous project-option evaluation or
GetAllUsesOfAllSymbols()has started.findstill returns at its requested timeout and reuses one in-flight task per project/cache key on retries, but that computation may continue in the background until FCS returns. Restart FsLangMCP to terminate a genuinely stuck FCS operation. - FCS tools fall back to script-style inference (
GetProjectOptionsFromScript) only when no.fsprojis found. Diagnostics and symbol data can be incomplete for multi-project solutions in this mode. project_healthis project-focused — it does not inspect whole solutions or resolve ambiguous directories.
About Tool Dependencies
dotnet tool packages cannot automatically install other global tools during installation. fslangmcp --bootstrap-tools runs dotnet tool update -g (or install if missing) for:
fsautocompleteionide.projinfo.tool
To install them manually instead:
dotnet tool install -g fsautocomplete
dotnet tool install -g ionide.projinfo.tool
No comments yet
Be the first to share your take.