fast-mcp-scala is a high-level Scala 3 library for building Model Context Protocol (MCP) servers. It provides two registration paths:
- Annotation-driven (
@Tool,@Resource,@Prompt+scanAnnotations) — zero-boilerplate on JVM and Scala.js/Bun - Typed contracts (
McpTool,McpPrompt,McpStaticResource,McpTemplateResource) — explicit, cross-platform (JVM + Scala.js)
Both paths converge on the same McpServer trait and support @Param metadata on parameters/fields.
Build tool: Mill 1.1.5 (configured in .mill-version)
Scala: 3.8.3
Plugins: mill-bun-plugin 0.2.0 (Scala.js + Bun integration)
# Aggregates (run across JVM + Scala.js)
./mill fast-mcp-scala.compile # Compile all platforms
./mill fast-mcp-scala.test # All tests (JVM + Bun conformance)
./mill fast-mcp-scala.reformat # Auto-format every Scala source
./mill fast-mcp-scala.checkFormat # Scalafmt check (CI uses this)
# Single-platform
./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.test com.tjclp.fastmcp.macros.ToolProcessorTest
# Publish
./mill fast-mcp-scala.jvm.publishLocal # Publish JVM artifact to ~/.ivy2/local
./mill fast-mcp-scala.js.publishLocal # Publish Scala.js artifact to ~/.ivy2/local
./mill -i __.publishLocal # Publish both artifactsfast-mcp-scala/
├── build.mill # Mill build definition
├── .mill-version # Mill version (1.1.5)
├── fast-mcp-scala/
│ ├── shared/src/ # Platform-independent code (JVM + JS)
│ │ └── com/tjclp/fastmcp/
│ │ ├── core/
│ │ │ ├── Annotations.scala # @Tool, @Param, @Resource, @Prompt
│ │ │ ├── Types.scala # ToolDefinition, Content, ToolInputSchema, etc.
│ │ │ └── Contracts.scala # McpTool, McpPrompt, McpDecoder, McpEncoder
│ │ ├── runtime/ # RefResolver
│ │ └── server/
│ │ ├── McpServerCore.scala # McpServerCore trait (abstract API)
│ │ ├── McpContext.scala # Platform-independent context base
│ │ ├── McpServerSettings.scala
│ │ └── manager/ # ToolManager, PromptManager, ResourceManager
│ ├── jvm/
│ │ ├── src/ # JVM-specific code
│ │ │ └── com/tjclp/fastmcp/
│ │ │ ├── core/Types.scala # TypeConversions (toJava extensions, private[fastmcp])
│ │ │ ├── macros/ # JVM-side macro/runtime support
│ │ │ │ ├── ToolProcessor.scala
│ │ │ │ ├── ResourceProcessor.scala
│ │ │ │ ├── PromptProcessor.scala
│ │ │ │ ├── RegistrationMacro.scala # scanAnnotations entry point
│ │ │ │ ├── JsonSchemaMacro.scala
│ │ │ │ ├── JacksonConverter.scala # extends McpDecoder (bridges to shared)
│ │ │ │ └── JacksonConversionContext.scala # extends McpDecodeContext
│ │ │ ├── server/
│ │ │ │ ├── FastMcpServer.scala # JVM implementation (extends McpServerCore)
│ │ │ │ ├── McpContext.scala # JvmMcpContext (private[fastmcp])
│ │ │ │ ├── McpServerBuilders.scala # McpServer companion (factory methods)
│ │ │ │ └── transport/
│ │ │ └── examples/
│ │ └── test/src/ # JVM test sources
│ └── js/ # Scala.js code (Bun-first runtime)
│ ├── src/ # JsMcpServer, TS SDK facades, Bun runtime, examples
│ └── test/src/ # Conformance, HTTP, codec, contract surface tests
object MyServer extends ZIOAppDefault:
@Tool(name = Some("add"), description = Some("Add two numbers"))
def add(@Param("First number") a: Int, @Param("Second number") b: Int): Int = a + b
override def run =
for
server <- ZIO.succeed(McpServer("MyServer"))
_ <- ZIO.attempt(server.scanAnnotations[MyServer.type])
_ <- server.runStdio()
yield ()case class AddArgs(@Param("First number") a: Int, @Param("Second number") b: Int)
val addTool = McpTool.derived[AddArgs, Int](
name = "add",
description = Some("Add two numbers")
) { args => ZIO.succeed(args.a + args.b) }
// Mount:
server.tool(addTool)| Annotations | Typed Contracts | |
|---|---|---|
| Platform | JVM only | JVM + Scala.js |
| Boilerplate | Zero (macro-driven) | Minimal (case class + builder) |
| Schema | Auto from method signature | Auto from case class via ToolSchemaProvider on JVM and JS |
@Param |
On method parameters | On case class fields |
| Composability | Methods on an object | First-class values |
| Best for | Quick servers, prototyping | Libraries, cross-platform, production |
@Tool- Marks a method as an MCP tool. Supports behavioral hints:title,readOnlyHint,destructiveHint,idempotentHint,openWorldHint,returnDirect
@Resource- Marks a method as an MCP resource (static or templated)@Prompt- Marks a method as an MCP prompt@Param- Describes parameters/fields with metadata:description: String- Parameter descriptionexample: Option[String]- Example valuerequired: Boolean- Override required statusschema: Option[String]- Custom JSON Schema override
McpTool[In, Out]- Typed tool withMcpTool.derivedfor auto-schema derivationMcpPrompt[In]- Typed prompt with manual argument metadataMcpStaticResource- Typed static resourceMcpTemplateResource[In]- Typed resource templateMcpDecoder[T]/McpEncoder[A]- Platform-neutral codecsToolSchemaProvider[A]- Auto-derivesinputSchemafrom@Param-annotated case classes on both JVM and JSMcpEncoderfalls back toJsonEncoder[A]→TextContent(a.toJson)via ZIO JSON
- Stdio (
runStdio()) — stdin/stdout, used by MCP clients - HTTP (
runHttp()) — streamable (sessions + SSE) by default, setstateless = truefor stateless
The codebase is split into three sibling trees under fast-mcp-scala/:
shared/— annotations, types, managers,McpServerCoretrait, typed contractsjvm/— Java SDK interop (TypeConversions,JvmMcpContext), macros, transports, examplesjs/— Bun-first Scala.js runtime (JsMcpServer), TS SDK facades, examples, tests
JVM module reads from shared/src/ + jvm/src/. JS module reads from shared/src/ + js/src/.
fast-mcp-scala wraps the Java MCP SDK 1.1.1 (mcp-core + mcp-json-jackson3). Interop is internal:
TypeConversions—private[fastmcp]extension methods (.toJava)JvmMcpContext—private[fastmcp], extendsMcpContextJacksonConverter extends McpDecoder— bridges JVM converters to shared codec layerJacksonConversionContext extends McpDecodeContext— Jackson 3 backed
Configured in build.mill (v3.5.6):
- Errors (fail build):
Null,TryPartial,TripleQuestionMark,ArrayEquals - Warnings:
Var,Return,AsInstanceOf,IsInstanceOf
Uses Scalafmt with config in .scalafmt.conf. Always run ./mill fast-mcp-scala.reformat before committing.
JVM tests in fast-mcp-scala/jvm/test/src/. Scala.js tests in fast-mcp-scala/js/test/src/.
Key test classes:
ToolProcessorTest- Integration tests for @Tool processingJsonSchemaMacroTest- Schema generation testsTypedContractsTest- Typed contract mounting testsZioHttpStatelessTransportTest- HTTP transport integration testsZioHttpStreamableTransportProviderTest- SSE transport testsConformanceTest(JS) - 17 cross-platform conformance tests against AnnotatedServerJsServerConformanceTest(JS) - pure-JS in-memory conformance againstJsMcpServerJsServerHttpTest(JS) - Bun HTTP routing coverage forrunHttp()
- CI (
.github/workflows/ci.yml): Runs on PRs and main pushes, tests on JDK 17, 21, 24 - Release (
.github/workflows/release.yml): Triggered byv*tags, publishes to Maven Central
- Platform-independent code goes in
shared/src/ - JVM-specific code stays in
jvm/src/ - Add tests in
jvm/test/src/orjs/test/src/ - Run
./mill fast-mcp-scala.test(runs both JVM and JS aggregates) - Run
./mill fast-mcp-scala.checkFormat(orreformat)
Macros are in fast-mcp-scala/jvm/src/com/tjclp/fastmcp/macros/. After changes:
rm -rf out/fast-mcp-scala && ./mill fast-mcp-scala.compile./mill fast-mcp-scala.jvm.publishLocal
./mill fast-mcp-scala.js.publishLocal
./mill -i __.publishLocalThen in your project use version 0.3.0-SNAPSHOT.
Key dependencies (versions in build.mill):
- Scala 3.8.3
- ZIO 2.1.20 - Effect system
- ZIO JSON 0.7.44 - JSON codecs (shared)
- ZIO HTTP 3.4.0 - HTTP transport
- Jackson 3.0.3 (
tools.jackson) - Runtime conversion (JVM) - Tapir 1.11.42 - Schema derivation
- Java MCP SDK 1.1.1 - Protocol implementation (
mcp-core+mcp-json-jackson3) - mill-bun-plugin 0.2.0 - Scala.js + Bun build integration
@modelcontextprotocol/sdk1.29.0 - TS MCP SDK (JS runtime + conformance tests)- WartRemover 3.5.6 - Code quality
- ScalaTest 3.2.19 - Testing