Code Samples
HTTP + MCP App
One application can expose the same business logic to HTTP clients and AI agents. This example gives an HTTP resource and an MCP endpoint the same GreetingService instance, then runs both listeners under one Soklet lifecycle.
You need only JDK 21+ and soklet-4.0.0.jar. The HTTP API listens on 127.0.0.1:18080; MCP listens on 127.0.0.1:18082.
Create the project
Download the JAR into an empty directory and create these four source files:
http-and-mcp/
soklet-4.0.0.jar
src/example/shared/
GreetingService.java
GreetingResource.java
GreetingsEndpoint.java
App.java
http-and-mcp/
soklet-4.0.0.jar
src/example/shared/
GreetingService.java
GreetingResource.java
GreetingsEndpoint.java
App.java
Share the service
Save this as src/example/shared/GreetingService.java:
package example.shared;
public final class GreetingService {
// Shared for this example. In a larger application,
// inject this service into both handlers.
public static final GreetingService INSTANCE = new GreetingService();
public record Greeting(String message) {}
public Greeting greet(String name) {
return new Greeting("Hello, " + name + "!");
}
}
package example.shared;
public final class GreetingService {
// Shared for this example. In a larger application,
// inject this service into both handlers.
public static final GreetingService INSTANCE = new GreetingService();
public record Greeting(String message) {}
public Greeting greet(String name) {
return new Greeting("Hello, " + name + "!");
}
}
The service has no Soklet dependency. Both handlers below delegate to its greet method.
Expose an HTTP route
Save this as src/example/shared/GreetingResource.java:
package example.shared;
import com.soklet.annotation.GET;
import com.soklet.annotation.PathParameter;
public final class GreetingResource {
@GET("/greetings/{name}")
public String greet(@PathParameter String name) {
return GreetingService.INSTANCE.greet(name).message();
}
}
package example.shared;
import com.soklet.annotation.GET;
import com.soklet.annotation.PathParameter;
public final class GreetingResource {
@GET("/greetings/{name}")
public String greet(@PathParameter String name) {
return GreetingService.INSTANCE.greet(name).message();
}
}
Soklet binds the path parameter and writes the returned string as a plain-text HTTP response.
Expose an MCP tool
Save this as src/example/shared/GreetingsEndpoint.java:
package example.shared;
import com.soklet.McpProtocolVersion;
import com.soklet.annotation.McpServerEndpoint;
import com.soklet.annotation.McpTool;
import com.soklet.annotation.McpToolArgument;
@McpServerEndpoint(
path = "/greetings/mcp",
name = "greetings",
version = "1.0.0",
protocolVersions = {
McpProtocolVersion.V2026_07_28,
McpProtocolVersion.V2025_11_25
}
)
public final class GreetingsEndpoint {
@McpTool(
name = "greet",
description = "Greets someone by name",
protocolVersions = {
McpProtocolVersion.V2026_07_28,
McpProtocolVersion.V2025_11_25
}
)
public GreetingService.Greeting greet(
@McpToolArgument(name = "name") String name) {
return GreetingService.INSTANCE.greet(name);
}
}
package example.shared;
import com.soklet.McpProtocolVersion;
import com.soklet.annotation.McpServerEndpoint;
import com.soklet.annotation.McpTool;
import com.soklet.annotation.McpToolArgument;
@McpServerEndpoint(
path = "/greetings/mcp",
name = "greetings",
version = "1.0.0",
protocolVersions = {
McpProtocolVersion.V2026_07_28,
McpProtocolVersion.V2025_11_25
}
)
public final class GreetingsEndpoint {
@McpTool(
name = "greet",
description = "Greets someone by name",
protocolVersions = {
McpProtocolVersion.V2026_07_28,
McpProtocolVersion.V2025_11_25
}
)
public GreetingService.Greeting greet(
@McpToolArgument(name = "name") String name) {
return GreetingService.INSTANCE.greet(name);
}
}
Soklet derives the MCP input and output schemas from the Java types. The service's record becomes the tool's structured output, without a separate JSON library.
Wire both listeners
Save this as src/example/shared/App.java:
package example.shared;
import com.soklet.HttpServer;
import com.soklet.McpServer;
import com.soklet.Soklet;
import com.soklet.SokletConfig;
public final class App {
public static void main(String[] args) throws InterruptedException {
SokletConfig config = SokletConfig.withHttpServer(
HttpServer.withPort(18080).host("127.0.0.1").build()
).mcpServer(
McpServer.withPort(18082).host("127.0.0.1").build()
).build();
System.out.println("HTTP: http://127.0.0.1:18080/greetings/Ada");
System.out.println("MCP: http://127.0.0.1:18082/greetings/mcp");
Soklet soklet = Soklet.run(config);
soklet.awaitShutdown();
}
}
package example.shared;
import com.soklet.HttpServer;
import com.soklet.McpServer;
import com.soklet.Soklet;
import com.soklet.SokletConfig;
public final class App {
public static void main(String[] args) throws InterruptedException {
SokletConfig config = SokletConfig.withHttpServer(
HttpServer.withPort(18080).host("127.0.0.1").build()
).mcpServer(
McpServer.withPort(18082).host("127.0.0.1").build()
).build();
System.out.println("HTTP: http://127.0.0.1:18080/greetings/Ada");
System.out.println("MCP: http://127.0.0.1:18082/greetings/mcp");
Soklet soklet = Soklet.run(config);
soklet.awaitShutdown();
}
}
Soklet's default instance provider constructs the handlers using their no-argument constructors. Both use the same static service, which has no mutable state and can handle concurrent requests. For constructor injection and integration with a dependency injection library, see Instance Creation.
Compile and run
From the project directory, compile all four source files:
javac -parameters -cp soklet-4.0.0.jar -processor com.soklet.SokletProcessor -d build src/example/shared/*.java
javac -parameters -cp soklet-4.0.0.jar -processor com.soklet.SokletProcessor -d build src/example/shared/*.java
-parameters preserves Java parameter names. -processor explicitly enables Soklet's annotation processor, which generates both the HTTP route index and MCP endpoint metadata.
Start the application:
java -cp "build:soklet-4.0.0.jar" example.shared.App
java -cp "build:soklet-4.0.0.jar" example.shared.App
On Windows, use "build;soklet-4.0.0.jar" for the runtime classpath. Keep the application terminal open while making the calls below. Use Ctrl+C in that terminal to request JVM shutdown and drain both listeners through their shared lifecycle.
Call the HTTP route
In a second terminal:
curl --fail-with-body --silent --show-error http://127.0.0.1:18080/greetings/Ada
curl --fail-with-body --silent --show-error http://127.0.0.1:18080/greetings/Ada
The response body is:
Hello, Ada!
Hello, Ada!
Call the MCP tool
Use the same name through MCP:
curl --fail-with-body --silent --show-error \
http://127.0.0.1:18082/greetings/mcp \
--header 'Content-Type: application/json' \
--header 'Accept: application/json, text/event-stream' \
--header 'MCP-Protocol-Version: 2026-07-28' \
--header 'Mcp-Method: tools/call' \
--header 'Mcp-Name: greet' \
--data '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "greet",
"arguments": {"name": "Ada"},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}'
curl --fail-with-body --silent --show-error \
http://127.0.0.1:18082/greetings/mcp \
--header 'Content-Type: application/json' \
--header 'Accept: application/json, text/event-stream' \
--header 'MCP-Protocol-Version: 2026-07-28' \
--header 'Mcp-Method: tools/call' \
--header 'Mcp-Name: greet' \
--data '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "greet",
"arguments": {"name": "Ada"},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}'
The JSON-RPC response's result.structuredContent contains:
{"message":"Hello, Ada!"}
{"message":"Hello, Ada!"}
The HTTP and MCP handlers adapt the same service result to their respective transports. The modern MCP call needs no initialization handshake. The endpoint also serves 2025-11-25; use the VS Code configuration from the MCP quickstart with its url changed to http://127.0.0.1:18082/greetings/mcp.
Next steps
This is an anonymous local example with no application rate quota. For remote deployment, configure MCP admission and policy, HTTP authentication and authorization, and production deployment settings.
Use the same source files in an existing Maven or Gradle project with the Building and Running configuration. The Toy Store App develops this shared-service pattern further with dependency injection, authentication, localization, and database access.

