FSharp.Refactor MCP server
2026년 11월 13일 커밋F# 코드의 의도, 성능 및 관용적 표현을 개선하기 위해 기능적 리팩터링 제안을 찾아 적용하는 오프라인 분석기입니다.
다른 이름 Thorium/fsharp-refactor, fsharp-refactor
- 스타
- 16
- Tools
- —
- 다운로드, 30일
- —
- 최신 버전
- 0.8.45
- 마지막 커밋
- 2026년 11월 13일
- 라이선스
- Unlicense
FSharp.Refactor MCP란?
FSharp.Refactor은(는) MCP server입니다. F# 코드의 의도, 성능 및 관용적 표현을 개선하기 위해 기능적 리팩터링 제안을 찾아 적용하는 오프라인 분석기입니다.
프로젝트 README에서 생성된 요약
FSharp.Refactor MCP server 정보
F# 개발자는 기능적 리팩터링 제안을 제공하는 오프라인 분석기를 통해 코드의 의도, 성능 및 관용적 표현을 개선할 수 있습니다. 이 도구는 인터넷 연결이나 AI 처리 없이 기존 프로젝트, 소스 파일 또는 스크립트를 읽어 개선 사항을 식별합니다. 명령줄 도구로 작동하며 일괄 실행이 가능하고, 전용 플러그인을 통해 VS Code 및 Visual Studio와 같은 에디터에 통합할 수 있습니다.
사용자는 fsharp-refactor dotnet tool을 사용하여 변경 사항을 적용할 수 있으며, 디스크에 기록하기 전에 수정 사항을 미리 볼 수 있는 dry run 기능을 지원합니다. 시스템은 안전하게 설계되어 이미 빌드되지 않는 코드에서는 실행을 거부하며, 적용된 수정 사항이 새로운 오류를 발생시키면 중단됩니다. --api-changes 플래gw를 통해 공개 메서드 업데이트를 관리하거나, --categories 플래그를 통해 정확성 또는 성능과 같은 특정 유형의 개선 사항을 대상으로 하는 등 세밀한 제어가 가능합니다.
활용 사례
- 1F# 프로젝트 일괄 리팩터링개발자는 명령줄을 사용하여 전체 솔루션에 걸쳐 여러 기능적 개선 사항을 적용할 수 있습니다.
- 2코드 변경 사항 안전하게 미리 보기소스 파일을 실제로 변경하지 않고 도구가 수행하려는 모든 수정 사항을 목록으로 확인할 수 있습니다.
- 3베이스라인을 통한 기술 부채 관리개발자는 기존 문제를 무시하고 새로운 리팩터링 기회를 찾는 데에만 집중할 수 있습니다.
프로젝트의 README를 바탕으로 작성되었습니다. 모든 활용 사례는 해당 내용의 인용문과 대조하여 확인합니다.
자동 검사
2026년 11월 13일에 실행됨전체 보고서 →아직 인증되지 않았습니다. 서버는 80점 이상의 점수, 심각한 결함 없음, 그리고 검사에 충분한 데이터를 갖추어야 합니다. 검사 방식 · 점수를 높이기 위해 수정할 항목
- 소스 저장소 연결 및 접근 가능통과
- 오픈 소스 라이선스 (OSI)통과
- 최근 1년 내 커밋 또는 릴리스통과
- 패키지 이름이 인기 패키지와 유사하지 않음검사되지 않음
- 원격 엔드포인트가 HTTPS 사용검사되지 않음
- 파괴적인 도구에 라벨 표시됨검사되지 않음
- 모델에 대한 숨겨진 지침 없음통과
- 패키지 및 해당 종속성에 중요한 보안 권고 없음검사되지 않음
README
GitHub에서 보기FSharp.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 질문
이 도구는 AI를 사용합니까?
아니요, 이 도구는 완전히 오프라인으로 작동합니다. 제작 과정에는 AI가 사용되었으나, 도구 자체는 AI를 사용하지 않습니다.
도구가 빌드를 깨뜨릴 수 있습니까?
아니요, 제안 사항은 'Hint' 심각도로 표시되어 빌드를 차단하지 않습니다. 또한, 이 도구는 이미 빌드가 가능한 프로젝트에서만 실행되며, 수정 사항 적용 시 오류가 발생하면 실행을 중단합니다.
CI/CD 파이프라인에서 사용할 수 있습니까?
네, CLI를 사용하여 GitHub 코드 스캔을 위한 SARIF 형식으로 결과를 보고하거나, --fail-on-findings 플래그를 사용하여 엄격한 CI 게이트를 생성할 수 있습니다.