Reference: MCP SDK
proInstallation (Gradle)
dependencies {
implementation(platform("org.http4k:http4k-bom:6.36.0.0"))
// for general MCP server development
implementation("org.http4k.pro:http4k-ai-mcp-sdk")
// if you want to connect to an MCP server
implementation("org.http4k.pro:http4k-ai-mcp-client")
// if you want to test MCP servers or MCP Apps
implementation("org.http4k.pro:http4k-ai-mcp-testing")
// for x402 payment-protected MCP tools
implementation("org.http4k.pro:http4k-ai-mcp-x402")
// for MPP payment-protected MCP tools
implementation("org.http4k.pro:http4k-ai-mcp-mpp")
}
Try it out!
Want to see http4k MCP in action? Visit the http4k Toolbox where you can download and install a fully working MCP app that integrates directly with Claude Desktop and other MCP clients.
About
The Model Context Protocol is an open standard created by Anthropic that defines how apps can feed information to AI language models. It creates a uniform way to link these models with various data sources and tools, which streamlines the integration process. MCP services can be deployed in a Server or Serverless environment.
MCP itself is based on the JSON RPC standard, which is used to communicate between the client and server. Messages are sent from client to server and then asynchronously from server to client. MCP defines a set of standardised capabilities which can be provided by the server or client. One use of these capabilities is to allow pre-trained models to have access to both live data and APIs that can be used by the model to provide answers to user requests. Another use is to provide agentic behaviour by providing standard communications between several MCP entities.
Currently the MCP standard supports the following transports:
- HTTP Streaming: Clients can interact with the MCP server by either a SSE connection (accepting
application/event-streamor via plain HTTP (acceptingapplication/json). Stream resumption and replay is supported on the SSE connection by calling GET with theLast-Event-IDheader. All traffic is served by calling the/mcpendpoint. - Server Sent Events + HTTP: Clients initiate an SSE connection to the server (on
/sse) which is used to send messages to the client asynchronously at any time. Further client -> server requests and notifications are sent via HTTP/messagesendpoint, with responses being sent back via the SSE. - Standard IO: Clients start a process that communicates with the server via JSON RPC messages via standard input and output streams.
The MCP capabilities include:
- Tools: are exposed by the server and can be used by the client to perform tasks. The tool consists of a description and a JSON Schema definition for the inputs and outputs.
- Prompts: given a set of inputs by a client, the server can generate a prompt parameterised to those inputs. This allows servers to generate prompts that are tailored to the client’s data.
- Resources: are exposed by the server and can be used by the client to access text or binary content an example of this is a browser tool that can access web pages.
- Roots: the client supplies a list of file roots to the server which can be used to resolve file paths.
- Completions: The server provides auto-completion of options for a particular Prompt or Resource.
- Sampling: An MCP server can request an LLN completion for text or binary content a connected Client.
- Elicitation: The server can request additional information from the user by rendering dynamic forms based on a supplied schema.
- Tasks: The server can create and manage long-running asynchronous operations with progress tracking.
Supported Protocol Versions
| Protocol | Version | Description |
|---|---|---|
| MCP | 2025-11-25 + Draft | Core protocol with Tasks, Elicitation, and Sampling support |
| MCP Apps | 2026-01-26 | Server-rendered UI components for Claude Desktop and other MCP clients |
http4k ❤️ Model Context Protocol
http4k provides very good support for the Model Context Protocol, and has been designed to make it easy to build your own MCP-compliant servers in Kotlin, using the familiar http4k methodology of simple and composable functional protocols. Each of the capabilities is modelled as a binding between a capability description and a function that exposes the capability. See Capability Types for more details.
The MCP support in http4k consists of two parts - the http4k-ai-mcp-sdk and
the http4k-mcp-desktop application which is used to connect the MCP server to
a desktop client such as Claude Desktop.
SDK: http4k-ai-mcp-sdk
The core SDK for working with the Model Context Protocol. You can build your own MCP-compliant applications using this module by plugging in capabilities into the server. The http4k-ai-mcp-sdk module provides a simple way to create either HTTP Streaming, SSE, StdIo or Websocket based servers. For StdIo-based servers, we recommend compiling your server to GraalVM for ease of distribution.
Capability Types
The MCP protocol is based on a set of capabilities that can be provided by the server or client. Each capability can be installed separately into the server, and the client can interact with the server using these capabilities.
Additional, when using one of the Streaming protocols, a Client object is passed to the Capability handler through the
request, allowing the server to send messages back to the client. These calls can be blocking or non-blocking, depending
on the client capability in question. These work most effectively when the client sends a Progress Token to the
server, which can be used to identify the operation being progressed.
Server Capability: Tools
Tools allow external MCP clients such as LLMs to request the server to perform bespoke functionality such as invoking an
API. The Tool capability is modelled as a function typealias ToolHandler = (ToolRequest) -> ToolResponse, filtered
with a ToolFilter, and can be
bound to a tool definition which describes it’s arguments and outputs using the http4k Lens system:
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.ToolFilter
import org.http4k.ai.mcp.ToolHandler
import org.http4k.ai.mcp.ToolRequest
import org.http4k.ai.mcp.ToolResponse
import org.http4k.ai.mcp.model.Content
import org.http4k.ai.mcp.model.Tool
import org.http4k.ai.mcp.model.localDate
import org.http4k.ai.mcp.then
import org.http4k.lens.with
import java.time.LocalDate
// tool argument inputs are typesafe lens
val toolArg = Tool.Arg.localDate().required("date", "date in format yyyy-mm-dd")
// the description of the tool exposed to clients
fun toolDefinitionFor(name: String): Tool = Tool(
"diary_for_${name.replace(" ", "_")}",
"details $name's diary appointments. Responds with a list of appointments for the given month",
toolArg,
)
// handles the actual call to tht tool
val diaryToolHandler: ToolHandler = {
val calendarData = mapOf(
LocalDate.of(2025, 3, 21) to listOf(
"08:00 - Breakfast meeting",
"11:00 - Dentist appointment",
"16:00 - Project review"
)
)
val date = toolArg(it)
val appointmentContent = calendarData[date]?.map { Content.Text("$date: $it") } ?: emptyList()
ToolResponse.Ok(appointmentContent)
}
// use a Filter to perform logging/tracing/metrics
val loggingTool = ToolFilter { next ->
{
println("Called with: $it")
val response = next(it)
println("Result was: $it")
response
}
}.then(diaryToolHandler)
object DiaryTool {
@JvmStatic
fun main() = println(
// invoke/test the tool offline - just invoke it like a function
loggingTool(
ToolRequest().with(Tool.Arg.localDate().required("date") of LocalDate.parse("2025-03-21"))
)
)
}
Complex Tools request arguments
The http4k MCP SDK also supports handling of complex arguments in the request (and response - MCP draft). This can be
done by using the auto() extension function and passing an example argument instance in order that the complex JSON
schema can be rendered. Note that the Kotlin Reflection JAR also needs to be present on the classpath to take advantage
of this feature, or you can supply a custom instance of ConfigurableMcpJson (Moshi-based) to work without reflection (
we recommend the use of the Kotshi compiler plugin to generate adapters for this
use-case).
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.ToolResponse
import org.http4k.ai.mcp.model.Tool
import org.http4k.ai.mcp.util.McpJson.auto
import org.http4k.lens.with
import org.http4k.routing.bind
// a complex response object
data class MavenJar(val org: String, val name: String, val version: Int)
// the auto() method is imported from McpJson (requires Kotlin Reflect)
val libDescription = Tool.Arg
.auto(MavenJar("org.http4k", "http4k-ai-mcp-sdk", 6))
.required("the maven dependency")
// the auto() method is imported from McpJson (requires Kotlin Reflect)
val nextVersion = Tool.Output.auto(MavenJar("org.http4k", "http4k-ai-mcp-sdk", 6)).toLens()
val getNextVersion = Tool(
"nextVersion",
"get the next maven version for a library",
libDescription,
output = nextVersion
)
object MavenTool {
@JvmStatic
fun main() = println(
getNextVersion bind {
// we can extract the class automatically using the lens
val lib: MavenJar = libDescription(it)
// and then inject the typesafe response object!
ToolResponse.Ok().with(nextVersion of lib.copy(version = lib.version + 1))
}
)
}
Tool Argument Types Reference
The Tool.Arg object provides type-safe builders for all common argument types:
| Type | Builder | Description |
|---|---|---|
String | Tool.Arg.string() | Basic string |
String | Tool.Arg.nonEmptyString() | Non-empty string |
String | Tool.Arg.nonBlankString() | Non-blank string |
Boolean | Tool.Arg.boolean() | Boolean |
Int | Tool.Arg.int() | Integer |
Long | Tool.Arg.long() | Long integer |
Double | Tool.Arg.double() | Floating point |
Float | Tool.Arg.float() | Float |
UUID | Tool.Arg.uuid() | UUID |
URI | Tool.Arg.uri() | URI |
Instant | Tool.Arg.instant() | Timestamp |
Duration | Tool.Arg.duration() | Duration |
Period | Tool.Arg.period() | Period |
LocalDate | Tool.Arg.localDate() | Date (ISO format) |
LocalTime | Tool.Arg.localTime() | Time |
ZonedDateTime | Tool.Arg.zonedDateTime() | Zoned datetime |
OffsetDateTime | Tool.Arg.offsetDateTime() | Offset datetime |
YearMonth | Tool.Arg.yearMonth() | Year-month |
ZoneId | Tool.Arg.zoneId() | Timezone |
Locale | Tool.Arg.locale() | Locale |
Enum<T> | Tool.Arg.enum<T>() | Enum values |
Value<T> | Tool.Arg.value(factory) | values4k types |
Regex | Tool.Arg.regexObject() | Regex pattern |
Base64 | Tool.Arg.base64() | Base64 encoded |
Server Capability: Completions
Completions give the server to standard autocomplete abilities based on partial input from a client. The Completion
capability is modelled as a
function typealias CompletionHandler = (CompletionRequest) -> CompletionResponse, filtered with a CompletionFilter,
and can be bound to a prompt definition which describes it’s arguments
using the http4k Lens system.
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.CompletionFilter
import org.http4k.ai.mcp.CompletionHandler
import org.http4k.ai.mcp.CompletionRequest
import org.http4k.ai.mcp.CompletionResponse
import org.http4k.ai.mcp.model.Reference
import org.http4k.ai.mcp.then
// the reference of the completion
val promptReference = Reference.Prompt("Greet")
// this function provides completion options for the "Greet" prompt, returning
// a list of all users whose names do not contain the letters already typed
val completionHandler: CompletionHandler = {
val allUsers = listOf("Alice", "Alex", "Albert", "Bob", "Charlie", "David")
val prefix = it.argument.value
CompletionResponse(allUsers.filter { it.startsWith(prefix) })
}
// use a Filter to perform logging/tracing/metrics
val loggingCompletion = CompletionFilter { next ->
{
println("Called with: $it")
val response = next(it)
println("Result was: $it")
response
}
}.then(completionHandler)
object ProvideCompletionOptionsForPrompt {
@JvmStatic
fun main() = println(
// invoke/test the completion offline - just invoke it like a function
loggingCompletion(
CompletionRequest("prefix", "Al")
)
)
}
Server Capability: Prompts
Prompts allow the server to generate a prompt based on the client’s inputs. The Prompt capability is modelled as a
function typealias PromptHandler = (PromptRequest) -> PromptResponse, filtered with a PromptFilter, and can be
bound to a prompt definition which describes it’s arguments
using the http4k Lens system.
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.CompletionFilter
import org.http4k.ai.mcp.PromptFilter
import org.http4k.ai.mcp.PromptHandler
import org.http4k.ai.mcp.PromptRequest
import org.http4k.ai.mcp.PromptResponse
import org.http4k.ai.mcp.model.Content
import org.http4k.ai.mcp.model.Message
import org.http4k.ai.mcp.model.Prompt
import org.http4k.ai.mcp.model.PromptName
import org.http4k.ai.mcp.then
import org.http4k.ai.model.Role.Companion.Assistant
import org.http4k.lens.int
import org.http4k.lens.with
// argument lenses for the prompt
val name = Prompt.Arg.required("name", "the name of the person to greet")
val age = Prompt.Arg.int().optional("age", "the age of the person to greet")
// the description of the prompt
val prompt: Prompt = Prompt(PromptName.of("Greet"), "Creates a greeting message for a person", name, age)
// handles the actual call to tht prompt
val greetingPromptHandler: PromptHandler = { req: PromptRequest ->
val content = when (age(req)) {
null -> Content.Text("Hello, ${name(req)}!")
else -> Content.Text("Hello, ${name(req)}! How is req being ${age(req)}?")
}
PromptResponse(listOf(Message(Assistant, content)))
}
// use a Filter to perform logging/tracing/metrics
val loggingPrompt = PromptFilter { next ->
{
println("Called with: $it")
val response = next(it)
println("Result was: $it")
response
}
}.then(greetingPromptHandler)
object GreetPersonPrompt {
@JvmStatic
fun main() = println(
// invoke/test the prompt offline - just invoke it like a function
loggingPrompt(
PromptRequest().with(
name of "David",
age of 30
)
)
)
}
Server Capability: Resources
Resources provide a way to interrogate the contents of data sources such as filesystem, database or website. The
Resource capability is modelled as a function typealias ResourceHandler = (ResourceRequest) -> ResourceResponse,
filtered with a ResourceFilter. Resources can be static or templated to provide bounds within which the client can
interact with the resource.
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.ResourceFilter
import org.http4k.ai.mcp.ResourceHandler
import org.http4k.ai.mcp.ResourceRequest
import org.http4k.ai.mcp.ResourceResponse
import org.http4k.ai.mcp.model.Resource
import org.http4k.ai.mcp.model.ResourceName
import org.http4k.ai.mcp.then
import org.http4k.client.JavaHttpClient
import org.http4k.core.Method.GET
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Uri
import org.jsoup.Jsoup
val websiteResource = Resource.Static(Uri.of("https://http4k.org"), ResourceName.of("HTTP4K"), "description")
// this function provides a static resource that contains all the links from the http4k website
val getLinksResourceHandler: ResourceHandler = {
val htmlPage = JavaHttpClient()(Request(GET, it.uri))
val links = getAllLinksFrom(htmlPage)
.map { Resource.Content.Text(it.text(), Uri.of(it.attr("href"))) }
ResourceResponse(links)
}
// use a Filter to perform logging/tracing/metrics
val loggingResource = ResourceFilter { next ->
{
println("Called with: $it")
val response = next(it)
println("Result was: $it")
response
}
}.then(getLinksResourceHandler)
private fun getAllLinksFrom(htmlPage: Response) = Jsoup.parse(htmlPage.bodyString())
.allElements.toList()
.filter { it.tagName() == "a" }
.filter { it.hasAttr("href") }
object LookupAllLinksFromWebResource {
@JvmStatic
fun main() = println(
// invoke/test the prompt offline - just invoke it like a function
loggingResource(ResourceRequest(Uri.of("https://http4k.org")))
)
}
Templated Resources
Resources can also be templated using RFC 6570 URI templates, allowing clients to access resources matching a pattern:
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.ResourceHandler
import org.http4k.ai.mcp.ResourceResponse
import org.http4k.ai.mcp.model.Resource
import org.http4k.routing.bind
import java.io.File
// Define a templated resource using RFC 6570 URI templates
val templatedFileResource = Resource.Templated(
uriTemplate = "file://{+path}", // {+path} allows slashes in the path
name = "files",
description = "Access files by path"
)
// Handler extracts the path from the URI and reads the file
val templatedFileResourceHandler: ResourceHandler = { request ->
val path = request.uri.toString().substringAfter("file://")
val file = File("/data", path)
when {
file.exists() && file.isFile -> ResourceResponse(
Resource.Content.Text(file.readText(), request.uri)
)
else -> throw IllegalArgumentException("File not found: $path")
}
}
// Bind the templated resource to its handler
val boundTemplatedFileResource = templatedFileResource bind templatedFileResourceHandler
Directory Resources
For file-based resources, http4k provides DirectoryResources which automatically exposes a directory as browsable resources:
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.server.capability.DirectoryResources
import org.http4k.ai.mcp.server.capability.RecursionMode.Flat
import org.http4k.ai.mcp.server.capability.RecursionMode.Recursive
import java.io.File
object DirectoryResourcesExample {
@JvmStatic
fun main() {
// Expose a directory as browsable MCP resources
// DirectoryResources implements the Resources interface
val recursiveResources = DirectoryResources(
dir = File("/path/to/documents"),
recursive = Recursive // walks subdirectories
)
val flatResources = DirectoryResources(
dir = File("/path/to/documents"),
recursive = Flat // only top-level files
)
// DirectoryResources provides:
// - listResources() - Lists all files in the directory
// - listTemplates() - Returns URI templates for dynamic access
// - read() - Reads file content by URI
// Pass to McpProtocol constructor for use with custom server setup,
// or use individual Resource.Static bindings for simpler cases
}
}
Server Capability: Reporting Progress
The Progress capability allows the server to report progress of a long-running operation to the client through the
progress() call.
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.CompletionHandler
import org.http4k.ai.mcp.CompletionRequest
import org.http4k.ai.mcp.CompletionResponse
val progress: CompletionHandler = { req ->
req.client.progress(50, 100.0, "half way done")
val allUsers = listOf("Alice", "Alex", "Albert", "Bob", "Charlie", "David")
val prefix = req.argument.value
CompletionResponse(allUsers.filter { it.startsWith(prefix) })
}
Client Capability: Sampling
Sampling allow the server to request information about the client’s request from it’s connected LLM, passing context.
Note that in order for this to work, a ProgressToken must be present in the request, which is used to identify the
operation being progressed.
package content.ecosystem.ai.reference.mcp
import dev.forkhandles.result4k.Failure
import dev.forkhandles.result4k.Result
import dev.forkhandles.result4k.Success
import dev.forkhandles.result4k.allValues
import dev.forkhandles.result4k.map
import org.http4k.ai.mcp.McpError
import org.http4k.ai.mcp.SamplingRequest
import org.http4k.ai.mcp.SamplingResponse
import org.http4k.ai.mcp.ToolHandler
import org.http4k.ai.mcp.ToolResponse.Error
import org.http4k.ai.mcp.ToolResponse.Ok
import org.http4k.ai.mcp.model.Content
import org.http4k.ai.mcp.model.Content.Text
import org.http4k.ai.mcp.model.Message
import org.http4k.ai.model.MaxTokens
import org.http4k.ai.model.Role.Companion.User
val request = SamplingRequest(listOf(Message(User, Text("Roast this tool!"))), MaxTokens.of(1000))
val roastingToolWithSampling: ToolHandler = { req ->
// at this point, the client will pass context to the model, and return a list of responses
val allContent: Result<String, McpError> = req.client.sample(request)
// a list of results is returned, so we can use `allValues` to get all content
.allValues()
.map { responses: List<SamplingResponse> ->
// collect the text from each response
responses
.filterIsInstance<SamplingResponse.Ok>()
.flatMap { it.content }
.filterIsInstance<Content.Text>()
.joinToString("")
}
when (allContent) {
is Success<String> -> Ok(listOf(Text(allContent.value)))
is Failure<McpError> -> Error("Failure sampling: ${allContent.reason}")
}
}
Client Capability: Elicitation
Elicitation allow the server to request additional information from the user in order to complete a task, by rendering a
dynamic form based on a supplied schema. You can use the passed Client to send the elicitation request to the client
and then wait for a response. Note that in order for this to work, a ProgressToken must be present in the request,
which is used to identify the operation being progressed.
A user has the option to accept, decline or cancel the elicitation request, and the server can handle these responses accordingly. .
package content.ecosystem.ai.reference.mcp
import dev.forkhandles.result4k.map
import dev.forkhandles.result4k.recover
import org.http4k.ai.mcp.ElicitationRequest
import org.http4k.ai.mcp.ElicitationResponse
import org.http4k.ai.mcp.ToolHandler
import org.http4k.ai.mcp.ToolResponse.Error
import org.http4k.ai.mcp.ToolResponse.Ok
import org.http4k.ai.mcp.model.Elicitation
import org.http4k.ai.mcp.model.Elicitation.Metadata.string.MaxLength
import org.http4k.ai.mcp.model.Elicitation.Metadata.string.MinLength
import org.http4k.ai.mcp.model.ElicitationAction.accept
import org.http4k.ai.mcp.model.int
import org.http4k.ai.mcp.model.string
val userName =
Elicitation.string().required("name", "What is your name?", "The user's name", MinLength(1), MaxLength(10))
val userAge =
Elicitation.int().required("age", "How old are you?", "The user's age", Elicitation.Metadata.integer.Min(18))
val greetingToolWithElicitation: ToolHandler = { req ->
val request = ElicitationRequest.Form("Please fill in your details", userName, userAge)
// at this point, the client will render
req.client.elicit(request)
.map {
when (it) {
is ElicitationResponse.Ok -> when (it.action) {
accept -> Ok("hello ${(userAge(it))}, when you are twice your age you will be ${2 * userAge(it)}!")
else -> Ok("hello stranger!")
}
is ElicitationResponse.Task -> error("not supported in this example")
}
}
.recover { Error("error: $it") }
}
Capability Packs: Composed MCP Capabilities
http4k MCP lets you combine any number of related capabilities into reusable collections using the CapabilityPack API.
This is perfect for organizing related tools, resources, or prompts that logically belong together and shipping them as
a module or library.
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.server.capability.CapabilityPack
import org.http4k.routing.bind
fun SetOfCapabilities() = CapabilityPack(
toolDefinitionFor("David") bind diaryToolHandler,
promptReference bind completionHandler,
websiteResource bind getLinksResourceHandler,
prompt bind greetingPromptHandler
)
x402 Payment-Protected Tools
The http4k-ai-mcp-x402 module integrates the x402 protocol with MCP, allowing you to require cryptocurrency payments for individual tool calls. Payment information is exchanged via the MCP _meta field on tool requests and responses.
The module provides two filters:
X402ToolFilter- AToolFilterthat wraps individual tools with payment requirements. Payment payloads are sent in_meta["x402/payment"]and settlement receipts are returned in_meta["x402/payment-response"]. Returns structuredPaymentRequirederrors when payment is missing or invalid.McpFilters.X402PaymentRequired- A lower-levelMcpFilterthat operates on raw MCP JSON-RPC requests for protocol-level payment gating.
Both filters use a PaymentCheck function to determine whether a request is Free or Required, allowing you to mix free and paid tools in the same server:
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.ToolResponse.Ok
import org.http4k.ai.mcp.model.Content
import org.http4k.ai.mcp.model.McpEntity
import org.http4k.ai.mcp.model.Tool
import org.http4k.ai.mcp.protocol.ServerMetaData
import org.http4k.ai.mcp.protocol.Version
import org.http4k.ai.mcp.server.capability.then
import org.http4k.ai.mcp.server.security.NoMcpSecurity
import org.http4k.ai.mcp.x402.PaymentCheck
import org.http4k.ai.mcp.x402.X402ToolFilter
import org.http4k.connect.x402.X402Facilitator
import org.http4k.connect.x402.Http
import org.http4k.connect.x402.model.AssetAddress
import org.http4k.connect.x402.model.PaymentAmount
import org.http4k.connect.x402.model.PaymentNetwork
import org.http4k.connect.x402.model.PaymentRequirements
import org.http4k.connect.x402.model.PaymentScheme
import org.http4k.connect.x402.model.WalletAddress
import org.http4k.core.Uri
import org.http4k.routing.bind
import org.http4k.routing.mcp
import org.http4k.server.JettyLoom
import org.http4k.server.asServer
fun `x402 mcp tool example`() {
val requirements = PaymentRequirements(
scheme = PaymentScheme.of("exact"),
network = PaymentNetwork.of("base-sepolia"),
asset = AssetAddress.of("0x036CbD53842c5426634e7929541eC2318f3dCF7e"),
amount = PaymentAmount.of("100"),
payTo = WalletAddress.of("0x1234567890abcdef1234567890abcdef12345678"),
maxTimeoutSeconds = 60
)
val facilitator = X402Facilitator.Http(Uri.of("https://x402-facilitator.example.com"))
// create a ToolFilter that requires payment for all tools it wraps
val paymentFilter = X402ToolFilter(facilitator) { PaymentCheck.Required(listOf(requirements)) }
// wrap individual tools with the payment filter
val paidTool = paymentFilter.then(
Tool("premium_data", "get premium data (requires payment)") bind {
Ok(listOf(Content.Text("Here is your premium data!")))
}
)
// free tools can be mixed alongside paid tools
val freeTool = Tool("free_data", "get free data") bind {
Ok(listOf(Content.Text("Here is your free data!")))
}
mcp(
ServerMetaData(McpEntity.of("x402 mcp server"), Version.of("0.1.0")),
NoMcpSecurity,
paidTool, freeTool
).asServer(JettyLoom(3001)).start()
}
MPP Payment-Protected MCP
The http4k-ai-mcp-mpp module integrates the Machine Payments Protocol (MPP) with MCP, allowing you to require payments for MCP interactions. Unlike x402, MPP has no facilitator — you implement MppVerifier directly. Payment credentials are exchanged via the MCP _meta field on requests and responses.
The module provides two filters:
MppToolFilter- AToolFilterthat wraps individual tools with payment challenges. Credentials are sent in_meta["org.paymentauth/credential"]and receipts are returned in_meta["org.paymentauth/receipt"]. Returns structured errors with challenges when payment is missing or invalid.McpFilters.MppPaymentRequired- A lower-levelMcpFilterthat operates on raw MCP JSON-RPC requests, enabling payment gating on any MCP resource — tools, prompts, resources, or any other server capability.
Both filters use an MppPaymentCheck function to determine whether a request is Free or Required, allowing you to mix free and paid capabilities in the same server.
Use MppToolFilter to protect individual tools:
package content.ecosystem.ai.reference.mcp
import dev.forkhandles.result4k.Success
import org.http4k.ai.mcp.ToolResponse.Ok
import org.http4k.ai.mcp.model.Content
import org.http4k.ai.mcp.model.McpEntity
import org.http4k.ai.mcp.model.Tool
import org.http4k.ai.mcp.mpp.MppPaymentCheck
import org.http4k.ai.mcp.mpp.MppPayments
import org.http4k.ai.mcp.mpp.MppToolFilter
import org.http4k.ai.mcp.protocol.ServerMetaData
import org.http4k.ai.mcp.protocol.Version
import org.http4k.ai.mcp.protocol.withExtensions
import org.http4k.ai.mcp.server.capability.then
import org.http4k.ai.mcp.server.security.NoMcpSecurity
import org.http4k.connect.mpp.MppVerifier
import org.http4k.connect.mpp.model.Challenge
import org.http4k.connect.mpp.model.ChallengeId
import org.http4k.connect.mpp.model.ChargeRequest
import org.http4k.connect.mpp.model.Currency
import org.http4k.connect.mpp.model.PaymentAmount
import org.http4k.connect.mpp.model.PaymentIntent
import org.http4k.connect.mpp.model.PaymentMethod
import org.http4k.connect.mpp.model.Realm
import org.http4k.connect.mpp.model.Receipt
import org.http4k.connect.mpp.model.ReceiptStatus
import org.http4k.routing.bind
import org.http4k.routing.mcp
import org.http4k.server.JettyLoom
import org.http4k.server.asServer
import java.time.Instant
fun `mpp mcp tool example`() {
val method = PaymentMethod.of("stripe")
val intent = PaymentIntent.of("charge")
val challenge = Challenge(
id = ChallengeId.of("challenge-123"),
realm = Realm.of("api.example.com"),
method = method,
intent = intent,
request = ChargeRequest(
amount = PaymentAmount.of("100"),
currency = Currency.of("USD")
)
)
val verifier = MppVerifier { credential ->
Success(
Receipt(
status = ReceiptStatus.success,
method = credential.challenge.method,
timestamp = Instant.now(),
challengeId = credential.challenge.id
)
)
}
// create a ToolFilter that requires payment for all tools it wraps
val paymentFilter = MppToolFilter(verifier) { MppPaymentCheck.Required(listOf(challenge)) }
// wrap individual tools with the payment filter
val paidTool = paymentFilter.then(
Tool("premium_data", "get premium data (requires payment)") bind {
Ok(listOf(Content.Text("Here is your premium data!")))
}
)
// free tools can be mixed alongside paid tools
val freeTool = Tool("free_data", "get free data") bind {
Ok(listOf(Content.Text("Here is your free data!")))
}
mcp(
ServerMetaData(McpEntity.of("mpp mcp server"), Version.of("0.1.0"))
.withExtensions(MppPayments(methods = listOf(method), intents = listOf(intent))),
NoMcpSecurity,
paidTool, freeTool
).asServer(JettyLoom(3001)).start()
}
Use McpFilters.MppPaymentRequired to gate all MCP requests at the protocol level — tools, prompts, resources, and any other capability:
package content.ecosystem.ai.reference.mcp
import dev.forkhandles.result4k.Success
import org.http4k.ai.mcp.ToolResponse.Ok
import org.http4k.ai.mcp.model.Content
import org.http4k.ai.mcp.model.McpEntity
import org.http4k.ai.mcp.model.Tool
import org.http4k.ai.mcp.mpp.MppPaymentCheck
import org.http4k.ai.mcp.mpp.MppPayments
import org.http4k.ai.mcp.protocol.ServerMetaData
import org.http4k.ai.mcp.protocol.Version
import org.http4k.ai.mcp.protocol.withExtensions
import org.http4k.ai.mcp.server.security.NoMcpSecurity
import org.http4k.connect.mpp.MppVerifier
import org.http4k.connect.mpp.model.Challenge
import org.http4k.connect.mpp.model.ChallengeId
import org.http4k.connect.mpp.model.ChargeRequest
import org.http4k.connect.mpp.model.Currency
import org.http4k.connect.mpp.model.PaymentAmount
import org.http4k.connect.mpp.model.PaymentIntent
import org.http4k.connect.mpp.model.PaymentMethod
import org.http4k.connect.mpp.model.Realm
import org.http4k.connect.mpp.model.Receipt
import org.http4k.connect.mpp.model.ReceiptStatus
import org.http4k.ai.mcp.mpp.MppPaymentRequired
import org.http4k.filter.McpFilters
import org.http4k.routing.bind
import org.http4k.routing.mcp
import org.http4k.server.JettyLoom
import org.http4k.server.asServer
import java.time.Instant
fun `mpp mcp filter example`() {
val method = PaymentMethod.of("stripe")
val intent = PaymentIntent.of("charge")
val challenge = Challenge(
id = ChallengeId.of("challenge-123"),
realm = Realm.of("api.example.com"),
method = method,
intent = intent,
request = ChargeRequest(
amount = PaymentAmount.of("100"),
currency = Currency.of("USD")
)
)
val verifier = MppVerifier { credential ->
Success(
Receipt(
status = ReceiptStatus.success,
method = credential.challenge.method,
timestamp = Instant.now(),
challengeId = credential.challenge.id
)
)
}
// McpFilter gates ALL MCP requests — tools, prompts, resources, etc.
val mcpFilter = McpFilters.MppPaymentRequired(verifier) { MppPaymentCheck.Required(listOf(challenge)) }
val tool = Tool("premium_data", "get premium data") bind {
Ok(listOf(Content.Text("Here is your premium data!")))
}
mcp(
ServerMetaData(McpEntity.of("mpp mcp server"), Version.of("0.1.0"))
.withExtensions(MppPayments(methods = listOf(method), intents = listOf(intent))),
NoMcpSecurity,
tool,
mcpFilter = mcpFilter
).asServer(JettyLoom(3001)).start()
}
MCP Apps: Server-Rendered UI
MCP Apps combine Tool and Resource capabilities to enable server-rendered UI components. When an LLM calls the tool, the client displays the HTML content from the associated resource. This is useful for interactive dashboards, forms, and visualisations.
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.model.Domain
import org.http4k.ai.mcp.model.apps.Csp
import org.http4k.ai.mcp.model.apps.McpAppResourceMeta
import org.http4k.ai.mcp.model.apps.McpAppVisibility.app
import org.http4k.ai.mcp.model.apps.McpAppVisibility.model
import org.http4k.ai.mcp.model.apps.McpApps
import org.http4k.ai.mcp.protocol.ServerMetaData
import org.http4k.ai.mcp.protocol.withExtensions
import org.http4k.ai.mcp.server.capability.extension.RenderMcpApp
import org.http4k.ai.mcp.server.security.NoMcpSecurity
import org.http4k.core.Uri
import org.http4k.routing.mcp
import org.http4k.server.Helidon
import org.http4k.server.asServer
object McpAppExample {
@JvmStatic
fun main() {
// Create an MCP App that renders a dashboard UI
val dashboardApp = RenderMcpApp(
name = "show_dashboard",
description = "Display the analytics dashboard",
uri = Uri.of("ui://dashboard"),
meta = McpAppResourceMeta(
csp = Csp(connectDomains = listOf(Domain.of("https://api.example.com"))),
prefersBorder = true
),
visibility = listOf(model, app)
) { request ->
// Return HTML content based on the request
"""
<html>
<body>
<h1>Analytics Dashboard</h1>
<p>Requested: ${request.uri}</p>
</body>
</html>
""".trimIndent()
}
// Create the MCP server with Apps extension enabled
val server = mcp(
ServerMetaData("Dashboard App", "1.0.0").withExtensions(McpApps),
NoMcpSecurity,
dashboardApp
)
server.asServer(Helidon(3001)).start()
}
}
MCP Apps support metadata for security and permissions:
- Content Security Policy (CSP) - Control allowed domains for connections, resources, and frames
- Permissions - Request camera, microphone, geolocation, or clipboard access
- Visibility - Control whether the app is visible to the model, the app UI, or both
MCP Apps Host
The McpAppsHost provides a local HTTP server for testing and running multiple MCP apps. It creates endpoints for listing apps, reading resources, and calling tools.
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.apps.McpAppsHost
import org.http4k.ai.mcp.model.apps.McpApps
import org.http4k.ai.mcp.protocol.ServerMetaData
import org.http4k.ai.mcp.protocol.withExtensions
import org.http4k.ai.mcp.server.capability.extension.RenderMcpApp
import org.http4k.ai.mcp.server.security.NoMcpSecurity
import org.http4k.ai.mcp.testing.McpClientFactory
import org.http4k.core.Uri
import org.http4k.routing.mcp
import org.http4k.server.Helidon
import org.http4k.server.asServer
object McpAppsHostExample {
@JvmStatic
fun main() {
// Create MCP app servers
val dashboardApp = mcp(
ServerMetaData("Dashboard", "1.0.0").withExtensions(McpApps),
NoMcpSecurity,
RenderMcpApp("show_dashboard", "Show dashboard", Uri.of("ui://dash")) {
"<h1>Dashboard</h1>"
}
)
val settingsApp = mcp(
ServerMetaData("Settings", "1.0.0").withExtensions(McpApps),
NoMcpSecurity,
RenderMcpApp("show_settings", "Show settings", Uri.of("ui://settings")) {
"<h1>Settings</h1>"
}
)
// Create host with multiple apps
// Use McpClientFactory.Test for in-memory testing
// Use McpClientFactory.Http for remote servers
val host = McpAppsHost(
McpClientFactory.Test(dashboardApp),
McpClientFactory.Test(settingsApp)
)
// Start the host server - provides endpoints for listing and accessing apps
host.asServer(Helidon(8099)).start()
// The host provides these endpoints:
// GET / - List all available MCP apps
// GET /api/resources - Read UI resource content (?serverId=...&uri=...)
// POST /api/tools/call - Call a tool from an MCP app
}
}
MCP Servers
Servers are created by combining the configured MCP Protocol with a set of capabilities, an optional security, and a binding to a Server or Serverless backend. The server can be started using any of the http4k server backends which support SSE ( see servers).
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.model.McpEntity
import org.http4k.ai.mcp.protocol.ServerMetaData
import org.http4k.ai.mcp.protocol.ServerProtocolCapability.ToolsChanged
import org.http4k.ai.mcp.protocol.Version
import org.http4k.ai.mcp.server.security.OAuthMcpSecurity
import org.http4k.core.Uri
import org.http4k.routing.bind
import org.http4k.routing.mcp
import org.http4k.server.Helidon
import org.http4k.server.asServer
object ServerStreamingExample {
@JvmStatic
fun main() {
// call the correct protocol method here - there are 5 to choose from!
val mcpServer = mcp(
// give the server an identity
ServerMetaData(McpEntity.of("http4k MCP Server"), Version.of("1.0.0"), ToolsChanged),
// insert a security implementation
OAuthMcpSecurity(Uri.of("https://oauth-server"), Uri.of("https://mcp-server/mcp")) { it == "my_oauth_token" },
// bind server capabilities here ...
toolDefinitionFor("David") bind diaryToolHandler,
promptReference bind completionHandler,
websiteResource bind getLinksResourceHandler,
prompt bind greetingPromptHandler
)
// simply start it up!
mcpServer.asServer(Helidon(3002)).start()
}
}
Alternatively you can use any non-SSE supporting server backend and forego the SSE support in lieu of request/response via JSON:
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.model.McpEntity
import org.http4k.ai.mcp.protocol.ServerMetaData
import org.http4k.ai.mcp.protocol.ServerProtocolCapability.ToolsChanged
import org.http4k.ai.mcp.protocol.Version
import org.http4k.ai.mcp.server.security.NoMcpSecurity
import org.http4k.routing.bind
import org.http4k.routing.mcpHttpNonStreaming
import org.http4k.server.SunHttp
import org.http4k.server.asServer
object ServerNonstreamingExample {
@JvmStatic
fun main() {
// this protocol version does not support SSE connections.
val mcpServer = mcpHttpNonStreaming(
ServerMetaData(McpEntity.of("http4k MCP Server"), Version.of("1.0.0"), ToolsChanged),
NoMcpSecurity,
toolDefinitionFor("David") bind diaryToolHandler,
)
// simply start it up on any server you like!
mcpServer.asServer(SunHttp(3002)).start()
}
}
There are a number of different ways customise the MCP protocol server to suit your needs. Features that can be configured are shown below. Note that the main SDK library is designed for simplicity - and you may have to drill down one level to access some of these customisations:
- Security - Basic, Bearer, API Key or auto-discovered (or custom!) OAuth (specification standard)
- Session validation (via
SessionProvider) - Ensure that the client is authenticated to access the contents of the session - Event Store (via
SessionEventStore) - Store and resume MCP event streams using the SSE last-event-id header - Event Tracking (via
SessionEventTracking) - Assign a unique ID to each event to track the progress of the event stream - Origin validation (via
FilterandSseFilter) - Protect against DNS rebinding attacks by configuring allowed origins
Important: Protecting Against DNS Rebinding Attacks
When deploying an MCP server that uses HTTP Streaming or SSE, you must implement Origin header validation to prevent
DNS rebinding attacks. These attacks can allow malicious websites to interact with your MCP server by changing IP
addresses after initial DNS
resolution, potentially bypassing same-origin policy protections. This can be done by implementing the HTTP (Filter)
and SSE specific (SseFilter) filter implementations and attaching them to the Polyhandler that is returned from the
mcpXXX() call.
The http4k-ai-mcp-sdk provides protection mechanisms that can be applied to your server:
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.model.McpEntity
import org.http4k.ai.mcp.protocol.ServerMetaData
import org.http4k.ai.mcp.protocol.ServerProtocolCapability.ToolsChanged
import org.http4k.ai.mcp.protocol.Version
import org.http4k.ai.mcp.server.security.BearerAuthMcpSecurity
import org.http4k.core.Method.DELETE
import org.http4k.core.Method.GET
import org.http4k.core.Method.POST
import org.http4k.core.then
import org.http4k.filter.AnyOf
import org.http4k.filter.CorsAndRebindProtection
import org.http4k.filter.CorsPolicy
import org.http4k.filter.OriginPolicy
import org.http4k.filter.PolyFilters
import org.http4k.filter.ServerFilters
import org.http4k.routing.bind
import org.http4k.routing.mcp
import org.http4k.server.Helidon
import org.http4k.server.asServer
fun main() {
val mcpServer = mcp(
ServerMetaData(McpEntity.of("http4k MCP Server"), Version.of("1.0.0"), ToolsChanged),
BearerAuthMcpSecurity { it == "my_bearer_token" },
toolDefinitionFor("David") bind diaryToolHandler,
)
// Define a CORS policy to protect against cross-origin requests and DNS rebinding attacks
val corsPolicy = CorsPolicy(
OriginPolicy.AnyOf("foo.com", "localhost"),
listOf("allowed-header"), listOf(GET, POST, DELETE)
)
PolyFilters.CorsAndRebindProtection(corsPolicy)
.then(mcpServer)
.asServer(Helidon(3002)).start()
}
Serverless Example
MCP capabilities can be bound to http4k Serverless functions using the HTTP
protocol in non-streaming mode. To activate this simply bind them into the non-streaming HTTP which is a simple
HttpHandler.
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.model.McpEntity
import org.http4k.ai.mcp.protocol.ServerMetaData
import org.http4k.ai.mcp.protocol.Version
import org.http4k.ai.mcp.server.security.NoMcpSecurity
import org.http4k.routing.bind
import org.http4k.routing.mcpHttpNonStreaming
import org.http4k.serverless.ApiGatewayV2LambdaFunction
import org.http4k.serverless.AppLoader
// This function is an AWS Lambda function.
class McpLambdaFunction : ApiGatewayV2LambdaFunction(AppLoader {
mcpHttpNonStreaming(
ServerMetaData(McpEntity.of("http4k mcp over serverless"), Version.of("0.1.0")),
NoMcpSecurity,
toolDefinitionFor("David") bind diaryToolHandler
)
})
Alternative Transports
In addition to HTTP streaming, http4k MCP supports WebSocket and JSON-RPC transports:
WebSocket Server
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.ToolResponse
import org.http4k.ai.mcp.model.Content.Text
import org.http4k.ai.mcp.model.Tool
import org.http4k.ai.mcp.protocol.ServerMetaData
import org.http4k.ai.mcp.server.security.NoMcpSecurity
import org.http4k.routing.bind
import org.http4k.routing.mcpWebsocket
import org.http4k.server.Helidon
import org.http4k.server.asServer
object WebsocketServerExample {
@JvmStatic
fun main() {
// Create a WebSocket-based MCP server
// Provides full-duplex communication over a single connection
val server = mcpWebsocket(
ServerMetaData("WebSocket MCP Server", "1.0.0"),
NoMcpSecurity,
Tool("ping", "Ping the server") bind {
ToolResponse.Ok(Text("pong"))
}
)
// WebSocket endpoint is available at /ws
server.asServer(Helidon(3001)).start()
}
}
JSON-RPC Server
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.ToolResponse
import org.http4k.ai.mcp.model.Content.Text
import org.http4k.ai.mcp.model.Tool
import org.http4k.ai.mcp.protocol.ServerMetaData
import org.http4k.ai.mcp.server.security.NoMcpSecurity
import org.http4k.routing.bind
import org.http4k.routing.mcpJsonRpc
import org.http4k.server.SunHttp
import org.http4k.server.asServer
object JsonrpcServerExample {
@JvmStatic
fun main() {
// Create a pure JSON-RPC based MCP server
// Accepts standard JSON-RPC 2.0 messages
val server = mcpJsonRpc(
ServerMetaData("JSON-RPC MCP Server", "1.0.0"),
NoMcpSecurity,
Tool("echo", "Echo the input") bind {
ToolResponse.Ok(Text("Echo received"))
}
)
// JSON-RPC endpoint is available at /jsonrpc
server.asServer(SunHttp(3001)).start()
}
}
MCP Client
http4k provides client classes to connect to your MCP servers via HTTP, SSE, JSONRPC or Websockets. The clients take care of the initial MCP handshake and provide a simple API to send and receive messages to the capabilities, or to register for notifications with an MCP server.
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.CompletionRequest
import org.http4k.ai.mcp.ElicitationResponse
import org.http4k.ai.mcp.PromptRequest
import org.http4k.ai.mcp.ResourceRequest
import org.http4k.ai.mcp.SamplingResponse
import org.http4k.ai.mcp.ToolRequest
import org.http4k.ai.mcp.client.http.HttpStreamingMcpClient
import org.http4k.ai.mcp.model.Content.Text
import org.http4k.ai.mcp.model.ElicitationAction.accept
import org.http4k.ai.mcp.model.McpEntity
import org.http4k.ai.mcp.model.Prompt
import org.http4k.ai.mcp.model.PromptName
import org.http4k.ai.mcp.model.Reference
import org.http4k.ai.mcp.model.Tool
import org.http4k.ai.mcp.model.localDate
import org.http4k.ai.mcp.protocol.Version
import org.http4k.ai.model.ModelName
import org.http4k.ai.model.Role
import org.http4k.ai.model.ToolName
import org.http4k.client.JavaHttpClient
import org.http4k.core.BodyMode
import org.http4k.core.BodyMode.*
import org.http4k.core.Uri
import org.http4k.lens.int
import org.http4k.lens.with
import java.time.LocalDate
object ClientExample {
@JvmStatic
fun main() {
val client = HttpStreamingMcpClient(
Uri.of("http://localhost:3001/mcp"),
McpEntity.of("http4k MCP Client"), Version.of("1.0.0"),
JavaHttpClient(responseBodyMode = Stream)
)
println(
">>> Server handshake\n" +
client.start()
)
println(
">>> Tool list\n" +
client.tools().list()
)
println(
">>> Tool calling\n" +
client.tools().call(
ToolName.of("diary_for_David"),
ToolRequest().with(
Tool.Arg.localDate().required("date") of LocalDate.parse("2025-03-21")
)
)
)
println(
">>> Prompt list\n" +
client.prompts().list()
)
println(
">>> Prompt calling\n" +
client.prompts().get(
PromptName.of("Greet"),
PromptRequest().with(
Prompt.Arg.required("name") of "David",
Prompt.Arg.int().optional("age") of 30
)
)
)
println(
">>> Completions\n" +
client.completions().complete(
Reference.Prompt("Greet"),
CompletionRequest("prefix", "Al")
)
)
println(
">>> Resource list\n" +
client.resources().list()
)
println(
">>> Resource reading\n" +
client.resources().read(
ResourceRequest(Uri.of("https://http4k.org"))
)
)
client.sampling().onSampled {
println(">>> Sampled: $it")
sequenceOf(SamplingResponse.Ok(ModelName.of("gpt-4"), Role.Assistant, listOf(Text("Sampled: $it"))))
}
client.elicitations().onElicitation {
println(">>> Elicitation: $it")
ElicitationResponse.Ok(accept).with(userName of "David", userAge of 30)
}
client.close()
}
}
Testing MCP Servers
http4k provides an in-memory test client for testing MCP servers without network overhead. Use the testMcpClient() extension on any PolyHandler to create a test client.
package content.ecosystem.ai.reference.mcp
import org.http4k.ai.mcp.ResourceRequest
import org.http4k.ai.mcp.ResourceResponse
import org.http4k.ai.mcp.ToolRequest
import org.http4k.ai.mcp.ToolResponse
import org.http4k.ai.mcp.model.Content.Text
import org.http4k.ai.mcp.model.Resource
import org.http4k.ai.mcp.model.Tool
import org.http4k.ai.mcp.model.string
import org.http4k.ai.mcp.protocol.ServerMetaData
import org.http4k.ai.mcp.server.security.NoMcpSecurity
import org.http4k.ai.mcp.testing.testMcpClient
import org.http4k.ai.model.ToolName
import org.http4k.core.Uri
import org.http4k.lens.with
import org.http4k.routing.bind
import org.http4k.routing.mcp
object TestingExample {
@JvmStatic
fun main() {
val nameLens = Tool.Arg.string().required("name")
// Create your MCP server
val mcpServer = mcp(
ServerMetaData("Test Server", "1.0.0"),
NoMcpSecurity,
Tool("greet", "Greet someone", nameLens) bind { request ->
ToolResponse.Ok(Text("Hello, ${nameLens(request)}!"))
},
Resource.Static("file://readme", "readme", "The readme") bind { req ->
ResourceResponse(Resource.Content.Text("# Welcome", req.uri))
}
)
// Create in-memory test client - no network needed!
val client = mcpServer.testMcpClient()
client.start()
// Test tools - returns McpResult<List<McpTool>>
val toolsResult = client.tools().list()
println("Available tools: $toolsResult")
// Call a tool
val callResult = client.tools().call(
ToolName.of("greet"),
ToolRequest().with(nameLens of "World")
)
println("Tool result: $callResult")
// Test resources - returns McpResult<List<McpResource>>
val resourcesResult = client.resources().list()
println("Available resources: $resourcesResult")
// Read a resource
val content = client.resources().read(ResourceRequest(Uri.of("file://readme")))
println("Resource content: $content")
// Test prompts - returns McpResult<List<McpPrompt>>
val promptsResult = client.prompts().list()
println("Available prompts: $promptsResult")
client.close()
}
}
For more control, use McpClientFactory which provides two modes:
McpClientFactory.Test(polyHandler)- In-memory client for unit testsMcpClientFactory.Http(serverUri)- HTTP client for integration tests
http4k-mcp-desktop
A desktop client that bridges StdIo-bound desktop clients such as Claude Desktop with your own MCP servers operating over HTTP/SSE, either locally or remotely. The desktop client is a simple native application that can be downloaded from the http4k GitHub, or built from the http4k source.
To use mcp-desktop client with clients such as Claude Desktop or Cursor:
- Download the
mcp-desktopbinary for your platform from: [https://github.com/http4k/mcp-desktop], or install it with brew:
brew tap http4k/tap
brew install http4k-mcp-desktop
- Configure Claude Desktop to use the
mcp-desktopbinary as an MCP server with the following configuration. You can find the configuration file inclaude_desktop_config.json, or by browsing through the developer settings menu. You can add as many MCP servers as you like. Note that Cursor users should use the--transport http-nonstreamor--transport jsonrpcoption for correct integration:
{
"mcpServers": {
"MyMcpServer": {
"command": "http4k-mcp-desktop",
// or path to the binary
"args": [
"--transport",
"--http-stream",
"--url",
"http://localhost:3001/mcp"
]
}
}
}
To build mcp-desktop from source:
- Clone the http4k MCP Desktop repo
- Install a GraalVM supporting JDK
- Run
./gradlew :native-compileto build the desktop client binary locally for your platform
