# 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.

::: info 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](https://ts.sdk.modelcontextprotocol.io/v2/migration/upgrade-to-v2.md).
:::

## 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](https://ts.sdk.modelcontextprotocol.io/v2/testing.md) 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](https://ts.sdk.modelcontextprotocol.io/v2/protocol-versions.md).
:::

## 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](https://ts.sdk.modelcontextprotocol.io/v2/clients/calling.md): only ask for what the server advertised. `getInstructions()` is the server's usage guide for the model — put it in the system prompt.

A fourth accessor, `getDiscoverResult()`, tells the eras apart at connect time. Present, it is the modern-era `DiscoverResult` — persistable with `JSON.stringify` and usable on a later connect as `prior: { kind: 'modern', discover }` to skip the probe. Absent on a connected client, the era is legacy. This page's client used the default legacy handshake:

```ts
// The default mode ran the legacy initialize handshake — no DiscoverResult.
console.log(client.getDiscoverResult());
```

```
undefined
```

Under `versionNegotiation: { mode: 'auto' }` against a 2026-era server it returns the advertisement — see [Protocol versions](https://ts.sdk.modelcontextprotocol.io/v2/protocol-versions.md#skip-the-probe-with-a-cached-verdict) for the cached-verdict shapes and [Caching discovery verdicts](https://ts.sdk.modelcontextprotocol.io/v2/advanced/gateway.md#caching-discovery-verdicts) for the full host-side loop.

## 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; `getDiscoverResult()` tells the eras apart (present = modern, absent = legacy).
- `close()` tears down the transport and rejects in-flight requests.
- Protocol-revision differences live on the protocol versions page, not here.
