The Problem: Server Connected, No Tools Visible
Your MCP server appears connected - the client shows a green status indicator or no errors in the logs - but when you try to use it, no tools appear in the tools list. This is one of the most frustrating MCP issues because the server seems to be working fine on the surface. The AI either says it has no tools available, or the tools panel shows zero tools for that server.
The typical symptoms include:
- Server shows as "connected" in your client's MCP settings
- No error messages in the logs
- But the tools list is empty or the AI does not know about any tools
- The server worked before but tools disappeared after an update
- Some tools show up but others are missing
- Tools appear intermittently - sometimes they load, sometimes they do not
This guide covers every known cause, shows you how to debug systematically using the MCP Inspector and client-specific tools, and provides code fixes for both TypeScript and Python servers.
Cause 1: Capabilities Not Declared
MCP servers must explicitly declare their capabilities during the initialization handshake. When a client connects to a server, the first thing that happens is a capability exchange. If the server does not declare the tools capability, the client will never send a tools/list request - it assumes the server has no tools to offer. This is the most common cause of missing tools, especially in custom servers built with the low-level MCP SDK.
TypeScript SDK Fix
// WRONG - no capabilities declared
const server = new Server({
name: "my-server",
version: "1.0.0"
});
// CORRECT - tools capability explicitly declared
const server = new Server({
name: "my-server",
version: "1.0.0",
}, {
capabilities: {
tools: {}
}
});
// If using McpServer (higher-level API), capabilities
// are auto-declared when you call server.tool():
const server = new McpServer({
name: "my-server",
version: "1.0.0",
});
server.tool("my_tool", "Description", { input: z.string() }, async ({ input }) => ({
content: [{ type: "text", text: `Result: ${input}` }],
}));
// McpServer automatically adds tools capability
Python FastMCP Fix
In Python with FastMCP, capabilities are usually auto-declared when you register tools with the @mcp.tool() decorator. However, if you are using the lower-level Server class directly, you must declare capabilities manually:
# Using FastMCP (capabilities auto-declared)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.tool()
def my_tool(input: str) -> str:
"""Does something useful"""
return f"Result: {input}"
mcp.run()
# Using low-level Server class (must declare manually)
from mcp.server import Server
from mcp.types import ServerCapabilities, ToolsCapability
server = Server("my-server")
# You MUST set capabilities before connecting
server.capabilities = ServerCapabilities(tools=ToolsCapability())
Cause 2: Tool Handler Not Registered
The server declares the tools capability but does not actually register any tool handlers. This can happen when your tool registration code throws a silent error during startup, or when you forget to register both the listing handler and the execution handler.
// Make sure BOTH handlers are registered BEFORE the server starts
// 1. List tools handler - tells clients what tools exist
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "my_tool",
description: "Does something useful",
inputSchema: {
type: "object",
properties: {
input: { type: "string", description: "The input value" }
},
required: ["input"]
}
}
]
};
});
// 2. Call tool handler - executes tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "my_tool") {
const input = request.params.arguments?.input;
return {
content: [{ type: "text", text: `Result: ${input}` }],
};
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
A common mistake is registering the ListToolsRequestSchema handler but forgetting the CallToolRequestSchema handler. Some clients show tools in the list but fail silently when trying to call them. Others may not show tools at all if the call handler is missing.
Cause 3: Initialization Race Condition
Some clients send the tools/list request very quickly after the connection is established - sometimes within milliseconds. If your server loads tools asynchronously from a database, API, or configuration file, the tool list may be empty when the client asks for it. This is especially common with servers that do dynamic tool registration.
Fix
Ensure all tools are registered synchronously during server construction, or complete async initialization before calling server.connect():
// TypeScript - load tools BEFORE connecting
const tools = await loadToolsFromDatabase();
tools.forEach(tool => server.registerTool(tool));
// THEN connect the transport
await server.connect(transport);
// Python - same pattern
tools = await load_tools_from_database()
for tool in tools:
register_tool(server, tool)
# THEN run the server
await server.run()
If your tools truly need to be loaded lazily (for example, discovering tools from a running service), implement the tools/list handler to block until initialization is complete rather than returning an empty list:
let toolsReady = false;
let toolsPromise = loadTools().then(t => { tools = t; toolsReady = true; });
server.setRequestHandler(ListToolsRequestSchema, async () => {
if (!toolsReady) await toolsPromise; // Wait for tools to load
return { tools };
});
Cause 4: Client-Specific Debugging
Each MCP client handles tool discovery differently. Understanding these differences helps you pinpoint whether the issue is server-side or client-side.
Claude Desktop
Claude Desktop caches the tool list from the initial connection. If you add new tools to a running server, Claude Desktop will not see them until you restart the application completely.
- Check the hammer icon in the chat input area - it should show a number like "3 tools" or "12 tools"
- Click the hammer icon to see the full tool list with descriptions
- If the hammer icon shows no number or shows 0, the server either failed to connect or returned no tools
- Restart Claude Desktop fully: Cmd+Q on macOS, Exit from system tray on Windows
- Check logs at
~/Library/Logs/Claude/mcp*.log(macOS) or%APPDATA%\Claude\logs\mcp*.log(Windows) for initialization errors
Cursor
Cursor shows tool count in Settings, MCP section. If the count is 0 but the server shows as connected, the issue is a capability or handler problem. Cursor has additional requirements:
- Tools are only available in Composer mode (Cmd/Ctrl + I), not regular chat
- Agent mode must be enabled in Composer, not just basic chat mode
- Cursor requires valid JSON Schema for every tool's
inputSchema- malformed schemas cause silent tool drops with no error in the UI - After fixing, you can restart individual servers in Settings, MCP without restarting Cursor
See the Cursor MCP error guide for more troubleshooting steps.
VS Code
VS Code MCP extensions vary in how they display tools. For GitHub Copilot's MCP support, check the extension's output channel (View, Output, select the MCP channel) for any warnings about skipped or invalid tools. VS Code may log messages like "Tool skipped: invalid inputSchema" that do not appear in other clients.
Client Behavior Comparison
| Behavior | Claude Desktop | Cursor | VS Code |
|---|---|---|---|
| Tool caching | Cached at startup, requires restart | Refreshes on server restart | Varies by extension |
| Invalid schema handling | May show tool but fail on call | Silently drops the tool | Logs warning, may skip tool |
| Tool visibility location | Hammer icon in chat input | Settings, MCP section | Extension output panel |
| Hot reload support | No (full restart required) | Yes (per-server restart) | Window reload |
Debugging with MCP Inspector
The MCP Inspector is the definitive debugging tool for tool visibility issues. It connects to your server directly, bypassing any client-specific behavior, and shows you exactly what the server returns for each MCP request. If tools appear in the Inspector but not in your client, the problem is client-side. If they do not appear in the Inspector either, the problem is in your server code.
Installing and Running the Inspector
# Run the Inspector UI (opens a web interface)
npx @modelcontextprotocol/inspector
# Connect to a specific server directly
npx @modelcontextprotocol/inspector npx -y @modelcontextprotocol/server-filesystem /tmp
# For Python servers
npx @modelcontextprotocol/inspector uvx my-mcp-server
# For custom servers
npx @modelcontextprotocol/inspector node ./my-server.js
Using the Inspector Step by Step
- Launch the Inspector - it opens a web interface in your browser
- Enter your server's command and arguments in the connection form
- Click "Connect" - you should see "Connected" status
- Click the "Tools" tab to see the list of tools the server exposes
- Click "List Tools" to send a
tools/listrequest manually - Inspect the raw JSON response - check that each tool has a valid name, description, and inputSchema
- Click on individual tools to test them with sample input
- Check the "Messages" tab to see the full JSON-RPC communication between Inspector and server
The Inspector also validates JSON Schema for you. If a tool's inputSchema is invalid, the Inspector may show a warning or error that your client silently swallows. Pay close attention to the raw response format.
Cause 5: Invalid Tool JSON Schema
Some clients silently drop tools with invalid JSON Schema in their inputSchema. This is one of the hardest issues to debug because there is no error message - the tool simply does not appear. Here are the most common schema errors and how to fix each one:
Common Schema Errors
// ERROR 1: Missing "type": "object" at top level
{
"name": "search",
"inputSchema": {
"properties": {
"query": { "type": "string" }
}
}
}
// FIX: Add "type": "object"
// ERROR 2: Required field references nonexistent property
{
"name": "search",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" }
},
"required": ["query", "nonexistent_field"]
}
}
// FIX: Remove "nonexistent_field" from required
// ERROR 3: Using unsupported schema features
{
"name": "search",
"inputSchema": {
"type": "object",
"oneOf": [
{ "properties": { "url": { "type": "string" } } },
{ "properties": { "query": { "type": "string" } } }
]
}
}
// FIX: Use simple properties instead of oneOf
// Some clients do not support oneOf, anyOf, allOf
// ERROR 4: Empty properties object
{
"name": "ping",
"inputSchema": {
"type": "object",
"properties": {}
}
}
// FIX: Either omit inputSchema entirely for no-arg tools,
// or add at least one optional property
Validating Schemas Programmatically
You can validate your tool schemas before deploying by using a JSON Schema validator:
// validate-tools.js
const Ajv = require("ajv");
const ajv = new Ajv();
const tools = [
{
name: "search",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search query" }
},
required: ["query"]
}
}
];
for (const tool of tools) {
try {
ajv.compile(tool.inputSchema);
console.log(`[OK] ${tool.name}: schema is valid`);
} catch (err) {
console.error(`[FAIL] ${tool.name}: ${err.message}`);
}
}
Cause 6: Server Returning Empty Tool List
Check if your tools/list handler has a conditional that might return an empty array. This often happens when environment variables control which tools are available, or when tools are loaded from external sources that fail silently:
// Bug: condition prevents tools from loading
server.setRequestHandler(ListToolsRequestSchema, async () => {
if (!process.env.ENABLE_TOOLS) { // Forgot to set env var!
return { tools: [] };
}
return { tools: myTools };
});
// Bug: async tool loading fails silently
server.setRequestHandler(ListToolsRequestSchema, async () => {
try {
const tools = await fetchToolsFromAPI(); // Network error!
return { tools };
} catch (e) {
// Swallowed error - returns undefined, client sees no tools
console.error(e);
}
return { tools: [] }; // Always return a valid response
});
Check your environment variables are correctly passed to the server process. GUI applications like Claude Desktop do not inherit your shell environment, so env vars set in .bashrc or .zshrc will not be available unless you add them to the "env" field in your MCP config.
Cause 7: Tool Names with Special Characters
Some clients have restrictions on tool names. While the MCP specification allows most characters, certain clients may not handle special characters well:
- Avoid spaces in tool names - use underscores or hyphens instead
- Avoid starting tool names with numbers
- Stick to alphanumeric characters, underscores, and hyphens
- Keep tool names reasonably short (under 64 characters)
- Use consistent casing -
snake_caseis the convention for MCP tools
Step-by-Step Fix Process
- Test with MCP Inspector - Run your server with the Inspector to see exactly what tools are returned in the
tools/listresponse. - Check capabilities - Verify your server declares
tools: {}in its capabilities during initialization. - Validate schemas - Ensure every tool has a valid
inputSchemawith"type": "object"at the top level. Run schemas through a JSON Schema validator. - Check handlers - Confirm both
ListToolsRequestSchemaandCallToolRequestSchemahandlers are registered (or bothtools/listandtools/callin the low-level API). - Check env vars - If tools are conditionally loaded based on environment variables, verify those vars are set in your MCP config's
"env"field. - Restart client - Fully restart your MCP client after making changes. Closing the window is not enough for Claude Desktop.
- Check client logs - Look for warnings or errors in client-specific log locations that might explain why tools were rejected.
- Try a different client - If tools show in the Inspector and in one client but not another, the issue is client-specific schema validation. Simplify your schemas to the most basic format.
If you are building a new server, follow our Build an MCP Server tutorial which covers proper tool registration from the start. For issues where the server does not even connect, see the spawn ENOENT guide or the connection refused guide.