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.
Documentation · Getting started · Tools reference
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
Bootstrap reads the manifest embedded in the installed FsLangMCP binary and
installs (or downgrades) FSAC, ProjInfo, and Fantomas to the exact reviewed
versions for that release. It also creates the dotnet-fantomas command alias
required by FSAC formatting.
Review dependency security and updates before using the server.
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
Narrow an ordinary site search when you need only one project, or request counts for several names without source snippets:
find { "query": "OrderId", "kind": "symbol", "scope": "project", "projectPath": "src/Core/Core.fsproj" }
find { "queries": ["OrderId", "OrderService"], "projectPath": "src/Core/Core.fsproj", "scope": "project" }
The paths are illustrative. Inventory rows keep independent definition/reference/unique-site
totals and point into the shared projectLedger. Its testProjectDetection value identifies the
project-health XML/package heuristic: isTestProject=true means recognized test project,
false means unmarked project, and a missing project reports null. These groups are not proof of
test or production execution. An incomplete count is a lower bound; narrow the project, split the
query batch, or retry with a larger timeoutMs before treating zero as absence. See the
find guide for the full contract.
check is the fast semantic edit loop, not the final Release gate. A clean verdict covers the
compiler options in its current FCS/check profile; configuration-specific diagnostics such as
Release-only FS3511 can still appear under optimized compilation. Before merge or release, run
dotnet build -c Release --warnaserror.
Navigate / understand
Diagnose / fix
Refactor preview
Review / cleanup
Analyzers
NuGet / types
Exact-position helpers
The raw textDocument_* tools and fsharp_signature_data require set_project
and LSP readiness. fcs_signature_help runs in-process with FCS project context.
Prefer the semantic tools above for free-form agent flows.
Meta
Example Agent Session
1. set_project {"projectPath": "/abs/path/MyApp.sln"}
→ readiness.lsp=true, loadedProjects=[...], fslangmcpVersion="0.19.0"
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/update the exact supported global runtime-tool versions--version— print the packaged FsLangMCP version and exit
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=300000FSLANGMCP_PROCESS_OUTPUT_LIMIT_CHARS=4194304(per redirected stream; excess output is drained but not retained)
Response Shape
- Success:
{"status": "ok", ...fields}or{"status": "ok", "result": <payload>} - Not ready:
{"status": "not_ready", "message": "..."}— FSAC workspace still loading - Wrong FSAC context:
{"status": "context_mismatch", "contextMatched": false, ...} - Restart required:
{"status": "restart_required", ...}— a live FSAC belongs to another project and the requested no-restart switch was not applied - Invalid args:
{"status": "invalid_args", "message": "..."}— required arg blank/missing - Infrastructure failure:
{"status": "infrastructure_error", "errorKind": "...", "message": "..."}
set_project keeps the boolean readiness flags for compatibility and adds
readiness.symbolIndexState / readiness.symbolIndexHint when the symbol index
is not ready. lspRestartRequested reports intent; legacy lspRestarted keeps
mirroring that request, while lspReplacedExistingProcess reports whether a
running FSAC process was actually replaced. FSAC-derived responses include
activeProjectPath, contextMatched, and sessionGeneration.
find has its own completeness contract: status="partial" means matches were
found but some projects were not analyzed; status="unknown" plus
resolution.matched=null means absence could not be proven. Separately,
coverage.complete says whether the project sweep finished, while
resolution.complete says whether this response delivered the entire site set; follow
truncated / nextCursor and verify totalEstimate.sites before treating a refactor count as
exhaustive. A v2 cursor may be retried with a different maxResults or timeoutMs, but changing
any result-shaping input returns cursor_query_mismatch; a changed source/result/coverage stream
returns cursor_stale, including removal of the last or selected declared solution member.
Deadline-incomplete initial pages have no cursor and require a restart; deadline-incomplete
continuations return no sites with retrySameCursor=true. This also applies when constructing
the final validation-error response exhausts the deadline: a well-formed continuation returns
cursor_validation_incomplete and can be retried to obtain the actual validation result.
Malformed cursors and invalid/missing-context requests keep their typed rejection routes.
check(speed="fast")
similarly exposes expected/received/missing/stale file coverage and never turns an
incomplete empty snapshot into clean.
For capped linked-field type alternatives, siteTypeAlternativesOmitted reports how many
alternatives were omitted and siteTypeAlternativesOffset identifies the first omitted index.
These per-site values are independent of page size and response budget.
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 compiler work has started.
findand project/workspacecheckstill return at their requested deadline; identical retries share the real in-flight worker, while a different key is rejected as busy instead of joining an unbounded queue. The admitted worker may continue until FCS returns. Restart FsLangMCP to terminate a genuinely stuck in-process compiler 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.dotnet tool update -g FsLangMcpdoes not affect an already-running server process — it's long-lived once your MCP client spawns it. Callfslangmcp_versionto confirm which version is actually running rather than assuming the on-disk one; version-sensitive testing needs a fresh MCP connection. Seedocs/troubleshooting.md.
About Tool Dependencies
dotnet tool packages cannot automatically install other global tools during
installation. The reviewed compatibility set is pinned in
dotnet-tools.json:
fsautocomplete0.83.0ionide.projinfo.tool0.74.2fantomas7.0.5
Install that exact set (including FSAC's required Fantomas command alias) for normal use:
fslangmcp --bootstrap-tools
For repository development, just runtime-tools materializes the same versions
under .runtime-tools/, and just live-fsac performs a real MCP → FSAC and
ProjInfo smoke. Both paths validate the same exact root manifest; the repository
path is isolated from any other globally installed tools.
Dependency security and updates
The FsLangMCP package graph and the libraries bundled inside separately installed runtime tools are different audit scopes. The pins above include advisory-listed MessagePack (FSAC/Fantomas) and System.Drawing.Common (ProjInfo) assemblies. #170 records the affected versions and the reachability assessment. No exploit was demonstrated in the supported flows assessed there; that is not proof of safety. Running a tool as a child process is not a security sandbox.
Changing a NuGet reference in FsLangMCP or your project does not replace those bundled assemblies. FsLangMCP can adopt compatible upstream fixes or a verified replacement, but documenting the risk does not remove the vulnerabilities.
- Keep your .NET SDK and project dependencies updated. Use NuGet Audit for direct and transitive project dependencies; a clean project audit does not cover the libraries bundled inside installed tools.
- Follow FsLangMCP releases
and security updates from FSAC, Fantomas, and ProjInfo. After updating FsLangMCP,
rerun
fslangmcp --bootstrap-toolsand restart your MCP connection. Bootstrap selects the installed release's exact reviewed pins, not the latest upstream versions, and can downgrade manually updated tools. Reinstalling unchanged pins does not remediate their advisories; newer tool versions need compatibility and dependency checks before they enter the supported set. - Load only projects you trust. Use an isolated environment for untrusted code; do not treat the MCP server or its child processes as a security boundary.
Treat this as a documented dependency risk, not an advisory-free toolchain. If your security policy disallows these dependencies, do not use the affected toolchain until a suitable remediation is available.