ScreenshotNeo

BlogAI agents

How to List Tools from an MCP Server

Use MCP's tools/list request or an SDK helper to discover tool names, descriptions, and input schemas, with pagination and refresh handling.

By the ScreenshotNeo team1 October 20266 min read

Direct answer: An MCP client lists the tools exposed by a server by sending the JSON-RPC method tools/list after it has connected and completed initialization. The response contains a result.tools array. Each entry includes a unique name, description, and input schema; keep requesting pages while result.nextCursor is present. The official specification defines this operation and its pagination behavior (MCP Tools specification).

What tools/list returns

A tool listing describes what the server advertises. It does not execute a tool. A definition normally includes:

  • name: the identifier used when calling the tool.
  • description: human-readable guidance for clients and users.
  • inputSchema: a JSON Schema describing valid arguments.
  • Optional display title and output-schema metadata, when supplied by the server.

Servers that support list-change notifications can advertise the listChanged capability. When the inventory changes, the server can send notifications/tools/list_changed; the client should then call tools/list again.

List tools with a raw JSON-RPC request

Use this approach when building a custom MCP transport or debugging protocol traffic. The request must be sent over the already-negotiated MCP connection after initialization.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}

A successful response has this shape (servers may include additional metadata):

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "take_screenshot",
        "description": "Capture a webpage screenshot",
        "inputSchema": {
          "type": "object",
          "properties": {
            "url": { "type": "string" }
          },
          "required": ["url"]
        }
      }
    ]
  }
}

Handle pagination

Do not assume one response contains the complete inventory. If the response includes result.nextCursor, send another request with that cursor in params:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": { "cursor": "eyJvZmZzZXQiOjEwMH0=" }
}

Continue until nextCursor is absent. Treat cursors as opaque values: do not decode, modify, or persist assumptions about their format.

TypeScript SDK

With a connected MCP TypeScript SDK Client, call listTools(). The v2 SDK reference documents automatic aggregation when no cursor is supplied (Client API).

const { tools } = await client.listTools();

for (const tool of tools) {
  console.log(tool.name);
  console.log(tool.description ?? "");
  console.dir(tool.inputSchema, { depth: null });
}

When you pass a cursor explicitly, the method returns one raw page so your code can control pagination. The documented automatic path has a configurable maximum page count (default 64); set an appropriate limit for unusually large inventories and handle an incomplete result explicitly.

Manual TypeScript pagination

type Tool = {
  name: string;
  description?: string;
  inputSchema: unknown;
};

const allTools: Tool[] = [];
let cursor: string | undefined;

for (;;) {
  const page = await client.listTools(cursor ? { cursor } : undefined);
  allTools.push(...page.tools);
  if (!page.nextCursor) break;
  cursor = page.nextCursor;
}

console.log(`Discovered ${allTools.length} tools`);

Check the installed SDK version because method overloads and return types can change.

Python SDK

After connecting with the official Python client, call client.list_tools() and inspect each returned tool object. See the Python SDK client reference for the version you use.

tools_result = await client.list_tools()

for tool in tools_result.tools:
    print(tool.name)
    print(tool.description or "")
    print(tool.inputSchema)

Confirm your package version’s pagination behavior before depending on implementation details. If the helper exposes a cursor, repeat the call until no next cursor remains; otherwise use the SDK’s documented aggregation behavior.

Inspecting schemas before a call

Use the schema to build forms, validate arguments, or generate an agent’s tool prompt. Preserve required fields, property types, enum values, and nested objects. A safe inventory view can print only names and descriptions while retaining the complete schema internally:

for (const tool of tools) {
  const required = tool.inputSchema?.required ?? [];
  console.log(`${tool.name} (required: ${required.join(", ")})`);
}

Listing is discovery, not authorization. Tool annotations and descriptions from an untrusted server can be misleading. Follow the specification’s guidance to keep tools visible to users and preserve a human ability to deny invocations (trust and safety guidance).

Refresh a cached tool inventory

  1. List tools after initialization and cache the definitions with the connection or session.
  2. If the server advertises listChanged, subscribe to notifications/tools/list_changed.
  3. On notification, invalidate the cache and call tools/list again, including all pages.
  4. Revalidate any pending UI forms or generated calls against the new schemas.

For servers without notifications, refresh at a schedule appropriate to your application or whenever a call fails because a tool is unknown.

Common errors and fixes

Error Likely cause Fix
Method not found The request was sent before initialization, to the wrong endpoint, or to a server without tools support. Complete the MCP handshake, verify the transport and protocol version, and check the server’s declared capabilities.
Only the first few tools appear The server paginated the result. Follow nextCursor until it is absent, or use an SDK helper that aggregates pages.
Duplicate tools The client appended a page twice after retrying. Deduplicate by name and retry a page only when the transport confirms the previous response was not received.
Schema is missing or unusable The server returned invalid metadata or the client discarded it while creating a summary. Retain the complete tool object and validate the server response before generating calls.
Stale tool list The server changed its inventory after the initial listing. Handle notifications/tools/list_changed and refresh; otherwise use a deliberate TTL.
SDK method or field error Examples target a different SDK version. Check the installed TypeScript or Python SDK reference and inspect the actual return object.

Performance, reliability, and cost

  • Performance: Listing transfers metadata, including schemas, so large inventories can be expensive in latency and memory. Cache per connection and render a concise summary while retaining full schemas for validation.
  • Reliability: Use request IDs, bounded retries, and cursor-safe recovery. If a connection drops, reconnect and restart listing rather than assuming a cursor remains valid.
  • Consistency: A notification and a subsequent listing can race with another server change. Treat the latest complete response as authoritative and refresh again if the server signals another change.
  • Cost: The MCP protocol does not define a listing fee. Any network, hosting, or provider charges come from the transport and server implementation, so apply your normal timeout and rate-limit policy.

Or skip the browser setup

If your MCP workflow needs screenshots as tools, ScreenshotNeo provides an MCP server with take_screenshot, get_page_info, and capture_pdf. You can discover those tools with the same tools/list flow, then invoke them from Claude, Cursor, or another MCP client.

For a direct screenshot without managing a browser, call the API documented at ScreenshotNeo docs:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. An MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Sign up free.

FAQ

Does tools/list run a tool?

No. It returns advertised definitions. Use the MCP tool-call method only after selecting and validating a tool.

Can I request one tool by name?

The standard operation lists pages of tools; clients normally filter the returned array locally by name.

Are tool descriptions trustworthy?

They are server-provided metadata. Treat untrusted annotations as untrusted input and keep authorization and human approval in your application.

What if the server has no tools?

A valid response can contain an empty tools array. Check capabilities and server configuration before treating that as an error.

Should I cache schemas forever?

No. Cache for the connection or a controlled TTL, and refresh when the server sends a list-change notification or a call reveals a mismatch.