Install
$ agentstack add mcp-camiloazula-coco-mcp ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Coco MCP Inspect and debug MCP servers in depth, from the command line or a native window.
Coco MCP is a tool for inspecting and debugging Model Context Protocol servers: connect to one, see what it offers, call it with any payload, read every message on the wire, and keep what you learned. It is one binary, written in Rust and drawn by GPUI: coco-mcp --cli runs a command, coco-mcp --desktop opens a native window, and both work from the same core.
Why
I wrote Coco because the way I was inspecting MCP servers kept getting in my way. I needed to send a raw JSON payload, not only what a form would let me type. I needed several servers connected at the same time, each with its own log. I needed the tool to stay fast with a long log and a large result on screen, and I needed it to remember the servers and the calls from yesterday. So Coco does those things first:
- Any payload. The Raw tab next to the generated form takes whatever
JSON you paste, checks it against the tool's schema before it is sent, and copies the request as JSON-RPC or curl exactly as it goes out.
- Many servers at once. Every server in the sidebar has its own
session, wire log and history, all connected together; switching is a click or ⌘K.
- Fast, and still fast an hour later. A native binary drawn on the GPU,
where every list, tree and text builds only the lines in view, so a result of a hundred thousand rows costs a frame what ten rows do.
- Memory. Servers, calls and snapshots persist, a call can be replayed
or opened back in the form, and each connect is diffed against the last snapshot so a breaking change in a server is noticed, not discovered.
The command line exists so the same things can be scripted, and so a breaking change can fail a CI job.
Built in Rust, drawn by GPUI
Coco is a native program from the wire to the pixels, and that shows in use:
- It starts in a blink and stays light. The window is a single native
binary: nothing to boot before the first frame, no runtime to install, no browser process beside it. Memory stays flat through a long session, since every table, log and tree is drawn from the model, not kept as markup.
- The UI is drawn on the GPU. The window is built with GPUI, the
engine the Zed editor is written in: a retained scene rendered through Metal, Vulkan or DirectX. A log drawer with thousands of messages, a schema tree with hundreds of nodes and a result of many megabytes scroll at the frame rate, and every list builds only the rows in view.
- One core, two faces. The protocol client, the storage, the schema
forms, the diff and the export formats are plain Rust crates with no UI in them. The command line and the window are two thin layers over the same code, so what one shows, the other can script, and neither can drift from the other.
- The whole UI runs headless. Because GPUI can render without a
display, every screen in this README is produced by a test that drives the real window against a mock server, and the animated tour above is assembled from those frames. What you see is what the tests saw.
- Rust end to end. No unsafe code, no
unwrapoutside tests, every
dependency under a permissive licence, audited in CI. Secrets go to the OS keyring through native bindings; the database is SQLite, compiled in.
What it does
- Connect to stdio servers (any command) and streamable HTTP servers, with
custom headers, bearer tokens or OAuth 2.1 (PKCE, dynamic registration). Secrets live in the OS keyring, never in the database.
- Speak either era of the protocol: the
initializehandshake (2024-11-05
to 2025-11-25) or the sessionless 2026-07-28 revision, chosen per server. Every feature follows the version the server agreed to and what it declared; one it cannot use stays on screen, disabled, and says why.
- Browse tools, resources, resource templates and prompts with an inline
filter and keyboard navigation.
- Call tools through a form generated from the tool's JSON Schema
(nested objects, arrays, enums, oneOf), or edit the raw JSON. Arguments are validated against the schema before they are sent.
- See responses as collapsible JSON, text, Markdown or images, with
round-trip times, in a panel of their own under the form, split by a separator you drag, and every wire message in a filterable log drawer (⌘J, zoomed over the columns with ⌘⇧J), where rows unfold, each on its own, into the same tree as a response, and a toggled row moves to the top of the drawer. A reconnect keeps the previous connection's messages above a separator, so the reason a server stopped is still there.
- Answer server-initiated requests: elicitation forms, sampling and
roots are shown as dialogs instead of being auto-rejected.
- Remember every call. The History view replays a call or loads its
arguments back into the form; its filter matches a call's name, arguments, result and error, so one word finds a call in a long history. The bin above the list empties it after a confirmation; the bin on a call deletes that call alone.
- Notice changes. Each connect is compared with the last stored snapshot
and a banner classifies every difference as breaking, compatible or cosmetic, direction-aware for input and output schemas.
- Take it away. Right-click any node of any tree to copy its value, its
path or its key; copy a whole response, a tool's schema, a request as JSON-RPC or as curl; export the snapshot, the wire log, the call history and the client config to files. Bearer tokens are redacted everywhere except the one action named for them.
- Bring it in. Import an
mcpServersfile from another MCP client and
every server it names is added at once, with any token it carried moved into the keyring.
- Script it with
coco-mcp --cli: snapshot, call, read, prompt, diff
(exit 1 on a breaking change) and export the saved servers as an mcpServers block.
| | | |---|---| | | |
Install
Every release ships prebuilt binaries for macOS (arm64 and x8664), Linux (x8664) and Windows (x86_64), built by the release workflow from the tagged source, so installing takes seconds. Building it yourself is the other option.
Quick install
Homebrew, on macOS and Linux, installs the binary of the latest release and brew upgrade coco-mcp follows new ones:
brew install camiloazula/coco/coco-mcp
The tap lives at camiloazula/homebrew-coco.
npm, on macOS, Linux and Windows, runs the binary of the latest release without installing anything else; npm install -g coco-mcp keeps it:
npx coco-mcp
The package is a small shim over one binary package per platform, and nothing is compiled on install; it lives at camiloazula/coco-mcp-npm.
The release archive, on any platform including Windows: download the one for your system from the latest release, check it against SHA256SUMS, and put coco-mcp somewhere on your PATH. A file saved by a browser is quarantined on macOS and refused until you allow it under Privacy & Security in System Settings, or clear the mark:
xattr -d com.apple.quarantine coco-mcp
Fetching the archive with curl sets no such mark. Windows asks once in the same way; "More info", then "Run anyway".
From source
Cargo (Rust 1.90 or newer) builds the current main on your machine; add --tag vX.Y.Z for a release:
cargo install --locked --git https://github.com/camiloazula/coco-mcp coco-mcp
--locked builds with the exact dependency versions in the committed Cargo.lock, the set the tests and the licence audit ran against. To update a Cargo install, run the install command again; cargo install --list shows what is installed and from which commit, and cargo uninstall coco-mcp removes it. Building needs a C toolchain and the system libraries GPUI and the keyring link against; the CI workflow in .github/workflows/ci.yml lists the packages it installs.
Two modes
However it arrived, it is one binary, coco-mcp:
coco-mcp # the window (`--desktop` says the same)
coco-mcp --cli --help # the command line; every command follows --cli
From a checkout, just run opens the window and just coco --help the command line.
The desktop mode
coco-mcp alone, or with --desktop, opens the window. Press ⌘N (or the + in the sidebar) to add a server. A stdio server is a command line, such as filesystem-server --root /tmp, an optional working directory and optional KEY=value environment lines. The line is split the way a shell splits it, so an argument with a space in it is quoted ("my notes"), but it never runs through a shell: nothing is expanded. An HTTP server is a URL plus optional headers and an auth mode. A server that is not connected shows that form as its pane, so it can be connected as it is or changed first; a connect that failed, or a session the server ended, leaves the reason under the fields, and a server that refused an OAuth login also offers Authorize; ⌘E moves the keyboard into it. For a connected server, the pencil next to the + (⌘E), or Settings at the end of the Server view's list, opens the same form prefilled; Save & reconnect saves the settings and starts a new session. The lists of a server that is not connected are the ones its last connection declared, shown faint until it connects again. The bin deletes the selected server after a confirmation. Each icon shows its action on hover. The plug at the end of a server's row connects it, or disconnects it when it is connected; a double-click on the row does the same.
Protocol eras
The server form's Protocol row chooses how the server is reached:
- Legacy (the default) starts with the
initializehandshake, offers
2025-11-25 and accepts any older version the server answers with.
- Auto asks with
server/discoverfor 2026-07-28 and falls back to the
handshake when the server does not speak it.
- Modern uses 2026-07-28 only, and says so when the server cannot.
The Server view names the agreed version and its era (Protocol 2026-07-28 · Modern). On a modern server the app works the same from the outside, by the revision's own means: sampling, elicitation and roots requests come inside a call and open the same dialogs; list changes and resource updates arrive on a subscriptions/listen stream the app keeps open; the log level travels with each request, and the server logs only while one runs; saved roots reach the server when it asks, with no change notice; and nothing pings the server, since the revision has no ping. Log level, sampling and roots carry a quiet "Deprecated in 2026-07-28" where they are used.
The Server view also holds the roots offered to the server, which can be edited there, the snapshots stored for it, any two of which can be compared in the change banner's terms, and the settings a connected server accepts (log level, subscriptions).
A feature that the agreed version or the server does not support is never hidden: its control is dimmed, its reason shows on hover ("The server doesn't offer resource subscriptions", "Connect to use this") and appears in the status bar when it is pressed.
Keyboard:
| Key | Action | |---|---| | ⌘K | Command palette (switch server, views, connect, edit, theme, log) | | ⌘N | Add server | | ⌘E | Edit the selected server | | ⌘⏎ | Call the tool, read the resource, get the prompt, or replay; in the server form, connect | | ⌘. | Cancel the running request; the server is told | | ⌘1 … ⌘5 | Tools, Resources, Prompts, History, Server | | ⌘J | Show or hide the log drawer (the panel button at the right of its header does the same, collapsed or not) | | ⌘⇧J | Zoom the log drawer in over the columns, or out again (the arrows button does the same) | | ⌘R / ⌘⇧R | Connect or reconnect / disconnect (the plug on the server's row does the same) | | ⌘⌫ | Delete the selected server (asks first; the bin next to + does the same) | | ⌘⇧C | Copy the whole response | | ⌘T | Dark or light theme (remembered) | | ⌘Q | Quit | | ↑ ↓ | Move in the focused list; ⏎ moves into the detail, or connects the selected server in the sidebar | | ⏎ / Esc | In a dialog: answer or confirm / cancel | | Esc | Close the copy menu, a confirmation, a pending server request, the palette or the server form, whichever is open; the log drawer stays |
The application menu has "About Coco MCP", which opens a small window with the version, the commit and date it was built from, the licence, and links to the source repository and the issue tracker. The File, Edit, Server and View menus carry the actions of the shortcuts and the palette (the call itself stays on ⌘⏎ and in the palette); an item that cannot run for the selected server is disabled. The Server menu also clears a server's call history and forgets its stored credentials, each after a confirmation, and the palette alone clears the log.
Getting servers in
File > Import Servers… reads an mcpServers file, the block MCP clients keep their server lists in, and adds every server it names. The protocol specifies the wire format, not the config file, so this is a convention rather than a standard: the reader takes the entries under mcpServers, under servers, or a bare object, which covers what clients write in practice. Nothing is connected: an imported file can name a dozen servers, and spawning a dozen processes is not what an import should do. Press ⌘R or the Connect button on the one you want.
A name already in the sidebar is skipped rather than overwritten, and an entry that describes neither a command nor a URL is reported instead of silently dropped. A server the database could not write is counted as not saved, and the status bar says why. An Authorization: Bearer header is moved out of the headers into the keyring, since ServerSpec keeps only non-secret headers; a placeholder such as the `` this app exports leaves the server without one, which the import summary counts and the edit form fills in. An entry configured for SSE is added as streamable HTTP, with a note, since that is the only HTTP transport the app speaks.
coco-mcp --cli import-config does the same without the window.
Getting data out
A debugger is only useful if what it shows can leave it, so everything on screen has a way out.
- Right-click any line of any JSON tree for its value, the same value on
one line, its path ($.content[0].text) and its key. A string copies as its text, not as a quoted JSON literal.
- Right-click a log row for its payload, for the whole JSON-RPC frame
with its id, for the line as the drawer shows it, and, when the app sent it in full to an HTTP server, for the curl that would send it again. None of it needs the row expanded. A server in the sidebar copies its client config the same way, and a text or resource block in a response copies its text or its URI.
- The copy icon next to a section copies that whole value: the response,
structuredContent, a tool's input or output schema, a history call's arguments, a server request, a diff's before and after. The one in the toolbar copies the request as it stands, before it is sent.
- The File menu writes four files through the save panel: the snapshot
(.json, the same artefact diff compares, so it works as a committed baseline), the wire log and the call history (.jsonl, one message or call per line), and the mcpServers config.
- A binary resource has a save button under it, which is the only way to
get the bytes of
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: camiloazula
- Source: camiloazula/coco-mcp
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.