This is the documentation for the v2 beta — looking for the v1 documentation?
Skip to content

Connect to a server

A client holds one connection to one server: construct a Client, pick a transport, and connect().

Create a client and connect over HTTP

Client takes a name and a version; StreamableHTTPClientTransport takes the server's MCP endpoint URL.

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

const client = new Client({ name: 'my-client', version: '1.0.0' });

const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'));

await client.connect(transport);

connect() runs the initialize handshake and resolves once it completes. The client now holds the negotiated protocol version, the server's capabilities, and its instructions.

Coming from v1?

Client and the transport classes keep their names — only the import paths moved, to @modelcontextprotocol/client and its /stdio subpath. Run the codemod, then see the upgrade guide.

Connect to a local process over stdio

For a server you run as a child process, change only the transport: StdioClientTransport, imported from @modelcontextprotocol/client/stdio, spawns the command and speaks JSON-RPC over its stdin and stdout.

ts
const client = new Client({ name: 'my-client', version: '1.0.0' });

const transport = new StdioClientTransport({ command: 'node', args: ['server.js'] });

await client.connect(transport);

server.js runs as a child of your process. close() shuts it down in order: close stdin, then SIGTERM, then SIGKILL.

TIP

InMemoryTransport.createLinkedPair() is the third transport: it links a Client and an McpServer inside one process, no network and no child process. Test a server builds on it.

Fall back to SSE for servers that predate Streamable HTTP

An SSE-only server speaks the older HTTP+SSE transport instead of Streamable HTTP. Try StreamableHTTPClientTransport first; when it fails, retry with SSEClientTransport on a fresh Client.

ts
try {
    const client = new Client({ name: 'my-client', version: '1.0.0' });
    await client.connect(new StreamableHTTPClientTransport(new URL(url)));
    return client;
} catch {
    const client = new Client({ name: 'my-client', version: '1.0.0' });
    await client.connect(new SSEClientTransport(new URL(url)));
    return client;
}

Whichever branch returns, the Client behaves the same from here on — nothing downstream depends on the transport.

INFO

versionNegotiation in ClientOptions controls which protocol revision connect() negotiates — see Protocol versions.

Read what the server told you at connect time

Three accessors return what the server declared during the handshake; all of them return undefined until connect() resolves.

ts
console.log(client.getServerVersion());
console.log(client.getServerCapabilities());
console.log(client.getInstructions());

Connected to a server named travel that registered one tool and set instructions, that prints:

{ name: 'travel', version: '2.1.0' }
{ tools: { listChanged: true } }
Call list-trips before book-trip. Dates are ISO 8601.

The capability object gates every verb on the next page: only ask for what the server advertised. getInstructions() is the server's usage guide for the model — put it in the system prompt.

Disconnect cleanly

Over Streamable HTTP, terminate the server-side session, then close the client.

ts
await transport.terminateSession();
await client.close();

close() tears down the transport and rejects every request still in flight with a CONNECTION_CLOSED error. terminateSession() returns without sending anything when the server never issued a session ID. On the other transports, close() alone is the whole teardown.

Recap

  • new Client({ name, version }), a transport, and connect() are the whole setup; connect() runs the initialize handshake.
  • StreamableHTTPClientTransport connects to remote servers; StdioClientTransport, from @modelcontextprotocol/client/stdio, spawns local ones; SSEClientTransport is the fallback for SSE-only servers.
  • InMemoryTransport.createLinkedPair() links a client and a server in one process.
  • After connect(), getServerVersion(), getServerCapabilities(), and getInstructions() return what the server declared.
  • close() tears down the transport and rejects in-flight requests.
  • Protocol-revision differences live on the protocol versions page, not here.