|
5 | 5 |
|
6 | 6 | An SDK for building [MCP](https://modelcontextprotocol.io/specification) (Model Context Protocol) servers and |
7 | 7 | clients in Scala 3 using boilerplate-less, type-safe APIs based on [Tapir](https://tapir.softwaremill.com/) |
8 | | -and [sttp](https://github.com/softwaremill/sttp), supporting the variety of the Scala ecosystem. Both servers |
9 | | -and clients can communicate over **Streamable HTTP** or **stdio**. |
| 8 | +and [sttp](https://github.com/softwaremill/sttp), supporting the variety of the Scala ecosystem. |
| 9 | + |
| 10 | +### Transport |
| 11 | + |
| 12 | +Chimp implements both streamable HTTP and stdio transports. Additional integration modules unlock streaming features of |
| 13 | +the MCP protocol with bidirectional communication between server and client. Currently supporting [Ox](https://ox.softwaremill.com/) and [ZIO](https://zio.dev/). |
10 | 14 |
|
11 | 15 | ### Quickstart |
12 | 16 |
|
13 | | -Run a basic MCP server with Netty exposing a simple _adder_ tool: |
| 17 | +#### HTTP |
| 18 | + |
| 19 | +Run a basic MCP server with Netty exposing a simple adder tool: |
14 | 20 |
|
15 | 21 | ```scala |
16 | 22 | //> using dep com.softwaremill.chimp::chimp-server:0.4.0 |
@@ -54,6 +60,112 @@ import io.circe.Json |
54 | 60 | backend.close() |
55 | 61 | ``` |
56 | 62 |
|
| 63 | +#### stdio |
| 64 | + |
| 65 | +Run a basic MCP server using stdio transport: |
| 66 | + |
| 67 | +```scala |
| 68 | +//> using dep com.softwaremill.chimp::chimp-server:0.4.0 |
| 69 | + |
| 70 | +import chimp.server.* |
| 71 | +import chimp.server.transport.ServerStdioTransport |
| 72 | +import io.circe.Codec |
| 73 | +import sttp.tapir.* |
| 74 | + |
| 75 | +case class EchoInput(message: String) derives Codec, Schema |
| 76 | + |
| 77 | +@main def stdioServer(): Unit = |
| 78 | + val echo = tool("echo").description("Echoes the message").input[EchoInput] |
| 79 | + .handle(i => ToolResult.text(i.message)) |
| 80 | + |
| 81 | + ServerStdioTransport().serve(McpServer(tools = List(echo))) |
| 82 | +``` |
| 83 | + |
| 84 | +Start the server as a subprocess and invoke the tool as an MCP client: |
| 85 | + |
| 86 | +```scala |
| 87 | +//> using dep com.softwaremill.chimp::chimp-client:0.4.0 |
| 88 | + |
| 89 | +import chimp.client.* |
| 90 | +import chimp.client.transport.ClientStdioTransport |
| 91 | +import chimp.protocol.* |
| 92 | +import io.circe.Json |
| 93 | + |
| 94 | +@main def stdioClient(): Unit = |
| 95 | + val transport = ClientStdioTransport(List("scala-cli", "run", "stdioServer.scala")) |
| 96 | + val client = McpClient(transport, Implementation("my-client", "0.1.0")) |
| 97 | + |
| 98 | + val result = client.callTool("echo", Json.obj("message" -> Json.fromString("hello"))) |
| 99 | + val _ = result.content.collect { case ToolContent.Text(_, text) => println(text) } |
| 100 | + |
| 101 | + client.close() |
| 102 | +``` |
| 103 | + |
| 104 | +#### Bidirectional streaming |
| 105 | + |
| 106 | +With the integration modules (like `chimp-server-ox` and `chimp-client-ox` for ox) bidirectional communication becomes possible. |
| 107 | +For example, run a basic streaming MCP server using Netty and ox: |
| 108 | + |
| 109 | +```scala |
| 110 | +//> using dep com.softwaremill.chimp::chimp-server-ox:0.4.0 |
| 111 | + |
| 112 | +import chimp.protocol.LoggingLevel |
| 113 | +import chimp.server.* |
| 114 | +import chimp.server.ox.OxServerHttpTransport |
| 115 | +import io.circe.{Codec, Json} |
| 116 | +import sttp.shared.Identity |
| 117 | +import sttp.tapir.* |
| 118 | +import sttp.tapir.server.netty.sync.NettySyncServer |
| 119 | + |
| 120 | +case class WorkInput(steps: Int) derives Codec, Schema |
| 121 | + |
| 122 | +@main def streamingServer(): Unit = |
| 123 | + val work = tool("work") |
| 124 | + .description("Reports progress and logs while running") |
| 125 | + .input[WorkInput] |
| 126 | + .streamingServerLogic[Identity]: (in, ctx, _) => |
| 127 | + for step <- 1 to in.steps do |
| 128 | + ctx.reportProgress(step.toDouble / in.steps, total = Some(1.0)) |
| 129 | + ctx.log(LoggingLevel.Info, Json.fromString(s"step $step of ${in.steps}")) |
| 130 | + ToolResult.text("done") |
| 131 | + |
| 132 | + val server = StreamingMcpServer[Identity]().withLoggingLevel(_ => ()).addStreamingTool(work) |
| 133 | + NettySyncServer().port(8080).addEndpoint(OxServerHttpTransport(List("mcp")).serve(server)).startAndWait() |
| 134 | +``` |
| 135 | + |
| 136 | +Connect and invoke the tool as an MCP client, receiving server's notifications while the tool call is in flight: |
| 137 | + |
| 138 | +```scala |
| 139 | +//> using dep com.softwaremill.chimp::chimp-client-ox:0.4.0 |
| 140 | + |
| 141 | +import chimp.client.McpClient |
| 142 | +import chimp.client.notifications.ServerNotification |
| 143 | +import chimp.client.transport.ox.OxClientHttpTransport |
| 144 | +import chimp.protocol.{Implementation, ToolContent} |
| 145 | +import io.circe.Json |
| 146 | +import ox.supervised |
| 147 | +import sttp.client4.DefaultSyncBackend |
| 148 | +import sttp.model.Uri.UriContext |
| 149 | +import sttp.shared.Identity |
| 150 | + |
| 151 | +@main def streamingClient(): Unit = |
| 152 | + supervised: |
| 153 | + val backend = DefaultSyncBackend() |
| 154 | + val transport = OxClientHttpTransport(backend, uri"http://localhost:8080/mcp") |
| 155 | + val client = McpClient.bidirectional[Identity](transport, Implementation("my-client", "0.1.0")) |
| 156 | + |
| 157 | + client.onServerNotification: |
| 158 | + case ServerNotification.Progress(params) => println(s"progress: ${params.progress}") |
| 159 | + case ServerNotification.LoggingMessage(params) => println(s"log: ${params.data}") |
| 160 | + case _ => () |
| 161 | + |
| 162 | + val result = client.callTool("work", Json.obj("steps" -> Json.fromInt(3))) |
| 163 | + val _ = result.content.collect { case ToolContent.Text(_, text) => println(text) } |
| 164 | + |
| 165 | + client.close() |
| 166 | + backend.close() |
| 167 | +``` |
| 168 | + |
57 | 169 | ## Documentation |
58 | 170 |
|
59 | 171 | Full documentation is available at **[chimp.softwaremill.com](https://chimp.softwaremill.com/)**. |
|
0 commit comments