Toolshed: one place to keep the MCP servers for Claude Code and Codex Developer Bart de Goede released toolshed, an open-source TUI and CLI that registers any Model Context Protocol (MCP) server with both Claude Code and Codex in one command, storing API tokens in the macOS or Linux OS keychain instead of plain-text config files. The tool pins npm and Python packages to the version current at install time — the example pins @modelcontextprotocol/server-memory to 2.1.0 — and supports --npx, --uvx, --url and arbitrary commands after --, with a `toolshed upgrade` command to move pinned packages to newer releases. Toolshed is itself an MCP server, letting an agent manage MCPs for both coding agents, and de Goede states it contains no StubHub code and is unaffiliated with his former employer. Every time I want to give one of my coding agents a new tool, the routine is the same: find the right JSON snippet in some README, paste an API token into a config file in my home directory, do it again for the other agents I run, and hope the server actually starts and keeps starting . So I built toolshed https://github.com/bartdegoede/toolshed , a TUI and one command that adds any MCP server to Claude Code and Codex, with the secrets in the OS keychain instead of in my dotfiles. Where this came from. At StubHub https://www.stubhub.com I worked here we used an internal tool built on a similar idea. We mostly use it to hook our agents up to internal tools, some most of which don’t have an MCP server at all, so that tool wraps around existing CLIs that use your personal credentials. I used that tool a lot, and I liked it enough that I wanted something like it for my own projects. toolshed is a separate, open-source project that I wrote from scratch and improved upon on my own time. It doesn’t contain any StubHub code or information, it isn’t the tool we use at work, and it’s not affiliated with or endorsed by StubHub. Opinions here are my own. Managing many MCPs gets annoying managing-many-mcps-gets-annoying MCP https://modelcontextprotocol.io the Model Context Protocol is a standard that lets coding agents talk to tools: a server advertises a list of tools and their descriptions “search Jira”, “open a browser”, “query the database” the agent can call to do work or retrieve relevant information. There are servers for nearly everything now, and when there isn’t one, writing your own is a prompt away with an AI subscription. This is all fine if you only have a couple, and use only one coding agent. To add one, you: 1. find the appropriate incantation in the server’s README is it npx ? uvx ? a Docker image? a URL? ; 2. register it with Claude Code or Codex , either with claude mcp add or by editing ~/.claude.json ; 3. paste an API token into that same JSON file, in plain text, next to everything else; 4. do it all again for Codex, which has its own CLI codex mcp add and its own TOML config ~/.codex/config.toml ; 5. restart the agent, and find out whether the server actually starts. None of this is hard. It’s tedious, the tokens sit in plain text in my home directory, and with every MCP, agent and project you add, you keep adding cognitive overhead “does Codex have access to this one?”, “is it enabled for my user or just for a project?” . What this does is give you a CLI to manage this more easily for Claude Code and Codex, and stores credentials in your keychain on MacOS or Linux . It is also an MCP server itself so your favorite agent can manage your MCPs for both agents through it. What it looks like what-it-looks-like Run toolshed and you get a list of your tools plus a few built-in examples . Press a to add one, t to start it and see which tools it offers, and i to install it. If you’d rather type than browse: toolshed add memory --npx @modelcontextprotocol/server-memory ✓ Added memory ✓ pinned to 2.1.0 ✓ memory offers 9 tools create entities Create multiple new entities in the knowledge graph … toolshed install memory ✓ memory → claude, codex --npx and --uvx cover npm and Python packages, --url covers remote servers, and anything after -- is just a command: toolshed add fetch --uvx mcp-server-fetch toolshed add docs --url https://mcp.context7.com/mcp toolshed add jira --secret JIRA TOKEN -- jira-mcp --site acme toolshed add acme --url https://mcp.acme.com/mcp \ --header 'Authorization=Bearer ${ACME TOKEN}' --secret ACME TOKEN Packages get pinned to the version that’s current when you add them. Without that, npx -y fetches whatever was published last every time your agent starts the server, and runs it with your token in its environment. toolshed upgrade moves them to a newer release when you decide to. If you’ve already added MCP servers manually, toolshed import or m in the TUI finds the ones Claude Code and Codex already have and tries to determine which of their values are secrets. It guesses from names like GITHUB TOKEN or an Authorization header, and you check the guesses you can add the ones it missed . The secrets move into the keychain, and the server is installed again through toolshed, so the token leaves the config file. Your registry only gets the names, never the values. How it works how-it-works toolshed is a single Go binary. The TUI is Bubble Tea https://github.com/charmbracelet/bubbletea , and MCP is handled by the official Go SDK https://github.com/modelcontextprotocol/go-sdk . There are four parts that are most interesting. One registry, two agents one-registry-two-agents Everything you add ends up in one small YAML file, ~/.toolshed/registry.yaml : tools: - id: jira command: jira-mcp args: --site, acme inputs: - key: JIRA TOKEN secret: true That file is the single source of truth. Installing a tool translates an entry into whatever parameters each agent needs to call the tool properly. For example, for Claude Code that’s claude mcp add-json , so Claude keeps owning its config format, and when it changes that format, toolshed doesn’t break. Codex works slightly different, and running codex mcp add ended up replacing the server’s table with the defaults ie every update quietly wipes customization like timeouts, disabled tools and per-tool approvals . It also can’t set a few things Codex does support, like a headers helper. So toolshed now edits Codex’s config.toml itself. It only touches the keys it manages and leaves every other byte as it was, comments included. go-toml ’s parser hands you the byte offset of every key and value, which makes that a lot less scary than it sounds. Because the registry is just a file, sharing a set of tools with a team means committing it somewhere and pointing TOOLSHED REGISTRY at it. Everyone can then run toolshed install and enter their own tokens. If someone later changes what a tool in that file runs, toolshed update shows you the change and asks before it hands your token to the new command. Secrets go in the keychain secrets-go-in-the-keychain This was the part I cared about most. If a tool has a secret input, toolshed stores the value in the macOS Keychain or the Secret Service https://specifications.freedesktop.org/secret-service/latest/ on Linux , and registers the server with the agent as a wrapper command instead of the real one: toolshed exec jira --secret JIRA TOKEN=jira -- jira-mcp --site acme The agent’s config only contains that line: no token anywhere. When the agent starts the server, toolshed exec reads the secrets from the keychain, adds them to the environment, and then replaces itself with the real server: bin, err := exec.LookPath command 0 if err = nil { return fmt.Errorf "%s: %w", args 0 , err } return syscall.Exec bin, command, shed.ExecEnv nil, refs syscall.Exec is the exec system call: the toolshed process becomes jira-mcp , with the same process ID and the same stdin and stdout. The agent talks MCP to the real server as if toolshed was never there, and there’s no wrapper process sitting in the middle forwarding bytes. It’s also why toolshed only runs on macOS and Linux: Windows doesn’t have an equivalent. toolshed exec doesn’t read the registry either. Everything it needs is in its own arguments, so an installed tool keeps working even if you move or delete your registry file. Remote servers get the same treatment, with a header instead of an environment variable. The --header 'Authorization=Bearer ${ACME TOKEN}' from earlier registers a headers helper with both agents: a small toolshed headers acme … command they run when they connect, which reads the token from the keychain and prints the headers. Again, no token in any config file. Does it actually work? does-it-actually-work The worst part of the old routine is step 5: finding out if a server is broken only after restarting the agent, and then digging through logs. So toolshed add starts the server straight away, does the MCP handshake, and lists the tools it offers that’s the “memory offers 9 tools” above . If that works, Claude Code and Codex will be able to talk to it too, and you have a better idea of what it offers. If it doesn’t, you get the error right there in your terminal. Later on, toolshed doctor runs the same check on everything you’ve installed, and also tells you when a secret is missing from the keychain or a tool’s command has disappeared from your PATH . Letting the agent do it letting-the-agent-do-it toolshed is also an MCP server itself. Run toolshed install toolshed and your agent gets list servers , inspect server , add server , install server and uninstall server . Then you can just ask: Add the Playwright MCP server, check it starts, and install it for this repo. The agent finds it and adds it. But since a server is just a command that toolshed runs on your machine, and I don’t want an agent or a prompt injection in some README it read deciding on its own what that command is going to be without adult supervision. So anything the agent adds is pending : toolshed won’t start it, not even to list its tools, until you’ve approved it. toolshed approve playwright shows you exactly what it will run, or you select it in the TUI and press i . After that, the agent can check that it starts and installs it. The other thing it can’t do is handle your secrets. I didn’t want API tokens ending up in a chat transcript, so if a server needs one, install server refuses, and the agent is told to send you to your terminal to run toolshed install