FastMCP-Scala について
fast-mcp-scala は Scala 3 向けの MCP サーバー構築ライブラリで、アノテーションと型付きコントラクトの 2 つの登録方式を提供します。JVM、Scala.js と Bun、Scala Native で動作し、Scala 開発者を対象としています。
主な機能
- 01@Tool アノテーションと McpTool による 2 つの登録方式
- 02@Param メタデータからマクロで派生する JSON Schema
- 03ツール、リソース、プロンプト、スキルへの対応
- 04readOnlyHint などの MCP ツールアノテーション
- 05ZIO 2 と zio-json を基盤とする実装
活用シーン
- 01Scala で単一ツールを持つ MCP サーバーのプロトタイプ作成
- 02本番コードベースにおけるモジュール間でのツール定義の共有
- 03Bun 上の Scala.js で stdio または HTTP サーバーを実行
MCP 対応
本ライブラリは MCP サーバーを構築するためのもので、ツール、リソース、プロンプト、スキルを MCP クライアントに公開し、MCP 2026-07-28 を対象とするプロトコル層を実装しています。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
fast-mcp-scala
MCP 向け Scala 3:JVM、Scala.js/Bun、Scala Native で使える、アノテーション駆動と型付きコントラクトの API です。
fast-mcp-scala は、Model Context Protocol サーバーを構築するための、開発者に使いやすいライブラリです。1 つのトレイトを拡張してツールを宣言すれば、準備完了です。
object HelloWorld extends McpServerApp[Stdio, HelloWorld.type]:
@Tool(name = Some("add"))
def add(@Param("a") a: Int, @Param("b") b: Int): Int = a + b
@Tool スタイルのアノテーションと、型付きの McpTool コントラクトという 2 つの登録方法は、同じバックエンドに集約されます。ZIO 2 と zio-json を基盤とし、JSON Schema は Scala 3 のマクロから直接導出されます。MCP プロトコルレイヤー全体(JSON-RPC、ワイヤー型、ルーター、トランスポート)は shared/ 内のネイティブな Scala 3 実装であり、ベンダー提供の SDK はありません。MCP 2026-07-28 を対象とし、以前のプロトコルリビジョンとの互換性を保つアダプターも備えています。
インストール
// sbt — JVM
libraryDependencies += "com.tjclp" %% "fast-mcp-scala" % "1.0.1"
// sbt — Scala.js (Bun-first) or Scala Native (stdio only, experimental); %%% picks the platform artifact
libraryDependencies += "com.tjclp" %%% "fast-mcp-scala" % "1.0.1"
//> using dep com.tjclp::fast-mcp-scala:1.0.1 // scala-cli, JVM
//> using dep com.tjclp::fast-mcp-scala::1.0.1 // scala-cli, Scala.js or Native (with `//> using platform ...`)
Scala 3.9.0 LTS に対してビルドされています。1.0.0 を利用するには Scala 3.9.0 以降が必要です(TASTy 28.9 を出力するため、Scala 3.8 以前のコンパイラーでは読み取れません。Scala 3.8 プロジェクトで使える最後のリリースは、Scala 3.8.3 でビルドされた RC3 プレリリースです)。1.0.0 は -experimental を付けずにコンパイルされているため、利用側でこのフラグは不要になりました(リリース候補では、すべての登録方法でフラグが必要でした。TJC-2335)。JVM:JDK 17 以降(CI では LTS リリースの 17、21、25 をテストします)。Scala.js:sjs1_3。Bun で動作し(第一級サポート)、stdio の場合は Node 18 以降でも動作します(1.0.0 のドッグフードでは Node 18 と 26 で検証済みですが、まだ CI には含まれていません。HTTP リスナーは Bun.serve のみです。また、ベアラートークンのタスク ID は globalThis.crypto から取得し、Node 18 では --experimental-global-webcrypto を指定した場合にのみ公開されます)。Scala 3.9 の出力には Scala.js 1.22 以降のリンカーが必要です(Mill:scalaJSVersion を明示した mill-bun 0.3.x、scala-cli:--js-version 1.22.0)。Scala Native:native0.5_3、stdio のみ、実験的です。プラットフォームの詳細とクイックスタートについては、docs/platforms.md をご覧ください。
クイックスタート
ツールを 1 つ備えた、単一ファイルのサーバーです。同じコードは HelloWorld.scala にあります。
//> using scala 3.9.0
//> using dep com.tjclp::fast-mcp-scala:1.0.1
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
import zio.* も、override def run も、ZIO.succeed(...) も必要ありません。McpServerApp[T, Self] トレイトが、サーバーの構築、アノテーションのスキャン、トランスポートのライフサイクルを処理します。トランスポートはファントム型パラメーター(Stdio / Http)であり、コンパイル時にランナーを選択します。
MCP Inspector を使って動作を確認します。
npx @modelcontextprotocol/inspector scala-cli scripts/quickstart.sc
初回実行時には、コンパイラーと依存関係(約 60 MB)がダウンロードされ、スクリプトがコンパイルされます。そのため、
Inspector の 15 秒の接続タイムアウトを超えることがあります(その場合、Connection closed とのみ報告されます)。
あらかじめ scala-cli compile scripts/quickstart.sc を実行するか、Inspector に --connect-timeout <ms> を渡してください。
登録方法の選択
アノテーション(@Tool + scanAnnotations) | 型付きコントラクト(McpTool) | |
|---|---|---|
| スタイル | オブジェクト上のメソッドをマクロで検出します | ファーストクラスの val です |
| スキーマ | メソッドのシグネチャと @Param から導出されます | ケースクラスのフィールドと @Param から導出されます |
| テスト | メソッドを直接呼び出します | 値の .handler を呼び出します |
| 合成可能性 | オブジェクトが公開するメソッドによります | リストにまとめたり、設定から生成したりできます |
| 最適な用途 | 手軽なサーバー、プロトタイプ、単一モジュールのアプリ | ライブラリ、モジュール間の共有、本番用コードベース |
どちらの方法もすべてのプラットフォームで動作し、同じサーバー上で併用できます。McpServerApp の tools / prompts / staticResources / templateResources をオーバーライドして、型付きコントラクトをアノテーション付きメソッドとともにマウントしてください。
case class AddArgs(a: Int, b: Int)
case class AddResult(sum: Int)
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
}
)
型付きツールの In はケースクラスです(MCP の arguments は常に JSON オブジェクトであるため、スキーマも JSON オブジェクトでなければなりません)。引数を取らないツールには、デコーダーを持たない McpTool[Unit, _] の代わりに、空の case class NoArgs() を使用します。ハンドラーのラムダは、通常の値、ZIO、Either[Throwable, _]、または scala.util.Try を返します。ToHandlerEffect[F[_], R] 型クラスが適切なリフトを選択します(R は ZIO の環境で、通常のビルダーでは Any です)。他のエフェクトシステムを使う場合は、エフェクト型 F に対する ToHandlerEffect[F, Any] を実装して、独自の given を定義できます。アノテーションを使う方法については AnnotatedServer.scala を、型付きコントラクトについては ContractServer.scala を参照してください。
ツールと @Param メタデータ
すべてのツールパラメーターには、派生する JSON Schema に反映されるメタデータを設定できます。
@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はスキーマのdescriptionフィールドに設定されますexamplesは JSON Schema のexamples配列に設定されます(クライアントは候補を表示できます)required = falseをOption[...]またはデフォルト値と組み合わせると、フィールドが任意になります。単独のOption[...]パラメーターはこれを指定しなくても任意ですが、デフォルト値だけではフィールドはrequiredから除外されません。引数が省略された場合にデフォルト値が適用されるため、任意として提示するにはrequired = falseを指定してくださいschemaは派生スキーマ全体を上書きする生の JSON Schema フラグメントです。派生プロパティ(descriptionとexamplesを含む)は完全に置き換えられるため、フラグメント内にもdescriptionを記述してください
アノテーション付きメソッドの戻り値は、String、Content、List[Content]、Array[Byte](またはそれらのいずれかの ZIO)でない限り、TextContent(result.toString) として送信されます。Some(1)、ケースクラス、Map は JSON ではなく、Scala の toString テキストとして届きます。クライアントが JSON を必要とする場合は、String/Content を返すか、型付きの McpTool(structuredContent 用の .withOutputSchema を使用)を使ってください。
オーバーロードは問題ありません。登録されるのはアノテーション付きのオーバーロードのみで、そのスキーマとハンドラーはその宣言から取得されます。アノテーション付きのオーバーロードを複数登録する場合は、それぞれ異なる name を指定する必要があります。同一オブジェクト内で名前やリソース URI パターンが重複すると、コンパイル時エラーになります。name、description、ヒントなどのアノテーション引数にはリテラル(Some("...")、Option("...")、None、または final val 定数)を指定する必要があります。それ以外を指定すると、コンパイル時エラーになります。
アノテーション付きメソッドはパラメーターリストがちょうど 1 つ(カリー化や using 句は不可)で、型パラメーターを持たず、パラメーターは最大 22 個です。また、スキャン対象のオブジェクトに直接宣言されたメンバーのみが登録されます(private/protected も含まれます。可視性によって公開が制限されることはないため、公開するものだけにアノテーションを付けてください)。アノテーション付きの継承メンバーや val など、サポートされていない形式はコンパイル時エラーとなり、該当箇所と修正方法が示されます。ネストしたオブジェクト内のアノテーション付きメソッドも報告されます(スキャン対象のオブジェクトが独自の宣言を持たない場合はエラー、それ以外はネストしたオブジェクトが別途スキャンされる可能性があるため警告)。
列挙型、ネストしたケースクラス、Option、コレクション、java.time の値は、ユーザー指定の givens なしで派生できます。独自のワイヤー形式には McpInputCodec を使用してください。docs/custom-types.md を参照してください。
ツールヒント
MCP Tool Annotations は、ツールの動作をクライアントに伝えます。@Tool に設定します。
| ヒント | 意味 |
|---|---|
title | 人が読みやすい表示名です(ワイヤーレベルの name とは異なります) |
readOnlyHint | ツールは状態を読み取るだけです。確認なしで安全に呼び出せます |
destructiveHint | ツールは状態を元に戻せない形で変更する可能性があります。クライアントは確認する必要があります |
idempotentHint | 同じ引数で繰り返し呼び出しても、1 回の呼び出しと同じ効果になります |
openWorldHint | ツールはローカルプロセスの外部(ネットワーク、ファイルシステム、API)にアクセスします |
returnDirect | LLM による後処理を省略し、結果をそのままユーザーに返します |
TaskManagerServer.scala では、実用的なツールセット全体にヒントを適用しています。
リソース(静的およびテンプレート)
静的リソースには固定 URI があり、パラメーターはありません。
@Resource(uri = "static://welcome", description = Some("A welcome message"))
def welcome(): String = "Welcome!"
テンプレートリソースでは、URI に {placeholders} を使用し、メソッドのパラメーター名と照合します。
@Resource(
uri = "users://{userId}/profile",
description = Some("User profile as JSON"),
mimeType = Some("application/json")
)
def userProfile(@Param("The user id") userId: String): String =
s"""{"userId":"$userId"}"""
テンプレートは、exposeTemplatesEndpoint が設定されている場合にのみ resources/templates/list で一覧表示されます
(resources/list ではテンプレートは一覧表示されません。デフォルトの false ではテンプレートエンドポイントは
空のページを返し、クライアントは {} URI からテンプレートを導出します。resources/read は一致する URI に対して
どちらの場合でも機能します)。AnnotatedServer.scala
で有効にできます。
override def settings = McpServerSettings(exposeTemplatesEndpoint = true)
プレースホルダーは、1 つのパスセグメント内にある 1 文字以上の文字列に一致します(/ は含みません)。リテラルテキストは正規表現ではなく、文字列そのものとして照合されます。同じセグメント内のプレースホルダーは、リテラルテキストで区切る必要があります(x://{a}{b} のようなテンプレートは登録時に拒否されるため、サーバーの起動に失敗します)。limits.maxUriChars(8192)を超えるクライアント URI は -32602 で拒否されます。
スキル(io.modelcontextprotocol/skills)
ツール、リソース、プロンプトとともにエージェントスキルを公開します。
スキルは、SKILL.md(YAML フロントマターと Markdown)とサポートファイルで構成されます。サーバーは
すべてのファイルを通常のリソースとして提供し、完全なマニフェスト(フロントマターをそのまま記載し、各ファイルの
sha256: ダイジェストとバイトサイズを含む)を返す skills/list / skills/get に応答し、
resources/directory/read でディレクトリを参照します。クロスプラットフォームで、すべてのトランスポートに対応しています。
object SkillsServer extends McpServerApp[Stdio, SkillsServer.type]:
override val skills: List[McpSkill] = List(
McpSkill.fromMarkdown(
skillPath = "acme/reconcile-positions",
markdown = """---
|name: reconcile-positions
|description: Reconcile broker positions against the ledger and flag breaks.
|---
|Read `references/rules.md` first.
|""".stripMargin,
files = Map(
"references/rules.md" -> SkillFile.text("# Rules\n"),
"assets/example.bin" -> SkillFile.binary(Array[Byte](0, 1, 2))
)
)
)
スキルを登録すると、拡張機能({"directoryRead": true})と resources
ケイパビリティが宣言されます。SkillSettings(enabled = true) は空のカタログを提供します。動的なカタログは
SkillProvider を実装します。JVM では、SkillDirectoryLoader がディスク上のスキルディレクトリを公開します(通常の
ファイルのみを対象とし、シンボリックリンクは拒否します)。ダイジェストは整合性を証明するものであり、信頼性を保証するものではありません。ホストは引き続き検証、承認し、何を有効化するかを決定します。詳しいガイドはdocs/skills.md、固定された仕様リビジョンと要件・テスト対応表は
docs/skills-conformance.mdを参照してください。
プロンプト
List[Message] を返します。MCP のフレーミングは fast-mcp-scala が処理します。
@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.")))
単一の String を返すプロンプトは、自動的に User メッセージにラップされます。
コンテキスト(McpContext)
@Tool メソッドに、名前が正確に ctx で型が McpContext のパラメーターを追加すると、クライアントが宣言した情報とケイパビリティ、リクエストメタデータを読み取り、進捗やログを送信できます(このパラメーターは名前で認識されるため、ツールのスキーマには含まれません。@Prompt メソッドと @Resource メソッドはコンテキストパラメーターを受け取りません)。
@Tool(name = Some("echo"), description = Some("Echo client and request context"))
def echo(
@Param(description = "Optional note to include", required = false) note: Option[String],
ctx: McpContext
): String =
val clientName = ctx.getClientInfo.map(_.name).getOrElse("Unknown Client")
s"Hello from $clientName${note.fold("")(n => s": $n")}"
クライアントから見えるログもコンテキスト経由で送信します。ctx.sendLogMessage(level, data) は
ZIO を返すため、ハンドラーも ZIO を返します。また、LoggingLevel(com.tjclp.fastmcp.core 内)は
1.0.0 以降ルートエクスポートされています。リリース候補版では名前を指定してインポートする必要がありましたが、現在もその方法は使えます。
import zio.*
import zio.json.ast.Json
import com.tjclp.fastmcp.core.LoggingLevel // optional since 1.0.0 (root-exported); the release candidates need it
@Tool(name = Some("echo_logged"), description = Some("Echo the note and log it"))
def echoLogged(@Param("Note to echo") note: String, ctx: McpContext): ZIO[Any, Throwable, String] =
ctx.sendLogMessage(LoggingLevel.Info, Json.Str(s"echo: $note")).as(note)
型付きコントラクトでは、ビルダーの .contextual を使用します。例:McpTool[In, Out](name = "echo").contextual { (in, ctx) => ... }。ハンドラーは (In, Option[McpContext]) を受け取ります。実行可能なデモは ContextEchoServer.scala を参照してください。
トランスポート
トランスポートは McpServerApp[T, Self] のファントム型パラメーターです。Stdio または Http を指定します。
stdio(Claude Desktop、MCP Inspector 向け)
object MyServer extends McpServerApp[Stdio, MyServer.type]:
@Tool(...) def hello(name: String): String = s"Hello, $name!"
HTTP(リモートクライアント、ロードバランサー、テストハーネス向け)
object MyHttpServer extends McpServerApp[Http, MyHttpServer.type]:
override def settings = McpServerSettings(port = 8090)
@Tool(...) def hello(name: String): String = s"Hello, $name!"
MCP 2026-07-28 では、runHttp() は POST /mcp ごとにステートレスな JSON-RPC メッセージを 1 件受け取り、JSON またはリクエスト単位の SSE ストリームで応答します。古いクライアントには、デフォルトで有効なレガシー初期化/セッションアダプターが対応します。
| 設定 | デフォルト | 説明 |
|---|---|---|
host | 127.0.0.1 | バインドアドレスです。コンテナーや外部公開には "0.0.0.0" を設定します |
port | 8000 | リッスンポートです |
stateless | false | レガシーセッションアダプターを無効にします。最新のリクエストは常にステートレスです |
すべての設定、必須リクエストヘッダー、エラーコードのマッピング、レガシーアダプター、および糖衣特性を使わない低レベルの構築方法については、docs/transports.md を参照してください。
ネイティブイメージ(GraalVM)
Stdio サーバーは、手書きのリーチャビリティメタデータなしで自己完結型の GraalVM バイナリにコンパイルできます(約 35 MB、即時起動、コンテナー内に JVM 不要)。登録とスキーマ導出はコンパイル時マクロで行われ、トランスポートの分離により、stdio 専用イメージから zio-http/netty を除外できます。依存関係から dev.zio:zio-http_3 を除外してください(netty は zio-http 経由でのみ導入されるため、この除外だけで両方を取り除けます)。HTTP サーバーもコンパイルでき、CI ではネイティブバイナリとして公式適合性テストスイートに合格します。手順、フラグ、メタデータ監査の手順については、docs/native-image.md を参照してください。
プラットフォーム
1 つのコア、3 つのターゲットです。プロトコル層は共有され、各プラットフォームが提供するのはトランスポートバックエンドのみです。
| JVM | Scala.js / Bun | Scala Native(実験的) | |
|---|---|---|---|
アノテーション、型付きコントラクト、McpServerApp | ✅ | ✅ | ✅ |
| Stdio | ✅ | ✅ | ✅(LLVM バイナリ) |
| ストリーマブル HTTP、MCP 2026-07-28 | ✅ ZIO HTTP | ✅ Bun.serve | ✗ 仕様上非対応¹ |
| レガシー HTTP セッションアダプター | ✅ | ✅ | ✗ 仕様上非対応¹ |
| Tasks 拡張 | ✅ | ✅ | ✅(stdio) |
| スタンドアロンバイナリ | GraalVM ネイティブイメージ | — | Scala Native 経由の LLVM |
¹ zio-http には Scala Native アーティファクトがないため、McpServerApp[Http] はそこでコンパイルできません。ソケットベースのバックエンドは開発中です(#81)。
公式 MCP 適合性テストスイートは、CI で JVM サーバー、Bun サーバー、および GraalVM ネイティブバイナリに対して実行され、想定される失敗のベースラインは空です。完全な互換性マトリクス、Bun と Scala Native のクイックスタートについては、docs/platforms.md を参照してください。カバレッジの詳細については、docs/spec-coverage.md を参照してください。
ドキュメント
- docs/transports.md — stdio、最新の Streamable HTTP、レガシーアダプター、すべての
McpServerSettingsフィールド - docs/tasks.md — 実験的な MCP Tasks 拡張機能
- docs/skills.md — Skills 拡張機能(
io.modelcontextprotocol/skills、SEP-2640):作成、URI、ダイジェスト、プロバイダー、ディレクトリの読み取り、ホストの責務 - docs/skills-conformance.md — 固定された仕様リビジョン、要件とテストの対応表、上流の適合性テスト実行
- docs/custom-types.md —
McpInputCodec、McpSchema、@Param(schema = ...)、McpTool.withSchema - docs/platforms.md — 対応状況の一覧、Bun および Scala Native での実行
- docs/native-image.md — stdio および HTTP サーバー向けの GraalVM レシピ
- docs/spec-coverage.md — MCP 2026-07-28 の対応状況一覧と検証方法
- docs/examples.md — サンプルサーバーと実行方法
- docs/architecture.md — ライブラリの構成
- docs/upgrading.md — コンパイラーエラー別に整理した、0.x またはリリース候補版のプロジェクトを 1.0.0 に移行する方法
- MCP 2026-07-28 protocol upgrade — ワイヤー上の動作、レビュー用一覧、リリースゲートの台帳
- docs/native-core-design.md — ネイティブコアの設計記録
- CHANGELOG.md · ROADMAP.md · CONTRIBUTING.md · SECURITY.md · DEPENDENCY_POLICY.md
Claude Desktop との統合
claude_desktop_config.json に追加します:
{
"mcpServers": {
"fast-mcp-scala-example": {
"command": "scala-cli",
"args": [
"-e",
"//> using scala 3.9.0",
"-e",
"//> using dep com.tjclp::fast-mcp-scala:1.0.1",
"--main-class",
"com.tjclp.fastmcp.examples.AnnotatedServer"
]
}
}
}
fast-mcp-scala のサンプルサーバーはデモ専用です。実用的な機能はありませんが、MCP の動作を簡単に確認できます。
ライセンス
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。