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: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
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:--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
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
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’slib/:
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.
Framework adapters
Harnest keeps connection identity and approval policy consistent. Same-named tools on different servers remain distinct.
Limit tools by runtime principal
Usepermission= to require one grant for every tool from a client. Use tool_permissions= to add a requirement to selected remote tools:
Call MCP from trusted code
Use the invocation-scoped facade when a tool or service chooses the remote operation:tools/search_catalog.py
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 optionalharnest_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
UseMCPClient.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.