scala-mcp

[md]Resources & resource templates

Resources expose readable data to clients (e.g. file:///config.json, app://status). Resource templates expose parametrised URIs — clients can fill in path segments to read specific instances. Snippets below are type-checked by mdoc.

import cats.effect.IO
import cats.syntax.all.*
import java.time.Instant
import net.andimiller.mcp.core.server.*
import net.andimiller.mcp.core.protocol.ResourceContent

case class Item(id: String):
  def toJson: String = s"""{"id":"$id"}"""

def lookupItem(id: String): IO[Item] = IO.pure(Item(id))

def readNote(user: String, note: String): IO[ResourceContent] =
  IO.pure(ResourceContent.text(s"app://users/$user/notes/$note", "...", Some("application/json")))

Resource creation

Static content

Use .staticContent when the body is a fixed string at server-construction time:

val staticRes: McpResource[IO, Unit] =
  resource
    .uri("file:///config.json")
    .name("Config File")
    .description("Application config")
    .mimeType("application/json")
    .staticContent[IO]("""{"key": "value"}""")

Dynamic content

Use .read when the body is computed on each read:

val dynamicRes: McpResource[IO, Unit] =
  resource
    .uri("app://status")
    .name("Server Status")
    .mimeType("text/plain")
    .read(() => IO.pure(s"Status at ${Instant.now}"))

Contextual

Resolved per-session with a context value:

trait MyStatusCtx:
  def getStatus: IO[String]

val ctxRes: McpResource[IO, MyStatusCtx] =
  contextualResource[MyStatusCtx]
    .uri("app://status")
    .name("Server Status")
    .read(ctx => ctx.getStatus)

The factory methods McpResource.static[IO](...) and McpResource.dynamic[IO](...) are equivalent to the fluent forms above and remain available.

Resource template creation

Resource templates use the same fluent entry point (resourceTemplate) but with a .path DSL that builds typed parameter extraction. Segments combine with *> / <* and named segments are extracted:

val itemTemplate: ResourceTemplate[IO, Unit] =
  resourceTemplate
    .path(path.static("app://items/") *> path.named("id"))
    .name("Item by ID")
    .description("Look up a single item by its ID")
    .mimeType("application/json")
    .read { id =>
      lookupItem(id).map(item =>
        ResourceContent.text(s"app://items/$id", item.toJson, Some("application/json"))
      )
    }

Multi-parameter templates combine named segments with .tupled:

val noteTemplate: ResourceTemplate[IO, Unit] =
  resourceTemplate
    .path(
      path.static("app://users/") *>
        (path.named("user"), path.static("/notes/") *> path.named("note")).tupled
    )
    .name("Note by User & ID")
    .read { case (user, note) => readNote(user, note) }