Skip to main content

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.

McpClient is an Effect service that wraps the @modelcontextprotocol/sdk client. It manages the connection lifecycle automatically — the underlying MCP Client is acquired when the layer is built and released (via client.close()) when the scope closes — so callers only need to call connect then toolCall.

McpClient service

import { McpClient } from "clanka/McpClient"
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"

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(options)

Open a Streamable HTTP transport to the given MCP server URL. Must be called before any toolCall.
options.url
string
required
The full HTTP or HTTPS URL of the MCP server endpoint, e.g. "https://tools.example.com/mcp". Internally passed to StreamableHTTPClientTransport from @modelcontextprotocol/sdk.
return
Effect<void, McpClientError>
Resolves to void on success. Fails with McpClientError if the connection handshake fails.

toolCall(options)

Invoke a named tool on the connected MCP server.
options.name
string
required
The tool name as registered on the MCP server.
options.arguments
Record<string, unknown>
required
A plain object of arguments forwarded verbatim to the server tool.
return
Effect<unknown, McpClientError>
Resolves to the tool’s response. Returns response.structuredContent when present, otherwise response.content. Fails with McpClientError if the server returns an error or the transport fails.

McpClient.layer

The standard Layer constructor. Provide this layer wherever McpClient is required.
import { layer } from "clanka/McpClient"
import * as Layer from "effect/Layer"

export const layer: Layer.Layer<McpClient>
The layer uses Effect.acquireRelease to guarantee the MCP Client instance is always closed, even if the program fails or is interrupted.
Because layer uses acquireRelease, it must be provided inside a Scope. Use Effect.scoped or Layer.toRuntime to supply one.

McpClientError

import * as Schema from "effect/Schema"

export class McpClientError extends Schema.TaggedErrorClass<McpClientError>()(
  "McpClientError",
  {
    cause: Schema.Defect,
  },
) {}
cause
Defect
The raw underlying exception thrown by the MCP SDK or the transport layer, captured as an Effect Defect.
McpClientError is a tagged Schema error class, so it can be matched with Effect.catchTag("McpClientError", ...) and serialised/deserialised via Effect Schema.

Full example: connect then call a tool

import * as Effect from "effect/Effect"
import { McpClient, McpClientError, layer } from "clanka/McpClient"

const program = Effect.gen(function* () {
  const mcp = yield* McpClient

  // 1. Connect to the MCP server
  yield* mcp.connect({ url: "https://tools.example.com/mcp" })

  // 2. Call a tool
  const result = yield* mcp.toolCall({
    name: "search_files",
    arguments: { query: "OutputFormatter", maxResults: 5 },
  })

  console.log(result)
}).pipe(
  Effect.scoped,
  Effect.provide(layer),
)

Effect.runPromise(program)

Wrapping McpClient calls as AgentTool

You can expose MCP tools to a Clanka agent by wrapping toolCall inside an AgentTool definition. The agent’s JS sandbox receives the tool and can call it directly.
import * as Effect from "effect/Effect"
import { McpClient } from "clanka/McpClient"
import type { AgentTool } from "clanka/AgentTool"

const searchFilesTool: AgentTool = {
  name: "search_files",
  description: "Search the codebase for files matching a query.",
  parameters: {
    type: "object",
    properties: {
      query: { type: "string" },
      maxResults: { type: "number" },
    },
    required: ["query"],
  },
  execute: (args) =>
    Effect.gen(function* () {
      const mcp = yield* McpClient
      return yield* mcp.toolCall({ name: "search_files", arguments: args })
    }),
}
Call mcp.connect(...) once at startup (e.g. in your layer or program setup) rather than inside each tool’s execute function. The McpClient layer holds a single Client instance for the lifetime of the scope.

Transport

McpClient uses StreamableHTTPClientTransport from @modelcontextprotocol/sdk/client/streamableHttp.js. This transport supports MCP’s streaming HTTP protocol and passes an AbortSignal through from Effect’s interruption model, so cancelling an Effect fiber will abort the in-flight HTTP request.

Build docs developers (and LLMs) love