Track MCP LogoTrack MCP
Track MCP LogoTrack MCP

The world's largest repository of Model Context Protocol servers. Discover, explore, and submit MCP tools.

Product

  • Categories
  • Top MCP
  • New & Updated
  • Submit MCP

Company

  • About

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy

© 2026 TrackMCP. All rights reserved.

Built with ❤️ by Krishna Goyal

    Fast Mcp Scala

    A quick and easy way to deploy MCP servers using Scala

    15 stars
    Scala
    Updated Sep 26, 2025

    Table of Contents

    • Contents
    • Installation
    • Quickstart
    • Choosing a registration path
    • Tools and @Param metadata
    • Tool hints
    • Resources (static and templated)
    • Prompts
    • Context (McpContext)
    • Transports
    • stdio (for Claude Desktop, MCP Inspector)
    • HTTP (for remote clients, load balancers, test harnesses)
    • Tasks (experimental, off by default)
    • Customizing decoding (Jackson 3)
    • Two backends, one API
    • Running on Bun
    • Spec coverage
    • Running examples
    • Claude Desktop integration
    • License
    • Developing locally
    • Build commands (Mill)
    • Consuming a local build

    Table of Contents

    • Contents
    • Installation
    • Quickstart
    • Choosing a registration path
    • Tools and @Param metadata
    • Tool hints
    • Resources (static and templated)
    • Prompts
    • Context (McpContext)
    • Transports
    • stdio (for Claude Desktop, MCP Inspector)
    • HTTP (for remote clients, load balancers, test harnesses)
    • Tasks (experimental, off by default)
    • Customizing decoding (Jackson 3)
    • Two backends, one API
    • Running on Bun
    • Spec coverage
    • Running examples
    • Claude Desktop integration
    • License
    • Developing locally
    • Build commands (Mill)
    • Consuming a local build

    Documentation

    fast-mcp-scala

    Scala 3 for MCP: annotation-driven and typed-contract APIs on both JVM and Scala.js/Bun.

    fast-mcp-scala is a developer-friendly library for building Model Context Protocol servers. Extend one trait, declare your tools, done:

    scala 3 raw
    object HelloWorld extends McpServerApp[Stdio, HelloWorld.type]:
      @Tool(name = Some("add"))
      def add(@Param("a") a: Int, @Param("b") b: Int): Int = a + b

    No override def run, no import zio.*, no ceremony. Two complementary registration paths converge on the same backend:

    • @Tool / @Resource / @Prompt annotations + scanAnnotations[T] for a zero-boilerplate, macro-driven experience (JVM + Scala.js/Bun)
    • McpTool, McpPrompt, McpStaticResource, McpTemplateResource for first-class, testable, cross-platform contract values — handlers return plain values, ZIO, Either[Throwable, _], or Try via the ToHandlerEffect typeclass

    Built on ZIO 2, Tapir-derived schemas, Jackson 3 (JVM) / zio-json (JS), the official Java MCP SDK 1.1.1, and the official TS MCP SDK 1.29.0. Transport is a phantom type parameter — McpServerApp[Stdio, Self.type] or McpServerApp[Http, Self.type] — with compile-time runner dispatch.

    Contents

    • Installation
    • Quickstart
    • Choosing a registration path
    • [Tools and @Param metadata](#tools-and-param-metadata)
    • Tool hints
    • Resources (static and templated)
    • Prompts
    • [Context (McpContext)](#context-mcpcontext)
    • Transports
    • Customizing decoding (Jackson 3)
    • Two backends, one API
    • Spec coverage
    • Running examples
    • Claude Desktop integration
    • Developing locally

    Installation

    scala 3 ignore
    // JVM — Java SDK-backed runtime with annotations, derived schemas, HTTP + stdio transports.
    libraryDependencies += "com.tjclp" %% "fast-mcp-scala" % "0.3.2"
    
    // Scala.js — TS SDK-backed runtime on Bun/Node + the same annotation and typed-contract APIs.
    libraryDependencies += "com.tjclp" %%% "fast-mcp-scala" % "0.3.2"

    Built against Scala 3.8.3. JVM requires JDK 17+. Scala.js artifact is published for sjs1_3 (Scala.js 1.x); runs on Bun (first-class) and Node 18+.

    Quickstart

    A single-file server with one tool — the same code lives in [HelloWorld.scala](fast-mcp-scala/jvm/src/com/tjclp/fastmcp/examples/HelloWorld.scala):

    scala 3 raw
    //> using scala 3.8.3
    //> using dep com.tjclp::fast-mcp-scala:0.3.2
    //> using options "-Xcheck-macros" "-experimental"
    
    import com.tjclp.fastmcp.{*, given}
    
    object HelloWorld extends McpServerApp[Stdio, HelloWorld.type]:
    
      @Tool(name = Some("add"), description = Some("Add two numbers"), readOnlyHint = Some(true))
      def add(@Param("First operand") a: Int, @Param("Second operand") b: Int): Int = a + b

    That's it — no import zio.*, no override def run, no ZIO.succeed(...). The McpServerApp[T, Self] trait handles server construction, annotation scanning, and transport lifecycle. Transport is a phantom type parameter (Stdio / Http) that compile-time-selects the runner.

    Exercise it through the MCP Inspector:

    bash
    npx @modelcontextprotocol/inspector scala-cli scripts/quickstart.sc

    Choosing a registration path

    Annotations (@Tool + scanAnnotations)Typed contracts (McpTool)
    PlatformJVM + Scala.js/BunJVM + Scala.js/Bun
    StyleMethods on an object, discovered by macroFirst-class vals
    SchemaDerived from method signature & @ParamDerived from case-class fields & @Param
    TestingCall the method directlyInvoke .handler on the value
    ComposabilityWhatever methods the object exposesCollect into lists, generate from config
    Best forQuick servers, prototypes, single-module appsLibraries, cross-module sharing, production codebases

    Both coexist on the same server — override tools / prompts / staticResources / templateResources on your McpServerApp to mount typed contracts alongside annotated methods:

    scala 3 raw
    object MyServer extends McpServerApp[Stdio, MyServer.type]:
      @Tool(name = Some("ping")) def ping(): String = "pong"
    
      override val tools = List(
        McpTool[AddArgs, AddResult](name = "add") { args =>
          AddResult(args.a + args.b)            // plain value — auto-lifted
        }
      )

    Handler lambdas return plain values, ZIO, Either[Throwable, _], or scala.util.Try — the ToHandlerEffect[F[_]] typeclass picks the right lift. Bring your own given for other effect systems (cats.effect.IO, Monix, ...).

    See [AnnotatedServer.scala](fast-mcp-scala/jvm/src/com/tjclp/fastmcp/examples/AnnotatedServer.scala) for the annotation path and [ContractServer.scala](fast-mcp-scala/jvm/src/com/tjclp/fastmcp/examples/ContractServer.scala) for typed contracts.

    Tools and @Param metadata

    Every tool parameter can carry metadata that flows into the derived JSON schema:

    scala 3 raw
    @Tool(name = Some("search"), description = Some("Search with optional filters"))
    def search(
        @Param(description = "Search query", examples = List("scala", "mcp"))
        query: String,
        @Param(description = "Maximum results", examples = List("10", "25"), required = false)
        limit: Option[Int],
        @Param(
          description = "Sort order",
          schema = Some("""{"type": "string", "enum": ["relevance", "date"]}""")
        )
        sortBy: String
    ): String = ???
    • description — populates the schema's description field
    • examples — populates the JSON Schema examples array (clients can show suggestions)
    • required = false — combined with Option[...] or a default value, marks the field optional
    • schema — raw JSON Schema fragment that overrides the derived schema entirely (useful for enum constraints, patterns, or numeric bounds Scala types can't express)

    Full demo in [AnnotatedServer.scala](fast-mcp-scala/jvm/src/com/tjclp/fastmcp/examples/AnnotatedServer.scala).

    Tool hints

    MCP Tool Annotations (a.k.a. behavioral hints) tell the client how your tool behaves. Set them on @Tool:

    HintMeaning
    titleHuman-readable display name (distinct from the wire-level name)
    readOnlyHintThe tool only reads state; safe to call without confirmation
    destructiveHintThe tool may irreversibly modify state — clients should confirm
    idempotentHintRepeated calls with the same args produce the same effect as one call
    openWorldHintThe tool reaches outside the local process (network, filesystem, APIs)
    returnDirectReturn the result directly to the user, skipping LLM post-processing
    scala 3 raw
    @Tool(
      name = Some("listTasks"),
      description = Some("List tasks with optional filtering"),
      readOnlyHint = Some(true),
      idempotentHint = Some(true),
      openWorldHint = Some(false)
    )
    def listTasks(filter: TaskFilter): List[Task] = ...

    See [TaskManagerServer.scala](fast-mcp-scala/jvm/src/com/tjclp/fastmcp/examples/TaskManagerServer.scala) for hints across a realistic tool set.

    Resources (static and templated)

    Static resources have a fixed URI and no parameters:

    scala 3 raw
    @Resource(uri = "static://welcome", description = Some("A welcome message"))
    def welcome(): String = "Welcome!"

    Templated resources use {placeholders} in the URI, matched against method parameter names:

    scala 3 raw
    @Resource(
      uri = "users://{userId}/profile",
      description = Some("User profile as JSON"),
      mimeType = Some("application/json")
    )
    def userProfile(@Param("The user id") userId: String): String = ...

    Prompts

    Return a List[Message] — fast-mcp-scala handles the MCP framing:

    scala 3 raw
    @Prompt(name = Some("greeting"), description = Some("Personalized greeting"))
    def greeting(
        @Param("Name of the person") name: String,
        @Param("Optional title", required = false) title: String = ""
    ): List[Message] =
      List(Message(Role.User, TextContent(s"Generate a warm greeting for $title $name.")))

    A prompt that returns a single String is automatically wrapped into a User message.

    Context (McpContext)

    Add an optional ctx: McpContext (annotation path) or use McpTool.contextual (typed-contract path) to access the client's declared info and capabilities:

    scala 3 raw
    def echo(args: Map[String, Any], ctx: Option[McpContext]): String =
      val clientName = ctx.flatMap(_.getClientInfo.map(_.name())).getOrElse("unknown")
      s"Hello from $clientName"

    Runnable demo: [ContextEchoServer.scala](fast-mcp-scala/jvm/src/com/tjclp/fastmcp/examples/ContextEchoServer.scala).

    Transports

    Transport is a phantom type parameter on McpServerApp[T, Self] — Stdio or Http. The matching TransportRunner[T] given resolves at compile time, so there's no run-time transport plumbing in user code.

    stdio (for Claude Desktop, MCP Inspector)

    scala 3 raw
    object MyServer extends McpServerApp[Stdio, MyServer.type]:
      @Tool(...) def hello(name: String): String = s"Hello, $name!"

    HTTP (for remote clients, load balancers, test harnesses)

    Flip to Http and override settings to tune the listener. runHttp() serves the full MCP Streamable HTTP spec: POST /mcp for JSON-RPC, the mcp-session-id header for session tracking, and SSE streams for long-running calls.

    scala 3 raw
    object MyHttpServer extends McpServerApp[Http, MyHttpServer.type]:
      override def settings = McpServerSettings(port = 8090)
    
      @Tool(...) def hello(name: String): String = s"Hello, $name!"

    Toggle stateless = true on McpServerSettings for request/response-only mode (no sessions, no SSE), useful behind load balancers.

    Need lower-level control? Skip the sugar trait and construct directly — val server = McpServer("name", "0.1.0") returns the platform-appropriate server, and you can call .tool(...) / .runHttp() yourself inside your own ZIOAppDefault.

    SettingDefaultDescription
    host0.0.0.0Bind address
    port8000Listen port
    httpEndpoint/mcpJSON-RPC endpoint path
    statelessfalseDisable sessions and SSE

    Curl recipes for both modes are in [HttpServer.scala](fast-mcp-scala/jvm/src/com/tjclp/fastmcp/examples/HttpServer.scala).

    Tasks (experimental, off by default)

    MCP Tasks (spec 2025-11-25) wrap long-running tools/call invocations in a durable, polled state machine. Clients send params.task: {ttl}, get a CreateTaskResult immediately, and then poll tasks/get / tasks/list / tasks/cancel / tasks/result until completion. Useful for LLM batch jobs, expensive computation, and integrations with external job APIs that would otherwise time out under request/response.

    Enable per server (off by default — the spec marks Tasks experimental):

    scala 3 raw
    val server = McpServer(
      name = "my-server",
      settings = McpServerSettings(tasks = TaskSettings(enabled = true))
    )

    Opt in per tool — annotation path:

    scala 3 raw
    @Tool(name = Some("expensive-op"), taskSupport = Some("optional"))
    def expensiveOp(@Param("input") x: String): String = ???

    Opt in per tool — typed-contract path:

    scala 3 raw
    val tool = McpTool[Args, Result](name = "expensive-op")(args => work(args))
      .withTaskSupport(TaskSupport.Optional)

    taskSupport values: "forbidden" (default), "optional" (clients may augment), "required" (clients must — bare calls return -32601).

    Transport limitations: Tasks are dispatched inside fast-mcp-scala's own ZIO HTTP transport because the upstream Java MCP SDK 1.1.1 doesn't yet implement them. As a result:

    • JVM: works on runHttp() with stateless = false (the default streamable transport). runStdio() and stateless HTTP fail-fast at startup if tasks.enabled is true.
    • JS / Bun: works on both stateful and stateless runHttp(). runStdio() fails fast.

    When enabled, the tasks capability is advertised at initialize and each opt-in tool surfaces execution.taskSupport on tools/list.

    Customizing decoding (Jackson 3)

    fast-mcp-scala uses Jackson 3 to turn raw JSON-RPC arguments into Scala values. Primitives, Scala enums, case classes, Option, List, Map, and java.time types work out of the box — no configuration required.

    For custom wire formats, supply a given JacksonConverter[T]:

    scala 3 raw
    import java.time.LocalDateTime
    
    given JacksonConverter[LocalDateTime] = JacksonConverter.fromPartialFunction[LocalDateTime] {
      case s: String => LocalDateTime.parse(s)
    }
    
    given JacksonConverter[Task] = DeriveJacksonConverter.derived[Task]

    The handler receives a JacksonConversionContext (not a raw Jackson mapper) — see [docs/jackson-converter-enhancements.md](docs/jackson-converter-enhancements.md) for the detailed API.

    Two backends, one API

    fast-mcp-scala is a single library with two real runtime backends — JVM and Scala.js/Bun — behind the same shared abstract API:

    code
    shared/src/                (platform-neutral Scala 3)
      ┌──────────────────────────────────────────────────────────┐
      │  annotations  │  typed contracts  │  Tool/Prompt/Resource │
      │  (@Tool, ...)│ (McpTool, McpPrompt)│  managers + McpContext│
      │  McpServerApp │  scanAnnotations  │  TransportRunner      │
      │  (sugar trait)│  (shared macros)  │  ToHandlerEffect      │
      └──────────┬─────────────────────────────┬─────────────────┘
                 │                             │
           jvm/src/ (FastMcpServer)      js/src/ (JsMcpServer)
           wraps Java MCP SDK            wraps TS MCP SDK via
           (mcp-core 1.1.1)              Scala.js facades, runs on Bun

    McpServerApp[T, Self] is the declarative entry point on both targets; the concrete backend resolves via the McpServerCoreFactory given (FastMcpServer on JVM, JsMcpServer on JS). Typed contracts (McpTool, McpPrompt, McpStaticResource, McpTemplateResource) compile and mount unchanged on both.

    What the Scala.js backend gives you:

    • A real MCP server runtime on Bun, wrapping the official @modelcontextprotocol/sdk — stdio (runStdio) and Streamable HTTP (runHttp) transports, with stateful (session + SSE) and stateless (JSON-response-only) modes.
    • AJV-based schema validation of tool arguments, matching the JVM server's behaviour.
    • JsMcpContext extension methods (getClientInfo, getClientCapabilities, getSessionId) for handlers that need client-session details.

    Current platform parity:

    CapabilityJVMScala.js (Bun-first)
    McpServerApp[T, Self] sugar trait✅✅
    @Tool / @Resource / @Prompt + scanAnnotations[T]✅✅
    Typed contracts (McpTool, McpPrompt, McpStaticResource, McpTemplateResource)✅✅
    ToolSchemaProvider[A] auto-derivation from @Param✅ via Tapir✅ via Tapir
    ToHandlerEffect[F] — plain values / ZIO / Either / Try✅✅
    Stdio transport✅ (Java SDK)✅ (TS SDK)
    Streamable HTTP — stateful (sessions + SSE)✅ (ZIO HTTP)✅ (Bun.serve + Web-Standard transport)
    Streamable HTTP — stateless✅✅
    Custom decoders✅ JacksonConverter✅ given JsonDecoder[T] → McpDecoder[T] via zio-json

    Node / Deno parity for the HTTP listener is a follow-up; the same WebStandardStreamableHTTPServerTransport works across runtimes, only the Bun.serve(...) entry point is Bun-specific today.

    Proof: the conformance suite at [JsServerConformanceTest.scala](fast-mcp-scala/js/test/src/com/tjclp/fastmcp/conformance/JsServerConformanceTest.scala) stands up a JsMcpServer in-process and drives every MCP operation through the official TS SDK client via InMemoryTransport; [JsServerHttpTest.scala](fast-mcp-scala/js/test/src/com/tjclp/fastmcp/conformance/JsServerHttpTest.scala) verifies the Bun HTTP routing; [ConformanceTest.scala](fast-mcp-scala/js/test/src/com/tjclp/fastmcp/conformance/ConformanceTest.scala) runs a JS client against the JVM server for cross-backend parity.

    Running on Bun

    scala 3 raw
    //> using scala 3.8.3
    //> using dep com.tjclp::fast-mcp-scala_sjs1:0.3.2
    
    import com.tjclp.fastmcp.{*, given}
    
    object HelloBun extends McpServerApp[Stdio, HelloBun.type]:
      @Tool(name = Some("add"), description = Some("Add two numbers"), readOnlyHint = Some(true))
      def add(@Param("First operand") a: Int, @Param("Second operand") b: Int): Int = a + b

    Same shape as the JVM — the McpServerApp trait picks up the Scala.js McpServerCoreFactory given and builds a JsMcpServer under the hood. For typed contracts on Scala.js, McpTool[...] now auto-generates the input schema as well; import sttp.tapir.generic.auto.* at the call site the same way you do on the JVM.

    Link with ./mill fast-mcp-scala.js.fastLinkJS, then bun run out/fast-mcp-scala/js/fastLinkJS.dest/main.js. See [HelloWorldJs.scala](fast-mcp-scala/js/src/com/tjclp/fastmcp/examples/HelloWorldJs.scala) and [HttpServerJs.scala](fast-mcp-scala/js/src/com/tjclp/fastmcp/examples/HttpServerJs.scala) for runnable references.

    Spec coverage

    fast-mcp-scala implements a focused subset of the MCP specification:

    CapabilityStatus
    Tools (list, call) + Tool Annotations/hints✅
    Static resources & resource templates✅
    Prompts with arguments✅
    McpContext (client info, capabilities)✅
    Stdio transport✅
    Streamable HTTP transport (sessions + SSE)✅
    Stateless HTTP transport✅
    Progress notifications❌ (not yet)
    Sampling❌ (not yet)
    Elicitation❌ (not yet)
    Completion❌ (not yet)
    Resource subscriptions❌ (not yet)
    Log level control❌ (not yet)

    See the CHANGELOG for release-by-release changes.

    Running examples

    JVM — [fast-mcp-scala/jvm/src/com/tjclp/fastmcp/examples/](fast-mcp-scala/jvm/src/com/tjclp/fastmcp/examples/):

    ExampleDemonstrates
    HelloWorld.scalaMinimum viable server — one tool, stdio
    AnnotatedServer.scalaFlagship annotation path — tools, hints, @Param features, resources, prompts
    ContractServer.scalaTyped contracts as first-class values; cross-platform story
    TaskManagerServer.scalaRealistic domain server — custom Jackson converters, hints across a CRUD-style surface
    ContextEchoServer.scalaMcpContext introspection inside a tool handler
    HttpServer.scalaHTTP transport (Streamable default, Stateless via a flag) with curl recipes
    bash
    ./mill fast-mcp-scala.jvm.runMain com.tjclp.fastmcp.examples.HelloWorld
    # or, via scala-cli:
    scala-cli scripts/quickstart.sc

    Scala.js / Bun — [fast-mcp-scala/js/src/com/tjclp/fastmcp/examples/](fast-mcp-scala/js/src/com/tjclp/fastmcp/examples/):

    ExampleDemonstrates
    HelloWorldJs.scalaMinimum viable server on Bun — one tool, stdio
    HttpServerJs.scalaStreamable HTTP transport on Bun — stateful sessions or stateless
    bash
    ./mill fast-mcp-scala.js.fastLinkJS
    bun run out/fast-mcp-scala/js/fastLinkJS.dest/main.js

    Claude Desktop integration

    Add to claude_desktop_config.json:

    json
    {
      "mcpServers": {
        "fast-mcp-scala-example": {
          "command": "scala-cli",
          "args": [
            "-e",
            "//> using dep com.tjclp::fast-mcp-scala:0.3.2",
            "--main-class",
            "com.tjclp.fastmcp.examples.AnnotatedServer"
          ]
        }
      }
    }

    fast-mcp-scala example servers are for demo purposes only — they don't do anything useful, but they make it easy to see MCP in action.

    For architectural detail, see [docs/architecture.md](docs/architecture.md).

    License

    MIT

    ---

    Developing locally

    Build commands (Mill)

    bash
    ./mill fast-mcp-scala.compile                                   # Compile JVM + Scala.js
    ./mill fast-mcp-scala.test                                      # All tests (JVM + Bun conformance)
    ./mill fast-mcp-scala.checkFormat                               # Scalafmt check (all sources)
    ./mill fast-mcp-scala.reformat                                  # Auto-format (all sources)
    ./mill fast-mcp-scala.jvm.test                                  # JVM tests only
    ./mill fast-mcp-scala.js.test.bunTest                           # Scala.js conformance tests only
    ./mill fast-mcp-scala.jvm.publishLocal                          # Publish JVM artifact to ~/.ivy2/local

    Consuming a local build

    After publishLocal:

    scala 3 ignore
    libraryDependencies += "com.tjclp" %% "fast-mcp-scala" % "0.3.3-SNAPSHOT"

    Or with Mill:

    scala 3 ignore
    def ivyDeps = Agg(
      ivy"com.tjclp::fast-mcp-scala:0.3.3-SNAPSHOT"
    )

    Or point scala-cli at a built JAR directly:

    scala 3 ignore
    //> using scala 3.8.3
    //> using jar "/absolute/path/to/out/fast-mcp-scala/jvm/jar.dest/out.jar"
    //> using options "-Xcheck-macros" "-experimental"

    Similar MCP

    Based on tags & features

    • MC

      Mcpmcp Server

      21
    • ES

      Esp Rainmaker Mcp

      Python·
      9
    • PE

      Personalizationmcp

      Python·
      12
    • FA

      Fal Mcp Server

      Python·
      8

    Trending MCP

    Most active this week

    • PL

      Playwright Mcp

      TypeScript·
      22.1k
    • SE

      Serena

      Python·
      14.5k
    • MC

      Mcp Playwright

      TypeScript·
      4.9k
    • MC

      Mcp Server Cloudflare

      TypeScript·
      3.0k
    View All MCP Servers

    Similar MCP

    Based on tags & features

    • MC

      Mcpmcp Server

      21
    • ES

      Esp Rainmaker Mcp

      Python·
      9
    • PE

      Personalizationmcp

      Python·
      12
    • FA

      Fal Mcp Server

      Python·
      8

    Trending MCP

    Most active this week

    • PL

      Playwright Mcp

      TypeScript·
      22.1k
    • SE

      Serena

      Python·
      14.5k
    • MC

      Mcp Playwright

      TypeScript·
      4.9k
    • MC

      Mcp Server Cloudflare

      TypeScript·
      3.0k