MCP server FSharp.Refactor
Коммит 13 нояб. 2026 г.Оффлайн-анализатор, который находит и применяет предложения по функциональному рефакторингу для улучшения читаемости, производительности и идиоматичности кода на F#.
Также известен как Thorium/fsharp-refactor, fsharp-refactor
- Звёзды
- 16
- Инструменты
- —
- Загрузки, 30 дней
- —
- Последняя версия
- 0.8.45
- Последний коммит
- 13 нояб. 2026 г.
- Лицензия
- Unlicense
Что такое FSharp.Refactor MCP?
FSharp.Refactor — это MCP server. Оффлайн-анализатор, который находит и применяет предложения по функциональному рефакторингу для улучшения читаемости, производительности и идиоматичности кода на F#.
Сводка сгенерирована из README проекта
О FSharp.Refactor MCP server
Разработчики F# могут улучшить читаемость, производительность и идиоматичность кода с помощью оффлайн-анализатора, который предлагает варианты функционального рефакторинга. Инструмент работает путем чтения существующих проектов, исходных файлов или скриптов для поиска возможностей улучшения, не требуя подключения к интернету или обработки через ИИ. Он функционирует как консольная утилита, которую можно запускать массово или интегрировать в редакторы, такие как VS Code и Visual Studio, через специализированные плагины.
Пользователи могут применять изменения с помощью dotnet-инструмента fsharp-refactor, который поддерживает режим dry run для предварительного просмотра модификаций перед их записью на диск. Система спроектирована с учетом безопасности: она отказывается работать с кодом, который не собирается, и завершается с ошибкой, если любое примененное исправление вносит новую ошибку. Инструмент обеспечивает детальный контроль с помощью таких флагов, как --api-changes для управления обновлениями публичных методов и --categories для выбора конкретных типов улучшений, например, корректности или производительности.
Что вы можете с ним делать
- 1Массовый рефакторинг F# проектовРазработчик может применить множество функциональных улучшений ко всему решению через командную строку.
- 2Безопасный предварительный просмотр изменений кодаВы можете просмотреть список всех модификаций, которые планирует внести инструмент, не изменяя исходные файлы.
- 3Управление техническим долгом с помощью базовых показателейРазработчики могут игнорировать существующие проблемы и фокусироваться только на новых возможностях для рефакторинга.
Взято из README проекта. Каждый вариант использования проверен на соответствие цитате из него.
Автоматические проверки
Запущено 13 нояб. 2026 г.Полный отчёт →Еще не проверено. Для сервера требуется оценка 80, отсутствие серьезных проблем и достаточно данных для нашей проверки. Как работают проверки · Что исправить, чтобы получить больше баллов
- Связанный и доступный исходный репозиторийПройдено
- Open source лицензия (OSI)Пройдено
- Коммит или релиз за последний годПройдено
- Имя пакета не похоже на популярный пакетНе проверялось
- Удаленная конечная точка использует HTTPSНе проверялось
- Деструктивные инструменты помеченыНе проверялось
- Нет скрытых инструкций для моделиПройдено
- Нет критических предупреждений в пакете или его зависимостяхНе проверялось
README
Открыть на GitHubFSharp.Refactor
Let's remove parentheses from the internet
Functional refactoring suggestions for F#. The tool clarifies the (agent-generated or old) code's intent, reduces non-alphanumeric noise, and uses built-in .NET libraries more efficiently.
- light bulb quick fixes in your editor
- command-line tool that applies them in bulk.
Suggestions are Hint severity: they mark an opportunity, not a defect, and
never gate your build. The priority rules, which point at
likely defects, show as warnings in the editor.
This project is the big brother of CSharp.Refactor
Using it
The tool is fully offline: AI has been used to make it, but it doesn't use AI.
Quick start
Nothing to configure - the tool reads your project, reports what it would change, and only edits when you tell it to. The tool runs on the .NET 10 SDK; the projects it works on can target any framework:
dotnet tool install --global fsharp-refactor
fsharp-refactor Your.fsproj --dry-run --api-changes
That prints every fix it would make, with file and position, and writes nothing. When the list looks right, drop the flag to apply them:
fsharp-refactor Your.fsproj --api-changes
It refuses a compilation that does not already build, and fails loudly if applying ever introduces an error.
If you don't want change public methods / API, drop --api-changes:
fsharp-refactor Your.fsproj
For light bulbs while you type, see VS Code / Ionide and Visual Studio IDE-plugin instructions below.
For agentic scenarios the dotnet tool supports MCP.
Using the tool: Point it at whatever you have - the kind is read off the path:
Your.fsproj |
one project |
Thing.fs |
one source file - its project is found and analysed, but only that file is edited |
build.fsx |
one script - no MSBuild step at all, so it starts instantly. A script with unresolvable references is not refused: the syntactic rules still run over it (an fsi-run script may reference things only the run supplies) |
Your.sln, Your.slnx |
every F# project the solution lists |
src/ |
the solution in that directory, or the projects beneath it |
"src/**/*.fsproj" |
everything the glob matches |
What it changes
Examples:
| Before | After | |
|---|---|---|
| Correctness | let s = new FileStream(p, m) |
use s = new FileStream(p, m) |
| Correctness | raise (Exception "boom") |
failwith "boom" |
| Performance | s.Contains "x" |
s.Contains 'x' |
| Performance | xs |> Seq.toList |> List.map f |
xs |> Seq.map f |> Seq.toList |
| Idiom | match b with | true -> 1 | false -> 0 |
if b then 1 else 0 |
| Redundancy | new StringBuilder() |
StringBuilder() |
| Redundancy | [<SerializableAttribute>] |
[<Serializable>] |
| Diagnostics | failwith "Error" |
failwith $"Error, calling f with x: {x}" |
A spread of what the rules do - the full list is in Refactorings:
Editor and CI setup
The analyzers ship as FSharp.Refactor.Analyzers.
The package is a development dependency: it only produces hints and quick
fixes - nothing from it flows into your compiled output.
NOTE: EVEN WHEN ADDING A NUGET REFERENCE, THIS ANALYSER WILL NOT COME TO OUTPUT PATH (BIN) AND WILL NOT BE PART OF YOUR PROJECT.
VS Code / Ionide
Easiest: install the FSharp.Refactor VS Code extension
- it bundles the analyzers and (with your consent) wires them into Ionide's settings globally, so every F# project gets the hints with no per-project setup.
Or wire a single project by hand: reference the package from the project you want analyzed:
<PackageReference Include="FSharp.Refactor.Analyzers" Version="*" PrivateAssets="all" />
then point Ionide at the restored analyzers in .vscode/settings.json:
{
"FSharp.enableAnalyzers": true,
"FSharp.analyzersPath": [
"~/.nuget/packages/fsharp.refactor.analyzers/<version>/analyzers/dotnet/fs"
]
}
replacing <version> with the version restore actually picked (the NuGet
cache always keys folders by version, so this one path cannot float; check
with ls ~/.nuget/packages/fsharp.refactor.analyzers/). On Windows the
cache lives under %USERPROFILE%\.nuget\packages. Suggestions
appear as Hint-severity diagnostics with a light-bulb one-click fix.
Ionide bundles its own FsAutoComplete; to run another one, dotnet tool install -g fsautocomplete and set FSharp.fsac.netCoreDllPath to its
fsautocomplete.dll (the extension above offers to do both).
Supported hosts
FSharp.Analyzers.SDK loads an analyzer assembly only when it was built
against the SDK's own major.minor version. The package ships one build,
in analyzers/dotnet/fs, for SDK 0.41 on .NET 10:
| SDK | Hosts |
|---|---|
| 0.41 | FsAutoComplete 0.86 and later (Ionide 8), fsharp-analyzers 0.41 |
An older host (Ionide 7, FsAutoComplete 0.85 and earlier, fsharp-analyzers
0.37 and earlier) loads nothing from this package; FSharp.Refactor.Analyzers
0.8.51 is the last release with builds for SDK 0.37 and 0.35.
If no suggestions appear, open Output -> "F# Language Service": it names the analyzer dlls it scanned and how many analyzers loaded, and a version-pairing skip is spelt out there rather than surfacing in the editor.
Visual Studio 2022-2026
Install the
FSharp.Refactor Visual Studio extension
(user-facing overview):
squiggles and Ctrl+. quick fixes in full Visual Studio, analyzed
through an FsAutoComplete sidecar the extension bundles.
CLI / CI
dotnet tool install --global fsharp-analyzers --version "0.41.*"
fsharp-analyzers --project src/YourProject.fsproj --analyzers-path ~/.nuget/packages/fsharp.refactor.analyzers/<version>/analyzers/dotnet/fs --code-root . --report analysis.sarif
fsharp-analyzers is the analyzer HOST, and it is the dotnet tool you
install, at a version from the supported hosts table:
an unpinned install takes the newest, which may be ahead of the package.
This package is not a tool: it is a library of analyzer assemblies
the host loads, so it is passed as a directory rather than installed. That
directory sits in the NuGet cache because an analyzer package deliberately
has no lib/ folder, declares no dependencies and is marked a development
dependency - a PackageReference therefore puts nothing in your bin, and
after a restore, the cache is where the assemblies live. --analyzers-path
takes any folder holding them and searches it recursively.
The CLI only REPORTS; it never edits your files, which is what you want in
CI (SARIF output works in GitHub code scanning). To apply the fixes, use our
own tool below. Individual rules can be turned off per repository with a
fsharprefactor.json - see Configuration below.
Applying fixes from the command line
The fsharp-refactor
dotnet tool applies the quick fixes directly to your files:
dotnet tool install --global fsharp-refactor
fsharp-refactor Your.fsproj [--dry-run] [--codes FR0002,FR0031] [--api-changes] [--jobs 4] [--max-passes 5]
(or from this repository:
dotnet run --project src/FSharp.Refactor.Tool -c Release -- Your.fsproj ...)
For a project it takes the exact compiler arguments from MSBuild; for a script FCS resolves the references itself and MSBuild never runs. Either way it then runs every analyzer, applies non-overlapping fixes bottom-up, and re-analyzes until a pass applies nothing - a fix can enable further fixes. It refuses a compilation that already has errors, and fails loudly if applying ever introduces one.
Warnings as errors: the project's own settings decide, and the tool never
overrides them - its verification is the build your CI runs. The
in-memory typecheck takes the compiler's own arguments, so --warnaserror
and <WarningsAsErrors> turn an F# warning a fix raises into an error the
pass rolls back. A C# project that references the F# one and turns an
analyzer warning into an error (error CA1859, error IDE0005) fails the
verification build as code, not as tooling noise, and the run says which
setting to change if you want such fixes anyway
(<WarningsNotAsErrors>CA1859</WarningsNotAsErrors>, or
dotnet_diagnostic.CA1859.severity = suggestion). <NoWarn> works too,
but silences the analyzer for everyone.
| Flag | |
|---|---|
--dry-run |
Report only: lists every fix it would make, with file and position, and writes nothing. Rewriting is never implicit - drop the flag to let it edit. |
--codes FR0002,FR0031 |
Restrict the run to chosen rules; the other analyzers do not run. |
--categories <list> |
Restrict the run to kinds of rule: correctness, performance, idiom, cosmetic. Combined with --codes it narrows further, in either order. The analyzers of the rules left out do not run at all. A filtered run can stop at a point a full run passes through: FR0157 (idiom) leaves a ` |
--jobs <n> |
Typecheck that many files at once (default 4, clamped to 2-4 by core count). Trades CPU for wall clock; because FCS reuses each file's prefix within one incremental build, the gain peaks around 4 and reverses if pushed higher. --jobs 1 is the sequential sweep. |
--framework <tfm> |
Analyse against this target framework instead of the narrowest one - see below. |
--max-passes <n> |
Fix-then-reanalyze iterations (default 5). A pass after the first sweeps only the files the previous pass changed and those compiled after the first of them. |
--help |
The same list, from the tool itself (-h and /? also work). |
--api-changes |
Also apply the cross-file fixes described below. |
--no-if-defs |
Never emit #if/#else/#endif pairs for capability fixes on multi-targeted projects (see below). The fixes stay plain, and any the legacy frameworks reject are put back by the final build check. |
--report <file> |
Write every finding the run surfaced to a file; the extension picks the format. .sarif (or .json) is SARIF 2.1.0 - what GitHub code scanning renders as inline PR annotations; .html is a self-contained page with per-rule grouping, highlighted source, before/after fixes and category filters; .csv is one row per finding for a spreadsheet. Pairs naturally with --dry-run for a CI lint gate. See CI setup below. A many-target run rewrites the report after every target, so a crash or a Ctrl-C an hour in still leaves what was found. Pointing the tool at a workspace of checkouts - a directory with no solution of its own whose sub-directories carry theirs, C:git say - analyses each checkout on its own (its solutions honoured, its fsharprefactor.json applied) into the one report, with paths relative to the workspace. |
--baseline <sarif> |
The ratchet: findings whose fingerprints appear in this earlier --report output are neither reported nor fixed - only what is NEW surfaces. Fingerprints hash the rule code, file name and normalized surrounding source, so they survive line shifts, other edits in the file, and different checkouts. Triage once, ratchet forever. |
--fail-on-findings |
Exit 3 when any finding survives the filters - the hard CI gate. The full exit contract: 0 clean, 1 analysis or apply failure, 2 usage error, 3 findings (only with this flag). |
| `--notes [on | off |
--notes only |
A review pass: every rule runs, only the findings WITHOUT a fix are listed inline, and nothing is written. That is the advisory rules (an em dash in Rules.md's fix column) plus the cases where a fixing rule can only advise. fsharp-refactor src/Your.fsproj --notes only --report notes.html writes them as a page. |
--format json |
Machine-readable stdout: progress prose moves to stderr and the run's findings leave as one JSON document (code, severity, fixable, position, message, fingerprint, source snippet). The default output stays human-readable. |
--rules |
Print the rule catalog - code, category, enabled-by-default (honors --format json). |
--create-config |
Write a fsharprefactor.json of this build's defaults - every rule, every run-level key, one comment each - into the current directory, or into <what> when that is a directory. It changes nothing until you edit it, and never overwrites an existing config. |
--mcp |
Serve the tool as an MCP server over stdio (newline-delimited JSON-RPC, no extra dependencies): tools analyze (target, codes/categories, parseOnly, apply) and list_rules. One warm typechecker lives across calls, so the first analyze pays the reference parse and the rest answer from a hot cache - the economics agent loops need. The server works inside one directory: fsharp-refactor <directory> --mcp, or the directory it is started in. A call's target must lie inside it (a relative target is relative to it), and an apply writes nothing outside it: a compilation whose fixes reach a file outside, such as a linked source or a #load, is put back whole and reported. --mcp --dry-run is a server that applies nothing; --define and --jobs hold for every call, and any other option is refused, since it would be ignored. Analysing a project still builds it, which runs its MSBuild logic, so the directory should be a tree you trust. |
--parse-only |
For a codebase that cannot COMPILE on this machine - a type provider needing its database, references that cannot restore. No MSBuild, no reference resolution: sources come straight from the fsproj's <Compile> items, and only the analyzers that never consult the typechecker run, about a third of them (the typed rules are excluded outright, not trusted to self-silence). It is not a substitute for a real run, and what survives is skewed the wrong way: measured across the corpus, roughly a quarter of the correctness rules and a quarter of the performance rules still fire, against three quarters of the cosmetic ones - so a clean --parse-only says very little, and says least about the things worth knowing. Findings lost run to 38% on a typed-heavy codebase and under 10% on one the cosmetic rules dominate. Safety shifts accordingly: instead of a build, the gate is that a pass must not RAISE the compilation's error count over its baseline, and the usual parse-level protections (comment guard, overlap holds) still apply. Limitations: #if branches behind conditional or computed DefineConstants are not parsed, wildcard <Compile> globs are refused, and multi-framework passes collapse to one. Review the diff - the all-frameworks build arbiter is exactly what this mode does without. |
CI setup (SARIF)
A dry run plus --report gives CI the full findings list without
touching a file; uploading the SARIF turns each finding into an inline
annotation on the pull request. Paths in the report are relative to the
repository that holds the target (the nearest .git above it), so the
tool may run from anywhere; each result carries the rule's description
and help link, the finding's own text with three lines of context, the
fix as a SARIF fixes entry (code scanning renders it as a suggested
change), a stable fingerprint, and the run's invocation record:
refactor-lint:
runs-on: ubuntu-latest
permissions:
security-events: write # required by upload-sarif
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x # the tool runs on .NET 10
- run: dotnet tool install --global fsharp-refactor
- run: fsharp-refactor src/Your.fsproj --dry-run --report findings.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: findings.sarif
category: fsharp-refactor
Two honest notes. First, a dry run exits 0 whether or not it found anything - findings are hints, not errors, and only a broken build or a crashed run fails the step. Annotations therefore inform without blocking; to make findings HARD-fail the job, add an explicit check:
jq -e '.runs[0].results | length == 0' findings.sarif
Second, scope the gate before turning it on: --categories correctness,performance keeps the signal defensible on a shared
repository (see Someone else's codebase),
and a fsharprefactor.json turns off anything the team has decided
against. The suppression comments described under
Configuration silence individual findings at the
line, for both the gate and editors, in one place.
Running alongside other analyzer packages
The fsharp-analyzers host loads every analyzer assembly it is
pointed at, so one invocation - and one FCS typecheck, the expensive
part - can run this package together with others built on the same
SDK, all findings landing in one report. With
G-Research's analyzers,
at a release on the same FSharp.Analyzers.SDK minor as the folder you
pass from this package (see Supported hosts):
fsharp-analyzers --project src/Your.fsproj \
--analyzers-path ~/.nuget/packages/fsharp.refactor.analyzers/<version>/analyzers/dotnet/fs \
~/.nuget/packages/g-research.fsharp.analyzers/<version>/analyzers/dotnet/fs \
--code-root . --report findings.sarif
Rule codes are disjoint (FR* here, GRA* there), and the
fsharpanalyzer: ignore-line suppression comments work for their codes
too - the machinery lives in the shared SDK, not in any one package.
The SDK is strict about version agreement between the host and every
analyzer assembly it loads; a mismatched assembly is skipped with a log
line, and the CLI exits non-zero for it. The day the two packages pin
different SDK minors, fall back to separate CI jobs - each producing
its own SARIF and uploading under its own category: (code scanning
keeps the streams apart) - at the cost of typechecking the project
once per job. Applying fixes stays this package's own tool either way:
fsharp-refactor applies only its own rules, and report-only
analyzers have nothing to collide with.
Someone else's codebase
Every rule is one of four kinds, shown in the last column of Refactorings:
| Kind | Count | |
|---|---|---|
correctness |
The code does something other than what it looks like it does: a race, a swallowed exception, a disposable that leaks, a comparison that never holds | 69 |
performance |
Correct, but doing work it need not: allocations that need not happen, repeated work, a scan where a lookup would do | 41 |
idiom |
The same behaviour written the way F# writes it. Worth doing, and worth agreeing on first - it is a matter of house style as much as anything | 57 |
cosmetic |
The punctuation and spelling of code. Real cleanups, and nobody's idea of a welcome pull request from a stranger | 17 |
Every version's notes are in CHANGELOG.md; the NuGet packages carry the last six.
Thirteen rules carry a priority flag on top of their category - the
likely defects and security holes too costly to hold back: FR0020,
FR0028, FR0032, FR0046, FR0047, FR0048, FR0061, FR0063, FR0065, FR0066,
FR0122, FR0126 and FR0127. Swallowed exceptions (FR0055) and public
mutables (FR0062) stay plain correctness: bad habits more often than
live defects. Their notes print
without --notes, editors show them as warnings, and SARIF carries them
at warning level. Rules.md marks them in its Priority column.
This matters when the repository is not yours. Running everything over a project you do not maintain and opening a pull request from the result is a good way to waste an afternoon of someone's life: no maintainer wants "removed an empty attribute argument list" across two hundred files, and a diff that size buries anything that mattered. A disposable that is never disposed is a different conversation entirely.
So for a codebase you are a guest in:
fsharp-refactor Their.fsproj --categories correctness,performance --dry-run
That is the set that earns its review time. For your own code, run the lot.
The category claims are measured, not assumed:
dotnet run -c Release --project benchmarks/PerfClaims
re-checks them on your machine, on BOTH axes - wall clock and allocation
(GC pressure is performance too). The contract: a performance rule's
rewrite must win on at least one axis, and an idiom rule's must hold
parity. FR0050 once emitted Seq.sum for a list - ~50%% slower than the
mutable loop it replaced, plus an enumerator allocation - which is how
the benchmark file, the rule's module-resolved output, and its idiom
recategorization all came to exist.
Multi-targeted projects
Nothing extra to do: a multi-targeted project is worked through framework by framework, narrowest first.
Capability fixes get both worlds - using the project's own vocabulary.
When a project also targets frameworks older than an overload (net4x,
netstandard2.0), the tool reads the fsproj's DefineConstants and looks
for a framework-shaped constant - NETSTANDARD21, NET8, digits
required - whose '$(TargetFramework)' == '...' conditions cover only
the modern frameworks. Names denoting a legacy framework (NET48,
NET451, NETSTANDARD2_0) are refused outright whatever their
conditions say: the SDK defines exactly those constants during the
legacy compilations themselves, where no fsproj parse can see them. Flavor names sharing the same condition
(SQLProvider defines MICROSOFTSQL right beside NETSTANDARD21) are
passed over: their meaning is the flavor, and a sibling project
compiling the same shared file may define them on legacy frameworks
too. If a constant qualifies and the file already uses conditional
compilation, FR0038 and FR0106 emit a pair instead of a fix the legacy
half cannot compile:
#if NETSTANDARD21
let orderNumber (s: string) = Int32.Parse(s.AsSpan(6, 5))
#else
let orderNumber (s: string) = Int32.Parse(s.Substring(6, 5))
Вопросы о FSharp.Refactor MCP
Использует ли этот инструмент ИИ?
Нет, инструмент полностью работает оффлайн. Хотя ИИ использовался при его создании, сам инструмент ИИ не использует.
Сломает ли инструмент сборку?
Нет, предложения помечаются уровнем важности 'Hint' и не блокируют сборку. Кроме того, инструмент отказывается запускаться на проектах, которые не собираются, и прекращает работу, если применение исправлений приводит к ошибкам.
Можно ли использовать это в CI/CD пайплайне?
Да, вы можете использовать CLI для вывода результатов в формате SARIF для сканирования кода в GitHub или использовать флаг --fail-on-findings для создания жесткого ограничения в CI.