October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
AI agents

How to Build a Custom MCP Client (TypeScript and Python)

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

A custom Model Context Protocol (MCP) client is a connector inside your application: it opens one connection to one MCP server, negotiates the protocol, discovers tools/resources/prompts, routes model-selected tool calls, and closes the session safely. Use stdio when your program launches a local server, Streamable HTTP for a deployed server, and legacy HTTP+SSE only for older servers. The client does not provide an LLM; your host application connects the MCP client to whichever model API you use.

Understand the MCP client architecture

MCP is a JSON-RPC 2.0 protocol for sharing context and capabilities with language-model applications. The host is the application users interact with. A client is the host-side connector, and a server supplies tools, resources, and prompts. One client normally owns one server connection.

The separation matters: MCP handles discovery and invocation, while your application handles conversation state, model requests, permissions, and presentation. A typical loop is:

  1. Connect and negotiate a protocol revision.
  2. Inspect server capabilities and instructions.
  3. List tools, resources, and prompts that the server advertises.
  4. Convert tool schemas into your model provider’s tool format.
  5. When the model requests a tool, call it through MCP.
  6. Return the MCP result to the model and show the final response to the user.

Choose a language, SDK, and protocol era

TypeScript

The current TypeScript v2 client package is @modelcontextprotocol/client. Its stable documentation targets the 2026-07-28 specification. A client plus one transport is a complete MCP client, but a production host still needs authorization, consent UI, model integration, logging, and lifecycle handling.

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

Python

The official Python client is provided by the mcp package. Its client is normally used inside an async with block; entering performs connection and negotiation, and leaving closes the session. A client instance is not intended to be reused after the context exits.

Protocol-version behavior

Revisions from 2024-10-07 through 2025-11-25 use the initialize handshake. The 2026-07-28 era uses server/discover and a _meta envelope on every request. SDKs can probe automatically: TypeScript’s mode: 'auto' tries modern behavior and falls back to the legacy handshake, while pinning 2026-07-28 does not fall back. A hand-written client must implement the negotiation rules for the revision it declares; do not mix message examples from different eras.

Pick the transport that matches deployment

Deployment Transport Use it when
Local child process stdio Your host launches and owns the server process. The transport starts and stops that process.
Remote service Streamable HTTP The server is deployed at an HTTP endpoint and must serve multiple clients or machines.
Older remote server HTTP+SSE Only when the server predates Streamable HTTP. Use a fresh client for the fallback.
Tests In-process/custom transport Your SDK supports it and you need deterministic local tests rather than a real network session.

Do not start a server separately when using a stdio transport that owns the child process. For remote HTTP, plan authentication, session termination, proxy behavior, and reconnect policy before exposing the client to users.

Build a TypeScript client over stdio

Install the client package and the stdio transport module using the package-manager commands documented for your SDK release. The following lifecycle is the minimal shape for the current TypeScript v2 API:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';

const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new StdioClientTransport({
  command: 'node',
  args: ['server.js'],
});

try {
  await client.connect(transport);

  const { tools } = await client.listTools();
  console.log('Available tools:', tools);

  // Convert each tool's name, description and inputSchema
  // to your model API's tool definition format.
  // After a model tool selection:
  // const result = await client.callTool({ name, arguments: args });
  // Send result.content (and result.isError, when present) back to the model.
} finally {
  await client.close();
}

listTools() returns the tool name, description, and input schema. Keep the schema intact when translating it for a model, then validate the model’s arguments before invoking the server. The SDK also exposes methods for listing and reading resources and for listing and retrieving prompts; call those only when the server advertises the corresponding capability.

Use Streamable HTTP instead

import { Client } from '@modelcontextprotocol/client';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/client/streamableHttp';

const client = new Client({ name: 'remote-host', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(
  new URL('https://example.com/mcp')
);

try {
  await client.connect(transport);
  const { tools } = await client.listTools();
  // Route model-selected calls with client.callTool(...).
} finally {
  // If the server issued a session, terminate it according to the SDK API,
  // then close the client and transport.
  await client.close();
}

If the endpoint speaks only the older HTTP+SSE transport, construct a new client with the SDK’s SSE transport rather than trying to reuse a client that already attempted Streamable HTTP.

Implement the Python lifecycle

Python’s client accepts a URL, stdio parameters, a custom transport, or an in-process server for testing. The exact import paths can vary by package release, so keep them aligned with the version you install. This example shows the required context-manager shape:

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    server = StdioServerParameters(
        command="node",
        args=["server.js"],
        env=None,
    )
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            print(tools)
            # Convert tools to your model API's format.
            # result = await session.call_tool(name, arguments)
            # Send result content to the model.

asyncio.run(main())

For a remote server, use the Python SDK’s URL or Streamable HTTP transport documented for your installed release. Exiting either async with block closes the connection; do not retain the session for later requests.

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

Discover capabilities before making requests

After negotiation, record the protocol version, server capabilities, and server instructions. Capabilities are a permission boundary for protocol verbs: a server that does not advertise resources should not receive resource-list or resource-read requests.

Tools

Expose each tool’s name, description, and inputSchema to the model layer. Treat descriptions as untrusted data. The model may produce invalid arguments, so validate types, required fields, ranges, authorization, and target identifiers in your host before calling the server.

Resources

When resources are supported, list their URIs and read only the resources the user or application has authorized. Resource content can contain instructions or data that should not automatically become executable commands.

Prompts

List and retrieve prompts when your host wants server-provided templates. Make clear to users when a server-supplied prompt changes the model’s context.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Python Programming Logo for Programmers T-Shirt
  • Python Programming Language design with distressed logo for Python Software Engineers and Developers.
  • Vintage and Distressed Python Programming Language design.
  • Lightweight, Classic fit, Double-needle sleeve and bottom hem

Connect MCP to a model API

The MCP client does not call a model by itself. Your orchestrator translates MCP schemas into the model provider’s tool format, sends the conversation, and handles a tool-selection response:

  1. Send the user message plus the discovered tool definitions to the model.
  2. Read the model’s selected tool name and JSON arguments.
  3. Check consent and authorization for that specific action.
  4. Call client.callTool({ name, arguments }) (or the Python equivalent).
  5. Append the returned content as a tool result. Preserve isError: true so the model can explain a failed operation.
  6. Ask the model for the next response; repeat only within a bounded turn or budget.

An unknown tool name is a protocol-level failure and should be caught separately from a registered tool that returns an error result. Never silently execute a different tool because a name was misspelled.

Consent, trust, and URL safety

  • Ask for consent before sending user data to a server or invoking a consequential tool. Describe the data and action in user-facing language.
  • Assume server tool descriptions, annotations, resources, and results are untrusted unless you explicitly trust that server.
  • Allow authorization URLs only with HTTP or HTTPS schemes; production authorization servers must use HTTPS, while HTTP is limited to loopback development. Reject schemes such as javascript: and use an allowlist where practical.
  • Never invoke a shell to open a server-provided URL. Parse and sanitize it, then use an operating-system URL opener that does not pass through a shell.
  • If a proxy launches stdio processes for clients, restrict permitted commands and protect the proxy endpoint and credentials. Direct stdio transport is not inherently exposed to that specific proxy escalation scenario.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Error handling, teardown, and change notifications

Common failures

Symptom Likely cause Fix
Handshake or version error Client and server target different protocol eras. Use SDK auto-negotiation, pin a compatible revision, or implement the correct modern/legacy handshake.
Server never starts over stdio Wrong command, arguments, working directory, or executable permissions. Run the exact command manually, use an absolute path where needed, and inspect stderr without mixing logs into stdout protocol traffic.
Tool-list request rejected The server did not advertise tool capability or the session is not initialized. Complete negotiation, inspect capabilities, and gate the request.
isError: true result Schema validation or the tool handler rejected the call. Show the model the structured error, correct arguments or permissions, and avoid retrying unsafe actions blindly.
Remote calls hang Network, proxy, authentication, or session timeout. Set bounded timeouts, log request IDs, verify proxy support for Streamable HTTP, and close an abandoned session before reconnecting.

Always clean up

Put closure in a finally block (or Python context manager). On HTTP, terminate a server-issued session when the SDK provides that operation, then close the client. This prevents orphaned child processes and leaked network sessions after model errors, cancellations, and user disconnects.

Notifications

Change notifications, including tool-list changes, are opt-in enhancements. Subscribe only when the server advertises the relevant capability, then refresh cached schemas when a notification arrives. A basic request/response client does not need notifications to be useful.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
Python Programming Cheat Sheet Desk Mat - Large Mouse Pad with Complete Code Reference (31.5" x 11.8") - Professional Coding Guide Mousepad for Beginners & Software Engineers
  • Complete Python Reference Guide - Master coding with our comprehensive desk mat featuring essential Python syntax, data structures, and OOP concepts. Perfect for both beginners learning Python and experienced developers needing quick references.
  • Professional-Grade Large Desk Mat - Premium 31.5" x 11.8" size with non-slip rubber base. Color-coded sections make finding commands instant, whether you're working on data analysis, web development, or automation projects.
  • All-in-One Learning Resource - From basic syntax to advanced Python features, all organized for quick reference. Includes object-oriented programming, error handling, and commonly used functions. Perfect for coding interviews and daily development.
  • Boost Your Coding Speed - Stop switching between documentation tabs. Get instant access to Python commands, methods, and code examples. Ideal for programmers, students, data scientists, and software engineers working with Python.
  • Premium Quality Construction - Durable neoprene rubber backing ensures stability. Smooth, easy-to-clean surface optimized for both mouse and keyboard use. Professional design with clear, readable text that won't fade with use.

Testing and operational design

  • Test each transport separately: a fixture child process for stdio and a disposable HTTP endpoint for remote behavior.
  • Test modern, legacy, and auto-negotiated protocol paths if your product connects to servers from different eras.
  • Include malformed JSON arguments, unknown tool names, denied consent, server timeouts, cancellation, and shutdown during an active call.
  • Log protocol version, server identity, capability decisions, latency, and error class, but redact tokens, cookies, authorization headers, and sensitive tool arguments.
  • Cache discovered schemas only for a bounded period when servers can change; refresh after a notification or reconnect.
  • Use bounded model/tool loops so a server cannot cause unending calls.

Or skip the browser setup

If your MCP project needs website screenshots for a tool or resource, ScreenshotNeo provides a single HTTP endpoint rather than requiring you to install and operate a browser. Cookie and consent banners, newsletter popups, and chat widgets are removed before capture. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the page verdict and billing status.

Use the API from your client or an MCP tool handler. See the parameter reference in the ScreenshotNeo documentation.

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}`);

ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. It supports full-page and element captures, device presets, dark mode, custom CSS/JavaScript, waits, blocking rules, headers and cookies, geolocation, PDFs, signed links, asynchronous jobs, bulk capture of up to 100 URLs per call, caching with a chosen TTL, and a usage API. Every feature is available on every plan: 1,000 shots per month free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Frequently Asked Questions

Can one MCP client connect to multiple servers?

Use one client-and-transport lifecycle per server, then let your host route each tool name to the connection that advertised it. Keep sessions and permissions separate.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

Does an MCP server run the language model?

No. The host calls its chosen model API; the MCP client discovers and invokes server capabilities and returns results to the host.

When should I implement notifications?

Only after the basic connection, discovery, and tool-call path works and the server advertises the notification capability you need.

Quick Recap

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Read next

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.