Metadata-Version: 2.4
Name: greenlight-mcp
Version: 0.6.0
Summary: See what your MCP server is actually doing -- a transparent stdio/HTTP proxy and live trace viewer for the Model Context Protocol.
License: MIT
Project-URL: Homepage, https://github.com/bradsward/greenlight
Project-URL: Issues, https://github.com/bradsward/greenlight/issues
Keywords: mcp,model-context-protocol,mcp-inspector,debugging,proxy,cli,llm
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Debuggers
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: rich>=13.0
Provides-Extra: serve
Requires-Dist: mcp>=1.0; extra == "serve"
Provides-Extra: dev
Requires-Dist: mcp>=1.0; extra == "dev"
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Requires-Dist: hypothesis>=6.0; extra == "dev"
Dynamic: license-file

```
  .-----.
  |  🔴  |
  |  🟡  |
  |  🟢  |
  '-----'
   greenlight
```

# greenlight

[![PyPI](https://img.shields.io/pypi/v/greenlight-mcp)](https://pypi.org/project/greenlight-mcp/)
[![Python](https://img.shields.io/pypi/pyversions/greenlight-mcp)](https://pypi.org/project/greenlight-mcp/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
[![tests](https://github.com/bradsward/greenlight/actions/workflows/tests.yml/badge.svg)](https://github.com/bradsward/greenlight/actions/workflows/tests.yml)

Listed in [awesome-mcp-devtools](https://github.com/Epistates/awesome-mcp-devtools#development-tools).

**See what your MCP server is actually doing.**

![greenlight tail showing a live trace with a slow call in yellow and a failed tool call in red with its real error message, then greenlight check listing the bugs it found in a broken server](examples/demo.gif)

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

```bash
uvx greenlight-mcp run -- npx -y @modelcontextprotocol/server-everything stdio
```

Then, in another terminal:

```bash
uvx greenlight-mcp tail -f
```

That's the official MCP reference server, with nothing to configure. It
needs [uv](https://docs.astral.sh/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:

```json
{
  "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:

```bash
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:

```bash
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

```bash
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`](examples/bad-session.jsonl):
a session recorded through the proxy against
[`tests/bad_fixture_server.py`](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.

### 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:

```bash
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

```bash
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](https://github.com/modelcontextprotocol/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.

## 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](CHANGELOG.md): what changed in each release.
- [`notes/`](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.
