EncodingMCP MCP server
Commit Nov 1, 2026Lets AI coding assistants read and edit text files in various encodings like Windows-1252 or UTF-16 without corrupting special characters.
Also known as thvedel/EncodingMCP
- Stars
- 0
- Tools
- 11
- Downloads, 30 days
- —
- Latest version
- —
- Last commit
- Nov 1, 2026
- License
- MIT
What is EncodingMCP MCP?
EncodingMCP lets AI coding assistants read and edit text files in various encodings like Windows-1252 or UTF-16 without corrupting special characters. It exposes 11 tools, such as path, head, tail.
Summary generated from the project's README
About EncodingMCP MCP server
EncodingMCP acts as an encoding-aware bridge between AI coding assistants and your local file system. Most LLM-based tools assume all files are UTF-8, which leads to character corruption when working with legacy codebases using Windows-1252, ISO-8859-x, or UTF-16. This server detects the specific encoding of a file using BOM, strict validation, or heuristics, providing the assistant with clean UTF-8 text while ensuring that any writes preserve the original encoding, BOM, and line-ending style.
Designed for Windows x64 environments, the server is a standalone executable that requires no runtime dependencies. It works with popular clients like Claude Desktop, Cursor, Windsurf, and GitHub Copilot via stdio transport. Developers can manage file encodings through a .encoding-mcp.json sidecar file or by setting explicit overrides for specific files and patterns. It is particularly useful for maintaining legacy C, C++, Delphi, or VB6 projects where converting the entire repository to UTF-8 is not feasible.
What you can do with it
- 1Prevent character corruption in legacy filesAsk the AI to edit a source file in Windows-1252 without destroying special characters like umlauts or smart quotes.
- 2Read non-UTF-8 files with metadataRequest the assistant to read a file and provide its detected encoding and line-ending style.
- 3Manage specific encoding overridesForce the assistant to treat a specific file or pattern as a certain encoding using manual overrides.
- 4Protect binary-sensitive formatsEnsure the AI does not attempt to rewrite binary-format files like Delphi forms as plain text.
Written from the project's README. Every use case is checked against a quote from it.
EncodingMCP MCP tools 11
Detected from the READMEpathheadtailcontentencodingforceeditsfilespatterndirectoryremove
| path | — | |
| head | — | |
| tail | — | |
| content | — | |
| encoding | — | |
| force | — | |
| edits | — | |
| files | — | |
| pattern | — | |
| directory | — | |
| remove | — |
Configuration
ENCODING_MCP_WORKSPACE
| Name | Description | |
|---|---|---|
| ENCODING_MCP_WORKSPACE | — |
Automated checks
Ran Nov 14, 2026Full report →Not verified yet. A server needs a score of 80, no serious finding, and enough data for us to check. How checks work · What to fix for more points
- Source repository linked and reachablethvedel/encodingmcp is public and reachablegithub.com/thvedel/encodingmcp ↗Pass
- Package names this repository as its sourceNot checked
- Build provenance (Sigstore attestation)Not checked
- Open source license (OSI)MITspdx.org/licenses/MIT.html ↗Pass
- Commit or release in the last yearLast commit or release 2026-11-01github.com/thvedel/encodingmcp ↗Pass
- OpenSSF ScorecardNot checked
- Package name not close to a popular packageNot checked
- No critical advisory in the package or its dependenciesNot checked
- No hidden instructions to the model11 tool descriptions and the README read; no override phrases, credential paths or hidden charactersPass
- Destructive tools are labelledNot checked
- Remote endpoint answers the MCP handshakeNot checked
README
View on GitHubEncodingMCP
Let AI coding assistants read and edit text files in any encoding — without corrupting them.
AI coding tools (GitHub Copilot, Claude Code, Cursor, Windsurf, Claude Desktop,
…) assume that every text file is UTF-8. A great deal of real-world source code
is not: files in Windows-1252, ISO-8859-x, UTF-16 and other encodings are the
norm in codebases that predate UTF-8 or that target Windows tooling. When an LLM
reads such a file as UTF-8 and writes it back, every æ, ø, å, ü, € or
smart quote is silently destroyed.
EncodingMCP is an MCP server that puts an
encoding-aware layer between the assistant and the disk. It detects the
encoding of each file (BOM, strict UTF-8 validation, codepage heuristics),
hands the assistant clean UTF-8, and converts back to the file's own encoding
on write — preserving BOM and line-ending style. Encodings are remembered per
file in a .encoding-mcp.json sidecar, and explicit overrides can be set per
file or per pattern.
It is language-agnostic. Typical candidates:
- C / C++ / C# / Java projects with Windows-1252 or ISO-8859-1 sources
- Classic VB6, VBScript and VBA exports
- PHP, Perl and Python 2 code written before UTF-8 became the default
- COBOL, RPG, Fortran and other mainframe or legacy sources
- SQL scripts,
.ini,.reg,.bat/.cmd,.propertiesand other config and resource files - Localisation files, CSV exports, HTML/XML without a declaration
- UTF-16 files produced by Windows tools
- Delphi and C++Builder projects (
.pas,.dfm,.dpr,.cpp,.h)
Who is this for?
- Anyone who uses an AI coding assistant on a codebase that is not (entirely)
UTF-8 and wants to stop reviewing diffs full of
øandä. - Teams maintaining legacy Windows applications where converting the whole repository to UTF-8 is not an option (toolchain, merge history, or policy).
- Developers working with UTF-16 or BOM-sensitive files that other tools silently rewrite.
- Delphi/C++Builder developers — the project originated there, and the binary-DFM guard is one example of how non-text formats are protected.
The server is a single standalone Windows executable with no runtime dependencies. You do not need Delphi to use it — only to build it from source.
Quick Start
Option A: Download prebuilt EXE (no Delphi required)
- Go to Releases
- Download
EncodingMCP.exe - Place it somewhere accessible (e.g.
C:\Tools\EncodingMCP.exe) - Configure your MCP client (see Installation)
Note (Windows): After downloading you may need to right-click the file → Properties → "Unblock", as Windows marks files downloaded from the internet. Alternatively:
Unblock-File -Path <path>in PowerShell.
Option B: Build from source
git clone https://github.com/thvedel/EncodingMCP.git
cd EncodingMCP
build.bat
The result is a standalone .exe under build\Win64\Release\.
Contents
| Path | Description |
|---|---|
EncodingMCP.dpr |
Main program (console application) |
src/MCP.*.pas |
Stdio transport, JSON-RPC 2.0, server dispatcher |
src/Encoding.*.pas |
Detection, heuristics, workspace, cache |
src/FileIO.*.pas |
Encoding-aware file reading/writing |
src/Tools.*.pas |
MCP tools (read/write/detect/override) |
tests/ |
DUnitX test suite |
build.bat |
Build script (main program + tests) |
Requirements
To use: Windows x64. Nothing else — it is a single standalone .exe.
To build from source:
- Delphi 12.3 (Studio 23.0) or Delphi 13.1 (Studio 37.0) — uses only RTL
(
System.JSON,System.SysUtils,System.Classes,System.IOUtils,System.Generics.*)
build.bat automatically searches for Delphi in standard locations and selects
the newest available version:
| Delphi version | Studio number | Path to rsvars.bat |
|---|---|---|
| 13.1 | 37.0 | C:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat |
| 12.3 | 23.0 | C:\Program Files (x86)\Embarcadero\Studio\23.0\bin\rsvars.bat |
If your installation is elsewhere, set the RSVARS environment variable
before running build.bat.
Build
build.bat
Building is done via msbuild /t:Rebuild against the two .dproj files —
always a full build, so stale DCUs can never hide errors. Output goes to
build\$(Platform)\$(Config)\ — by default build\Win64\Release\. Change
PLATFORM/CONFIG in build.bat to build differently (e.g. Win32
or Debug).
You can also open EncodingMCP.dproj directly in RAD Studio and build from there.
Installation
The server communicates via stdio (stdin/stdout JSON-RPC). Any MCP client that supports stdio transport can use it. Below are examples for the most popular clients.
In all examples, replace <PATH> with the absolute path to
EncodingMCP.exe (either downloaded from Releases or built locally).
Backslashes in JSON must be escaped as \\.
Workspace roots
The server only serves files inside its allowed workspace roots (see Security). Without configuration it falls back to the workspace containing the directory the client starts it in, which is often not your project. Declare the roots explicitly in the client config instead.
There are two equivalent ways; both accept any number of roots:
-
args— one array element per root:"command": "<PATH>", "args": ["C:\\Projects\\MyApp", "C:\\Projects\\Shared", "D:\\Components"] -
env— theENCODING_MCP_WORKSPACEvariable with a semicolon-separated list:"command": "<PATH>", "args": [], "env": { "ENCODING_MCP_WORKSPACE": "C:\\Projects\\MyApp;C:\\Projects\\Shared;D:\\Components" }
If both are present, the roots are combined. Each root must be an absolute path
to an existing directory — anything else is skipped with a warning on stderr.
Sub-directories are included automatically, so list the top-level folders only.
The first root is the default for set_encoding_override when no directory
is given. Roots advertised by the MCP client during initialize are added to
the configured set as well.
GitHub Copilot
Copilot CLI (also used by IDE integrations that embed the CLI, e.g. RAD
Studio): edit %USERPROFILE%\.copilot\mcp-config.json for a global setup, or
.mcp.json in the repository root for a per-project setup:
{
"mcpServers": {
"encoding-bridge": {
"type": "local",
"command": "C:\\Tools\\EncodingMCP.exe",
"args": ["C:\\Projects\\MyApp", "C:\\Projects\\Shared"],
"tools": ["*"]
}
}
}
VS Code (Copilot Chat agent mode): edit .vscode\mcp.json in the
workspace, or add the same block under "mcp" → "servers" in the user
settings.json:
{
"servers": {
"encoding-bridge": {
"type": "stdio",
"command": "C:\\Tools\\EncodingMCP.exe",
"args": [],
"env": {
"ENCODING_MCP_WORKSPACE": "${workspaceFolder};C:\\Projects\\Shared"
}
}
}
}
VS Code expands ${workspaceFolder} to the open folder, so the same file can
be committed and used by the whole team. Restart the Copilot session (CLI) or
run MCP: Restart Server (VS Code) after changing the config.
Windsurf / Codeium
Edit %USERPROFILE%\.codeium\windsurf\mcp_config.json:
{
"mcpServers": {
"encoding-bridge": {
"command": "<PATH>",
"args": [],
"disabled": false
}
}
}
Restart Windsurf after the change.
Claude Code
claude mcp add encoding-bridge "<PATH>"
Claude Desktop
Edit the config file:
- Windows:
%APPDATA%\Claude\claude_desktop_config.json - macOS:
~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"encoding-bridge": {
"command": "<PATH>",
"args": []
}
}
}
Cursor
Edit .cursor/mcp.json in your project (or the global config):
{
"mcpServers": {
"encoding-bridge": {
"command": "<PATH>",
"args": []
}
}
}
Other MCP clients
Any client that supports MCP stdio transport can launch the EXE directly.
Protocol: JSON-RPC 2.0 over stdin/stdout. Server name: encoding-bridge.
Debug logging
Set the environment variable ENCODING_MCP_LOG_LEVEL=debug for detailed logging
on stderr. Supported levels: debug, info (default), warning, error.
Available tools
read_text_file
Reads a text file with automatic encoding detection and returns the content as UTF-8 along with metadata.
| Parameter | Type | Description |
|---|---|---|
path |
string | Absolute path to the file |
head |
integer | Optional: only first N lines |
tail |
integer | Optional: only last N lines |
startLine |
integer | Optional: 1-based line number to start reading from. Use with endLine for a specific range. Takes priority over head/tail. |
endLine |
integer | Optional: 1-based line number to stop at (inclusive). Use with startLine for a specific range. Takes priority over head/tail. |
contextLines |
integer | Optional: extra lines to include before and after the startLine/endLine range. Only applies when a line range is specified. |
metadataOnly |
boolean | Optional: if true, return only metadata (encoding, lineEnding, totalLines, etc.) without file content. |
searchText |
string | Optional: search for lines containing this text (case-insensitive). Returns matching lines with line-number prefixes, separated by ... between non-contiguous regions. Use contextLines for surrounding context. Takes priority over head/tail/startLine/endLine. |
Output (JSON in content[0].text):
{
"path": "...",
"encoding": "Windows-1252",
"hasBom": false,
"lineEnding": "CRLF",
"confidence": 0.95,
"source": "detected",
"format": "text",
"fromCache": false,
"bytesRead": 1234,
"totalLines": 42,
"lineNumberStart": 1,
"returnedLines": 42,
"matchCount": 3,
"content": "#include \"legacy.h\" ..."
}
sourcetells where the encoding came from:bom,fileOverride,patternOverrideordetected.formatistext. Binary files that merely look like text are refused instead of being mangled; for example a Delphi form stored in binary format is rejected with an error andformat: "binary-dfm".- For a pure-ASCII file the response also contains
writeRecommendation: "Windows-1252"and anote:ASCII(even with high confidence) only means that no byte above 0x7F exists, so the codepage cannot be determined. See Encoding policy.
write_text_file
Writes UTF-8 content to a file in the correct encoding. If the file exists, its encoding is re-checked on disk before every write (changes made by the IDE, git or other tools are respected), and per-file and pattern overrides apply even if the file has never been read. New files default to UTF-8 with BOM. Every write holds an exclusive lock on the file (see Concurrency).
| Parameter | Type | Description |
|---|---|---|
path |
string | Absolute path |
content |
string | UTF-8 content |
encoding |
string | Optional target encoding (UTF-8, Windows-1252, ...). Changing only the encoding keeps the existing line endings; the BOM is kept, added when switching from an 8-bit encoding to Unicode, and dropped for 8-bit encodings. |
lineEnding |
string | Optional (CRLF/LF/CR). Default preserves existing. |
hasBom |
boolean | Optional. Default preserves existing (true for new UTF files). true on a pure-ASCII file writes UTF-8 with BOM. |
createIfMissing |
boolean | Default true |
dryRun |
boolean | Optional: report encoding, BOM, line ending, upgrades and warnings without writing |
force |
boolean | Optional (default false): allow replacing a binary DFM with text |
Output (JSON in content[0].text):
{
"path": "...",
"encoding": "Windows-1252",
"hasBom": false,
"lineEnding": "CRLF",
"bytesWritten": 1234,
"created": false,
"source": "detected",
"previousEncoding": "ASCII",
"upgradedFrom": "ASCII",
"warnings": ["..."]
}
encoding is always the encoding actually written. source is bom,
fileOverride, patternOverride, detected, explicit (the encoding
parameter) or default (new file). previousEncoding is the encoding of the
existing file on disk. upgradedFrom and warnings appear only when the
encoding had to change. dryRun: true is echoed when set.
Encoding policy
Applies to write_text_file, write_text_files and edit_text_file:
- ASCII upgrade: a pure-ASCII file has no byte that reveals its codepage.
When non-ASCII characters are written to it, the file becomes Windows-1252
(the Delphi default) — never UTF-8 without BOM, which Delphi would misread as
ANSI (
øinstead ofø). The response reportsupgradedFrom: "ASCII"and a warning. Use an override,encodingorhasBom: trueto get UTF-8 with BOM instead. - Unrepresentable characters: if the target encoding cannot represent a
character (e.g. an emoji in Windows-1252), nothing is written. The error
response contains
unrepresentableChars(count) andsample; there is no silent?substitution. - Binary DFM guard: an existing Delphi form stored in binary format is never
overwritten with text unless
force: trueis given.read_text_fileandedit_text_filerefuse such files withformat: "binary-dfm".
edit_text_file
Edits a text file by search/replace or line-range replacement, preserving the
file's original encoding, BOM, and line-ending style. The
encoding policy applies. The edit is rejected with
reason: "modified" if another process changed the file between the read and
the write (see Concurrency).
| Parameter | Type | Description |
|---|---|---|
path |
string | Absolute path to the file (must exist) |
oldText |
string | Text to find and replace. Leave empty for range mode. |
newText |
string | Replacement text (required) |
startLine |
integer | Optional: 1-based start line for range replacement |
endLine |
integer | Optional: 1-based end line (inclusive) for range replacement |
maxReplacements |
integer | Optional: max replacements (default 1). 0 = unlimited. |
dryRun |
boolean | Optional: if true, compute the result without writing to disk. |
edits |
array | Optional: array of atomic edits (see below). Overrides top-level oldText/newText. |
Modes:
- Search/replace: provide
oldText+newText. IfmaxReplacementsis 1 (default) and multiple matches exist, an error is returned with the match count. - Range replacement: provide
startLine+endLine+newTextwith emptyoldText. The line range is replaced entirely; an emptynewTextdeletes the lines including their line breaks.startLine= total lines + 1 appends at the end of the file; a largerstartLineis an error. A trailing line break innewTextdoes not add an extra blank line. - The two modes are mutually exclusive per edit:
oldTexttogether withstartLine/endLineis rejected instead of silently ignoring the range. - Multi-edit (atomic): provide an
editsarray. If any edit fails, nothing is written. Each item supportsoldText,newText,startLine,endLine,maxReplacements.
Line endings: oldText and newText may use \n; both are normalized to
the file's line-ending style before matching and inserting. For files with mixed
line endings the dominant style is used.
Multi-edit semantics:
- All line ranges refer to the original file, regardless of their order in the array. They are applied from the end of the file towards the beginning, so one range edit never shifts the line numbers of another.
- Overlapping ranges, and two edits appending at the same position, are rejected.
- Search/replace edits are applied afterwards, in array order, to the result of the range edits.
- If an
oldTextwould match a different number of times before and after the range edits, the order is ambiguous and the call is rejected. - Error messages that concern one specific edit are prefixed with
Edit N:. Errors about the call as a whole (overlapping ranges, emptyeditsarray) have no prefix.
Output (JSON in content[0].text):
{
"path": "...",
"encoding": "Windows-1252",
"hasBom": false,
"lineEnding": "CRLF",
"bytesWritten": 1234,
"replacements": 1,
"changed": true,
"diff": "@@ -1,3 +1,3 @@\n context\n-old line\n+new line\n context\n"
}
upgradedFrom and warnings are added when the encoding policy
changed the encoding; dryRun: true is echoed when set. replacements counts
search/replace matches that were replaced plus 1 for each range edit.
Diff format: the diff field is only present when changed is true. It is
a unified diff with 3 lines of context; nearby changes share one hunk, distant
changes get separate hunks (@@ -a,b +c,d @@), so unchanged lines between two
edits are not repeated. Diff lines are always separated by \n, whatever the
file's line endings — the file itself is written in its own style. A line whose
terminator differs from the file's style is marked with [LF], [CR] or
[CRLF], and a missing final line break is shown as
\ No newline at end of file.
read_text_files
Reads multiple files in a single call. Reduces MCP round-trips during
cross-file refactoring. Each file entry supports the same parameters as
read_text_file. Errors for individual files are reported inline without
aborting the batch.
| Parameter | Type | Description |
|---|---|---|
files |
array | Array of file specifications (see below) |
Each entry in files:
| Parameter | Type | Description |
|---|---|---|
path |
string | Absolute path to the file (required) |
head |
integer | Optional: first N lines |
tail |
integer | Optional: last N lines |
startLine |
integer | Optional: 1-based start line |
endLine |
integer | Optional: 1-based end line (inclusive) |
contextLines |
integer | Optional: extra context lines |
metadataOnly |
boolean | Optional: skip content |
searchText |
string | Optional: case-insensitive line search |
Output (JSON in content[0].text):
{
"totalFiles": 3,
"succeeded": 2,
"failed": 1,
"results": [
{ "path": "...", "encoding": "UTF-8", "content": "...", ... },
{ "path": "...", "encoding": "Windows-1252", "content": "...", ... },
{ "path": "...", "error": "File not found: ..." }
]
}
write_text_files
Writes multiple files in a single call with encoding-aware conversion. Each file entry can specify its own encoding, lineEnding, and hasBom options. Errors for individual files are reported inline without aborting the batch.
| Parameter | Type | Description |
|---|---|---|
files |
array | Array of file specifications (see below) |
Each entry in files:
| Parameter | Type | Description |
|---|---|---|
path |
string | Absolute path to the file (required) |
content |
string | UTF-8 content to write (required) |
encoding |
string | Optional: target encoding (UTF-8, Windows-1252, etc.) |
lineEnding |
string | Optional: CRLF, LF, or CR |
hasBom |
boolean | Optional: write a BOM |
createIfMissing |
boolean | Optional (default true): create if not exists |
dryRun |
boolean | Optional: report the result of the write without writing |
force |
boolean | Optional (default false): allow replacing a binary DFM with text |
Output (JSON in content[0].text):
{
"totalFiles": 2,
"succeeded": 2,
"failed": 0,
"results": [
{ "path": "...", "encoding": "UTF-8", "bytesWritten": 123, "created": true, ... },
{ "path": "...", "encoding": "Windows-1252", "bytesWritten": 456, "created": false, ... }
]
}
list_files
Lists files in a directory recursively, optionally filtered by glob pattern. Returns relative paths from the specified directory.
| Parameter | Type | Description |
|---|---|---|
path |
string | Absolute path to the directory to list (must be within workspace) |
pattern |
string | Optional: glob pattern to filter files (e.g. *.c, *.sql, *.pas) |
Lock and temporary files of writes in progress (*.lock, *.<pid>-<guid>.tmp)
are never listed.
Output (JSON in content[0].text):
{
"path": "C:/Projects/MyApp",
"totalFiles": 12,
"files": ["src/legacy.c", "scripts/report.sql", "config.ini", "Forms/Main.pas", ...]
}
detect_encoding
Analyses the whole file (no size limit) with exactly the same resolution as
read_text_file (BOM, overrides, detection), so both tools always report the
same encoding. Returns format, encoding, hasBom, confidence,
lineEnding, source, bytesRead and the candidate scores, plus
writeRecommendation/note for pure-ASCII files. A binary DFM is reported
with format: "binary-dfm" and a note instead of an encoding.
| Parameter | Type |
|---|---|
path |
string |
set_encoding_override
Sets or removes a manual encoding override for one file or for a pattern.
| Parameter | Type | Description |
|---|---|---|
path |
string | Absolute path of a single file (either path or pattern) |
pattern |
string | File-name pattern (*.pas) or path pattern relative to the workspace root (src/**/*.dfm) |
directory |
string | Optional, pattern only: a directory inside the workspace that selects the workspace root. Defaults to the first workspace root. |
encoding |
string | Encoding to apply. Required unless remove is true. |
remove |
boolean | If true, remove the override instead of setting it |
- Patterns without
/match the file name in any folder. Patterns with/match the path relative to the workspace root and support*,?and**. Other glob syntax is rejected with an error instead of being accepted silently. - Pattern overrides are stored in the outermost sidecar and therefore apply
to the whole repository, including subfolders with their own
.dproj. A matching pattern in a nearer sidecar takes precedence. - With
remove, a pattern is removed from every sidecar betweendirectoryand the outermost root; the response lists them inremovedFrom. - Overrides take precedence over detection, but a BOM always wins.
- The response contains
sidecar,root,matchesandappliesTo, so it is clear where the override was stored and which files it covers.
EncodingMCP MCP questions
What tools does EncodingMCP MCP provide?
It exposes 11 tools, including path, head, tail, content, encoding.
What encodings does it support?
It supports a wide range of encodings including Windows-1252, ISO-8859-x, UTF-16, and others, detecting them via BOM, strict UTF-8 validation, or codepage heuristics.
Does it work on non-Windows systems?
No, the server is a standalone Windows x64 executable.
How does it handle file encoding detection?
It uses BOM, strict UTF-8 validation, and codepage heuristics to detect encoding, and can remember encodings per file in a .encoding-mcp.json sidecar.