Soklet Logo

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

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 + "!");
  }
}

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();
  }
}

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);
  }
}

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();
  }
}

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

-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

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

The response body is:

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": {}
      }
    }
  }'

The JSON-RPC response's result.structuredContent contains:

{"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.

Previous
Barebones App