Skip to main content
MCPClient declares a connection to an external MCP server. Direct clients live under root mcp/; each public filename supplies the connection identity and exports a zero-argument client() factory returning MCPClient.

Scaffold a connection

Create an authenticated Streamable HTTP client without putting its token in source:
The generated mcp/catalog.py reads the token through ${CATALOG_MCP_TOKEN}, sends it as Authorization: Bearer <token>, and prefixes discovered tool names with catalog_. Choose where and how the token is sent when a server uses a different header scheme:
The command accepts only the environment variable’s name. It rejects token values, credentials embedded in URLs, invalid header names, and advanced-mode projects where mcp/ discovery is not active.
mcp/catalog.py
Use MCPClient.sse(...) only for a legacy SSE server. Compilation does not connect. Tool discovery is lazy; explicitly configured subscriptions start with the serving runtime.

Discover what a server provides

Developers do not need to know resource URIs or prompt names in advance. From the agent folder, query a configured connection:
Use identifiers returned by discovery; the URI and prompt above are examples. Add --project ./my-agent when outside the agent folder. Commands use the agent’s Python environment and connection credentials without loading a model or starting subscription handlers. Inspection returns capabilities, tool schemas, resources, URI templates, and prompt argument definitions. Each catalogue is one page: pass a returned nextCursor to harnest mcp inspect catalog --catalog resources --cursor '…'. The other catalogues are tools, resource-templates, and prompts. Servers without an optional capability return an empty catalogue. The local playground’s MCP view offers the same discovery and explicit retrieval. It accepts only loopback, same-origin requests and is absent from deployed artifacts without the development UI.

Give the agent access to context

The connection above is enough. Managed ADK and LangGraph agents receive namespaced helpers based on the server’s advertised capabilities and your allowlists: Resource and prompt helpers are additive. Setting resources=[] or prompts=[] hides that category’s helpers and denies retrieval. An empty catalogue does not disable an advertised capability: resources or prompts can appear later. Harnest does not eagerly inject their contents into instructions. Trusted code can use the same governed operations:
tools/load_context.py
The facade also exposes inspect(), list_tools(cursor=None), list_resources(cursor=None), list_resource_templates(cursor=None), list_prompts(cursor=None), and get_prompt(name, arguments=None). Inspection and tool-catalogue listing are developer operations, not additional model tools. These methods remain available regardless of model exposure and still enforce permissions, allowlists, auditing, and invocation lifetime. Unsupported optional catalogues return empty lists; unsupported reads fail explicitly. Prompt arguments are strings; returned messages remain reference data, not system instructions. Outside an invocation, use a developer-owned connection:
connect() uses the LangGraph lifecycle identity by default; pass framework="adk" when sharing an ADK-owned lifecycle. Resource/prompt requests are independently scoped; do not rely on per-session server state shared with tool calls. Streamable HTTP clients first call server/discover for protocol 2026-07-28. CLI inspection, playground discovery, agent tools, resources, and prompts use this same path. Unsupported discovery/protocol responses and older servers’ HTTP 400 handshake rejections fall back to the SDK’s initialize handshake. Authentication failures, modern header-validation errors, and transport failures do not trigger downgrade. harnest mcp inspect CLIENT --json reports the selected protocolVersion. Tool calls are sent once; Harnest does not automatically replay them after a disconnect or an input-required response. Interactive multi-round-trip input handling is not supported on the newer HTTP path yet. Stdio and legacy SSE continue using the installed SDK’s supported protocol versions.

Restrict retrieval

Omit resources or prompts to allow discovery and retrieval of that category; use [] to deny it. Allowlists match exact URIs or prompt names. Listing an allowed URI template does not authorize arbitrary expanded URIs: concrete reads must also be allowed. max_content_bytes bounds returned content (default 1 MiB, configurable from 1 KiB to 16 MiB), not the SDK’s transport allocation. Binary content stays in MCP’s encoded representation. Client-wide permission protects resource/prompt helpers as well as remote tools. tool_permissions applies only to named remote tools. No permission tag means no access when a default-deny Agent Runtime Principal is active.

Subscribe to retained resource updates

Subscriptions belong on the MCP client, not on a public HTTP route. Import an async handler from the agent’s lib/:
mcp/catalog.py
lib/catalog_events.py
event.reason is start, update, or reconnect. Handlers have no inherited user/session context: choose an application-authorized identity explicitly if your service invokes an agent or enqueues a task. The handler timeout defaults to 30 seconds (handler_timeout_seconds). No public subscription-management endpoint is created. The default subscription method is SDK 1.x resources/subscribe, supported on stdio, SSE, and Streamable HTTP. For a stateless endpoint, set method="subscriptions/listen" on MCPSubscription; this uses the shared 2026-07-28 HTTP transport used by discovery and agent tools. Subscription method selection stays explicit: Harnest does not silently change your listener’s wire semantics. Harnest subscribes before the initial read, requires acknowledgment, and reads again after reconnecting. Initial failure fails runtime startup; established listeners reconnect with backoff up to 30 seconds. SDK 1.x listeners also probe retained state during idle periods to detect disconnected sessions. Shutdown cancels streams before closing client lifecycle resources.
Latest-value recovery is not replay of every missed event. Identical content is deduplicated in process only, after successful handling. Restarts and multiple replicas can deliver again. Make handlers idempotent; use a durable queue or provider event history when every event must be processed.

Framework adapters

Harnest keeps connection identity and approval policy consistent. Same-named tools on different servers remain distinct.

Limit tools by runtime principal

Use permission= to require one grant for every tool from a client. Use tool_permissions= to add a requirement to selected remote tools:
When an Agent Runtime Principal is active, Harnest hides an MCP operation unless a client-wide or operation-specific permission applies and every applicable permission is present. A completely untagged MCP client has no available operations. Harnest checks the requirement again when either the model or trusted code calls the tool. The MCP server or its gateway remains responsible for authorizing the remote request.

Call MCP from trusted code

Use the invocation-scoped facade when a tool or service chooses the remote operation:
tools/search_catalog.py
The facade exposes neither the raw transport nor credentials. Calls use the same approval, Tool Lifecycle, and MCP Client Lifecycle paths as model-selected MCP tools.

Authentication and gateways

Read URLs and static credentials from the deployment environment. For user-scoped access, resolve a token through Authentication and Credentials. Customize the HTTP client only for a gateway, mTLS, or transport policy. Put shared clients in lifecycle resources because adapters may call the factory more than once.

Direct connections and plugins

Combine APIs with Fused

Use the optional harnest_fused package to provision one Fused MCP server from multiple OpenAPI specifications. Include all operations or select operations per spec, then connect through a standard MCP Client. Provisioning is an explicit setup step.

Connect to OpenAPI at runtime

Use MCPClient.from_openapi(...) to load a local or remote specification when the MCP connection starts. Each spec has its own runtime FastMCP bridge, with normal MCP policies and environment credential references. No client-generation command is needed.