Skip to content

MCP Client API Reference

Client libraries for connecting to MCP servers.

MCPClient

promptise.mcp.client.MCPClient

Production-grade MCP client for a single server.

Supports HTTP (Streamable HTTP), SSE, and stdio transports with Bearer token or API key authentication.

Parameters:

Name Type Description Default
url str | None

Server endpoint URL (for HTTP/SSE). Mutually exclusive with command.

None
transport str

"http" (default), "sse", or "stdio".

'http'
headers dict[str, str] | None

Extra HTTP headers sent on every request.

None
bearer_token str | None

Pre-issued Bearer token. When provided, an Authorization: Bearer <token> header is injected automatically. Obtain tokens from your Identity Provider or the server's token endpoint — the client never generates tokens itself.

None
api_key str | None

Pre-shared API key. When provided, an x-api-key header is injected automatically. Use this for simple secret-based auth when JWT is overkill.

None
command str | None

Executable for stdio transport (e.g. "python").

None
args list[str] | None

Arguments for the stdio command.

None
env dict[str, str] | None

Environment variables for the stdio process.

None
cwd str | None

Working directory for the stdio subprocess. When None the subprocess inherits the parent process's working directory.

None
timeout float

HTTP request timeout in seconds.

30.0
elicitation_callback ElicitationFnT | None

Answers the server's MCP elicitation requests (elicitation/create) — e.g. a server-side approval gate asking the human behind this client to confirm a tool call. Same signature as the MCP SDK's ClientSession callback: async (context, params) -> ElicitResult | ErrorData. The client declares the elicitation capability only when this is set; without it, servers are told elicitation is unsupported and fail-closed servers deny the gated call. A callback that raises is answered with an error, never an acceptance. See :func:promptise.approval.approval_elicitation_callback to route requests to an approval handler.

None
auto_reconnect bool

Re-open the MCP session when an HTTP or SSE server loses it (default True). When a request is answered 404 because the server no longer knows the session — a restart, a redeploy, a replica without the session — the client opens a new session (initialize) and retries that request once, as the MCP specification requires. The server never ran the refused request, so the retry is safe. A connection that drops during a call is not retried (the tool may have run); the next call opens a new session instead. Ignored for stdio.

True
bearer_token_provider BearerTokenProvider | None

For short-lived tokens, instead of bearer_token: a callable returning the current token (or None), asked on every HTTP request. It receives force_refresh — True after the server answered 401, when the request is re-sent once with the refreshed token. A session opened with a token the provider has since replaced is reopened before the next call; calls still running on the old session finish there and are never re-sent. If the provider raises, the operation fails with :class:MCPCredentialError and nothing is sent. Sync callables run in a worker thread. HTTP and SSE only.

None

Example — unauthenticated::

async with MCPClient(url="http://localhost:8080/mcp") as client:
    tools = await client.list_tools()

Example — with Bearer token::

async with MCPClient(
    url="http://localhost:8080/mcp",
    bearer_token="eyJhbGciOiJIUzI1NiIs...",
) as client:
    result = await client.call_tool("search", {"query": "python"})

Example — with short-lived tokens from an identity::

async with MCPClient(
    url="http://localhost:8080/mcp",
    bearer_token_provider=lambda force: identity.get_credential(
        "api://my-server", force_refresh=force
    ),
) as client:
    result = await client.call_tool("search", {"query": "python"})

Example — with API key::

async with MCPClient(
    url="http://localhost:8080/mcp",
    api_key="my-secret-key",
) as client:
    tools = await client.list_tools()

Example — fetch token from server endpoint::

token = await MCPClient.fetch_token(
    "http://localhost:8080/auth/token",
    client_id="my-agent",
    client_secret="agent-secret",
)
async with MCPClient(
    url="http://localhost:8080/mcp",
    bearer_token=token,
) as client:
    tools = await client.list_tools()

headers property

Current static HTTP headers (read-only copy).

A token from bearer_token_provider is set per request and is not included.

in_flight_calls property

Tool calls sent on this connection that have not returned yet.

An elicitation handler uses this to relate a server request to the call that caused it. With exactly one call in flight, a request arriving meanwhile is attributed to that call; with none or several, it cannot be tied to one.

Read from inside an elicitation callback that is answering a request on a per-caller session derived from this client (see :meth:with_bearer_token), it returns that session's calls: the connection the request actually arrived on.

session property

The underlying MCP ClientSession, or None if not connected.

session_generation property

How many MCP sessions this client has opened.

1 after connecting; each transparent re-initialisation after a lost session adds one. Compare the value before and after a call to tell whether the server's session — and so possibly its tool list — changed underneath it.

supports_bearer_token property

Whether a per-caller bearer token can be sent to this server.

True for HTTP and SSE. False for stdio, which has no request headers: a stdio server runs with the agent's own privileges for every caller.

transport property

The transport this client connects with ("http", "sse", "stdio").

__aenter__() async

Connect to the server and initialise the session.

Raises:

Type Description
MCPConnectionRejectedError

The HTTP server answered the handshake with a 4xx status (e.g. 401 Unauthorized, or 404 for a URL that is not an MCP endpoint).

MCPClientError

Any other connection or handshake failure.

__aexit__(*exc) async

Close the session and transport.

Cleanup is best-effort and never raises: the session is closed in the task that opened it, so it is safe to call from any task.

call_tool(name, arguments=None, *, progress_callback=None) async

Call a tool on the connected server.

Parameters:

Name Type Description Default
name str

Tool name.

required
arguments dict[str, Any] | None

Tool arguments dict.

None
progress_callback ProgressFnT | None

async (progress, total, message) -> None awaited for each progress notification the server sends for this call (ProgressReporter.report() on a Promptise server). Servers only send progress when the call carries a progress token, which is attached only when this is set.

None

Returns:

Type Description
CallToolResult

MCP CallToolResult with content list.

fetch_token(token_url, client_id, client_secret, *, timeout=10.0) async staticmethod

Fetch a Bearer token with an OAuth 2.0 client_credentials grant.

Sends a standard RFC 6749 token request: a form-encoded (application/x-www-form-urlencoded) POST with grant_type=client_credentials, client_id and client_secret. That works against the MCP server's built-in token issuer (:meth:~promptise.mcp.server.MCPServer.enable_token_endpoint) and against any token endpoint that accepts client_secret_post. A built-in issuer from Promptise 1.3.2 or earlier only reads JSON; when one answers that the body must be JSON, the request is retried once as JSON.

For production, obtain tokens from your Identity Provider (Auth0, Keycloak, Okta, etc.) and pass them directly via bearer_token.

Parameters:

Name Type Description Default
token_url str

Full URL of the token endpoint (e.g. http://localhost:8080/auth/token).

required
client_id str

Client identifier registered on the server.

required
client_secret str

Client secret for authentication.

required
timeout float

HTTP request timeout in seconds.

10.0

Returns:

Type Description
str

The access token string (ready to pass as bearer_token).

Raises:

Type Description
MCPClientError

On network errors or authentication failure.

get_prompt(name, arguments=None) async

Get a prompt, rendered with arguments.

Parameters:

Name Type Description Default
name str

Prompt name.

required
arguments dict[str, Any] | None

Prompt arguments. MCP carries them as strings: strings are sent as is, other values as JSON (3 → "3"); a Promptise server converts them back to the prompt's parameter types.

None

Returns:

Type Description
GetPromptResult

MCP GetPromptResult with description and messages.

Raises:

Type Description
MCPClientError

The server refused the request (unknown prompt, missing argument, denied, ...) or the connection failed.

list_prompts() async

List the server's prompts (every page).

Returns:

Type Description
list[Prompt]

MCP Prompt objects with name, description and

list[Prompt]

arguments.

list_resource_templates() async

List the server's resource templates (every page).

Returns:

Type Description
list[ResourceTemplate]

MCP ResourceTemplate objects with uriTemplate, name,

list[ResourceTemplate]

description and mimeType.

list_resources() async

List the server's static resources (every page).

Returns:

Type Description
list[Resource]

MCP Resource objects with uri, name, description

list[Resource]

and mimeType.

list_tools() async

List all tools from the connected server.

Returns:

Type Description
list[Tool]

List of MCP Tool objects with name, description, inputSchema.

read_resource(uri) async

Read a resource by URI (a static resource or a template expansion).

Parameters:

Name Type Description Default
uri str

The resource URI, e.g. "docs://pages/refunds".

required

Returns:

Type Description
ReadResourceResult

MCP ReadResourceResult. Each item of contents carries

ReadResourceResult

mimeType and either text (TextResourceContents) or

ReadResourceResult

base64 blob data (BlobResourceContents).

Raises:

Type Description
MCPClientError

The server refused the read (unknown URI, denied by auth or a guard, ...) or the connection failed.

with_bearer_token(bearer_token)

Return a new, unconnected client that authenticates as bearer_token.

The copy keeps this client's URL, transport, timeout, headers (including x-api-key) and elicitation callback, and replaces any Authorization header, whatever its casing, with Bearer <bearer_token>. Used to open a session per caller, so one caller's token is never sent on another caller's requests, while the server can still ask the caller's human to approve a gated call. A bearer_token_provider (for instance the agent's own identity) is not copied: the caller's token takes its place.

Raises:

Type Description
MCPClientError

For a stdio client, which cannot carry headers.

MCPClientError

promptise.mcp.client.MCPClientError

Bases: RuntimeError

Raised when an MCP client operation fails.

MCPConnectionRejectedError

promptise.mcp.client.MCPConnectionRejectedError

Bases: MCPClientError

Raised when an HTTP MCP server rejects the session with a 4xx status.

Typically 401/403 (missing or wrong bearer_token/api_key) or 404 (wrong endpoint URL). Retrying with the same configuration will fail the same way.

Also raised when a server rejects the credential of an established session (mid_session=True), typically because a static bearer_token expired. The call that hit it fails at once.

Attributes:

Name Type Description
status_code

The HTTP status the server answered with.

reason

The HTTP reason phrase (e.g. "Unauthorized").

url

The endpoint that was contacted.

server_name

The server's name when connected through :class:~promptise.mcp.client.MCPMultiClient or build_agent.

mid_session

True when an established session was rejected, False when the handshake was.

for_server(server_name)

Return a copy of this error that names server_name.

MCPMultiClient

promptise.mcp.client.MCPMultiClient

Connect to multiple MCP servers and aggregate their tools.

Each server gets its own MCPClient with independent auth / headers. Tools are tracked per-server so call_tool routes to the correct one.

Tool name collisions: If two servers expose a tool with the same name, the server that comes last in clients wins: list_tools returns only its copy, call_tool routes to it, and a warning names both servers. The other copies are not reachable through this client. Consider using server-specific prefixes on your MCP servers to avoid collisions.

Per-caller tokens: call_tool(..., bearer_token=...) sends that token instead of the client's own credentials. Each distinct token gets its own session to an HTTP/SSE server (opened on first use, reused by later calls with the same token), so concurrent calls for different users never share headers. stdio servers have no request headers: the token is ignored there and a warning is logged once.

Server restarts: when a server loses the session (restart, redeploy), its MCPClient opens a new one and retries the call once; this client then re-lists that server's tools so routing follows the tools the new deployment serves.

Parameters:

Name Type Description Default
clients dict[str, MCPClient]

Mapping of server name → MCPClient instance.

required
elicitation_callback ElicitationFnT | None

Default handler for MCP elicitation requests, installed on every client that was not given its own elicitation_callback. Same signature as :class:MCPClient's. The SDK callback does not say which server asked, so set a callback per MCPClient instead when the handler needs the server name. Per-caller sessions inherit their server's callback, so a call made with bearer_token=... can still answer elicitation requests.

None
max_caller_sessions int

Most idle per-caller sessions kept open. Least recently used sessions are closed first.

256
caller_session_idle_timeout float

Seconds after which an unused per-caller session is closed.

300.0
multi = MCPMultiClient({
    "hr": MCPClient(url="http://localhost:8080/mcp", bearer_token="..."),
    "docs": MCPClient(url="http://localhost:9090/mcp", api_key="secret"),
})
async with multi:
    tools = await multi.list_tools()
    result = await multi.call_tool("search_employees", {"query": "python"})

prompt_to_server property

Read-only view of prompt name → server name mapping.

resource_to_server property

Read-only view of static resource URI → server name mapping.

servers property

Read-only view of server name → client mapping.

template_to_server property

Read-only view of resource URI template → server name mapping.

tool_to_server property

Read-only view of tool name → server name mapping.

__aenter__() async

Connect to all servers.

__aexit__(*exc) async

Disconnect from all servers.

Every client is closed even when closing another one fails or the caller is cancelled meanwhile; the cancellation is re-raised once they all are.

call_tool(name, arguments=None, *, bearer_token=None, progress_callback=None) async

Call a tool, automatically routing to the correct server.

Parameters:

Name Type Description Default
name str

Tool name (as discovered via list_tools).

required
arguments dict[str, Any] | None

Tool arguments dict.

None
bearer_token str | None

Send this token as Authorization: Bearer ... instead of the server's configured credentials, over a session dedicated to this token. Ignored (with a one-time warning) for stdio servers.

None
progress_callback ProgressFnT | None

Receives the call's progress notifications; see :meth:MCPClient.call_tool.

None

Returns:

Type Description
CallToolResult

MCP CallToolResult.

Raises:

Type Description
MCPClientError

If the tool name is unknown or the call fails.

MCPConnectionRejectedError

The server refused bearer_token.

get_prompt(name, arguments=None, *, server=None, bearer_token=None) async

Get a prompt, routing to the server that serves name.

Parameters:

Name Type Description Default
name str

Prompt name.

required
arguments dict[str, Any] | None

Prompt arguments (see :meth:MCPClient.get_prompt).

None
server str | None

Get the prompt from this server instead of routing by name.

None
bearer_token str | None

Get it as this caller, over the caller's own session (see :meth:read_resource).

None

Raises:

Type Description
MCPClientError

No server serves name, or the request failed.

MCPConnectionRejectedError

The server refused bearer_token.

list_prompts() async

Discover prompts from every connected server.

A server that does not serve prompts is skipped. If two servers serve a prompt with the same name, the last one wins: only its copy is listed (a warning is logged) — pass server= to :meth:get_prompt to pick another.

list_resource_templates() async

Discover resource templates from every connected server.

A server that does not serve resources is skipped. Updates the template routing used by :meth:read_resource.

list_resources() async

Discover static resources from every connected server.

A server that does not serve resources is skipped. Updates the URI → server routing used by :meth:read_resource.

list_tools() async

Discover tools from all connected servers.

Returns:

Type Description
list[Tool]

Combined list of tools from all servers, one per name. When

list[Tool]

two servers expose a tool with the same name, only the copy

list[Tool]

from the server that comes last in clients is returned,

list[Tool]

and call_tool routes that name to it (a warning is logged).

list[Tool]

The _tool_to_server mapping is updated so call_tool

list[Tool]

routes correctly.

read_resource(uri, *, server=None, bearer_token=None) async

Read a resource, routing to the server that serves uri.

Parameters:

Name Type Description Default
uri str

Resource URI (static, or matching a server's template).

required
server str | None

Read from this server instead of routing by URI.

None
bearer_token str | None

Read as this caller: the token is sent as Authorization: Bearer ... over the caller's own session, exactly as for :meth:call_tool. Ignored (with a one-time warning) for stdio servers.

None

Raises:

Type Description
MCPClientError

No server serves uri, or the read failed.

MCPConnectionRejectedError

The server refused bearer_token.

server_for_prompt(name) async

Name of the server that serves prompt name (discovering prompts if needed).

Raises:

Type Description
MCPClientError

No connected server serves name.

server_for_resource(uri) async

Name of the server that serves uri (discovering resources if needed).

Raises:

Type Description
MCPClientError

No connected server serves uri.

InFlightToolCall

promptise.mcp.client.InFlightToolCall dataclass

A call_tool request awaiting its result.

Exposed through :attr:MCPClient.in_flight_calls so an elicitation handler can relate a server's elicitation/create request to the tool call that triggered it. MCP carries no such link on the wire, so a request is attributed to a call only when exactly one is in flight.

Attributes:

Name Type Description
name str

The tool being called.

arguments dict[str, Any]

A copy of the arguments the call was sent with.

context Context

A snapshot of the caller's :mod:contextvars taken when the call started (e.g. the agent's CallerContext). The elicitation handler runs in the session's own task, so this is the only way back to the caller's context.

MCPToolAdapter

promptise.mcp.client.MCPToolAdapter

Discover MCP tools and convert them to LangChain BaseTool instances.

Backed by the Promptise MCP Client.

Parameters:

Name Type Description Default
multi MCPMultiClient

Connected MCPMultiClient.

required
on_before OnBefore | None

Callback fired before each tool invocation.

None
on_after OnAfter | None

Callback fired after each tool invocation.

None
on_error OnError | None

Callback fired on tool errors.

None
optimize Any | None

Tool optimization config (schema minification etc.).

None
forward_caller_token bool | Collection[str]

Which servers' tools send the invoking CallerContext.bearer_token instead of the client's own credentials: True for every server, False for none (default), or a collection of server names. stdio servers cannot receive it (see :meth:MCPMultiClient.call_tool).

False
on_progress OnProgress | None

Callback fired for each progress notification a server sends during a tool call, as (tool_name, progress, total, message). May be sync or async. When None, no progress is requested from servers.

None
multi = MCPMultiClient({"hr": MCPClient(...)})
async with multi:
    adapter = MCPToolAdapter(multi)
    tools = await adapter.as_langchain_tools()
    # Pass `tools` to build_agent(extra_tools=tools)

as_langchain_tools() async

Discover tools and return them as LangChain BaseTool instances.

Each tool's args_schema is a recursively-built Pydantic model that preserves nested object structure, descriptions, defaults, and constraints from the MCP server's JSON Schema.

When optimize is set, static optimizations (schema minification, description truncation) are applied to reduce token cost.

Returns:

Type Description
list[BaseTool]

List of BaseTool instances ready for LangGraph.

list_tool_info() async

Return human-readable tool metadata for introspection.