Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

To see what tools an MCP server advertises, send the MCP tools/list request and record the returned tool definitions. The standard-library Python script below does that over MCP’s stdio transport, follows continuation cursors, and saves a JSON profile you can compare later. It does not call the tools: an advertised definition is not proof that an invocation works, matches its schema, or is safe.

Use the script when you can launch the server as a local subprocess. It does not support Streamable HTTP or SSE. MCP’s tools specification separates discovery with tools/list from invocation with tools/call.

What an MCP tool profile tells you

An MCP tool definition describes a capability the server advertises. It includes a tool name and input schema; depending on what the server returns, it can also include a title, description, output schema, and annotations. The script preserves each full tool object, including fields it does not summarize, so you can inspect schema constraints that a short field list would miss.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The result describes the interface returned during discovery, not the server’s implementation or runtime behavior. Descriptions and annotations are server-provided metadata, not independent verification. To test behavior, a client must separately invoke a tool with tools/call and evaluate the result.

How to list an MCP server’s tools without installing the SDK

This minimal profiler uses only Python’s standard library and the MCP stdio transport. Pass it the command that launches your server, followed by any arguments that command needs. The server must speak MCP over standard input and output: JSON-RPC messages are newline-delimited, and standard output is reserved for protocol messages. Diagnostic output belongs on standard error.

Save this as profile_mcp.py:

#!/usr/bin/env python3
import argparse
import datetime as dt
import json
import subprocess
import sys

PROTOCOL_VERSION = "2025-06-18"


def send(process, message):
    process.stdin.write(json.dumps(message, separators=(",", ":")) + "n")
    process.stdin.flush()


def receive_response(process, expected_id):
    while True:
        line = process.stdout.readline()
        if not line:
            raise RuntimeError("Server closed stdout before replying")
        try:
            message = json.loads(line)
        except json.JSONDecodeError as exc:
            raise RuntimeError("Expected a JSON-RPC message on stdout") from exc
        # Ignore notifications and responses to other request IDs.
        if message.get("id") != expected_id:
            continue
        if "error" in message:
            raise RuntimeError(f"MCP error: {message['error']}")
        return message.get("result", {})


def request(process, request_id, method, params):
    send(process, {
        "jsonrpc": "2.0",
        "id": request_id,
        "method": method,
        "params": params,
    })
    return receive_response(process, request_id)


def main():
    parser = argparse.ArgumentParser(
        description="Profile tools advertised by an MCP stdio server"
    )
    parser.add_argument("--output", default="mcp-tool-profile.json")
    parser.add_argument("command", nargs=argparse.REMAINDER)
    args = parser.parse_args()
    command = args.command
    if command and command[0] == "--":
        command = command[1:]
    if not command:
        parser.error("provide the server launch command after --")

    process = subprocess.Popen(
        command,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        text=True,
        bufsize=1,
    )
    try:
        initialized = request(process, 1, "initialize", {
            "protocolVersion": PROTOCOL_VERSION,
            "capabilities": {},
            "clientInfo": {"name": "mcp-tool-profiler", "version": "1.0"},
        })
        negotiated_version = initialized.get("protocolVersion")
        if not negotiated_version:
            raise RuntimeError("Initialize response did not include protocolVersion")

        send(process, {
            "jsonrpc": "2.0",
            "method": "notifications/initialized",
            "params": {},
        })

        tools = []
        page_count = 0
        cursor = None
        saw_continuation = False
        while True:
            params = {"cursor": cursor} if cursor is not None else {}
            result = request(process, page_count + 2, "tools/list", params)
            page_count += 1
            tools.extend(result.get("tools", []))
            cursor = result.get("nextCursor")
            if cursor is None:
                break
            saw_continuation = True

        profile = {
            "profiledAt": dt.datetime.now(dt.timezone.utc).isoformat(),
            "transport": "stdio",
            "launchCommand": command,
            "protocolVersion": negotiated_version,
            "pageCount": page_count,
            "continuationCursorReturned": saw_continuation,
            "paginationComplete": True,
            "toolCount": len(tools),
            "tools": tools,
        }
        with open(args.output, "w", encoding="utf-8") as output:
            json.dump(profile, output, indent=2, ensure_ascii=False)
            output.write("n")
        print(f"Wrote {len(tools)} tools from {page_count} page(s) to {args.output}")
    finally:
        process.terminate()
        process.wait()


if __name__ == "__main__":
    main()

Run it and read the profile

  1. Save the code as profile_mcp.py in an environment with Python 3.
  2. Run it with the server launch command after --. For example: python3 profile_mcp.py --output tools.json -- python server.py. Replace python server.py with the actual command and arguments for your MCP server.
  3. Open the generated JSON file. Each item in tools is the server’s raw tool definition. Inspect inputSchema.required for mandatory fields and inputSchema.properties for declared fields and their types; retain schema constraints such as enums and nested object definitions. Check outputSchema when present.

The profile records the UTC timestamp, launch command, negotiated protocol version, number of pages, whether a continuation cursor appeared, whether pagination completed, and total tool count. It does not record an HTTP endpoint or authorization scope: this version profiles a local stdio process, whose launch command is its target identifier.

What the script’s pagination and handshake mean

MCP clients initialize a session before making ordinary requests. The script sends an initialize request with a client protocol version and capabilities, reads the server’s negotiated protocol version, then sends notifications/initialized before listing tools. It uses newline-delimited JSON-RPC messages over the subprocess pipes rather than treating the server as an HTTP endpoint.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

tools/list may return a nextCursor. When it does, the script sends another list request with that cursor and continues until the server returns no cursor. paginationComplete is true only after that loop reaches a response without a continuation cursor. If the process closes early, emits invalid protocol output, or returns an MCP error, the script stops instead of presenting a partial inventory as complete.

The example requests protocol version 2025-06-18, the version represented in the linked schema reference. The profile records the version the server actually returns. If a server cannot negotiate this requested version, use a client version supported by that server rather than treating a failed initialization as an empty tool list.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Compare profiles without mistaking them for compatibility tests

Profiles are useful for spotting changes in the advertised surface, such as a renamed tool, a changed required field, or an altered schema. They do not establish that the server’s behavior is backward-compatible or that a call using the declared schema will succeed.

  • For a meaningful comparison, use the same server version, launch configuration, authorization or access context, transport, and negotiated protocol version.
  • Compare tool count and names, required input fields, and the full input and output schemas. Keep the raw profile so you can review differences beyond the summary.
  • Record access context alongside the file if it affects which tools the server exposes. The specification allows the returned tool set to vary with authorization.
  • Do not interpret descriptions, annotations, or schemas as proof of safety or verified implementation behavior.

For integrations beyond this small stdio example, the official Python SDK client guide shows how to inspect listed tool metadata, including names, titles, descriptions, and input schemas. Its client API is a better fit when an application needs production transport handling rather than a compact inventory script.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.