You added a server, restarted the client, and the tool list is empty - or it connects and then does nothing useful. Most of page one for this problem is a pile of fixes in no particular order, so people try them at random and cannot tell which one worked. The fix order matters more than the fixes.
Why is my MCP server not working?
Usually because the server process never started, and the client did not tell you. Clients launch MCP servers in a stripped environment that does not load your shell profile, so a command that works in your terminal fails when the client runs it. The other common causes are invalid JSON in the config and stray output corrupting the stdio channel.

Diagnose in order, not at random
Each step below ends in a verdict about one layer. Stop at the first one that fails; everything after it is untestable until that layer is fixed.
| # | The test | A pass proves | A fail means |
|---|---|---|---|
| 1 | Point the same client at a known-good public server | Your client, its config file and its JSON are all fine | The fault is in the client or the config, not in the server you were installing |
| 2 | Run the exact command and args from your config in a plain shell | The server starts and the package resolves - so if the client still cannot attach, the difference is the environment the client launches it in | You now have the real error the client swallowed: missing runtime, wrong package name, missing variable |
| 3 | Watch what the process writes to stdout on startup | The channel carries JSON-RPC and nothing else | A stray log line is corrupting every message - the server has a stdout discipline bug |
| 4 | Ask the client to list tools, then make one read call | You are connected and authorised | Connection is fine; this is auth or scope, not a connection problem |
| 5 | Ask a question the server should obviously answer | The tool surface is legible to the model | Connection and auth are fine; the model is picking wrong or picking nothing |
Step 1 - is it the client, or the server?
Before touching the failing server, prove the client works. Point it at a server you know is healthy and requires no credentials, using the same config file and the same restart.
npx -y @aioproductoscom/mcp@latest
Started without a PRODUCTOS_TOKEN, that runs in demo mode against a fully seeded showcase workspace called Brightline - a read-only subset of the tools, no account, no credentials, nothing to cancel. A working connection looks like two things, in this order: the client lists tools, and a first call returns typed records with ids rather than a sentence. Ask it something ordinary in your own words, like which features have the most customer requests behind them.
If that works, your client, your JSON and your restart procedure are all fine, and the fault is in the other server. If it also fails, stop debugging the other server entirely - you have a client-side problem, and the config mechanics are covered in running a local MCP server.
A second option, if you want a known-good server that makes no network calls at all: AIOProductOS Studio is a free, MIT-licensed, fully-local server, installed with npx -y @aioproductoscom/mcp-studio.
Step 2 - did the process ever start?
This is the step page one skips, and it resolves more cases than everything else combined. Take the command and the args from your config, exactly as written, and run them yourself in a plain shell. The error the client swallowed appears immediately: a runtime that is not installed, a package name that does not resolve, a required variable that is unset.
If it runs in your terminal but still fails in the client, you have learned the actual fault - the environments differ. Your shell loaded a profile that put a version manager or a package binary on the PATH. The client, launched from the dock, never read that profile, so node or npx or uvx may not exist as far as it is concerned. Give the config an absolute path to the runtime instead of a bare command, and pass anything the server needs explicitly rather than assuming your exported variables are visible.
Two things to check before you conclude anything here. Confirm the config file is valid JSON - a trailing comma means the client loads nothing and says nothing. And confirm you fully restarted the client, not just closed a window; most read the server list once, at startup.
Step 3 - is the transport clean?
On stdio, stdout is the protocol channel. A single console.log or print in the server body writes non-JSON into the middle of the message stream and corrupts every exchange after it. The symptom is a server that clearly launched and still never attaches, or one that attaches and drops. Logging belongs on stderr; stdout carries JSON-RPC only.
You are running someone else's server, so this is not yours to patch - but it is yours to identify, because it changes what you do next. Pin an earlier version and see if the noise disappears; if it does, you have a reportable regression rather than a local misconfiguration. If the server is remote rather than local, the equivalent check is plainer: confirm the endpoint URL is reachable and returns what you expect before assuming the client is at fault. (Authoring concerns - return shapes, naming, versioning - are covered in what we learned building two MCP servers.)
Step 4 - is it auth, not connection?
Connected, tools listed, and every call comes back empty or denied. That is not a connection failure and no amount of config editing will fix it. Either the OAuth flow was started and never completed, or the token in play is scoped to less than you are asking for, or you are authenticated as an account with no data in it.
The tell is that reads succeed and writes fail: that is scope, working as designed. Check what the server says it grants before assuming a bug - and if the server exposes an identity or whoami tool, call it, because "which account am I" is frequently the whole answer. Whether that grant is one you should accept is a separate question, and the decision procedure is here.
Step 5 - is it the tool surface?
Connection is fine, auth is fine, and the model still does nothing useful. Two variants. If it never calls a tool, the descriptions are not telling it when to; name the tool explicitly in your prompt once and see whether the call succeeds - if it does, the connection is healthy and the selection is the problem. If it calls the wrong tool, the surface has overlapping names and you are watching a retrieval failure, not an integration failure.
This is also where a large surface earns scrutiny. Our own hosted endpoint exposes 71 tools plus three interactive ui:// apps; that is a big surface, and it stays workable only because the names disambiguate on their own, with the surface documented tool by tool so you can check what you are pointing at before you connect.
When it works for you and not your colleague
If your install is fine and someone else's is not, nothing is broken - that is what a per-machine stdio install does at team scale, where five people means five installs and three different versions of the same server name. That ceiling is a different problem with a different answer, covered in where a local MCP server stops paying.
The honest counter-case
Sometimes the answer is that the server is broken and you should stop.
Steps 1 and 2 both pass and step 3 fails on the current release. You have found a bug in someone else's code. Pin the last version that worked, file the issue with the exact stdout you captured, and move on - debugging your own setup further is wasted time, because your setup is fine.
The server has no identifiable maintainer and no recent releases. MCP is a small enough standard that a server which fails to hand back a tool list is often just unfinished. Reading its source to find out is a legitimate choice; so is picking a different server and spending the afternoon on your actual work.
And local stdio may still be the right call. If the capability is genuinely local - your filesystem, your browser, a device on your desk - none of the team-scale objections apply, and the per-machine install is the correct architecture rather than a compromise. Fix the PATH and keep it.
Whatever you are installing, run the order rather than the list: client, process, transport, auth, surface. If you want to run it against a real product spine on your own data instead of a seeded workspace, the MCP surface and every connection method are documented here.