scala-mcp

[md]Tools

Tools are the most-used MCP feature: typed, schema-described functions an AI client can call. The snippets below are type-checked by mdoc — they share the imports and placeholder types in the setup block.

import cats.effect.IO
import io.circe.{Decoder, Encoder}
import net.andimiller.mcp.core.schema.JsonSchema
import net.andimiller.mcp.core.server.*
import net.andimiller.mcp.core.protocol.ToolResult

case class MyRequest(value: String)  derives JsonSchema, Decoder, Encoder.AsObject
case class MyResponse(value: String) derives JsonSchema, Decoder, Encoder.AsObject

trait MyCtx:
  def doSomething(req: MyRequest): IO[MyResponse]

Fluent builder

.run on a non-contextual builder erases the input/output types into JSON schemas and returns a Tool.Resolved[F] — the form a Server dispatches:

val myTool: Tool.Resolved[IO] =
  tool.name("my_tool")
    .description("Tool description")
    .in[MyRequest]
    .out[MyResponse]
    .run(req => IO.pure(MyResponse(req.value)))

Returning ToolResult directly

.runResult lets you return ToolResult[Out] (Success / Text / Error) directly when the call can fail or wants to short-circuit:

val mayFail: Tool.Resolved[IO] =
  tool.name("risky")
    .in[MyRequest]
    .out[MyResponse]
    .runResult(req => IO.pure(ToolResult.Error("nope")))

Contextual tools

A contextual tool receives a per-session context value when called. Use this for state, auth-derived identity, or per-session resources. The return type keeps the context, input, and output types so the server can wire it up later:

val ctxTool: Tool[IO, MyCtx, MyRequest, MyResponse] =
  contextualTool[MyCtx]
    .name("my_tool")
    .description("Tool description")
    .in[MyRequest]
    .out[MyResponse]
    .run((ctx, req) => ctx.doSomething(req))

Tool.builder[IO] and Tool.contextual[MyCtx] are equivalent to the tool and contextualTool helpers above and remain available.