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

> Your MCP server connects, but the tool list is empty, or a call fails with "Tool not found". How to read the raw tools/list response, and the seven causes behind it: stale builds, missing capabilities, tools registered too late, Python import traps, toolsets and flags, tools the client drops, and tools that arrive later.

**Source:** https://mcpplaygroundonline.com/blog/mcp-server-no-tools-showing  
**Author:** Nikhil Tiwari  
**Published:** 2026-11-10  
**Category:** Troubleshooting  
**Reading time:** 10 min read  
**Topics:** mcp server no tools showing, mcp tools list empty, mcp tool not found, mcp server connected but no tools, claude desktop mcp no tools, cursor mcp no tools available, mcp tools/list, fastmcp tools not showing, mcp unknown tool error, mcp tools capability

---

📖 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

-   [Which problem do you have?](#which)
-   [Check the raw tools/list response first](#raw)
-   [1\. The client is running an old build or another config](#stale)
-   [2\. The server never declared the tools capability](#capability)
-   [3\. Tools are registered after the server connects](#late)
-   [4\. Python: the tools module was never imported](#python)
-   [5\. Toolsets, read-only mode and switched-off tools](#flags)
-   [6\. The client drops tools it can't use](#dropped)
-   [7\. Tools that arrive later: list\_changed and pagination](#dynamic)
-   [Fixing "Tool not found" errors](#not-found)
-   [FAQ](#faq)

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](/blog/fix-spawn-npx-enoent-mcp) or [why a server works in the terminal but not in Claude Desktop](/blog/mcp-server-works-in-terminal-not-claude-desktop). New to the protocol? Read [what the Model Context Protocol is](/blog/what-is-model-context-protocol) 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 tools

The server sent an empty list, or the client never asked

Fixes 1–4, 7

Some tools missing

Hidden by a flag or token scope, switched off, or dropped by the client

Fixes 5–6

Tools listed, call says "not found"

The called name doesn't match a registered tool

[Tool not found](#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](https://modelcontextprotocol.io/specification/2025-11-25/server/tools) 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](https://cursor.com/docs/context/mcp)).
-   **Claude Code:** `/mcp` lists each server, its status and its tools.
-   **A remote server:** paste the URL into the [free MCP tester →](https://mcpplaygroundonline.com/mcp-test-server) and it lists tools in the browser.

For a local stdio server, [MCP Playground Desktop](/blog/mcp-playground-desktop-guide) 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](/blog/mcp-stdio-unexpected-token-json)).

## 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](/blog/test-python-mcp-server-locally-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](https://github.com/github/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](/blog/mcp-server-env-variables-not-working)).

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](https://modelcontextprotocol.io/specification/2025-11-25/server/tools) 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](/blog/agent-studio-desktop-local-mcp-tools), and [tool description quality](/blog/mcp-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](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/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 example**

JSON-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](/blog/mcp-playground-desktop-guide) 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 →](/local-mcp-server-testing)

## 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 →](/local-mcp-server-testing)

## Frequently asked questions

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


---

_Canonical page: https://mcpplaygroundonline.com/blog/mcp-server-no-tools-showing — MCP Playground (mcpplaygroundonline.com), the free browser-based tool for testing MCP servers and building AI agents._
