Welcome to mcp-tutorials, a collection of educational projects demonstrating various aspects of the Model Context Protocol (MCP) using the Kotlin SDK. Each "part" in this repository focuses on a specific feature or interaction pattern of MCP.
This first tutorial (located in the part1 module) showcases a generalized MCP client written in Kotlin that can connect to any STDIO-based MCP server. For demonstration purposes, it's designed to interact with the simple "greet" tool provided by the HelloWorldServer from our companion mcp-hello-world project.
This example is ideal for understanding:
- How to build a flexible MCP client capable of launching external servers as subprocesses.
- Dynamic discovery and interactive invocation of server-provided tools.
- Basic MCP client-server communication using standard I/O.
- What is MCP?
- Project Structure
- Prerequisites
- Building the Project
- Running Part 1
- How Part 1 Works
- Troubleshooting
- Further Learning
The Model Context Protocol (MCP) is an open-source standard for connecting AI applications to external systems. It provides a standardized way for AI applications (like LLMs) to access data sources, tools, and workflows, enabling them to retrieve information and perform tasks in the external world.
Think of MCP as a universal adapter for AI models, allowing them to extend their capabilities beyond their internal knowledge to interact with real-world systems.
This project has a multi-module Gradle setup. For this tutorial, we focus on the part1 module:
mcp-tutorials/
├── build.gradle.kts // Root Gradle configuration
├── settings.gradle.kts // Gradle settings for multi-module project
├── part1/ // The first tutorial module (our client)
│ ├── build.gradle.kts
│ └── src/main/kotlin/eu/torvian/mcp/tutorials/part1/
│ ├── Main.kt // Entry point for the generic client
│ ├── ServerConfig.kt // Data class for subprocess configuration
│ └── StdioMcpClient.kt // The generalized MCP client implementation
├── docs/ // (Future) Additional documentation specific to these tutorials
└── gradle/
├── libs.versions.toml // Version catalog for dependencies
└── wrapper/ // Gradle wrapper files
Note: The HelloWorldServer used in this tutorial is from a separate project. You will need to build its JAR separately.
- Java 17 or later: Required for running Kotlin applications on the JVM.
- Gradle: (Optional) If you don't use the provided Gradle wrapper, ensure you have Gradle installed.
- Basic understanding of Kotlin: Familiarity with Kotlin syntax and concepts will be helpful.
mcp-hello-world-server.jar: The server JAR from themcp-hello-worldproject. You can obtain it by cloning and building that repository:git clone https://github.com/rwachters/mcp-hello-world.git cd mcp-hello-world ./gradlew :server:jar # The server JAR will be at server/build/libs/mcp-hello-world-server.jar cd .. # Return to mcp-tutorials root
This project uses Gradle to build a "fat JAR" for the part1 client, which includes all necessary dependencies to run independently.
- Clone this repository:
git clone https://github.com/rwachters/mcp-tutorials.git cd mcp-tutorials - Build the
part1client JAR:This will produce./gradlew :part1:jar
part1/build/libs/part1.jar(or similar, depending on your build configuration).
To run part1, you will execute its client JAR and provide the command needed to launch an MCP server as an argument. For this tutorial, we'll use the HelloWorldServer JAR from the mcp-hello-world project.
- Ensure you have built the
part1client JAR (as per Building the Project). - Ensure you have the
mcp-hello-world-server.jar(as per Prerequisites). For simplicity in the example below, we assume you've copied it to the root of themcp-tutorialsproject (or you can provide its full path). - Execute the
part1client application:The client will start, launch the server as a child process, connect to it, and then enter an interactive loop. The combined output from both the client and its server subprocess will appear in the same terminal:# Assuming mcp-hello-world-server.jar is in the root of mcp-tutorials java -jar part1/build/libs/part1.jar java -jar mcp-hello-world-server.jarClient: Starting server process: java -jar mcp-hello-world-server.jar Client: Server process started with PID: 12345 Starting Hello World MCP Server... Client: Successfully connected to MCP server. Client: Discovered tools from server: greet --- Interactive Tool Caller --- Type a tool name to call it, or 'quit' to exit. > Enter tool name: greet > Enter value for 'name' (string, REQUIRED): Alice Client: Calling tool 'greet' with arguments: {name=Alice} Server: Called 'greet' with name='Alice'. Responding with: 'Hello, Alice!' Server Response: Hello, Alice! > Enter tool name: quit Client: MCP connection closed. Client: Server subprocess terminated.
You can use the part1 client with any STDIO-based MCP server by adjusting the command-line arguments. Remember to set any required environment variables in your shell before running the client.
-
Generic Docker Server:
# Set your API key in the shell first (e.g., export MY_API_KEY="your_secret") java -jar part1/build/libs/part1.jar docker run -i --rm -e MY_API_KEY my/mcp-server-image(Note:
-iis for interactive mode,-e MY_API_KEYtells Docker to pass the host'sMY_API_KEYenv var into the container.) -
UV Server Example:
# Set Python path or virtual environment activation in the shell if needed java -jar part1/build/libs/part1.jar uv python -m my_uv_mcp_server_module
-
mcp-hello-world-server.jar(frommcp-hello-worldproject):- This is a separate MCP server application.
- It initializes an
MCP Serverinstance with basic capabilities. - Defines a
Toolnamedgreetwith anameargument, using the MCP Kotlin SDK's schema definition. - Registers a lambda function for the
greettool that extracts thenameargument and returns a "Hello, [name]!"TextContentas aCallToolResult. - Connects to a
StdioServerTransport, which allows it to communicate via standard input/output streams with a client. - Important: Its initial
println("Starting...")message is written toSystem.errto avoid interfering with the client's MCP protocol parsing onSystem.out.
-
StdioMcpClient.kt(in thispart1module):- Initializes an
MCP Clientinstance. - In
connectToServer(), it launches the specified MCP server command (e.g.,java -jar mcp-hello-world-server.jar) as a separate child process usingProcessBuilder. - It then sets up a
StdioClientTransportto communicate with the server subprocess's standard I/O streams. Any environment variables set in the client's shell environment will be inherited by the server subprocess. - After connecting, it calls
mcp.listTools()to dynamically discover all tools offered by the server. - The
interactiveToolLoop()allows the user to input a tool name and, based on the tool'sinputSchema, prompts for necessary arguments. It then callsmcp.callTool()to execute the server's chosen tool. - The structured
CallToolResultfrom the server is then processed, and itsTextContent(or error messages) is printed to the console.
- Initializes an
-
build.gradle.kts(Fat JARs):- The Gradle build script for the
part1module is configured to create a "fat JAR". This means all dependencies (likekotlin-stdlib,kotlinx-coroutines-core, and the MCP Kotlin SDK itself) are bundled directly into thepart1.jarfile. This makes it easily runnable withjava -jar. - Special
excluderules are applied in thejartask to preventDuplicate entryerrors during the build, particularly forMETA-INFfiles andmodule-info.classfiles that often cause conflicts when merging JARs.
- The Gradle build script for the
NoClassDefFoundError: This typically means your JAR is not a "fat JAR" and is missing runtime dependencies. Ensure you're building with the fat JAR configuration inpart1/build.gradle.ktsand using./gradlew :part1:jar.Duplicate entryduring build: Check yourpart1/build.gradle.ktsjartask for thefrom(configurations.runtimeClasspath.get()...)block and ensure theexcluderules forMETA-INFfiles are present and correct.- Client fails to connect (hangs or errors):
- Ensure the command and arguments you provide to the client for launching the server are absolutely correct and fully specify how to start the target MCP server.
- Verify there are no unexpected issues starting the subprocess (e.g., permissions,
java,docker,uvcommands not in your system's PATH). - Crucially: The
StdioMcpClientexpects only valid MCP JSON-RPC messages on the server'sstdout. If the server prints any plain text (e.g., startup logs, warnings) tostdoutbefore or during the MCP handshake, the client's MCP parser will likely fail or hang. Ensure such output is directed tostderrby the server. (OurHelloWorldServerdoes this correctly by writing startup messages toSystem.err).
- SLF4J warnings:
SLF4J(W): No SLF4J providers were found.is a common warning. SLF4J is a logging facade. To remove the warning, you can add a logging implementation likeslf4j-simpleto yourpart1/build.gradle.ktsdependencies (e.g.,implementation("org.slf4j:slf4j-simple:2.0.13")). This is purely for logging output and doesn't affect the core MCP functionality.
- Model Context Protocol Documentation: https://modelcontextprotocol.io/introduction
- MCP Kotlin SDK GitHub: https://github.com/modelcontextprotocol/kotlin-sdk
- Kotlin Coroutines Documentation: https://kotlinlang.org/docs/coroutines-guide.html
- Related Project: MCP Hello World (Kotlin): For the source code of the
HelloWorldServerused in this tutorial, and a simpler client/server example, visit https://github.com/rwachters/mcp-hello-world.