gglint
Keep the MCP servers of every coding agent in your repository in sync.
Claude Code, Codex, Cursor and OpenCode each read MCP servers from their own config file, in
their own format. In a repository set up for more than one of them, those files drift apart:
a server is added to .mcp.json but not to .cursor/mcp.json, a version is pinned in one
file and not the other, or an agent has no MCP config at all. gglint finds every difference
and adds the missing servers for you, written in each agent's own format.
- One view of every agent. A table shows which agent gets which server, and how each one differs.
- Safe fixes.
--fixonly adds servers. It never changes or removes an existing entry, and keeps your comments and formatting.--overwrite, when you ask for it, makes each server start with the same command and arguments everywhere. - Each agent's own format. A server is translated between JSON and TOML, between each agent's syntax for environment variables, and between local and remote server types.
- Fast and read-only by default. gglint reads a handful of files at fixed paths and finishes in under a millisecond. It never starts a server or downloads anything.
- Built for CI. It exits with status 1 when it finds a problem, and
--format linesprints one problem per line.
In November 2026 we checked the 258 most-starred GitHub repositories set up for two or more of these agents. Of the 179 that configure MCP servers, 128 (72%) had at least one of the problems gglint reports.
Quick start
From the root of a repository, run gglint with uv. Nothing needs to be installed first:
uvx gglint mcp # report every problem
uvx gglint mcp --fix # add the missing servers
git diff # review what changed
Without uv, pipx run gglint mcp does the same.
Installation
To install gglint and run it as gglint:
uv tool install gglint
pipx install gglint
pip install gglint
gglint is a single compiled program, published on PyPI with prebuilt wheels for Linux (x86_64 and aarch64, glibc and musl), macOS (Intel and Apple silicon) and Windows (x64 and ARM64). Any Python 3.8 or later can install it.
To build it from source, install a Rust toolchain (1.88 or later) and run:
cargo install --git https://github.com/corridor/gglint --locked
Usage
gglint mcp [--fix [--create] [--overwrite]] [--format table|lines] [PATH]
gglint mcp --servers [PATH]
| Option | Effect |
|---|---|
| (none) | Report every problem and draw each server in each agent. |
--fix |
Add the servers missing from existing config files, then report what's left. |
--fix --create |
Also create the config file of an agent that has its own directory but no MCP config. |
--fix --overwrite |
Also make each server start with the same command and arguments in every agent. |
--format lines |
Print one line per problem instead of the table, for editors and CI. |
--servers |
List every configured server, file by file, without comparing them. |
PATH |
The repository's root. Defaults to the current directory. |
| Exit status | Meaning |
|---|---|
0 |
Every agent gets the same servers, set up the same way. |
1 |
gglint found a problem, one is left after --fix, or there are no MCP config files. |
Colours are used when standard output is a terminal and NO_COLOR isn't set. The summary
line, with counts and timing, goes to standard error.
What gglint checks
| Rule | Reported when | Fixed by |
|---|---|---|
missing-server |
A server in one agent's config file is missing from another agent's. | --fix |
missing-config |
An agent has its own directory but no MCP config file, while other agents have servers. | --fix --create |
different-server |
The same server runs another version or has other arguments. | --fix --overwrite |
| The same server runs another program or URL, has other environment variables or headers, or is turned off in one agent. | Reported only | |
renamed-server |
The same server has a different name in some agents. | Reported only |
Differences in how a server is set up are sometimes deliberate, such as each agent running
its own runtime or port, so --fix only reports them. When the command lines should match,
--overwrite makes them match; everything else is left to you.
Matching servers. Servers are matched by name, then by what they run, so a server named differently in one agent is still recognised as the same server. The way a package is launched doesn't count as a difference:
npx -y pkg,npx --yes pkg@latest,pnpm dlx pkg,yarn dlx pkg,bunx pkgandbun x pkgall run the npm packagepkg.uvx pkg,uv tool run pkgandpipx run pkgall run the PyPI packagepkg.npx mcp-remote <url>reaches the remote server at<url>.- No version and
latestboth mean the newest version. ${KEY}in Claude Code and${env:KEY}in Cursor refer to the same environment variable.
Environment variables and headers are compared by name only, since their values can be secrets.
Supported agents
| Agent | MCP config file | Agent directory | Format |
|---|---|---|---|
| Claude Code | .mcp.json |
.claude/ |
JSON, mcpServers |
| Codex | .codex/config.toml |
.codex/ |
TOML, [mcp_servers.<name>] |
| Cursor | .cursor/mcp.json |
.cursor/ |
JSON, mcpServers |
| OpenCode | opencode.json, opencode.jsonc |
.opencode/ |
JSON with comments, mcp.servers or mcp |
gglint compares the agents that have an MCP config file or their own directory at the
repository's root. A CLAUDE.md, .cursorrules or AGENTS.md file on its own doesn't
count, because it doesn't show that the agent is set up in the repository.
How fixing works
--fix adds and never changes or removes anything that is already written.
- A missing server is copied from the first config file where it's turned on, in the order Claude Code, Codex, Cursor, OpenCode, and added to each config file that lacks it.
- A config file is created only with
--create, and only for an agent with its own directory. It receives every server that is turned on in the other agents. - A command line is changed only with
--overwrite. A server that runs another version of the same program, or the same program with other arguments, gets the command and arguments of that same first config file. Its environment variables, headers, on/off state and keys only one agent has, such as Codex'scwd, are kept. Review the result withgit diff: a difference such as a runtime chosen per agent, or a path that starts with Cursor's${workspaceFolder}, may have been deliberate. - JSON is edited with jsonc-parser and TOML with toml_edit, so comments and formatting are kept.
- Running
--fixa second time changes nothing. - A file excluded by
.gitignoreis still updated, and markedgitignoredin the output, because the change won't be committed.
References to environment variables are rewritten in each agent's syntax, in values and in command lines:
| Agent | Environment variable KEY |
|---|---|
| Claude Code | ${KEY} |
| Cursor | ${env:KEY} |
| OpenCode | {env:KEY} |
| Codex | env_vars = ["KEY"], env_http_headers, or bearer_token_env_var for Authorization: Bearer |
Remote servers get "type": "http" in Claude Code, or "sse" for a URL ending in /sse.
OpenCode servers get "type": "local" or "remote", with the command and its arguments in
one list.
A server is skipped, and listed under Not added:, when its setup can't be expressed in the
other agent's format: for example Claude Code's ${KEY:-default}, Cursor's
${workspaceFolder}, or an environment variable inside a command line, which Codex doesn't
expand.
Example
$ gglint mcp --fix
Changed 3 files:
.mcp.json added linear
.codex/config.toml added github, playwright, linear
.cursor/mcp.json added github
Review the changes with `git diff`.
The same github server, written for Codex and Cursor:
# .codex/config.toml
+[mcp_servers.github]
+url = "https://api.githubcopilot.com/mcp/"
+bearer_token_env_var = "GITHUB_TOKEN"
# .cursor/mcp.json
+ "github": {
+ "url": "https://api.githubcopilot.com/mcp/",
+ "headers": {
+ "Authorization": "Bearer ${env:GITHUB_TOKEN}"
+ }
+ }
With --overwrite, Cursor's playwright also starts as in Claude Code:
$ gglint mcp --fix --overwrite
Changed 3 files:
.mcp.json added linear
.codex/config.toml added github, playwright, linear
.cursor/mcp.json added github; updated playwright
# .cursor/mcp.json
- "args": ["-y", "@playwright/mcp@latest"]
+ "args": ["-y", "@playwright/mcp@0.0.78", "--headless"]
$ gglint mcp --fix --create
Changed 1 file:
opencode.json (new) added github, playwright, linear
Output formats
--format lines prints one problem per line: where it is, the rule, and a description, with
the server's other locations indented below it.
$ gglint mcp --format lines
./.mcp.json:3:5 missing-server `github` is set up for Claude Code but not for Codex (.codex/config.toml) or Cursor (.cursor/mcp.json)
./.mcp.json:8:5 different-server `playwright` runs different versions of the npm package `@playwright/mcp`: 0.0.78 in Claude Code; the newest in Cursor
also at ./.cursor/mcp.json:3:5
./.mcp.json:3:5 missing-config OpenCode has a .opencode directory but no opencode.json, so it gets none of the 3 MCP servers set up for Claude Code and Cursor
--servers lists every server as it's written. Environment variables and headers are shown
by name, never by value.
$ gglint mcp --servers
./.mcp.json:3:5 claude-code github https://api.githubcopilot.com/mcp/ (headers=[Authorization])
./.mcp.json:8:5 claude-code playwright npx -y @playwright/mcp@0.0.78 --headless
./.cursor/mcp.json:3:5 cursor playwright npx -y @playwright/mcp@latest
./.cursor/mcp.json:7:5 cursor linear https://mcp.linear.app/sse
Continuous integration
Fail a pull request when the agents' MCP servers drift apart:
# .github/workflows/mcp.yml
name: MCP servers
on: [pull_request]
jobs:
gglint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pipx run --spec gglint==0.1.1 gglint mcp --format lines
Security
- Read-only by default. Without
--fix, gglint reads files and writes nothing. - Nothing is executed or fetched. MCP servers aren't started, packages aren't resolved, and environment variables aren't expanded. It's safe to run on an untrusted repository.
- Secrets stay out of the output. Environment variables and headers are printed by name only.
- One external program. During
--fix, gglint runsgit check-ignoreto mark files your.gitignoreexcludes.
Limitations
- Servers added by Claude Code plugins, enabled in
.claude/settings.json, aren't read. A server provided by a plugin can be reported as missing from Claude Code. - User-level settings such as
~/.claude.jsonor~/.codex/config.tomlaren't read. - Only config files at the repository's root are read, so each package of a monorepo is checked separately.
- VS Code and GitHub Copilot (
.vscode/mcp.json) and Gemini CLI (.gemini/settings.json) aren't supported yet. - Codex only reads
.codex/config.tomlin projects you have marked as trusted.
Development
cargo test # unit and integration tests
cargo clippy --all-targets # lints
cargo fmt # formatting
tests/corpus.rs runs --fix against a directory of real repositories and checks that
nothing existing is removed or changed, that config files are created only with --create
and only in an agent's directory, that no file becomes unreadable, that problems never
increase, and that a second run changes nothing:
GGLINT_CORPUS=path/to/clones cargo test --release --test corpus -- --ignored --nocapture
The code lives in src/harness/:
mcp.rs reads each agent's config, compare.rs finds the problems, and fix.rs writes the
fixes.
Releasing
- Update
versioninCargo.tomland add the release toCHANGELOG.md. - Merge to
main. - Publish a GitHub release tagged
v<version>, for examplev0.1.0.
The release workflow builds and tests every wheel, publishes them to PyPI, and attaches them to the release.
License
gglint is licensed under the Apache License 2.0. The licenses of the libraries compiled into it are listed in THIRD_PARTY_LICENSES.
Metadata
Release files for gglint 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| gglint-0.1.1.tar.gz | 58.9 kB | With provenance | Details for gglint-0.1.1.tar.gz |
Built distributions (wheels)
Total release size: 4.1 MB
Release files / gglint-0.1.1.tar.gz
| Download URL | gglint-0.1.1.tar.gz |
|---|---|
| Size | 58.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d064f7bfd083aec79e2e51eb9d2eedd3f391b4f36aa9b3e8448b0513742a8142
|
|
BLAKE2b-256 checksum How to use checksums |
657041d8c3f17f88f7728310fac33cf76d584f19cdbae2fa57e55e0029260671
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logRelease files / gglint-0.1.1-py3-none-win_arm64.whl
| Download URL | gglint-0.1.1-py3-none-win_arm64.whl |
|---|---|
| Size | 451.0 kB |
| Tags | Python 3 Windows ARM64 |
|
SHA-256 checksum How to use checksums |
18a462185462ebc030315204816600f764f0cb4fb2f2f4626ff7336e9e358aaf
|
|
BLAKE2b-256 checksum How to use checksums |
e66495cfd7a674502159dfac4248a88d57686bf67dd207fd7513887f572c8849
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logRelease files / gglint-0.1.1-py3-none-win_amd64.whl
| Download URL | gglint-0.1.1-py3-none-win_amd64.whl |
|---|---|
| Size | 484.1 kB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
de120e0f8f8c4290081adcd64bed54c6720aaaf8dc77445aa567899edd7e1425
|
|
BLAKE2b-256 checksum How to use checksums |
3054630bbca9abd13cd6f3aa3949b01ab44438b37059045874b573e082c1520b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logRelease files / gglint-0.1.1-py3-none-musllinux_1_2_x86_64.whl
| Download URL | gglint-0.1.1-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 582.0 kB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
0c430398b9c12172f921863e2e6d50d827c8fac5c35ec23ee11dd37b0aadeaf2
|
|
BLAKE2b-256 checksum How to use checksums |
63e2e93b941aafe28e6a47a905173ee715e7d0d4eb7398578e4ecf70d9ca2bdf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logRelease files / gglint-0.1.1-py3-none-musllinux_1_2_aarch64.whl
| Download URL | gglint-0.1.1-py3-none-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 541.4 kB |
| Tags | Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
67c1e4279eba2a03a3d1337b4d277a171ea63d1354712c54bcb7c37357285ee6
|
|
BLAKE2b-256 checksum How to use checksums |
2249b2a85642a808431118299d9402e7ea7498e91faf3eb39adf53e1156d808d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logRelease files / gglint-0.1.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | gglint-0.1.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 533.2 kB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
c5ced586b41c3dc2b45425e3a82145251f98b2de25f5a5bacf795ba49b91764d
|
|
BLAKE2b-256 checksum How to use checksums |
d14c4a2bbc69dbb2097d927d34e4fddcb70ef6c0ee48502958b0bd1d8ca37670
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logRelease files / gglint-0.1.1-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
| Download URL | gglint-0.1.1-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
|---|---|
| Size | 495.6 kB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
4061de47ef4c35401431ebf085c37fb97a32afa3645ce5172b8c42c36a51ea5f
|
|
BLAKE2b-256 checksum How to use checksums |
ac233eee8e9c67d0f1903fc0a35ffdab64541fd700436094ecf94290584c2121
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logRelease files / gglint-0.1.1-py3-none-macosx_11_0_arm64.whl
| Download URL | gglint-0.1.1-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 481.3 kB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
55ff3ec713625c01a86297896e62f321c55461fb5c7c471f3baea0317e33601a
|
|
BLAKE2b-256 checksum How to use checksums |
6091e81f813574872d1a17b89087ea8ea1b7acc2c05691b51bcc4a7dff1e7bff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logRelease files / gglint-0.1.1-py3-none-macosx_10_12_x86_64.whl
| Download URL | gglint-0.1.1-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 512.8 kB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
3304eee0bcbfb406707f0e7381efe657d7ac68893d0466818e22daba05ff022a
|
|
BLAKE2b-256 checksum How to use checksums |
a40468c5c986e2de74eaf288e77e1a79c600ef019d5238a56623412782b4940e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logSecurity for gglint 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 9 files in this release have been uploaded with provenance metadata.
Attestation for gglint-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.