Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Effectful-Tech/clanka/llms.txt
Use this file to discover all available pages before exploring further.
Clanka ships with McpClient, a thin Effect wrapper around the @modelcontextprotocol/sdk client. It lets you connect to any MCP server that supports the Streamable HTTP transport and call its tools from your Effect application — including from inside custom AgentTools.
The McpClient service
McpClient is a standard Effect Context.Service with two methods:
export class McpClient extends Context.Service<
McpClient,
{
connect(options: {
readonly url: string
}): Effect.Effect<void, McpClientError>
toolCall(options: {
readonly name: string
readonly arguments: Record<string, unknown>
}): Effect.Effect<unknown, McpClientError>
}
>()("clanka/McpClient") {}
connect establishes a session with the MCP server at the given URL using StreamableHTTPClientTransport. toolCall invokes a named tool on that server and returns its result (either structuredContent or content from the MCP response).
McpClientError
Both methods can fail with McpClientError, a tagged Schema error that wraps the underlying cause:
export class McpClientError extends Schema.TaggedErrorClass<McpClientError>()(
"McpClientError",
{
cause: Schema.Defect,
},
) {}
Handle it with Effect.catchTag("McpClientError", ...) wherever you need to recover from connection or invocation failures.
Setting up the layer
Provide McpClient.layer in your Effect layer stack. The layer acquires an MCP Client instance and releases it when the scope closes:
import { McpClient } from "clanka/McpClient"
import * as Layer from "effect/Layer"
const McpLayer = McpClient.layer
The layer requires a Scope (provided automatically by Effect’s runtime for scoped resources) and no other services.
Acquire the McpClient service
import * as Effect from "effect/Effect"
import { McpClient } from "clanka/McpClient"
const program = Effect.gen(function* () {
const mcp = yield* McpClient
Connect to the MCP server
yield* mcp.connect({ url: "http://localhost:8080/mcp" })
connect must be called before any toolCall. If the server is
unreachable, the effect fails with McpClientError.Call a tool
const result = yield* mcp.toolCall({
name: "get_weather",
arguments: { city: "Berlin" },
})
console.log(result)
})
Full example
import * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"
import { McpClient, McpClientError } from "clanka/McpClient"
import { NodeServices } from "@effect/platform-node"
import { NodeRuntime } from "@effect/platform-node"
const program = Effect.gen(function* () {
const mcp = yield* McpClient
// Connect to the MCP server
yield* mcp.connect({ url: "http://localhost:8080/mcp" }).pipe(
Effect.catchTag("McpClientError", (err) =>
Effect.die(`Failed to connect: ${err.cause}`),
),
)
// Call a tool
const result = yield* mcp.toolCall({
name: "get_weather",
arguments: { city: "Berlin", units: "metric" },
}).pipe(
Effect.catchTag("McpClientError", (err) =>
Effect.die(`Tool call failed: ${err.cause}`),
),
)
console.log("MCP tool result:", result)
}).pipe(Effect.scoped)
program.pipe(
Effect.provide(McpClient.layer),
Effect.provide(NodeServices.layer),
NodeRuntime.runMain,
)
The most powerful use of McpClient is wrapping MCP tool calls inside custom AgentTools, so the agent can invoke them from its JavaScript scripts. Declare a Tool.make entry that takes the MCP tool name and arguments, call McpClient in the handler, and provide McpClient.layer alongside your toolkit handlers.
import * as Tool from "effect/unstable/ai/Tool"
import * as Toolkit from "effect/unstable/ai/Toolkit"
import * as Schema from "effect/Schema"
import * as Effect from "effect/Effect"
import { McpClient, McpClientError } from "clanka/McpClient"
import * as Agent from "clanka/Agent"
import * as Layer from "effect/Layer"
import { NodeServices, NodeHttpClient } from "@effect/platform-node"
// 1. Declare a custom tool that proxies an MCP tool call
const WeatherTool = Tool.make("getWeather", {
description: "Fetch current weather for a city via the MCP weather server.",
parameters: Schema.Struct({
city: Schema.String,
units: Schema.optional(Schema.Literal("metric", "imperial")),
}),
success: Schema.Unknown,
dependencies: [McpClient],
})
const WeatherToolkit = Toolkit.make(WeatherTool)
// 2. Implement the handler using McpClient
const WeatherToolHandlers = WeatherToolkit.toLayer(
Effect.gen(function* () {
const mcp = yield* McpClient
// Connect once when the layer is built
yield* mcp.connect({ url: "http://mcp-server:8080/mcp" }).pipe(
Effect.catchTag("McpClientError", (err) => Effect.die(err.cause)),
)
return WeatherToolkit.of({
getWeather: Effect.fn("WeatherTool.getWeather")(function* (options) {
return yield* mcp.toolCall({
name: "get_weather",
arguments: options,
}).pipe(
Effect.catchTag("McpClientError", (err) => Effect.die(err.cause)),
)
}),
})
}),
)
// 3. Provide McpClient.layer and the toolkit handlers to the agent
const AgentLayer = Agent.layerLocal({
directory: process.cwd(),
tools: WeatherToolkit,
}).pipe(
Layer.provide(WeatherToolHandlers),
Layer.provide(McpClient.layer),
Layer.provide(NodeServices.layer),
Layer.provide(NodeHttpClient.layerUndici),
)
With this wiring in place, the agent can call await getWeather({ city: "Berlin" }) from any script it writes, and the result flows back through MCP.
If you need to proxy many MCP tools, consider building a factory that creates
one Tool.make entry per MCP tool name, then merge them all into a single
toolkit with Toolkit.make(...tools).
McpClient.layer uses Effect.acquireRelease internally, so the underlying
MCP client is closed automatically when the enclosing Scope finalizes.
Always run the layer inside a scoped context (e.g. Effect.scoped or an
Effect runtime that manages scopes).