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.
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.
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.
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.
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.
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, andconnect()are the whole setup;connect()runs theinitializehandshake.StreamableHTTPClientTransportconnects to remote servers;StdioClientTransport, from@modelcontextprotocol/client/stdio, spawns local ones;SSEClientTransportis the fallback for SSE-only servers.InMemoryTransport.createLinkedPair()links a client and a server in one process.- After
connect(),getServerVersion(),getServerCapabilities(), andgetInstructions()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.