.-----.
| 🔴 |
| 🟡 |
| 🟢 |
'-----'
greenlight
greenlight
Listed in awesome-mcp-devtools.
See what your MCP server is actually doing.
When an MCP integration misbehaves, you're usually debugging blind: the client says a tool "failed" or quietly never calls it, and you can't see what was sent or what came back. Greenlight is a transparent proxy that sits between your real client (Claude Desktop, Claude Code, Cursor, Windsurf) and your real server, relays every byte unchanged, and records every JSON-RPC message so you can watch it live, read it back later, or lint it.
tail: a live, color-coded trace. Green means ok, yellow means slow, red means failed, and failed calls show the tool's real error message.check: catches server bugs that never show up as a failed call, like printing to stdout, tool schemas the Claude API rejects, HTTP 401s that clients report as "failed to connect", or requests that never got a response.stats: a summary with a CI-friendly exit code.serve: exposes the trace as an MCP server, so your coding agent can read the failures itself.
Try it in 30 seconds
uvx greenlight-mcp run -- npx -y @modelcontextprotocol/server-everything stdio
Then, in another terminal:
uvx greenlight-mcp tail -f
That's the official MCP reference server, with nothing to configure. It
needs uv and Node.js. Prefer pip?
pip install greenlight-mcp, then use greenlight wherever this README
says uvx greenlight-mcp.
Use it with your real client
Wrap the server command in your client's MCP config:
{
"mcpServers": {
"my-server": {
"command": "uvx",
"args": ["greenlight-mcp", "run", "--name", "my-server", "--",
"npx", "-y", "@some/mcp-server"]
}
}
}
Or let Greenlight write the snippet for you:
greenlight wrap
wrap finds your Claude Desktop, Claude Code, Cursor, or Windsurf config
and prints each server entry rewritten to run through Greenlight. It's
read-only and never edits the file, and it only shows the command and
args it changes, never your env.
It also writes full paths: Python's, and your server's own command
(npx becomes the path your terminal resolves it to). Clients often
launch servers with a smaller PATH than your terminal has, which is where
most spawn npx ENOENT / "Server disconnected" failures come from. It
warns if a command isn't on your PATH either, or if an argument is a
relative path the client won't be able to find. If a server still can't
be started, greenlight run says why on stderr instead of dumping a
traceback.
Logs go to ~/.greenlight/sessions/, whichever directory the client
launched the server from. To put them somewhere else, use --log-dir or
set GREENLIGHT_SESSIONS_DIR. If the log directory can't be written,
Greenlight warns on stderr and keeps relaying. It never stops your server
from starting.
For a remote Streamable HTTP server, proxy the URL instead:
greenlight run --http http://127.0.0.1:9000/mcp
Point your client at the local URL it prints. You get the same logs, and
the same tail, check, and stats.
Commands
greenlight tail # replay the most recent session
greenlight tail -f # follow a live session
greenlight check # lint the session for protocol/server bugs
greenlight stats # counts, latency by method, pass/fail
check: bugs that don't look like failures
$ greenlight check examples/bad-session.jsonl
session: examples/bad-session.jsonl
error stdout-pollution server wrote 1 non-JSON-RPC line(s) to stdout, e.g. 'server
starting up...'. In the stdio transport stdout is for
protocol messages only -- send logs and prints to stderr.
Clients may drop these lines, fail to parse, or disconnect.
error tool-schema-combinator tool 'either_or' inputSchema has top-level anyOf. It's valid
JSON Schema, but the Claude API rejects it with a 400, and
in Claude Code a single such tool can fail every request in
the session. Make the root a plain object (nested anyOf is
fine) and enforce the either/or rule in the tool handler
error tool-duplicate-name tool name 'ok_tool' appears more than once in a single
tools/list response
warning unanswered-request 'tools/call' (id 3) never got a response from the server
...
Real output, abridged, from examples/bad-session.jsonl:
a session recorded through the proxy against
tests/bad_fixture_server.py, a server
built to make these mistakes on purpose.
It also flags non-object input schemas, tool names some clients reject, responses to ids nobody sent, and very slow calls or slow startup.
Two that are worth knowing about: a top-level oneOf/anyOf/allOf
in a tool's input schema is valid JSON Schema, but the Claude API rejects
it with a 400, and in Claude Code a single such tool can fail every
request in the session without saying which one. And for HTTP servers,
a 401/403 is rejected before JSON-RPC, so a client just says "failed
to connect"; Greenlight records the status and WWW-Authenticate header
and check reports it.
Errors exit non-zero, and so do warnings with --strict. --json gives
machine-readable output.
--capture-payloads: what was actually sent (opt-in)
By default Greenlight records metadata only: methods, ids, timings and error text. It never stores your requests' arguments or the content a server returns, so the logs are safe to leave on. When a call fails and you need to see what the client really sent:
greenlight run --capture-payloads -- npx -y @some/mcp-server
greenlight tail --payloads
17:50:42.604 -> tools/call
params: {"name":"boom","arguments":{},"_meta":{}}
17:50:42.604 <- tools/call 0.81ms FAILED (tool error): Error executing tool boom: this tool always fails, on purpose
args: {"name":"boom","arguments":{},"_meta":{}}
result: {"content":[{"text":"Error executing tool boom: this tool always fails, on purpose","type":"text"}],"isError":true}
Real output. A failed result carries the arguments that caused it, under
args:, next to what came back.
Secrets are redacted before anything is written: values under
secret-looking keys (api_key, authorization, password, token, ...),
and well-known shapes like API keys, GitHub and Slack tokens, JWTs,
private keys and user:password@ URLs. Long base64 data, like a
screenshot, becomes a size note, and each payload is cut at 8192
characters (--payload-limit).
That's best effort, not a guarantee. A secret in free text that matches
none of those shapes gets through, so treat captured logs as sensitive.
They're created owner-only, and the serve tools only include payloads
when your agent asks for them (include_payloads).
CI
Both check and stats exit non-zero on failure. stats fails on tool
errors (result.isError) as well as JSON-RPC errors. That matters
because a failed tool call arrives as a successful JSON-RPC response,
and it's easy to miss if you only check for the obvious kind:
greenlight run --log-dir ./trace -- python my_server.py < scripted_session.jsonl
greenlight check --log-dir ./trace && greenlight stats --log-dir ./trace
serve: let your agent read the trace
pip install "greenlight-mcp[serve]"
greenlight serve
Add greenlight serve to your coding agent's MCP config, and it can call
get_failures, get_problems, get_trace, or get_session_stats
itself. It sees the real error messages, so you don't have to paste
terminal output.
Server won't connect?
"Server disconnected", spawn npx ENOENT, "Connection closed (-32000)":
the usual cause is that your client starts the server with a smaller PATH,
and a different working directory, than your terminal has. So the command
works for you and not for the client.
greenlight wrap writes full paths into the config it suggests, and warns
about commands that aren't on your PATH and relative paths the client
can't resolve. If the server still can't be started, greenlight run
says what was missing on stderr (which clients usually capture in the
server's log) instead of dumping a traceback. Once it starts but misbehaves,
greenlight check and tail show what it did.
Why not MCP Inspector?
They do different jobs. Inspector is a UI you drive by hand to test a server in isolation. Greenlight sits in the path of your real client and records what actually happened in a real session. That includes the client's exact requests, which is usually where the bug turns out to be. It also leaves a durable log you can check in CI.
Other proxies exist too. mcpsnoop
is a Go transparent proxy with a live terminal UI and call replay, and if
a proxy to watch and replay traffic is all you want, it's worth a look.
Greenlight leans toward telling you what's wrong: check for server bugs
and for tool schemas the Claude API rejects, HTTP 401s that never show up
as JSON-RPC errors, the real error message from a failed tool call, and
serve, so your coding agent can read the trace itself. It's a Python
package you can run with uvx.
How it works
greenlight run spawns your server as a subprocess and relays
stdin/stdout on two threads. It parses each line as JSON-RPC, correlates
each response with its request by id (so every response knows its method
and latency), and appends the result to a JSONL file. The one rule
everything depends on: nothing but the server's own bytes ever reaches
Greenlight's stdout. Status output goes to stderr, and logs go to disk.
The base install depends only on rich. Python 3.10+, tested on Linux
and Windows.
More
- CHANGELOG.md: what changed in each release.
notes/: a running engineering log of what broke, how it was found, and why each fix is what it is.- Issues and PRs welcome. If Greenlight misreads a real session, the log file is the most useful thing you can attach.
Metadata
Release files for greenlight-mcp 0.7.0
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 | |
|---|---|---|---|---|
| greenlight_mcp-0.7.0.tar.gz | 66.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| greenlight_mcp-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 107.1 kB
Release files / greenlight_mcp-0.7.0.tar.gz
| Download URL | greenlight_mcp-0.7.0.tar.gz |
|---|---|
| Size | 66.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
52a7a2529baf2ee1030f7662a988596366583874035239b8deaacd3a21ce36e8
|
|
BLAKE2b-256 checksum How to use checksums |
533040badd741477916b54519a83fbf1d11d93075eb856b6f2046dadedfc64cc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / greenlight_mcp-0.7.0-py3-none-any.whl
| Download URL | greenlight_mcp-0.7.0-py3-none-any.whl |
|---|---|
| Size | 40.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ed4ec156e38aec31469e04110f2a2af916a45d13f4adf2dbcdf28f086fa12b96
|
|
BLAKE2b-256 checksum How to use checksums |
03e1444c1c1803ba7b3a8194b52b320cf003071ac39348184505948b65fbc23d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Security for greenlight-mcp 0.7.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A 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.