From 3f7ed625b0c26ed1041d66e3e045f524247a0707 Mon Sep 17 00:00:00 2001 From: kubinio123 Date: Fri, 29 May 2026 11:25:28 +0200 Subject: [PATCH 1/7] feat: bootstrap sphinx documentation --- .github/workflows/ci.yml | 10 ++-- .readthedocs.yaml | 13 +++++ build.sbt | 21 +++++++ docs/.gitignore | 2 + docs/Makefile | 20 +++++++ docs/conf.py | 85 +++++++++++++++++++++++++++++ docs/index.md | 10 ++++ docs/requirements.txt | 4 ++ docs/watch.sh | 2 + generated-docs/out/.gitignore | 2 + generated-docs/out/Makefile | 20 +++++++ generated-docs/out/conf.py | 85 +++++++++++++++++++++++++++++ generated-docs/out/index.md | 10 ++++ generated-docs/out/requirements.txt | 4 ++ generated-docs/out/watch.sh | 2 + 15 files changed, 284 insertions(+), 6 deletions(-) create mode 100644 .readthedocs.yaml create mode 100644 docs/.gitignore create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.md create mode 100644 docs/requirements.txt create mode 100755 docs/watch.sh create mode 100644 generated-docs/out/.gitignore create mode 100644 generated-docs/out/Makefile create mode 100644 generated-docs/out/conf.py create mode 100644 generated-docs/out/index.md create mode 100644 generated-docs/out/requirements.txt create mode 100755 generated-docs/out/watch.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3b7081..4014956 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,8 +10,6 @@ permissions: pull-requests: write # labeler, auto-merge requirement jobs: build: - # run on 1) push, 2) external PRs, 3) softwaremill-ci PRs - # do not run on internal, non-steward PRs since those will be run by push to branch if: | github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository || @@ -44,6 +42,8 @@ jobs: # TODO bring this step back after first release with new project structure (client + server) # - name: Verify that examples compile using Scala CLI # run: sbt -v "project examples" verifyExamplesCompileUsingScalaCli + - name: Compile documentation + run: sbt -v compileDocs - name: Unit tests run: sbt -v test - name: Client conformance tests @@ -52,8 +52,8 @@ jobs: run: sbt -v "serverConformance/conformance server" - name: Integration tests run: sbt -v "testOnly -- -n Integration" - - uses: actions/upload-artifact@v5 # upload test results - if: success() || failure() # run this step even if previous step failed + - uses: actions/upload-artifact@v5 + if: success() || failure() with: name: 'tests-results' path: '**/test-reports/TEST*.xml' @@ -68,13 +68,11 @@ jobs: sttp-native: 1 label: - # only for PRs by if: github.event.pull_request.user.login == 'softwaremill-ci' uses: softwaremill/github-actions-workflows/.github/workflows/label.yml@main secrets: inherit auto-merge: - # only for PRs by softwaremill-ci if: github.event.pull_request.user.login == 'softwaremill-ci' needs: [ build, label ] uses: softwaremill/github-actions-workflows/.github/workflows/auto-merge.yml@main diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..d46ba5a --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +sphinx: + configuration: generated-docs/out/conf.py + +python: + install: + - requirements: generated-docs/out/requirements.txt + +build: + os: ubuntu-22.04 + tools: + python: "3.12" diff --git a/build.sbt b/build.sbt index 9f1295b..508e7f4 100644 --- a/build.sbt +++ b/build.sbt @@ -228,3 +228,24 @@ lazy val clientConformance = (project in file("client-conformance")) } ) .dependsOn(client) + +val compileDocs: TaskKey[Unit] = taskKey[Unit]("Compiles docs module throwing away its output") +compileDocs := { + (docs / mdoc).toTask(" --out target/chimp-docs").value +} + +lazy val docs: Project = (project in file("generated-docs")) + .enablePlugins(MdocPlugin) + .settings(commonSettings) + .settings( + mdocIn := file("docs"), + moduleName := "chimp-docs", + mdocVariables := Map( + "VERSION" -> version.value + ), + mdocOut := file("generated-docs/out"), + mdocExtraArguments := Seq("--clean-target"), + publishArtifact := false, + name := "docs" + ) + .dependsOn(core, server, client, clientZio) diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..e4297f9 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +_build +_build_html diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..1b80377 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = chimp +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..a19cc49 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# +# chimp documentation build configuration file. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# https://about.readthedocs.com/blog/2024/07/addons-by-default/ +import os + +# Define the canonical URL if you are using a custom domain on Read the Docs +html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "") + +# Tell Jinja2 templates the build is running on Read the Docs +if os.environ.get("READTHEDOCS", "") == "True": + if "html_context" not in globals(): + html_context = {} + html_context["READTHEDOCS"] = True + +# -- General configuration ------------------------------------------------ + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['myst_parser', 'sphinx_rtd_theme'] + +myst_enable_extensions = ['attrs_block'] + +# The suffix(es) of source filenames. +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'chimp' +copyright = u'2026, SoftwareMill' +author = u'SoftwareMill' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'0.1' +# The full version, including alpha/beta/rc tags. +release = u'0.1' + +# The language for content autogenerated by Sphinx. +language = 'en' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'default' + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. +html_theme = 'sphinx_rtd_theme' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'chimpdoc' + +highlight_language = 'scala' + +# configure edit on github: https://docs.readthedocs.io/en/latest/guides/vcs.html +html_context = { + 'display_github': True, + 'github_user': 'softwaremill', + 'github_repo': 'chimp', + 'github_version': 'master', + 'conf_py_path': '/docs/', +} diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..f8094b5 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,10 @@ +# chimp: build MCP servers and clients in Scala 3 + +Welcome! Documentation content is being prepared — see the [README on GitHub](https://github.com/softwaremill/chimp) in the meantime. + +```{eval-rst} +.. toctree:: + :maxdepth: 2 + :caption: Getting started + +``` diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..e854807 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +sphinx_rtd_theme==2.0.0 +sphinx==7.3.7 +sphinx-autobuild==2024.4.16 +myst-parser==2.0.0 diff --git a/docs/watch.sh b/docs/watch.sh new file mode 100755 index 0000000..24c4372 --- /dev/null +++ b/docs/watch.sh @@ -0,0 +1,2 @@ +#!/bin/bash +sphinx-autobuild . _build/html diff --git a/generated-docs/out/.gitignore b/generated-docs/out/.gitignore new file mode 100644 index 0000000..e4297f9 --- /dev/null +++ b/generated-docs/out/.gitignore @@ -0,0 +1,2 @@ +_build +_build_html diff --git a/generated-docs/out/Makefile b/generated-docs/out/Makefile new file mode 100644 index 0000000..1b80377 --- /dev/null +++ b/generated-docs/out/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = chimp +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/generated-docs/out/conf.py b/generated-docs/out/conf.py new file mode 100644 index 0000000..a19cc49 --- /dev/null +++ b/generated-docs/out/conf.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# +# chimp documentation build configuration file. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# https://about.readthedocs.com/blog/2024/07/addons-by-default/ +import os + +# Define the canonical URL if you are using a custom domain on Read the Docs +html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "") + +# Tell Jinja2 templates the build is running on Read the Docs +if os.environ.get("READTHEDOCS", "") == "True": + if "html_context" not in globals(): + html_context = {} + html_context["READTHEDOCS"] = True + +# -- General configuration ------------------------------------------------ + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['myst_parser', 'sphinx_rtd_theme'] + +myst_enable_extensions = ['attrs_block'] + +# The suffix(es) of source filenames. +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'chimp' +copyright = u'2026, SoftwareMill' +author = u'SoftwareMill' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'0.1' +# The full version, including alpha/beta/rc tags. +release = u'0.1' + +# The language for content autogenerated by Sphinx. +language = 'en' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'default' + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. +html_theme = 'sphinx_rtd_theme' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'chimpdoc' + +highlight_language = 'scala' + +# configure edit on github: https://docs.readthedocs.io/en/latest/guides/vcs.html +html_context = { + 'display_github': True, + 'github_user': 'softwaremill', + 'github_repo': 'chimp', + 'github_version': 'master', + 'conf_py_path': '/docs/', +} diff --git a/generated-docs/out/index.md b/generated-docs/out/index.md new file mode 100644 index 0000000..f8094b5 --- /dev/null +++ b/generated-docs/out/index.md @@ -0,0 +1,10 @@ +# chimp: build MCP servers and clients in Scala 3 + +Welcome! Documentation content is being prepared — see the [README on GitHub](https://github.com/softwaremill/chimp) in the meantime. + +```{eval-rst} +.. toctree:: + :maxdepth: 2 + :caption: Getting started + +``` diff --git a/generated-docs/out/requirements.txt b/generated-docs/out/requirements.txt new file mode 100644 index 0000000..e854807 --- /dev/null +++ b/generated-docs/out/requirements.txt @@ -0,0 +1,4 @@ +sphinx_rtd_theme==2.0.0 +sphinx==7.3.7 +sphinx-autobuild==2024.4.16 +myst-parser==2.0.0 diff --git a/generated-docs/out/watch.sh b/generated-docs/out/watch.sh new file mode 100755 index 0000000..24c4372 --- /dev/null +++ b/generated-docs/out/watch.sh @@ -0,0 +1,2 @@ +#!/bin/bash +sphinx-autobuild . _build/html From 2a8e17d4db3743cd329c292d480a8e55073cc636 Mon Sep 17 00:00:00 2001 From: kubinio123 Date: Fri, 29 May 2026 11:31:22 +0200 Subject: [PATCH 2/7] feat: bootstrap sphinx documentation --- docs/.gitignore | 1 + docs/README.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 docs/README.md diff --git a/docs/.gitignore b/docs/.gitignore index e4297f9..b38170e 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,2 +1,3 @@ _build _build_html +.venv diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..90d02bf --- /dev/null +++ b/docs/README.md @@ -0,0 +1,28 @@ +# chimp documentation + +Source for the chimp documentation site, built with Sphinx + MyST and hosted on Read the Docs. + +## Run locally + +From this folder: + +``` +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +./watch.sh +``` + +Open . Edits to `.md` files live-reload in the browser. + +Next time, just: + +``` +source .venv/bin/activate +./watch.sh +``` + +## Notes + +- `@VERSION@` and other mdoc variables are **not** substituted in this mode. For a fully-rendered preview, run `sbt 'docs/mdoc'` from the repo root and serve `generated-docs/out/` instead. +- Scala code snippets are verified by `sbt compileDocs` (also runs in CI). From 8ab804ffb68a0aa890a02ae644f0f1637e7d9979 Mon Sep 17 00:00:00 2001 From: kubinio123 Date: Fri, 29 May 2026 11:44:58 +0200 Subject: [PATCH 3/7] feat: bootstrap sphinx documentation --- README.md | 99 +++--------------------------- build.sbt | 2 +- docs/client/index.md | 41 +++++++++++++ docs/conf.py | 2 +- docs/index.md | 11 +++- docs/server/index.md | 73 ++++++++++++++++++++++ generated-docs/out/.gitignore | 1 + generated-docs/out/README.md | 28 +++++++++ generated-docs/out/client/index.md | 41 +++++++++++++ generated-docs/out/conf.py | 2 +- generated-docs/out/index.md | 6 +- generated-docs/out/server/index.md | 73 ++++++++++++++++++++++ 12 files changed, 281 insertions(+), 98 deletions(-) create mode 100644 docs/client/index.md create mode 100644 docs/server/index.md create mode 100644 generated-docs/out/README.md create mode 100644 generated-docs/out/client/index.md create mode 100644 generated-docs/out/server/index.md diff --git a/README.md b/README.md index f7142b0..a7b8679 100644 --- a/README.md +++ b/README.md @@ -1,112 +1,29 @@ -# Chimp MCP Server +# Chimp MCP [![CI](https://github.com/softwaremill/chimp/actions/workflows/ci.yml/badge.svg)](https://github.com/softwaremill/chimp/actions/workflows/ci.yml) [![Scala 3](https://img.shields.io/badge/scala-3-blue.svg)](https://www.scala-lang.org/) -A library for building [MCP](#mcp-protocol) (Model Context Protocol) servers in Scala 3, based on [Tapir](https://tapir.softwaremill.com/). Describe MCP tools with type-safe input, expose them over a JSON-RPC HTTP API. +A library for building [MCP](https://modelcontextprotocol.io/specification/2025-03-26) (Model Context Protocol) servers and clients in Scala 3, based on [Tapir](https://tapir.softwaremill.com/). Describe MCP tools with type-safe input, expose them over JSON-RPC HTTP, and talk to MCP servers from your code. Integrates with any Scala stack, using any of the HTTP server implementations supported by Tapir. ---- +## Documentation -## Quickstart +Full documentation is available at **[chimp.readthedocs.io](https://chimp.readthedocs.io)**: -Add the dependency to your `build.sbt`: +- [Server](https://chimp.readthedocs.io/en/latest/server/) — exposing tools as an MCP server +- [Client](https://chimp.readthedocs.io/en/latest/client/) — connecting to an MCP server -```scala -libraryDependencies += "com.softwaremill.chimp" %% "core" % "0.1.8" -``` - -### Example: the simplest MCP server - -Below is a self-contained, [scala-cli](https://scala-cli.virtuslab.org)-runnable example: - -```scala -//> using dep com.softwaremill.chimp::core:0.1.8 - -import chimp.* -import sttp.tapir.* -import sttp.tapir.server.netty.sync.NettySyncServer - -// define the input type for your tool -case class AdderInput(a: Int, b: Int) derives io.circe.Codec, Schema - -@main def mcpApp(): Unit = - // describe the tool providing the name, description, and input type - val adderTool = tool("adder").description("Adds two numbers").input[AdderInput] - - // combine the tool description with the server-side logic - val adderServerTool = adderTool.handle(i => Right(s"The result is ${i.a + i.b}")) - - // create the MCP server endpoint; it will be available at http://localhost:8080/mcp - val mcpServerEndpoint = mcpEndpoint(List(adderServerTool), List("mcp")) - - // start the server - NettySyncServer().port(8080).addEndpoint(mcpServerEndpoint).startAndWait() -``` - -### More examples - -Are available [here](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/chimp). - ---- - -## MCP Protocol - -Chimp implements the HTTP transport of the [MCP protocol](https://modelcontextprotocol.io/specification/2025-03-26) (version **2025-03-26**). Only tools are supported, via the following JSON-RPC commands: - -- Initialization and capabilities negotiation (`initialize`) -- Listing available tools (`tools/list`) -- Invoking a tool (`tools/call`) - -All requests and responses use JSON-RPC 2.0. Tool input schemas are described using JSON Schema, auto-generated from Scala types. - ---- - -## Defining Tools and Server Logic - -- Use `tool(name)` to start defining a tool. -- Add a description and annotations for metadata and hints. -- Specify the input type (must have a Circe `Codec` and Tapir `Schema`). -- Provide the server logic as a function from input to `Either[String, String]` (or a generic effect type). - - Use `handle` to connect the tool definition with the server logic when the use of headers is not required. - - Use `handleWithHeaders` to connect the tool definition with the server logic when headers are required. -- Create a Tapir endpoint by providing your tools to `mcpEndpoint` -- Start an HTTP server using your preferred Tapir server interpreter. - ---- - -## Dependencies - -Chimp uses the [circe](https://github.com/circe/circe) JSON library, to decode the incoming input, as well as handle -JSON-RPC envelopes (both for responses and requests). - ---- - -## Using with ZIO - -When using ZIO, you might have to explicitly state the effect type that you are using, as the Tapir-ZIO integration requires -a `RIO[R, A]` effect (which is an alias for `ZIO[R, Throwable, A]`), for example: - -```scala -val myServerTool = myTool.serverLogic[[X] =>> RIO[Any, X]]: (input, headers) => - ZIO.succeed(???) -``` - ---- +Runnable examples live in [`examples/`](examples/src/main/scala/chimp). ## Contributing Contributions are welcome! Please open issues or pull requests. ---- - ## Commercial Support We offer commercial support for Tapir and related technologies, as well as development services. [Contact us](https://softwaremill.com) to learn more about our offer! ---- - ## Copyright -Copyright (C) 2025 SoftwareMill [https://softwaremill.com](https://softwaremill.com). \ No newline at end of file +Copyright (C) 2026 SoftwareMill [https://softwaremill.com](https://softwaremill.com). diff --git a/build.sbt b/build.sbt index 508e7f4..2ec867a 100644 --- a/build.sbt +++ b/build.sbt @@ -244,7 +244,7 @@ lazy val docs: Project = (project in file("generated-docs")) "VERSION" -> version.value ), mdocOut := file("generated-docs/out"), - mdocExtraArguments := Seq("--clean-target"), + mdocExtraArguments := Seq("--clean-target", "--exclude", ".venv", "--exclude", "_build"), publishArtifact := false, name := "docs" ) diff --git a/docs/client/index.md b/docs/client/index.md new file mode 100644 index 0000000..9e96246 --- /dev/null +++ b/docs/client/index.md @@ -0,0 +1,41 @@ +# Client + +Chimp ships an MCP client that connects to any MCP-compliant server. The client is parameterised over an effect type `F[_]`, and is paired with a pluggable transport that carries JSON-RPC messages. + +## Quickstart + +Add the dependency to your `build.sbt`: + +```scala +libraryDependencies += "com.softwaremill.chimp" %% "chimp-client" % "0.1.8" +``` + +For the ZIO streaming transport, also add: + +```scala +libraryDependencies += "com.softwaremill.chimp" %% "chimp-client-zio" % "0.1.8" +``` + +## Two client flavours + +- `McpClient[F]` — unidirectional. The client issues requests and receives responses; suitable for any `Transport[F]`. +- `BidirectionalMcpClient[F]` — extends `McpClient[F]` and additionally supports server-initiated interactions: resource subscriptions, roots-changed notifications, and notification listeners. Requires a `BidirectionalTransport[F]`. + +Both are created via the `McpClient` companion object after the initialization handshake completes. + +## Transports + +| Transport | Sync (`F = Identity`) | Streaming | +|---|---|---| +| HTTP | `HttpTransport` | `StreamingHttpTransport` | +| stdio | `StdioTransport` | `StreamingStdioTransport` | + +The streaming variants are bidirectional and unlock `BidirectionalMcpClient`. The sync variants are simpler and direct-style. + +## Notifications + +When using a `BidirectionalMcpClient`, register a `ServerNotificationListener` via `onServerNotification` to react to server-pushed events (resource updates, list-changed notifications, log messages). + +## More examples + +See the runnable examples under [`examples/`](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/chimp). diff --git a/docs/conf.py b/docs/conf.py index a19cc49..126483c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'README.md', '.venv'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'default' diff --git a/docs/index.md b/docs/index.md index f8094b5..54a9517 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,10 +1,17 @@ # chimp: build MCP servers and clients in Scala 3 -Welcome! Documentation content is being prepared — see the [README on GitHub](https://github.com/softwaremill/chimp) in the meantime. +Chimp is a library for building [MCP](https://modelcontextprotocol.io/specification/2025-03-26) (Model Context Protocol) servers and clients in Scala 3, based on [Tapir](https://tapir.softwaremill.com/). Describe MCP tools with type-safe inputs; talk to MCP servers from your code. ```{eval-rst} .. toctree:: :maxdepth: 2 - :caption: Getting started + :caption: Server + server/index + +.. toctree:: + :maxdepth: 2 + :caption: Client + + client/index ``` diff --git a/docs/server/index.md b/docs/server/index.md new file mode 100644 index 0000000..a4c5d3f --- /dev/null +++ b/docs/server/index.md @@ -0,0 +1,73 @@ +# Server + +Chimp lets you expose MCP tools over a JSON-RPC HTTP API. Tool inputs are described with type-safe Scala types; the JSON schema and JSON-RPC plumbing are generated for you. + +## Quickstart + +Add the dependency to your `build.sbt`: + +```scala +libraryDependencies += "com.softwaremill.chimp" %% "chimp-server" % "0.1.8" +``` + +### Example: the simplest MCP server + +Below is a self-contained, [scala-cli](https://scala-cli.virtuslab.org)-runnable example: + +```scala +//> using dep com.softwaremill.chimp::chimp-server:0.1.8 + +import chimp.* +import sttp.tapir.* +import sttp.tapir.server.netty.sync.NettySyncServer + +// define the input type for your tool +case class AdderInput(a: Int, b: Int) derives io.circe.Codec, Schema + +@main def mcpApp(): Unit = + // describe the tool providing the name, description, and input type + val adderTool = tool("adder").description("Adds two numbers").input[AdderInput] + + // combine the tool description with the server-side logic + val adderServerTool = adderTool.handle(i => Right(s"The result is ${i.a + i.b}")) + + // create the MCP server endpoint; it will be available at http://localhost:8080/mcp + val mcpServerEndpoint = mcpEndpoint(List(adderServerTool), List("mcp")) + + // start the server + NettySyncServer().port(8080).addEndpoint(mcpServerEndpoint).startAndWait() +``` + +### More examples + +Available [here](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/chimp). + +## MCP Protocol + +Chimp implements the HTTP transport of the [MCP protocol](https://modelcontextprotocol.io/specification/2025-03-26) (version **2025-03-26**). Only tools are supported, via the following JSON-RPC commands: + +- Initialization and capabilities negotiation (`initialize`) +- Listing available tools (`tools/list`) +- Invoking a tool (`tools/call`) + +All requests and responses use JSON-RPC 2.0. Tool input schemas are described using JSON Schema, auto-generated from Scala types. + +## Defining tools and server logic + +- Use `tool(name)` to start defining a tool. +- Add a description and annotations for metadata and hints. +- Specify the input type (must have a Circe `Codec` and Tapir `Schema`). +- Provide the server logic as a function from input to `Either[String, String]` (or a generic effect type). + - Use `handle` to connect the tool definition with the server logic when the use of headers is not required. + - Use `handleWithHeaders` to connect the tool definition with the server logic when headers are required. +- Create a Tapir endpoint by providing your tools to `mcpEndpoint`. +- Start an HTTP server using your preferred Tapir server interpreter. + +## Using with ZIO + +When using ZIO, you might have to explicitly state the effect type that you are using, as the Tapir-ZIO integration requires a `RIO[R, A]` effect (which is an alias for `ZIO[R, Throwable, A]`), for example: + +```scala +val myServerTool = myTool.serverLogic[[X] =>> RIO[Any, X]]: (input, headers) => + ZIO.succeed(???) +``` diff --git a/generated-docs/out/.gitignore b/generated-docs/out/.gitignore index e4297f9..b38170e 100644 --- a/generated-docs/out/.gitignore +++ b/generated-docs/out/.gitignore @@ -1,2 +1,3 @@ _build _build_html +.venv diff --git a/generated-docs/out/README.md b/generated-docs/out/README.md new file mode 100644 index 0000000..3bade1c --- /dev/null +++ b/generated-docs/out/README.md @@ -0,0 +1,28 @@ +# chimp documentation + +Source for the chimp documentation site, built with Sphinx + MyST and hosted on Read the Docs. + +## Run locally + +From this folder: + +``` +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +./watch.sh +``` + +Open . Edits to `.md` files live-reload in the browser. + +Next time, just: + +``` +source .venv/bin/activate +./watch.sh +``` + +## Notes + +- `0.1.8+11-2a8e17d4+20260529-1138-SNAPSHOT` and other mdoc variables are **not** substituted in this mode. For a fully-rendered preview, run `sbt 'docs/mdoc'` from the repo root and serve `generated-docs/out/` instead. +- Scala code snippets are verified by `sbt compileDocs` (also runs in CI). diff --git a/generated-docs/out/client/index.md b/generated-docs/out/client/index.md new file mode 100644 index 0000000..9e96246 --- /dev/null +++ b/generated-docs/out/client/index.md @@ -0,0 +1,41 @@ +# Client + +Chimp ships an MCP client that connects to any MCP-compliant server. The client is parameterised over an effect type `F[_]`, and is paired with a pluggable transport that carries JSON-RPC messages. + +## Quickstart + +Add the dependency to your `build.sbt`: + +```scala +libraryDependencies += "com.softwaremill.chimp" %% "chimp-client" % "0.1.8" +``` + +For the ZIO streaming transport, also add: + +```scala +libraryDependencies += "com.softwaremill.chimp" %% "chimp-client-zio" % "0.1.8" +``` + +## Two client flavours + +- `McpClient[F]` — unidirectional. The client issues requests and receives responses; suitable for any `Transport[F]`. +- `BidirectionalMcpClient[F]` — extends `McpClient[F]` and additionally supports server-initiated interactions: resource subscriptions, roots-changed notifications, and notification listeners. Requires a `BidirectionalTransport[F]`. + +Both are created via the `McpClient` companion object after the initialization handshake completes. + +## Transports + +| Transport | Sync (`F = Identity`) | Streaming | +|---|---|---| +| HTTP | `HttpTransport` | `StreamingHttpTransport` | +| stdio | `StdioTransport` | `StreamingStdioTransport` | + +The streaming variants are bidirectional and unlock `BidirectionalMcpClient`. The sync variants are simpler and direct-style. + +## Notifications + +When using a `BidirectionalMcpClient`, register a `ServerNotificationListener` via `onServerNotification` to react to server-pushed events (resource updates, list-changed notifications, log messages). + +## More examples + +See the runnable examples under [`examples/`](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/chimp). diff --git a/generated-docs/out/conf.py b/generated-docs/out/conf.py index a19cc49..126483c 100644 --- a/generated-docs/out/conf.py +++ b/generated-docs/out/conf.py @@ -60,7 +60,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'README.md', '.venv'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'default' diff --git a/generated-docs/out/index.md b/generated-docs/out/index.md index f8094b5..8839a44 100644 --- a/generated-docs/out/index.md +++ b/generated-docs/out/index.md @@ -1,10 +1,12 @@ # chimp: build MCP servers and clients in Scala 3 -Welcome! Documentation content is being prepared — see the [README on GitHub](https://github.com/softwaremill/chimp) in the meantime. +Chimp is a library for building [MCP](https://modelcontextprotocol.io/specification/2025-03-26) (Model Context Protocol) servers and clients in Scala 3, based on [Tapir](https://tapir.softwaremill.com/). Describe MCP tools with type-safe inputs; talk to MCP servers from your code. ```{eval-rst} .. toctree:: :maxdepth: 2 - :caption: Getting started + :caption: Topics + server/index + client/index ``` diff --git a/generated-docs/out/server/index.md b/generated-docs/out/server/index.md new file mode 100644 index 0000000..a4c5d3f --- /dev/null +++ b/generated-docs/out/server/index.md @@ -0,0 +1,73 @@ +# Server + +Chimp lets you expose MCP tools over a JSON-RPC HTTP API. Tool inputs are described with type-safe Scala types; the JSON schema and JSON-RPC plumbing are generated for you. + +## Quickstart + +Add the dependency to your `build.sbt`: + +```scala +libraryDependencies += "com.softwaremill.chimp" %% "chimp-server" % "0.1.8" +``` + +### Example: the simplest MCP server + +Below is a self-contained, [scala-cli](https://scala-cli.virtuslab.org)-runnable example: + +```scala +//> using dep com.softwaremill.chimp::chimp-server:0.1.8 + +import chimp.* +import sttp.tapir.* +import sttp.tapir.server.netty.sync.NettySyncServer + +// define the input type for your tool +case class AdderInput(a: Int, b: Int) derives io.circe.Codec, Schema + +@main def mcpApp(): Unit = + // describe the tool providing the name, description, and input type + val adderTool = tool("adder").description("Adds two numbers").input[AdderInput] + + // combine the tool description with the server-side logic + val adderServerTool = adderTool.handle(i => Right(s"The result is ${i.a + i.b}")) + + // create the MCP server endpoint; it will be available at http://localhost:8080/mcp + val mcpServerEndpoint = mcpEndpoint(List(adderServerTool), List("mcp")) + + // start the server + NettySyncServer().port(8080).addEndpoint(mcpServerEndpoint).startAndWait() +``` + +### More examples + +Available [here](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/chimp). + +## MCP Protocol + +Chimp implements the HTTP transport of the [MCP protocol](https://modelcontextprotocol.io/specification/2025-03-26) (version **2025-03-26**). Only tools are supported, via the following JSON-RPC commands: + +- Initialization and capabilities negotiation (`initialize`) +- Listing available tools (`tools/list`) +- Invoking a tool (`tools/call`) + +All requests and responses use JSON-RPC 2.0. Tool input schemas are described using JSON Schema, auto-generated from Scala types. + +## Defining tools and server logic + +- Use `tool(name)` to start defining a tool. +- Add a description and annotations for metadata and hints. +- Specify the input type (must have a Circe `Codec` and Tapir `Schema`). +- Provide the server logic as a function from input to `Either[String, String]` (or a generic effect type). + - Use `handle` to connect the tool definition with the server logic when the use of headers is not required. + - Use `handleWithHeaders` to connect the tool definition with the server logic when headers are required. +- Create a Tapir endpoint by providing your tools to `mcpEndpoint`. +- Start an HTTP server using your preferred Tapir server interpreter. + +## Using with ZIO + +When using ZIO, you might have to explicitly state the effect type that you are using, as the Tapir-ZIO integration requires a `RIO[R, A]` effect (which is an alias for `ZIO[R, Throwable, A]`), for example: + +```scala +val myServerTool = myTool.serverLogic[[X] =>> RIO[Any, X]]: (input, headers) => + ZIO.succeed(???) +``` From 08ef4b7dae021f4bd7230463095ed6bd022d0c68 Mon Sep 17 00:00:00 2001 From: kubinio123 Date: Fri, 29 May 2026 11:56:54 +0200 Subject: [PATCH 4/7] feat: update readme --- README.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a7b8679..12d097d 100644 --- a/README.md +++ b/README.md @@ -3,18 +3,14 @@ [![CI](https://github.com/softwaremill/chimp/actions/workflows/ci.yml/badge.svg)](https://github.com/softwaremill/chimp/actions/workflows/ci.yml) [![Scala 3](https://img.shields.io/badge/scala-3-blue.svg)](https://www.scala-lang.org/) -A library for building [MCP](https://modelcontextprotocol.io/specification/2025-03-26) (Model Context Protocol) servers and clients in Scala 3, based on [Tapir](https://tapir.softwaremill.com/). Describe MCP tools with type-safe input, expose them over JSON-RPC HTTP, and talk to MCP servers from your code. - -Integrates with any Scala stack, using any of the HTTP server implementations supported by Tapir. +An SDK for building [MCP](https://modelcontextprotocol.io/specification) (Model Context Protocol) servers and +clients in Scala 3 using boilerplate-less, type-safe APIs based on [Tapir](https://tapir.softwaremill.com/) +and [sttp](https://github.com/softwaremill/sttp), supporting the variety of the Scala ecosystem. ## Documentation -Full documentation is available at **[chimp.readthedocs.io](https://chimp.readthedocs.io)**: - -- [Server](https://chimp.readthedocs.io/en/latest/server/) — exposing tools as an MCP server -- [Client](https://chimp.readthedocs.io/en/latest/client/) — connecting to an MCP server - -Runnable examples live in [`examples/`](examples/src/main/scala/chimp). +Full documentation is available at **[chimp.readthedocs.io](https://chimp.readthedocs.io)**. Runnable examples live +in [`examples/`](examples/src/main/scala/chimp). ## Contributing @@ -22,7 +18,8 @@ Contributions are welcome! Please open issues or pull requests. ## Commercial Support -We offer commercial support for Tapir and related technologies, as well as development services. [Contact us](https://softwaremill.com) to learn more about our offer! +We offer commercial support for Tapir and related technologies, as well as development +services. [Contact us](https://softwaremill.com) to learn more about our offer! ## Copyright From 8ce910d542c0fca4b507cd2278184c882dbb85fa Mon Sep 17 00:00:00 2001 From: kubinio123 Date: Fri, 29 May 2026 12:11:52 +0200 Subject: [PATCH 5/7] feat: update readme --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/README.md b/README.md index 12d097d..ec9cb53 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,52 @@ An SDK for building [MCP](https://modelcontextprotocol.io/specification) (Model clients in Scala 3 using boilerplate-less, type-safe APIs based on [Tapir](https://tapir.softwaremill.com/) and [sttp](https://github.com/softwaremill/sttp), supporting the variety of the Scala ecosystem. +### Quickstart + +Run a basic MCP server with Netty exposing a simple _adder_ tool: + +```scala +//> using dep com.softwaremill.chimp::chimp-server:0.2.0 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.13.19 + +import chimp.server.* +import io.circe.Codec +import sttp.tapir.* +import sttp.tapir.server.netty.sync.NettySyncServer + +case class AdderInput(a: Int, b: Int) derives Codec, Schema + +@main def server(): Unit = + val adder = tool("adder").description("Adds two numbers").input[AdderInput] + .handle(i => Right(s"Result: ${i.a + i.b}")) + + NettySyncServer().port(8080).addEndpoint(mcpEndpoint(List(adder), List("mcp"))).startAndWait() +``` + +Connect and invoke the tool as an MCP client: + +```scala +//> using dep com.softwaremill.chimp::chimp-client:0.2.0 +//> using dep com.softwaremill.sttp.client4::core:4.0.24 + +import chimp.client.* +import chimp.client.transport.HttpTransport +import chimp.protocol.* +import sttp.client4.* +import io.circe.Json + +@main def client(): Unit = + val backend = DefaultSyncBackend() + val transport = HttpTransport(backend, uri"http://localhost:8080/mcp") + val client = McpClient(transport, Implementation("my-client", "0.1.0")) + + val result = client.callTool("adder", Json.obj("a" -> Json.fromInt(2), "b" -> Json.fromInt(3))) + val _ = result.content.collect { case ToolContent.Text(_, text) => println(text) } + + client.close() + backend.close() +``` + ## Documentation Full documentation is available at **[chimp.readthedocs.io](https://chimp.readthedocs.io)**. Runnable examples live From aee4bbdd547458aa93a11f20d8a272a14228900b Mon Sep 17 00:00:00 2001 From: kubinio123 Date: Sun, 31 May 2026 12:58:24 +0200 Subject: [PATCH 6/7] feat: client documentation --- docs/README.md | 12 +++- docs/client/capabilities.md | 34 +++++++++ docs/client/examples.md | 88 +++++++++++++++++++++++ docs/client/index.md | 44 +++--------- docs/client/quickstart.md | 41 +++++++++++ docs/client/transport.md | 40 +++++++++++ docs/conf.py | 2 +- docs/index.md | 6 +- docs/requirements.txt | 1 + docs/server/index.md | 8 +-- generated-docs/out/README.md | 2 +- generated-docs/out/client/capabilities.md | 34 +++++++++ generated-docs/out/client/examples.md | 88 +++++++++++++++++++++++ generated-docs/out/client/index.md | 39 +++------- generated-docs/out/client/transport.md | 40 +++++++++++ generated-docs/out/conf.py | 2 +- generated-docs/out/index.md | 7 +- generated-docs/out/requirements.txt | 1 + 18 files changed, 413 insertions(+), 76 deletions(-) create mode 100644 docs/client/capabilities.md create mode 100644 docs/client/examples.md create mode 100644 docs/client/quickstart.md create mode 100644 docs/client/transport.md create mode 100644 generated-docs/out/client/capabilities.md create mode 100644 generated-docs/out/client/examples.md create mode 100644 generated-docs/out/client/transport.md diff --git a/docs/README.md b/docs/README.md index 90d02bf..fe031b6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,7 +22,17 @@ source .venv/bin/activate ./watch.sh ``` +## Publishing changes + +Read the Docs builds from `generated-docs/out/`, **not** from this `docs/` folder. After editing the docs, regenerate that output with mdoc: + +``` +sbt compileDocs +``` + +Commit both `docs/` (the source) and `generated-docs/` (the mdoc output) — if `generated-docs/` is stale, the published site won't reflect your changes. + ## Notes -- `@VERSION@` and other mdoc variables are **not** substituted in this mode. For a fully-rendered preview, run `sbt 'docs/mdoc'` from the repo root and serve `generated-docs/out/` instead. +- `@VERSION@` and other mdoc variables are **not** substituted in the local watch mode. For a fully-rendered preview, run `sbt docs/mdoc` from the repo root and serve `generated-docs/out/` instead. - Scala code snippets are verified by `sbt compileDocs` (also runs in CI). diff --git a/docs/client/capabilities.md b/docs/client/capabilities.md new file mode 100644 index 0000000..27464bd --- /dev/null +++ b/docs/client/capabilities.md @@ -0,0 +1,34 @@ +# Client capabilities + +Beyond calling tools, an MCP client can advertise capabilities that let the server interact with it. Chimp supports: + +- [Roots](https://modelcontextprotocol.io/specification/2025-11-25/client/roots) — exposing the filesystem boundaries the client can operate in. +- [Sampling](https://modelcontextprotocol.io/specification/2025-11-25/client/sampling) — letting the server request an LLM completion through the client. +- [Elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation) — letting the server request additional input from the user. +- [Logging](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging) — receiving log messages forwarded by the server. +- [Notifications](https://modelcontextprotocol.io/specification/2025-11-25/basic/index#notifications) — receiving server-pushed events such as resource updates and list changes. + +```{note} +All of these require the server to push messages to the client, so they only work over a **bidirectional, streaming transport** (e.g. `ZioStreamingHttpTransport`). They are unavailable on the plain `HttpTransport`. +``` + +Create the client with `McpClient.bidirectional`, providing a handler for each capability you want to enable — only capabilities backed by a handler are advertised to the server: + +```scala +val client = McpClient.bidirectional[Task]( + transport, + clientInfo = Implementation("my-client", "0.1.0"), + rootsHandler = Some(() => ZIO.succeed(ListRootsResult(roots = List(Root("file:///workspace", Some("workspace")))))), + // samplingHandler = Some(...), + // elicitationHandler = Some(...), +) +``` + +Register a listener for server notifications with `onServerNotification`: + +```scala +client.onServerNotification { + case ServerNotification.ResourceUpdated(uri) => ZIO.logInfo(s"resource changed: $uri") + case _ => ZIO.unit +} +``` diff --git a/docs/client/examples.md b/docs/client/examples.md new file mode 100644 index 0000000..7a70b9f --- /dev/null +++ b/docs/client/examples.md @@ -0,0 +1,88 @@ +# Examples + +## HTTP client + +A synchronous client over `HttpTransport`, calling a tool: + +```scala +//> using dep com.softwaremill.chimp::chimp-client:0.2.0 +//> using dep com.softwaremill.sttp.client4::core:4.0.23 + +import chimp.client.* +import chimp.client.transport.HttpTransport +import chimp.protocol.* +import io.circe.Json +import sttp.client4.DefaultSyncBackend +import sttp.model.Uri.UriContext +import sttp.shared.Identity + +@main def httpClient(): Unit = + val backend = DefaultSyncBackend() + val transport = HttpTransport[Identity](backend, uri"http://localhost:8080/mcp") + val client = McpClient[Identity](transport, Implementation("my-client", "0.1.0")) + + val result = client.callTool("adder", Json.obj("a" -> Json.fromInt(2), "b" -> Json.fromInt(3))) + result.content.collect { case ToolContent.Text(_, text) => println(text) } + + client.close() + backend.close() +``` + +## STDIO client + +A synchronous client that launches a local MCP server as a subprocess over `StdioTransport`: + +```scala +//> using dep com.softwaremill.chimp::chimp-client:0.2.0 + +import chimp.client.* +import chimp.client.transport.StdioTransport +import chimp.protocol.* +import io.circe.Json +import sttp.shared.Identity + +@main def stdioClient(): Unit = + val transport = StdioTransport(command = List("my-mcp-server")) + val client = McpClient[Identity](transport, Implementation("my-client", "0.1.0")) + + val result = client.callTool("adder", Json.obj("a" -> Json.fromInt(2), "b" -> Json.fromInt(3))) + result.content.collect { case ToolContent.Text(_, text) => println(text) } + + client.close() +``` + +## Roots over a ZIO streaming transport + +[Roots](https://modelcontextprotocol.io/specification/2025-11-25/client/roots) require a bidirectional, streaming transport — here `ZioStreamingHttpTransport`: + +```scala +//> using dep com.softwaremill.chimp::chimp-client-zio:0.2.0 +//> using dep com.softwaremill.sttp.client4::zio:4.0.23 + +import chimp.client.* +import chimp.client.transport.zio.ZioStreamingHttpTransport +import chimp.protocol.* +import sttp.client4.httpclient.zio.HttpClientZioBackend +import sttp.model.Uri.UriContext +import zio.* + +object RootsClient extends ZIOAppDefault: + def run = + HttpClientZioBackend.scoped().flatMap { backend => + ZIO.scoped { + for + transport <- ZioStreamingHttpTransport.scoped(backend, uri"http://localhost:8080/mcp") + client <- McpClient.bidirectional[Task]( + transport, + clientInfo = Implementation("my-client", "0.1.0"), + rootsHandler = Some(() => + ZIO.succeed(ListRootsResult(roots = List(Root("file:///workspace", Some("workspace")))))) + ) + tools <- client.listTools() + _ <- Console.printLine(s"server exposes ${tools.tools.size} tools") + yield () + } + } +``` + +More runnable examples live in [`examples/`](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/examples). diff --git a/docs/client/index.md b/docs/client/index.md index 9e96246..08d9bed 100644 --- a/docs/client/index.md +++ b/docs/client/index.md @@ -1,41 +1,13 @@ # Client -Chimp ships an MCP client that connects to any MCP-compliant server. The client is parameterised over an effect type `F[_]`, and is paired with a pluggable transport that carries JSON-RPC messages. +Chimp ships an MCP client that connects to any MCP-compliant server. The client is parameterised over an effect type `F[_]` and is paired with a pluggable transport that carries JSON-RPC messages. -## Quickstart +```{eval-rst} +.. toctree:: + :maxdepth: 2 -Add the dependency to your `build.sbt`: - -```scala -libraryDependencies += "com.softwaremill.chimp" %% "chimp-client" % "0.1.8" -``` - -For the ZIO streaming transport, also add: - -```scala -libraryDependencies += "com.softwaremill.chimp" %% "chimp-client-zio" % "0.1.8" + quickstart + transport + capabilities + examples ``` - -## Two client flavours - -- `McpClient[F]` — unidirectional. The client issues requests and receives responses; suitable for any `Transport[F]`. -- `BidirectionalMcpClient[F]` — extends `McpClient[F]` and additionally supports server-initiated interactions: resource subscriptions, roots-changed notifications, and notification listeners. Requires a `BidirectionalTransport[F]`. - -Both are created via the `McpClient` companion object after the initialization handshake completes. - -## Transports - -| Transport | Sync (`F = Identity`) | Streaming | -|---|---|---| -| HTTP | `HttpTransport` | `StreamingHttpTransport` | -| stdio | `StdioTransport` | `StreamingStdioTransport` | - -The streaming variants are bidirectional and unlock `BidirectionalMcpClient`. The sync variants are simpler and direct-style. - -## Notifications - -When using a `BidirectionalMcpClient`, register a `ServerNotificationListener` via `onServerNotification` to react to server-pushed events (resource updates, list-changed notifications, log messages). - -## More examples - -See the runnable examples under [`examples/`](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/chimp). diff --git a/docs/client/quickstart.md b/docs/client/quickstart.md new file mode 100644 index 0000000..6080588 --- /dev/null +++ b/docs/client/quickstart.md @@ -0,0 +1,41 @@ +# Quickstart + +Add the dependency to your `build.sbt`: + +```scala +libraryDependencies += "com.softwaremill.chimp" %% "chimp-client" % "0.2.0" +``` + +## Example: the simplest MCP client + +Below is a self-contained, [scala-cli](https://scala-cli.virtuslab.org)-runnable example that connects to an MCP server over HTTP and invokes a tool: + +```scala +//> using dep com.softwaremill.chimp::chimp-client:0.2.0 +//> using dep com.softwaremill.sttp.client4::core:4.0.23 + +import chimp.client.* +import chimp.client.transport.HttpTransport +import chimp.protocol.* +import io.circe.Json +import sttp.client4.DefaultSyncBackend +import sttp.model.Uri.UriContext +import sttp.shared.Identity + +@main def mcpClient(): Unit = + val backend = DefaultSyncBackend() + val transport = HttpTransport[Identity](backend, uri"http://localhost:8080/mcp") + val client = McpClient[Identity](transport, Implementation("my-client", "0.1.0")) + + val result = client.callTool("adder", Json.obj("a" -> Json.fromInt(2), "b" -> Json.fromInt(3))) + result.content.collect { case ToolContent.Text(_, text) => println(text) } + + client.close() + backend.close() +``` + +For streaming transports (e.g. ZIO), also add: + +```scala +libraryDependencies += "com.softwaremill.chimp" %% "chimp-client-zio" % "0.2.0" +``` diff --git a/docs/client/transport.md b/docs/client/transport.md new file mode 100644 index 0000000..64aac4a --- /dev/null +++ b/docs/client/transport.md @@ -0,0 +1,40 @@ +# Transport + +A transport carries JSON-RPC messages between the client and the server. There are two families: + +- **Unidirectional** (`Transport[F]`) — the client sends a message and optionally gets a response back. Enough for calling tools, listing resources, etc. +- **Bidirectional** (`BidirectionalTransport[F]`) — additionally lets the server push messages to the client (server-initiated requests and notifications). Required for [client capabilities](capabilities.md). + +The streaming transports are abstract; their concrete, effect-specific implementations live in separate modules (e.g. ZIO). + +```{mermaid} +classDiagram + class Transport~F~ { + <> + +send(msg) Option~Message~ + +close() + } + class BidirectionalTransport~F~ { + <> + +onIncoming(handler) + } + class HttpTransport~F~ + class StdioTransport + class StreamingHttpTransport~F, S~ { + <> + } + class StreamingStdioTransport~F~ { + <> + } + + Transport <|-- BidirectionalTransport + Transport <|-- HttpTransport + BidirectionalTransport <|-- StdioTransport + BidirectionalTransport <|-- StreamingHttpTransport + BidirectionalTransport <|-- StreamingStdioTransport +``` + +## Backends + +- **HTTP** transports run on any [sttp](https://sttp.softwaremill.com/en/latest/) backend. The streaming HTTP transports additionally require a backend with streaming capability. +- **STDIO** transports, on the other hand, can run using plain JDK components (synchronous), or using various libraries that support asynchronous streaming. diff --git a/docs/conf.py b/docs/conf.py index 126483c..bcdedda 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,7 +28,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['myst_parser', 'sphinx_rtd_theme'] +extensions = ['myst_parser', 'sphinx_rtd_theme', 'sphinxcontrib.mermaid'] myst_enable_extensions = ['attrs_block'] diff --git a/docs/index.md b/docs/index.md index 54a9517..20dd460 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,8 @@ -# chimp: build MCP servers and clients in Scala 3 +# chimp: for MCP servers and clients in Scala 3 -Chimp is a library for building [MCP](https://modelcontextprotocol.io/specification/2025-03-26) (Model Context Protocol) servers and clients in Scala 3, based on [Tapir](https://tapir.softwaremill.com/). Describe MCP tools with type-safe inputs; talk to MCP servers from your code. +Chimp is an SDK for building [MCP](https://modelcontextprotocol.io/specification) (Model Context Protocol) servers and +clients in Scala 3 using boilerplate-less, type-safe APIs based on [Tapir](https://tapir.softwaremill.com/) +and [sttp](https://github.com/softwaremill/sttp), supporting the variety of the Scala ecosystem. ```{eval-rst} .. toctree:: diff --git a/docs/requirements.txt b/docs/requirements.txt index e854807..08863b6 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,3 +2,4 @@ sphinx_rtd_theme==2.0.0 sphinx==7.3.7 sphinx-autobuild==2024.4.16 myst-parser==2.0.0 +sphinxcontrib-mermaid==0.9.2 diff --git a/docs/server/index.md b/docs/server/index.md index a4c5d3f..f8aa6a2 100644 --- a/docs/server/index.md +++ b/docs/server/index.md @@ -7,7 +7,7 @@ Chimp lets you expose MCP tools over a JSON-RPC HTTP API. Tool inputs are descri Add the dependency to your `build.sbt`: ```scala -libraryDependencies += "com.softwaremill.chimp" %% "chimp-server" % "0.1.8" +libraryDependencies += "com.softwaremill.chimp" %% "chimp-server" % "0.2.0" ``` ### Example: the simplest MCP server @@ -15,7 +15,7 @@ libraryDependencies += "com.softwaremill.chimp" %% "chimp-server" % "0.1.8" Below is a self-contained, [scala-cli](https://scala-cli.virtuslab.org)-runnable example: ```scala -//> using dep com.softwaremill.chimp::chimp-server:0.1.8 +//> using dep com.softwaremill.chimp::chimp-server:0.2.0 import chimp.* import sttp.tapir.* @@ -38,9 +38,7 @@ case class AdderInput(a: Int, b: Int) derives io.circe.Codec, Schema NettySyncServer().port(8080).addEndpoint(mcpServerEndpoint).startAndWait() ``` -### More examples - -Available [here](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/chimp). +More runnable examples live in [`examples/`](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/examples). ## MCP Protocol diff --git a/generated-docs/out/README.md b/generated-docs/out/README.md index 3bade1c..7f26f40 100644 --- a/generated-docs/out/README.md +++ b/generated-docs/out/README.md @@ -24,5 +24,5 @@ source .venv/bin/activate ## Notes -- `0.1.8+11-2a8e17d4+20260529-1138-SNAPSHOT` and other mdoc variables are **not** substituted in this mode. For a fully-rendered preview, run `sbt 'docs/mdoc'` from the repo root and serve `generated-docs/out/` instead. +- `0.1.8+14-8ce910d5+20260531-1230-SNAPSHOT` and other mdoc variables are **not** substituted in this mode. For a fully-rendered preview, run `sbt 'docs/mdoc'` from the repo root and serve `generated-docs/out/` instead. - Scala code snippets are verified by `sbt compileDocs` (also runs in CI). diff --git a/generated-docs/out/client/capabilities.md b/generated-docs/out/client/capabilities.md new file mode 100644 index 0000000..27464bd --- /dev/null +++ b/generated-docs/out/client/capabilities.md @@ -0,0 +1,34 @@ +# Client capabilities + +Beyond calling tools, an MCP client can advertise capabilities that let the server interact with it. Chimp supports: + +- [Roots](https://modelcontextprotocol.io/specification/2025-11-25/client/roots) — exposing the filesystem boundaries the client can operate in. +- [Sampling](https://modelcontextprotocol.io/specification/2025-11-25/client/sampling) — letting the server request an LLM completion through the client. +- [Elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation) — letting the server request additional input from the user. +- [Logging](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging) — receiving log messages forwarded by the server. +- [Notifications](https://modelcontextprotocol.io/specification/2025-11-25/basic/index#notifications) — receiving server-pushed events such as resource updates and list changes. + +```{note} +All of these require the server to push messages to the client, so they only work over a **bidirectional, streaming transport** (e.g. `ZioStreamingHttpTransport`). They are unavailable on the plain `HttpTransport`. +``` + +Create the client with `McpClient.bidirectional`, providing a handler for each capability you want to enable — only capabilities backed by a handler are advertised to the server: + +```scala +val client = McpClient.bidirectional[Task]( + transport, + clientInfo = Implementation("my-client", "0.1.0"), + rootsHandler = Some(() => ZIO.succeed(ListRootsResult(roots = List(Root("file:///workspace", Some("workspace")))))), + // samplingHandler = Some(...), + // elicitationHandler = Some(...), +) +``` + +Register a listener for server notifications with `onServerNotification`: + +```scala +client.onServerNotification { + case ServerNotification.ResourceUpdated(uri) => ZIO.logInfo(s"resource changed: $uri") + case _ => ZIO.unit +} +``` diff --git a/generated-docs/out/client/examples.md b/generated-docs/out/client/examples.md new file mode 100644 index 0000000..bc66ddf --- /dev/null +++ b/generated-docs/out/client/examples.md @@ -0,0 +1,88 @@ +# Examples + +## HTTP client + +A synchronous client over `HttpTransport`, calling a tool: + +```scala +//> using dep com.softwaremill.chimp::chimp-client:0.2.0 +//> using dep com.softwaremill.sttp.client4::core:4.0.23 + +import chimp.client.* +import chimp.client.transport.HttpTransport +import chimp.protocol.* +import io.circe.Json +import sttp.client4.DefaultSyncBackend +import sttp.model.Uri.UriContext +import sttp.shared.Identity + +@main def httpClient(): Unit = + val backend = DefaultSyncBackend() + val transport = HttpTransport[Identity](backend, uri"http://localhost:8080/mcp") + val client = McpClient[Identity](transport, Implementation("my-client", "0.1.0")) + + val result = client.callTool("adder", Json.obj("a" -> Json.fromInt(2), "b" -> Json.fromInt(3))) + result.content.collect { case ToolContent.Text(_, text) => println(text) } + + client.close() + backend.close() +``` + +## STDIO client + +A synchronous client that launches a local MCP server as a subprocess over `StdioTransport`: + +```scala +//> using dep com.softwaremill.chimp::chimp-client:0.2.0 + +import chimp.client.* +import chimp.client.transport.StdioTransport +import chimp.protocol.* +import io.circe.Json +import sttp.shared.Identity + +@main def stdioClient(): Unit = + val transport = StdioTransport(command = List("my-mcp-server")) + val client = McpClient[Identity](transport, Implementation("my-client", "0.1.0")) + + val result = client.callTool("adder", Json.obj("a" -> Json.fromInt(2), "b" -> Json.fromInt(3))) + result.content.collect { case ToolContent.Text(_, text) => println(text) } + + client.close() +``` + +## Roots over a ZIO streaming transport + +[Roots](https://modelcontextprotocol.io/specification/2025-11-25/client/roots) require a bidirectional, streaming transport — here `ZioStreamingHttpTransport`: + +```scala +//> using dep com.softwaremill.chimp::chimp-client-zio:0.2.0 +//> using dep com.softwaremill.sttp.client4::zio:4.0.23 + +import chimp.client.* +import chimp.client.transport.zio.ZioStreamingHttpTransport +import chimp.protocol.* +import sttp.client4.httpclient.zio.HttpClientZioBackend +import sttp.model.Uri.UriContext +import zio.* + +object RootsClient extends ZIOAppDefault: + def run = + HttpClientZioBackend.scoped().flatMap { backend => + ZIO.scoped { + for + transport <- ZioStreamingHttpTransport.scoped(backend, uri"http://localhost:8080/mcp") + client <- McpClient.bidirectional[Task]( + transport, + clientInfo = Implementation("my-client", "0.1.0"), + rootsHandler = Some(() => + ZIO.succeed(ListRootsResult(roots = List(Root("file:///workspace", Some("workspace")))))) + ) + tools <- client.listTools() + _ <- Console.printLine(s"server exposes ${tools.tools.size} tools") + yield () + } + } +``` + +More runnable examples live in [`examples/`](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/chimp). diff --git a/generated-docs/out/client/index.md b/generated-docs/out/client/index.md index 9e96246..3e1ea9a 100644 --- a/generated-docs/out/client/index.md +++ b/generated-docs/out/client/index.md @@ -1,41 +1,24 @@ # Client -Chimp ships an MCP client that connects to any MCP-compliant server. The client is parameterised over an effect type `F[_]`, and is paired with a pluggable transport that carries JSON-RPC messages. - -## Quickstart +Chimp ships an MCP client that connects to any MCP-compliant server. The client is parameterised over an effect type `F[_]` and is paired with a pluggable transport that carries JSON-RPC messages. Add the dependency to your `build.sbt`: ```scala -libraryDependencies += "com.softwaremill.chimp" %% "chimp-client" % "0.1.8" +libraryDependencies += "com.softwaremill.chimp" %% "chimp-client" % "0.2.0" ``` -For the ZIO streaming transport, also add: +For streaming transports (e.g. ZIO), also add: ```scala -libraryDependencies += "com.softwaremill.chimp" %% "chimp-client-zio" % "0.1.8" +libraryDependencies += "com.softwaremill.chimp" %% "chimp-client-zio" % "0.2.0" ``` -## Two client flavours - -- `McpClient[F]` — unidirectional. The client issues requests and receives responses; suitable for any `Transport[F]`. -- `BidirectionalMcpClient[F]` — extends `McpClient[F]` and additionally supports server-initiated interactions: resource subscriptions, roots-changed notifications, and notification listeners. Requires a `BidirectionalTransport[F]`. - -Both are created via the `McpClient` companion object after the initialization handshake completes. - -## Transports - -| Transport | Sync (`F = Identity`) | Streaming | -|---|---|---| -| HTTP | `HttpTransport` | `StreamingHttpTransport` | -| stdio | `StdioTransport` | `StreamingStdioTransport` | +```{eval-rst} +.. toctree:: + :maxdepth: 2 -The streaming variants are bidirectional and unlock `BidirectionalMcpClient`. The sync variants are simpler and direct-style. - -## Notifications - -When using a `BidirectionalMcpClient`, register a `ServerNotificationListener` via `onServerNotification` to react to server-pushed events (resource updates, list-changed notifications, log messages). - -## More examples - -See the runnable examples under [`examples/`](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/chimp). + transport + capabilities + examples +``` diff --git a/generated-docs/out/client/transport.md b/generated-docs/out/client/transport.md new file mode 100644 index 0000000..25538c5 --- /dev/null +++ b/generated-docs/out/client/transport.md @@ -0,0 +1,40 @@ +# Transport + +A transport carries JSON-RPC messages between the client and the server. There are two families: + +- **Unidirectional** (`Transport[F]`) — the client sends a message and optionally gets a response back. Enough for calling tools, listing resources, etc. +- **Bidirectional** (`BidirectionalTransport[F]`) — additionally lets the server push messages to the client (server-initiated requests and notifications). Required for [client capabilities](capabilities.md). + +The streaming transports are abstract; their concrete, effect-specific implementations live in separate modules (e.g. ZIO). + +```{mermaid} +classDiagram + class Transport~F~ { + <> + +send(msg) Option~Message~ + +close() + } + class BidirectionalTransport~F~ { + <> + +onIncoming(handler) + } + class HttpTransport~F~ + class StdioTransport + class StreamingHttpTransport~F, S~ { + <> + } + class StreamingStdioTransport~F~ { + <> + } + class EffectSpecificStreaming { + ox, ZIO, Pekko, etc. + } + + Transport <|-- BidirectionalTransport + Transport <|-- HttpTransport + BidirectionalTransport <|-- StdioTransport + BidirectionalTransport <|-- StreamingHttpTransport + BidirectionalTransport <|-- StreamingStdioTransport + StreamingHttpTransport <|-- EffectSpecificStreaming + StreamingStdioTransport <|-- EffectSpecificStreaming +``` diff --git a/generated-docs/out/conf.py b/generated-docs/out/conf.py index 126483c..bcdedda 100644 --- a/generated-docs/out/conf.py +++ b/generated-docs/out/conf.py @@ -28,7 +28,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['myst_parser', 'sphinx_rtd_theme'] +extensions = ['myst_parser', 'sphinx_rtd_theme', 'sphinxcontrib.mermaid'] myst_enable_extensions = ['attrs_block'] diff --git a/generated-docs/out/index.md b/generated-docs/out/index.md index 8839a44..54a9517 100644 --- a/generated-docs/out/index.md +++ b/generated-docs/out/index.md @@ -5,8 +5,13 @@ Chimp is a library for building [MCP](https://modelcontextprotocol.io/specificat ```{eval-rst} .. toctree:: :maxdepth: 2 - :caption: Topics + :caption: Server server/index + +.. toctree:: + :maxdepth: 2 + :caption: Client + client/index ``` diff --git a/generated-docs/out/requirements.txt b/generated-docs/out/requirements.txt index e854807..08863b6 100644 --- a/generated-docs/out/requirements.txt +++ b/generated-docs/out/requirements.txt @@ -2,3 +2,4 @@ sphinx_rtd_theme==2.0.0 sphinx==7.3.7 sphinx-autobuild==2024.4.16 myst-parser==2.0.0 +sphinxcontrib-mermaid==0.9.2 From 5b7dcabf671097467a20e95d72a7e529a4c9a606 Mon Sep 17 00:00:00 2001 From: kubinio123 Date: Sun, 31 May 2026 13:06:27 +0200 Subject: [PATCH 7/7] feat: client documentation --- README.md | 3 +- docs/client/index.md | 13 ----- docs/client/quickstart.md | 2 + docs/index.md | 10 +++- docs/server/index.md | 71 ------------------------ docs/server/protocol.md | 9 +++ docs/server/quickstart.md | 39 +++++++++++++ docs/server/tools.md | 10 ++++ docs/server/zio.md | 8 +++ generated-docs/out/README.md | 12 +++- generated-docs/out/client/examples.md | 2 +- generated-docs/out/client/index.md | 24 -------- generated-docs/out/client/quickstart.md | 43 +++++++++++++++ generated-docs/out/client/transport.md | 10 ++-- generated-docs/out/index.md | 16 ++++-- generated-docs/out/server/index.md | 73 ------------------------- generated-docs/out/server/protocol.md | 9 +++ generated-docs/out/server/quickstart.md | 39 +++++++++++++ generated-docs/out/server/tools.md | 10 ++++ generated-docs/out/server/zio.md | 8 +++ 20 files changed, 215 insertions(+), 196 deletions(-) delete mode 100644 docs/client/index.md delete mode 100644 docs/server/index.md create mode 100644 docs/server/protocol.md create mode 100644 docs/server/quickstart.md create mode 100644 docs/server/tools.md create mode 100644 docs/server/zio.md delete mode 100644 generated-docs/out/client/index.md create mode 100644 generated-docs/out/client/quickstart.md delete mode 100644 generated-docs/out/server/index.md create mode 100644 generated-docs/out/server/protocol.md create mode 100644 generated-docs/out/server/quickstart.md create mode 100644 generated-docs/out/server/tools.md create mode 100644 generated-docs/out/server/zio.md diff --git a/README.md b/README.md index ec9cb53..321ca86 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,7 @@ import io.circe.Json ## Documentation -Full documentation is available at **[chimp.readthedocs.io](https://chimp.readthedocs.io)**. Runnable examples live -in [`examples/`](examples/src/main/scala/chimp). +Full documentation is available at **[chimp.softwaremill.com](https://chimp.softwaremill.com/)**. ## Contributing diff --git a/docs/client/index.md b/docs/client/index.md deleted file mode 100644 index 08d9bed..0000000 --- a/docs/client/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# Client - -Chimp ships an MCP client that connects to any MCP-compliant server. The client is parameterised over an effect type `F[_]` and is paired with a pluggable transport that carries JSON-RPC messages. - -```{eval-rst} -.. toctree:: - :maxdepth: 2 - - quickstart - transport - capabilities - examples -``` diff --git a/docs/client/quickstart.md b/docs/client/quickstart.md index 6080588..538f576 100644 --- a/docs/client/quickstart.md +++ b/docs/client/quickstart.md @@ -1,5 +1,7 @@ # Quickstart +Chimp ships an MCP client that connects to any MCP-compliant server. The client is parameterised over an effect type `F[_]` and is paired with a pluggable transport that carries JSON-RPC messages. + Add the dependency to your `build.sbt`: ```scala diff --git a/docs/index.md b/docs/index.md index 20dd460..da56c91 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,11 +9,17 @@ and [sttp](https://github.com/softwaremill/sttp), supporting the variety of the :maxdepth: 2 :caption: Server - server/index + server/quickstart + server/protocol + server/tools + server/zio .. toctree:: :maxdepth: 2 :caption: Client - client/index + client/quickstart + client/transport + client/capabilities + client/examples ``` diff --git a/docs/server/index.md b/docs/server/index.md deleted file mode 100644 index f8aa6a2..0000000 --- a/docs/server/index.md +++ /dev/null @@ -1,71 +0,0 @@ -# Server - -Chimp lets you expose MCP tools over a JSON-RPC HTTP API. Tool inputs are described with type-safe Scala types; the JSON schema and JSON-RPC plumbing are generated for you. - -## Quickstart - -Add the dependency to your `build.sbt`: - -```scala -libraryDependencies += "com.softwaremill.chimp" %% "chimp-server" % "0.2.0" -``` - -### Example: the simplest MCP server - -Below is a self-contained, [scala-cli](https://scala-cli.virtuslab.org)-runnable example: - -```scala -//> using dep com.softwaremill.chimp::chimp-server:0.2.0 - -import chimp.* -import sttp.tapir.* -import sttp.tapir.server.netty.sync.NettySyncServer - -// define the input type for your tool -case class AdderInput(a: Int, b: Int) derives io.circe.Codec, Schema - -@main def mcpApp(): Unit = - // describe the tool providing the name, description, and input type - val adderTool = tool("adder").description("Adds two numbers").input[AdderInput] - - // combine the tool description with the server-side logic - val adderServerTool = adderTool.handle(i => Right(s"The result is ${i.a + i.b}")) - - // create the MCP server endpoint; it will be available at http://localhost:8080/mcp - val mcpServerEndpoint = mcpEndpoint(List(adderServerTool), List("mcp")) - - // start the server - NettySyncServer().port(8080).addEndpoint(mcpServerEndpoint).startAndWait() -``` - -More runnable examples live in [`examples/`](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/examples). - -## MCP Protocol - -Chimp implements the HTTP transport of the [MCP protocol](https://modelcontextprotocol.io/specification/2025-03-26) (version **2025-03-26**). Only tools are supported, via the following JSON-RPC commands: - -- Initialization and capabilities negotiation (`initialize`) -- Listing available tools (`tools/list`) -- Invoking a tool (`tools/call`) - -All requests and responses use JSON-RPC 2.0. Tool input schemas are described using JSON Schema, auto-generated from Scala types. - -## Defining tools and server logic - -- Use `tool(name)` to start defining a tool. -- Add a description and annotations for metadata and hints. -- Specify the input type (must have a Circe `Codec` and Tapir `Schema`). -- Provide the server logic as a function from input to `Either[String, String]` (or a generic effect type). - - Use `handle` to connect the tool definition with the server logic when the use of headers is not required. - - Use `handleWithHeaders` to connect the tool definition with the server logic when headers are required. -- Create a Tapir endpoint by providing your tools to `mcpEndpoint`. -- Start an HTTP server using your preferred Tapir server interpreter. - -## Using with ZIO - -When using ZIO, you might have to explicitly state the effect type that you are using, as the Tapir-ZIO integration requires a `RIO[R, A]` effect (which is an alias for `ZIO[R, Throwable, A]`), for example: - -```scala -val myServerTool = myTool.serverLogic[[X] =>> RIO[Any, X]]: (input, headers) => - ZIO.succeed(???) -``` diff --git a/docs/server/protocol.md b/docs/server/protocol.md new file mode 100644 index 0000000..bc9cd7f --- /dev/null +++ b/docs/server/protocol.md @@ -0,0 +1,9 @@ +# MCP Protocol + +Chimp implements the HTTP transport of the [MCP protocol](https://modelcontextprotocol.io/specification/2025-03-26) (version **2025-03-26**). Only tools are supported, via the following JSON-RPC commands: + +- Initialization and capabilities negotiation (`initialize`) +- Listing available tools (`tools/list`) +- Invoking a tool (`tools/call`) + +All requests and responses use JSON-RPC 2.0. Tool input schemas are described using JSON Schema, auto-generated from Scala types. diff --git a/docs/server/quickstart.md b/docs/server/quickstart.md new file mode 100644 index 0000000..2356494 --- /dev/null +++ b/docs/server/quickstart.md @@ -0,0 +1,39 @@ +# Quickstart + +Chimp lets you expose MCP tools over a JSON-RPC HTTP API. Tool inputs are described with type-safe Scala types; the JSON schema and JSON-RPC plumbing are generated for you. + +Add the dependency to your `build.sbt`: + +```scala +libraryDependencies += "com.softwaremill.chimp" %% "chimp-server" % "0.2.0" +``` + +## Example: the simplest MCP server + +Below is a self-contained, [scala-cli](https://scala-cli.virtuslab.org)-runnable example: + +```scala +//> using dep com.softwaremill.chimp::chimp-server:0.2.0 + +import chimp.* +import sttp.tapir.* +import sttp.tapir.server.netty.sync.NettySyncServer + +// define the input type for your tool +case class AdderInput(a: Int, b: Int) derives io.circe.Codec, Schema + +@main def mcpApp(): Unit = + // describe the tool providing the name, description, and input type + val adderTool = tool("adder").description("Adds two numbers").input[AdderInput] + + // combine the tool description with the server-side logic + val adderServerTool = adderTool.handle(i => Right(s"The result is ${i.a + i.b}")) + + // create the MCP server endpoint; it will be available at http://localhost:8080/mcp + val mcpServerEndpoint = mcpEndpoint(List(adderServerTool), List("mcp")) + + // start the server + NettySyncServer().port(8080).addEndpoint(mcpServerEndpoint).startAndWait() +``` + +More runnable examples live in [`examples/`](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/examples). diff --git a/docs/server/tools.md b/docs/server/tools.md new file mode 100644 index 0000000..db323ea --- /dev/null +++ b/docs/server/tools.md @@ -0,0 +1,10 @@ +# Defining tools and server logic + +- Use `tool(name)` to start defining a tool. +- Add a description and annotations for metadata and hints. +- Specify the input type (must have a Circe `Codec` and Tapir `Schema`). +- Provide the server logic as a function from input to `Either[String, String]` (or a generic effect type). + - Use `handle` to connect the tool definition with the server logic when the use of headers is not required. + - Use `handleWithHeaders` to connect the tool definition with the server logic when headers are required. +- Create a Tapir endpoint by providing your tools to `mcpEndpoint`. +- Start an HTTP server using your preferred Tapir server interpreter. diff --git a/docs/server/zio.md b/docs/server/zio.md new file mode 100644 index 0000000..ccd5d9b --- /dev/null +++ b/docs/server/zio.md @@ -0,0 +1,8 @@ +# Using with ZIO + +When using ZIO, you might have to explicitly state the effect type that you are using, as the Tapir-ZIO integration requires a `RIO[R, A]` effect (which is an alias for `ZIO[R, Throwable, A]`), for example: + +```scala +val myServerTool = myTool.serverLogic[[X] =>> RIO[Any, X]]: (input, headers) => + ZIO.succeed(???) +``` diff --git a/generated-docs/out/README.md b/generated-docs/out/README.md index 7f26f40..8bdae51 100644 --- a/generated-docs/out/README.md +++ b/generated-docs/out/README.md @@ -22,7 +22,17 @@ source .venv/bin/activate ./watch.sh ``` +## Publishing changes + +Read the Docs builds from `generated-docs/out/`, **not** from this `docs/` folder. After editing the docs, regenerate that output with mdoc: + +``` +sbt compileDocs +``` + +Commit both `docs/` (the source) and `generated-docs/` (the mdoc output) — if `generated-docs/` is stale, the published site won't reflect your changes. + ## Notes -- `0.1.8+14-8ce910d5+20260531-1230-SNAPSHOT` and other mdoc variables are **not** substituted in this mode. For a fully-rendered preview, run `sbt 'docs/mdoc'` from the repo root and serve `generated-docs/out/` instead. +- `0.1.8+15-aee4bbdd+20260531-1302-SNAPSHOT` and other mdoc variables are **not** substituted in the local watch mode. For a fully-rendered preview, run `sbt docs/mdoc` from the repo root and serve `generated-docs/out/` instead. - Scala code snippets are verified by `sbt compileDocs` (also runs in CI). diff --git a/generated-docs/out/client/examples.md b/generated-docs/out/client/examples.md index bc66ddf..7a70b9f 100644 --- a/generated-docs/out/client/examples.md +++ b/generated-docs/out/client/examples.md @@ -85,4 +85,4 @@ object RootsClient extends ZIOAppDefault: } ``` -More runnable examples live in [`examples/`](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/chimp). +More runnable examples live in [`examples/`](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/examples). diff --git a/generated-docs/out/client/index.md b/generated-docs/out/client/index.md deleted file mode 100644 index 3e1ea9a..0000000 --- a/generated-docs/out/client/index.md +++ /dev/null @@ -1,24 +0,0 @@ -# Client - -Chimp ships an MCP client that connects to any MCP-compliant server. The client is parameterised over an effect type `F[_]` and is paired with a pluggable transport that carries JSON-RPC messages. - -Add the dependency to your `build.sbt`: - -```scala -libraryDependencies += "com.softwaremill.chimp" %% "chimp-client" % "0.2.0" -``` - -For streaming transports (e.g. ZIO), also add: - -```scala -libraryDependencies += "com.softwaremill.chimp" %% "chimp-client-zio" % "0.2.0" -``` - -```{eval-rst} -.. toctree:: - :maxdepth: 2 - - transport - capabilities - examples -``` diff --git a/generated-docs/out/client/quickstart.md b/generated-docs/out/client/quickstart.md new file mode 100644 index 0000000..538f576 --- /dev/null +++ b/generated-docs/out/client/quickstart.md @@ -0,0 +1,43 @@ +# Quickstart + +Chimp ships an MCP client that connects to any MCP-compliant server. The client is parameterised over an effect type `F[_]` and is paired with a pluggable transport that carries JSON-RPC messages. + +Add the dependency to your `build.sbt`: + +```scala +libraryDependencies += "com.softwaremill.chimp" %% "chimp-client" % "0.2.0" +``` + +## Example: the simplest MCP client + +Below is a self-contained, [scala-cli](https://scala-cli.virtuslab.org)-runnable example that connects to an MCP server over HTTP and invokes a tool: + +```scala +//> using dep com.softwaremill.chimp::chimp-client:0.2.0 +//> using dep com.softwaremill.sttp.client4::core:4.0.23 + +import chimp.client.* +import chimp.client.transport.HttpTransport +import chimp.protocol.* +import io.circe.Json +import sttp.client4.DefaultSyncBackend +import sttp.model.Uri.UriContext +import sttp.shared.Identity + +@main def mcpClient(): Unit = + val backend = DefaultSyncBackend() + val transport = HttpTransport[Identity](backend, uri"http://localhost:8080/mcp") + val client = McpClient[Identity](transport, Implementation("my-client", "0.1.0")) + + val result = client.callTool("adder", Json.obj("a" -> Json.fromInt(2), "b" -> Json.fromInt(3))) + result.content.collect { case ToolContent.Text(_, text) => println(text) } + + client.close() + backend.close() +``` + +For streaming transports (e.g. ZIO), also add: + +```scala +libraryDependencies += "com.softwaremill.chimp" %% "chimp-client-zio" % "0.2.0" +``` diff --git a/generated-docs/out/client/transport.md b/generated-docs/out/client/transport.md index 25538c5..64aac4a 100644 --- a/generated-docs/out/client/transport.md +++ b/generated-docs/out/client/transport.md @@ -26,15 +26,15 @@ classDiagram class StreamingStdioTransport~F~ { <> } - class EffectSpecificStreaming { - ox, ZIO, Pekko, etc. - } Transport <|-- BidirectionalTransport Transport <|-- HttpTransport BidirectionalTransport <|-- StdioTransport BidirectionalTransport <|-- StreamingHttpTransport BidirectionalTransport <|-- StreamingStdioTransport - StreamingHttpTransport <|-- EffectSpecificStreaming - StreamingStdioTransport <|-- EffectSpecificStreaming ``` + +## Backends + +- **HTTP** transports run on any [sttp](https://sttp.softwaremill.com/en/latest/) backend. The streaming HTTP transports additionally require a backend with streaming capability. +- **STDIO** transports, on the other hand, can run using plain JDK components (synchronous), or using various libraries that support asynchronous streaming. diff --git a/generated-docs/out/index.md b/generated-docs/out/index.md index 54a9517..da56c91 100644 --- a/generated-docs/out/index.md +++ b/generated-docs/out/index.md @@ -1,17 +1,25 @@ -# chimp: build MCP servers and clients in Scala 3 +# chimp: for MCP servers and clients in Scala 3 -Chimp is a library for building [MCP](https://modelcontextprotocol.io/specification/2025-03-26) (Model Context Protocol) servers and clients in Scala 3, based on [Tapir](https://tapir.softwaremill.com/). Describe MCP tools with type-safe inputs; talk to MCP servers from your code. +Chimp is an SDK for building [MCP](https://modelcontextprotocol.io/specification) (Model Context Protocol) servers and +clients in Scala 3 using boilerplate-less, type-safe APIs based on [Tapir](https://tapir.softwaremill.com/) +and [sttp](https://github.com/softwaremill/sttp), supporting the variety of the Scala ecosystem. ```{eval-rst} .. toctree:: :maxdepth: 2 :caption: Server - server/index + server/quickstart + server/protocol + server/tools + server/zio .. toctree:: :maxdepth: 2 :caption: Client - client/index + client/quickstart + client/transport + client/capabilities + client/examples ``` diff --git a/generated-docs/out/server/index.md b/generated-docs/out/server/index.md deleted file mode 100644 index a4c5d3f..0000000 --- a/generated-docs/out/server/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# Server - -Chimp lets you expose MCP tools over a JSON-RPC HTTP API. Tool inputs are described with type-safe Scala types; the JSON schema and JSON-RPC plumbing are generated for you. - -## Quickstart - -Add the dependency to your `build.sbt`: - -```scala -libraryDependencies += "com.softwaremill.chimp" %% "chimp-server" % "0.1.8" -``` - -### Example: the simplest MCP server - -Below is a self-contained, [scala-cli](https://scala-cli.virtuslab.org)-runnable example: - -```scala -//> using dep com.softwaremill.chimp::chimp-server:0.1.8 - -import chimp.* -import sttp.tapir.* -import sttp.tapir.server.netty.sync.NettySyncServer - -// define the input type for your tool -case class AdderInput(a: Int, b: Int) derives io.circe.Codec, Schema - -@main def mcpApp(): Unit = - // describe the tool providing the name, description, and input type - val adderTool = tool("adder").description("Adds two numbers").input[AdderInput] - - // combine the tool description with the server-side logic - val adderServerTool = adderTool.handle(i => Right(s"The result is ${i.a + i.b}")) - - // create the MCP server endpoint; it will be available at http://localhost:8080/mcp - val mcpServerEndpoint = mcpEndpoint(List(adderServerTool), List("mcp")) - - // start the server - NettySyncServer().port(8080).addEndpoint(mcpServerEndpoint).startAndWait() -``` - -### More examples - -Available [here](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/chimp). - -## MCP Protocol - -Chimp implements the HTTP transport of the [MCP protocol](https://modelcontextprotocol.io/specification/2025-03-26) (version **2025-03-26**). Only tools are supported, via the following JSON-RPC commands: - -- Initialization and capabilities negotiation (`initialize`) -- Listing available tools (`tools/list`) -- Invoking a tool (`tools/call`) - -All requests and responses use JSON-RPC 2.0. Tool input schemas are described using JSON Schema, auto-generated from Scala types. - -## Defining tools and server logic - -- Use `tool(name)` to start defining a tool. -- Add a description and annotations for metadata and hints. -- Specify the input type (must have a Circe `Codec` and Tapir `Schema`). -- Provide the server logic as a function from input to `Either[String, String]` (or a generic effect type). - - Use `handle` to connect the tool definition with the server logic when the use of headers is not required. - - Use `handleWithHeaders` to connect the tool definition with the server logic when headers are required. -- Create a Tapir endpoint by providing your tools to `mcpEndpoint`. -- Start an HTTP server using your preferred Tapir server interpreter. - -## Using with ZIO - -When using ZIO, you might have to explicitly state the effect type that you are using, as the Tapir-ZIO integration requires a `RIO[R, A]` effect (which is an alias for `ZIO[R, Throwable, A]`), for example: - -```scala -val myServerTool = myTool.serverLogic[[X] =>> RIO[Any, X]]: (input, headers) => - ZIO.succeed(???) -``` diff --git a/generated-docs/out/server/protocol.md b/generated-docs/out/server/protocol.md new file mode 100644 index 0000000..bc9cd7f --- /dev/null +++ b/generated-docs/out/server/protocol.md @@ -0,0 +1,9 @@ +# MCP Protocol + +Chimp implements the HTTP transport of the [MCP protocol](https://modelcontextprotocol.io/specification/2025-03-26) (version **2025-03-26**). Only tools are supported, via the following JSON-RPC commands: + +- Initialization and capabilities negotiation (`initialize`) +- Listing available tools (`tools/list`) +- Invoking a tool (`tools/call`) + +All requests and responses use JSON-RPC 2.0. Tool input schemas are described using JSON Schema, auto-generated from Scala types. diff --git a/generated-docs/out/server/quickstart.md b/generated-docs/out/server/quickstart.md new file mode 100644 index 0000000..2356494 --- /dev/null +++ b/generated-docs/out/server/quickstart.md @@ -0,0 +1,39 @@ +# Quickstart + +Chimp lets you expose MCP tools over a JSON-RPC HTTP API. Tool inputs are described with type-safe Scala types; the JSON schema and JSON-RPC plumbing are generated for you. + +Add the dependency to your `build.sbt`: + +```scala +libraryDependencies += "com.softwaremill.chimp" %% "chimp-server" % "0.2.0" +``` + +## Example: the simplest MCP server + +Below is a self-contained, [scala-cli](https://scala-cli.virtuslab.org)-runnable example: + +```scala +//> using dep com.softwaremill.chimp::chimp-server:0.2.0 + +import chimp.* +import sttp.tapir.* +import sttp.tapir.server.netty.sync.NettySyncServer + +// define the input type for your tool +case class AdderInput(a: Int, b: Int) derives io.circe.Codec, Schema + +@main def mcpApp(): Unit = + // describe the tool providing the name, description, and input type + val adderTool = tool("adder").description("Adds two numbers").input[AdderInput] + + // combine the tool description with the server-side logic + val adderServerTool = adderTool.handle(i => Right(s"The result is ${i.a + i.b}")) + + // create the MCP server endpoint; it will be available at http://localhost:8080/mcp + val mcpServerEndpoint = mcpEndpoint(List(adderServerTool), List("mcp")) + + // start the server + NettySyncServer().port(8080).addEndpoint(mcpServerEndpoint).startAndWait() +``` + +More runnable examples live in [`examples/`](https://github.com/softwaremill/chimp/tree/master/examples/src/main/scala/examples). diff --git a/generated-docs/out/server/tools.md b/generated-docs/out/server/tools.md new file mode 100644 index 0000000..db323ea --- /dev/null +++ b/generated-docs/out/server/tools.md @@ -0,0 +1,10 @@ +# Defining tools and server logic + +- Use `tool(name)` to start defining a tool. +- Add a description and annotations for metadata and hints. +- Specify the input type (must have a Circe `Codec` and Tapir `Schema`). +- Provide the server logic as a function from input to `Either[String, String]` (or a generic effect type). + - Use `handle` to connect the tool definition with the server logic when the use of headers is not required. + - Use `handleWithHeaders` to connect the tool definition with the server logic when headers are required. +- Create a Tapir endpoint by providing your tools to `mcpEndpoint`. +- Start an HTTP server using your preferred Tapir server interpreter. diff --git a/generated-docs/out/server/zio.md b/generated-docs/out/server/zio.md new file mode 100644 index 0000000..ccd5d9b --- /dev/null +++ b/generated-docs/out/server/zio.md @@ -0,0 +1,8 @@ +# Using with ZIO + +When using ZIO, you might have to explicitly state the effect type that you are using, as the Tapir-ZIO integration requires a `RIO[R, A]` effect (which is an alias for `ZIO[R, Throwable, A]`), for example: + +```scala +val myServerTool = myTool.serverLogic[[X] =>> RIO[Any, X]]: (input, headers) => + ZIO.succeed(???) +```