Back to Blog
TroubleshootingNov 10, 202610 min read

MCP Server Connected but No Tools Showing? 7 Fixes (+ "Tool Not Found")

NT

Nikhil Tiwari

MCP Playground

πŸ“– TL;DR

  • Look at the raw tools/list response first. It tells you whether the server sent no tools or the client hid them.
  • Empty list from the server: usually a stale build, tools registered after the server connected, or a Python module that was never imported.
  • No tools capability in initialize: the client won't even ask for tools.
  • Tools listed by the server but missing in the client: a toolset or read-only flag, a tool switched off in the client, or a name or schema the client rejects.
  • "Tool not found" means the name the client sent doesn't match a registered tool: a renamed tool, a stale client list, or a prefixed name.

Table of Contents

Your MCP server shows a green dot. The client says it's connected, but no tools are showing, or the model tries a tool and gets "Tool not found".

This one is frustrating because nothing crashed. There's no stack trace to search for, and restarting the client changes nothing.

I'll show you how to read the one response that settles it: tools/list. Then I'll walk through the seven causes I see most often, with the exact error text from the official SDKs.

If the server doesn't start at all, this is the wrong post. Start with fixing spawn npx ENOENT or why a server works in the terminal but not in Claude Desktop. New to the protocol? Read what the Model Context Protocol is first.

Which problem do you have?

"No tools" covers three different failures. Find yours before changing any code.

What you see What it means Go to
Server connected, 0 toolsThe server sent an empty list, or the client never askedFixes 1–4, 7
Some tools missingHidden by a flag or token scope, switched off, or dropped by the clientFixes 5–6
Tools listed, call says "not found"The called name doesn't match a registered toolTool not found

Check the raw tools/list response first

Every MCP client does the same two steps. It sends initialize, reads the server's capabilities, then sends tools/list if the server says it has tools.

You need to see both responses as raw JSON-RPC. The client's tool picker can't tell you whether the server or the client lost the tools.

A healthy initialize result includes a tools key (the MCP tools spec says servers with tools MUST declare it):

"capabilities": {
  "tools": { "listChanged": true }
}

And a healthy tools/list result has at least one entry:

{ "tools": [ { "name": "get_weather", "description": "...", "inputSchema": { "type": "object", ... } } ] }

Now read the result:

  • No tools capability: the client skips tools/list. Go to fix 2.
  • "tools": []: the server registered nothing by the time it answered. Go to fixes 1, 3 and 4.
  • Tools in the response, none in the client: the client hid them. Go to fixes 5 and 6.
  • A nextCursor field: there are more pages. Go to fix 7.

Where to see the raw messages

  • Claude Desktop: ~/Library/Logs/Claude/mcp-server-NAME.log on a Mac, %APPDATA%\Claude\logs\ on Windows. It logs the messages and the server's stderr.
  • Cursor: Cmd/Ctrl+Shift+U opens the Output panel. Pick the MCP channel from the dropdown (Cursor MCP docs).
  • Claude Code: /mcp lists each server, its status and its tools.
  • A remote server: paste the URL into the free MCP tester β†’ and it lists tools in the browser.

For a local stdio server, MCP Playground Desktop shows every JSON-RPC message in its bottom JSON-RPC panel, next to a Server stderr tab. More on that at the end.

1. The client is running an old build or another config

This is the most common cause, and the least interesting. The client is not running the code you just edited.

  • TypeScript not rebuilt. Your config points at build/index.js, but you edited src/index.ts. Run npm run build, or tsc --watch while you work.
  • Cached npx package. npx -y some-server can reuse a cached copy. Pin a version, such as some-server@1.4.0, to know what runs.
  • Wrong config file. Claude Desktop, Cursor, VS Code and Claude Code each read their own file, and Cursor and Claude Code also have project-level ones.
  • Not fully restarted. Closing the window isn't quitting. Use ⌘Q on a Mac, or Quit from the Windows system tray.

A quick proof: print a version line to stderr at startup, and check that the log shows it. If the line isn't there, the client is running something else.

console.error("my-server starting, build", new Date().toISOString());

Use console.error, not console.log. On stdio, stdout is the protocol channel (see why stdout breaks stdio servers).

2. The server never declared the tools capability

A client only sends tools/list if the server declared tools in initialize. No capability, no request, no tools. The server never even sees the question.

With the high-level APIs you rarely hit this. The TypeScript SDK's McpServer declares tools the first time you register a tool, and Python's FastMCP declares it for you.

The low-level Server class is different. You list capabilities yourself, and the TypeScript SDK refuses a tools handler without them:

Error: Server does not support tools (required for tools/list)

That error crashes the server at startup, so you'd see it in the log. The fix is to declare the capability in the constructor:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";

const server = new Server(
  { name: "my-server", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

Hand-written servers and other-language SDKs can skip the check. They answer tools/list fine, but the capability is missing, so clients never call it. Check the initialize result.

3. Tools are registered after the server connects

The client asks for tools right after initialize. Anything you register later can miss that moment.

TypeScript: register before connect()

This pattern looks harmless. It loads config, connects, then registers tools when a slow call finishes:

const server = new McpServer({ name: "my-server", version: "1.0.0" });
await server.connect(new StdioServerTransport());

const schema = await loadSchemaFromDb();   // slow
server.registerTool("query", { inputSchema: { sql: z.string() } }, handler);

In the TypeScript SDK (v1.32), the first registerTool also declares the tools capability. Doing that after connect() throws:

Error: Cannot register capabilities after connecting to transport

By then the client has already connected and seen no tools capability. Here the process then dies. Inside a try/catch or an un-awaited promise, it keeps running with zero tools, and the error only shows in stderr.

Register every tool first, then call connect() last.

Need tools that depend on runtime data? Register at least one tool before connecting. After that, the SDK sends notifications/tools/list_changed for each new tool (fix 7).

Python: code after mcp.run() never runs

mcp.run() blocks until the server exits. A @mcp.tool() below it is never registered.

mcp = FastMCP("my-server")
mcp.run()            # blocks here

@mcp.tool()          # never reached
def search(q: str) -> str: ...

Keep mcp.run() at the very bottom, inside if __name__ == "__main__":.

4. Python: the tools module was never imported

A decorator only runs when Python imports the file that holds it. Split your tools into tools.py, never import it, and the list is empty.

The fix seems obvious: import it in server.py. But there's a trap that catches a lot of people:

# server.py  (you run: python server.py)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
import tools
if __name__ == "__main__":
    mcp.run()

# tools.py
from server import mcp     # imports server.py AGAIN, as a new module

@mcp.tool()
def search(q: str) -> str: ...

When you run python server.py, that file is the module __main__. from server import mcp loads it a second time under the name server. That creates a second FastMCP instance, and your tools register on it.

The instance that runs, in __main__, has no tools. There's no error.

Fix it by putting the instance in its own module that both files import:

# app.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")

# tools.py
from app import mcp
@mcp.tool()
def search(q: str) -> str: ...

# server.py
from app import mcp
import tools  # noqa: F401  registers the tools
if __name__ == "__main__":
    mcp.run()

The full setup, with uv run --directory so the imports resolve, is in testing a Python MCP server locally with uv.

One more Python note: FastMCP logs Tool already exists: NAME when two functions share a name. The first one wins. If a tool behaves like an older version, check stderr for that warning.

5. Toolsets, read-only mode and switched-off tools

Many servers decide which tools to list from their config. The tool exists in the code, but your settings filter it out.

The official GitHub MCP server is a good example. With no toolsets set, it only loads context, repos, issues, pull_requests and users. Actions and code security tools don't appear until you ask:

"env": {
  "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_your_token_here",
  "GITHUB_TOOLSETS": "default,actions,code_security"
}

Its --read-only flag also skips every write tool, even ones you name. Other servers follow the same idea. Check the README for:

  • Toolset or feature flags, like GITHUB_TOOLSETS or --tools.
  • Read-only or safe modes that drop write tools.
  • Tools gated on credentials. Some servers only list tools the token can use. If the key is missing, you can get an empty list instead of an error (see env variables not reaching your server).

Then check the client. Claude Desktop, Cursor and VS Code all let you switch servers or tools off in their MCP or tools settings, and a switched-off tool isn't sent to the model.

On the server side, the TypeScript SDK can hide a tool with tool.disable(). A disabled tool is left out of tools/list, and calling it returns Tool NAME disabled.

6. The client drops tools it can't use

Sometimes the server's response is fine, and the tool still never reaches the model. The client, or the model API behind it, rejected the definition.

The spec sets the rules most clients enforce:

  • Names: 1 to 128 characters, only A–Z, a–z, 0–9, _, - and .. No spaces or other symbols.
  • Unique names within a server.
  • inputSchema must be a JSON Schema object, never null. For a tool with no arguments, use { "type": "object", "additionalProperties": false }.

Model APIs add their own limits on top. Clients prefix tool names with the server name, which uses up characters. A long server name plus a long tool name can push past a model's limit. Keep both short.

Schemas are the other trap. An array with no items, or unusual keywords, can be accepted by one model and rejected by another. When a client drops a tool, it usually says so in its MCP log, not in the chat.

If you want to know which models accept your schemas, run the same server across several. That's covered in Agent Studio on desktop, and tool description quality covers getting models to pick the right tool.

7. Tools that arrive later: list_changed and pagination

Some servers load tools after startup: from a plugin folder, an OpenAPI file, or a remote catalog. The first tools/list comes back empty or short, and the rest arrive later.

The server should then send:

{ "jsonrpc": "2.0", "method": "notifications/tools/list_changed" }

That only works if the server declared "listChanged": true, and if the client re-fetches when told. If the client ignores it, you keep the empty list until you reconnect.

Two fixes, in order of preference:

  1. Load everything before you connect. A few hundred milliseconds of startup is better than an empty tool list.
  2. If tools really are dynamic, send list_changed and test with a client that re-fetches. Use the client's refresh or reconnect button to confirm the tools appear.

tools/list also supports pagination. A server with many tools can return a page plus nextCursor. A client that doesn't follow the cursor only shows the first page.

If you wrote the server, return all tools in one page unless you have hundreds. If you didn't, look for nextCursor in the raw response before blaming the server.

Fixing "Tool not found" errors

Here the tools are listed. The model calls one, and the server says it doesn't exist. The wording depends on the SDK:

Server Error text
TypeScript SDK (McpServer)MCP error -32602: Tool NAME not found, returned as a tool result with isError: true
Python SDK (FastMCP)Unknown tool: NAME, returned as a tool result with isError: true
Spec exampleJSON-RPC error -32602, "Unknown tool: NAME"

Read the NAME in the error carefully. It shows exactly what the client sent, and that usually gives the cause away:

  • A renamed tool. You renamed search to search_docs, but the client kept its old list. Restart or refresh the server in the client.
  • A prefixed name sent to the server. Clients show tools with the server's name added, like mcp__github__create_issue in Claude Code. If that full name reaches your server, the client or your own agent code didn't strip the prefix.
  • A made-up name. The model guessed a tool that sounds right, like get_issue when you have issue_read. Clear descriptions help; so does fewer, well-named tools.
  • Case or separators. Names are case-sensitive. createIssue and create_issue are different tools.
  • A disabled tool in the TypeScript SDK returns Tool NAME disabled instead.

The quickest test is to call the tool yourself with the exact name from tools/list, outside any model. If that works, the problem is in the client or the model, not your server.

Test it outside the client

Every fix above starts with the same question: what did the server actually send? MCP Playground Desktop answers it for local stdio servers:

  • A Tools tab with a count, filled from tools/list. If it says No tools, the server returned none.
  • A Server tab showing the server's version, protocol version and declared capabilities. If tools isn't declared, the app doesn't ask for tools, just like a client.
  • A JSON-RPC panel with every message, and a Server stderr tab for your startup line, Tool already exists warnings and stack traces.
  • A Refresh button that runs tools/list again, for servers that load tools late.
  • Import from Claude Desktop, Cursor, VS Code, Windsurf and Claude Code, so you test the exact command your client runs.

Call a tool by hand there, then try it with a model in Agent Studio. Get MCP Playground Desktop for Mac and Windows β†’

FAQ

Why does my MCP server connect but show no tools?+
Either the server returned an empty tools/list, or the client never asked because the server didn't declare the tools capability. Check the raw initialize and tools/list responses. The usual causes are a stale build, tools registered after the server connected, or a Python tools module that was never imported.
What does "Cannot register capabilities after connecting to transport" mean?+
In the TypeScript SDK, the first registerTool call also declares the tools capability. If that first call happens after server.connect(), the SDK throws this error. Register all tools before calling connect().
Why are my FastMCP tools not showing up?+
The decorators only run when their file is imported, and code after mcp.run() never runs. If tools.py does "from server import mcp" while you run server.py directly, Python loads server.py twice and the tools register on a second FastMCP instance. Put the instance in its own module and import it from both files.
Why are some GitHub MCP server tools missing?+
With no toolsets set, the official GitHub MCP server only loads context, repos, issues, pull_requests and users. Set GITHUB_TOOLSETS, for example "default,actions", to add more. Read-only mode also removes every write tool.
How do I fix "Tool not found" in MCP?+
Compare the name in the error with the names in tools/list. Common causes are a renamed tool the client still has cached, a server-prefixed name reaching the server, a name the model made up, or a case mismatch. Restart the server in the client, and call the tool by hand to confirm the server side works.
My server adds tools at runtime. Why doesn't the client see them?+
The server must declare listChanged and send notifications/tools/list_changed, and the client must re-fetch tools/list when it gets that notification. If your client doesn't, load the tools before connecting, or reconnect after they're added.

Conclusion

An MCP server with no tools showing is almost never a mystery once you see the raw initialize and tools/list responses. They tell you whether the server sent nothing or the client hid it.

From there it's usually a stale build, tools registered too late, a Python import trap, or a toolset flag. "Tool not found" comes down to the exact name the client sent.

Test your local MCP server with MCP Playground Desktop β†’

NT

Written by Nikhil Tiwari

15+ years in product development. AI enthusiast building developer tools that make complex technologies accessible to everyone.

Build, compare & ship MCP agents

Connect any MCP server, run evals on it, compare 90+ models side-by-side, deploy hosted servers, and save reusable agents you can export as an API β€” all in your browser.

Try for Free β†’