Skip to content

Commit 4fadcb1

Browse files
authored
ZIO client streaming transport and generic tests (#148)
* feat: split client into regular and bidirectional one depending on transport # Conflicts: # client/src/main/scala/chimp/client/McpClientImpl.scala # client/src/main/scala/chimp/client/transport/HttpTransport.scala * feat: effectful client creation, initialize internally, avoids having transient client state * refactor: small refactor * refactor: more readable response handling in http transport * feat: zio client parts * refactor: rename * refactor: logging * refactor: logging * feat: integration tests WIP * feat: integration tests WIP * feat: integration tests WIP * fix: flush chunks eagerly for ZIO stdio transport * feat: add integration tests to ci * fix: merge error * feat: extend integration tests WIP * fix: fix resource type tool tests * fix: formatting * refactor: abstract test cases over mcp client only instead over transport * feat: default, scoped and layered factory methods for zio transport to match sttp * fix: fix zio stdio transport hung on close * fix: remove streams evidence from streaming stdio transport, it makes sense only to narrow down possible sttp http backend choices * review: resolve critical findings * feat: configurable timeout of async zio transport * feat: require resumability for SSE stream from streaming http transports, implement for ZIO and generic test using toxiproxy * review: avoid Thread.sleep(), abstract sleep to implement for effects * wip: few more test cases for server sens events and client responses * wip: few more test cases for server sens events and client responses * fix: fix restarting SSE listener on transport close by introducing closing flag * fix: take closing ref into account when resuming stream for pending request too * fix: define retry policy for SSE stream reconnect instead of fixed delay * fix: improve shutdown, make pending requests truly internal * fix: move -32000 error to enum * fix: specific version of everything container and latest toxiproxy
1 parent 9552c29 commit 4fadcb1

39 files changed

Lines changed: 1502 additions & 274 deletions

.github/workflows/ci.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,14 @@ jobs:
4444
# TODO bring this step back after first release with new project structure (client + server)
4545
# - name: Verify that examples compile using Scala CLI
4646
# run: sbt -v "project examples" verifyExamplesCompileUsingScalaCli
47-
- name: Test
47+
- name: Unit tests
4848
run: sbt -v test
49-
- name: Client conformance
49+
- name: Client conformance tests
5050
run: sbt -v "clientConformance/conformance client --suite core"
51-
- name: Server conformance
51+
- name: Server conformance tests
5252
run: sbt -v "serverConformance/conformance server"
53+
- name: Integration tests
54+
run: sbt -v "testOnly -- -n Integration"
5355
- uses: actions/upload-artifact@v5 # upload test results
5456
if: success() || failure() # run this step even if previous step failed
5557
with:

build.sbt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ val slf4jV = "2.0.18"
88
val logbackV = "1.5.32"
99
val tapirV = "1.13.19"
1010
val sttpClientV = "4.0.24"
11+
val zioV = "2.1.26"
12+
val zioProcessV = "0.8.0"
13+
val testcontainersScalaV = "0.41.8"
1114

1215
lazy val verifyExamplesCompileUsingScalaCli = taskKey[Unit]("Verify that each example compiles using Scala CLI")
1316

@@ -22,6 +25,8 @@ lazy val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq(
2225
}.value,
2326
Test / scalacOptions += "-Wconf:msg=unused value of type org.scalatest.Assertion:s",
2427
Test / scalacOptions += "-Wconf:msg=unused value of type org.scalatest.compatible.Assertion:s",
28+
Test / test / testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-l", "Integration"),
29+
Test / parallelExecution := false,
2530
scalacOptions ++= Seq("-Wunused:all", "-Werror")
2631
)
2732

@@ -30,7 +35,7 @@ val scalaTest = "org.scalatest" %% "scalatest" % scalaTestV % Test
3035
lazy val root = (project in file("."))
3136
.settings(commonSettings: _*)
3237
.settings(publishArtifact := false, name := "chimp")
33-
.aggregate(core, server, client, examples, serverConformance, clientConformance)
38+
.aggregate(core, server, client, clientZio, examples, serverConformance, clientConformance)
3439

3540
val conformance = inputKey[Unit]("Run the MCP conformance harness via npx, extra args are passed through")
3641

@@ -68,18 +73,35 @@ lazy val client: Project = (project in file("client"))
6873
name := "chimp-client",
6974
libraryDependencies ++= Seq(
7075
scalaTest,
71-
"com.softwaremill.sttp.client4" %% "core" % sttpClientV
76+
"com.softwaremill.sttp.client4" %% "core" % sttpClientV,
77+
"com.dimafeng" %% "testcontainers-scala-scalatest" % testcontainersScalaV % Test,
78+
"ch.qos.logback" % "logback-classic" % logbackV % Test,
79+
"com.dimafeng" %% "testcontainers-scala-toxiproxy" % testcontainersScalaV % Test
7280
)
7381
)
7482
.dependsOn(core)
7583

84+
lazy val clientZio: Project = (project in file("client-streaming/client-zio"))
85+
.settings(commonSettings: _*)
86+
.settings(
87+
name := "chimp-client-zio",
88+
libraryDependencies ++= Seq(
89+
scalaTest,
90+
"dev.zio" %% "zio" % zioV,
91+
"dev.zio" %% "zio-streams" % zioV,
92+
"dev.zio" %% "zio-process" % zioProcessV,
93+
"com.softwaremill.sttp.client4" %% "zio" % sttpClientV
94+
)
95+
)
96+
.dependsOn(client % "compile->compile;test->test")
97+
7698
lazy val examples = (project in file("examples"))
7799
.settings(commonSettings: _*)
78100
.settings(
79101
publishArtifact := false,
80102
name := "examples",
81103
libraryDependencies ++= Seq(
82-
"com.softwaremill.sttp.client4" %% "core" % "4.0.24",
104+
"com.softwaremill.sttp.client4" %% "core" % sttpClientV,
83105
"com.softwaremill.sttp.tapir" %% "tapir-netty-server-sync" % tapirV,
84106
"com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % tapirV,
85107
"ch.qos.logback" % "logback-classic" % logbackV

client-conformance/src/main/scala/chimp/conformance/client/Main.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,12 @@ object Main:
3434
try
3535
scenario match
3636
case "initialize" =>
37-
val client = McpClient[Identity](transport, clientInfo, protocolVersion = protocolVersion)
38-
val _ = client.initialize()
37+
val client = McpClient[Identity](transport, clientInfo, protocolVersion)
3938
client.close()
4039
0
4140

4241
case "tools_call" =>
43-
val client = McpClient[Identity](transport, clientInfo, protocolVersion = protocolVersion)
44-
val _ = client.initialize()
42+
val client = McpClient[Identity](transport, clientInfo, protocolVersion)
4543
val _ = client.callTool(
4644
"add_numbers",
4745
Json.obj("a" -> Json.fromInt(2), "b" -> Json.fromInt(3))

0 commit comments

Comments
 (0)