{"id":49526061,"url":"https://github.com/rwachters/mcp-tutorials","last_synced_at":"2026-05-02T03:14:37.011Z","repository":{"id":340895744,"uuid":"1082863415","full_name":"rwachters/mcp-tutorials","owner":"rwachters","description":"Kotlin tutorials for Model Context Protocol (MCP), featuring a generic client that connects to any STDIO MCP server to dynamically discover and interact with its tools.","archived":false,"fork":false,"pushed_at":"2026-02-27T01:50:56.000Z","size":80,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-02-27T09:18:17.883Z","etag":null,"topics":["ai","ai-agent","client-server","example","interoperability","kotlin","llm","llm-agent","mcp","model-context-protocol","sdk","stdio","tool-calling","tools","tutorial"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rwachters.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-24T22:21:00.000Z","updated_at":"2026-02-27T01:50:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rwachters/mcp-tutorials","commit_stats":null,"previous_names":["rwachters/mcp-tutorials"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/rwachters/mcp-tutorials","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwachters%2Fmcp-tutorials","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwachters%2Fmcp-tutorials/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwachters%2Fmcp-tutorials/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwachters%2Fmcp-tutorials/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rwachters","download_url":"https://codeload.github.com/rwachters/mcp-tutorials/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwachters%2Fmcp-tutorials/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32521323,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-02T01:12:54.858Z","status":"online","status_checked_at":"2026-05-02T02:00:05.923Z","response_time":132,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-agent","client-server","example","interoperability","kotlin","llm","llm-agent","mcp","model-context-protocol","sdk","stdio","tool-calling","tools","tutorial"],"created_at":"2026-05-02T03:14:36.281Z","updated_at":"2026-05-02T03:14:36.981Z","avatar_url":"https://github.com/rwachters.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MCP Tutorials (Kotlin)\n\nWelcome 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.\n\n## Part 1: Generic STDIO Client with \"Hello World\" Server\n\nThis 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.\n\nThis example is ideal for understanding:\n*   How to build a flexible MCP client capable of launching external servers as subprocesses.\n*   Dynamic discovery and interactive invocation of server-provided tools.\n*   Basic MCP client-server communication using standard I/O.\n\n## Table of Contents\n\n*   [What is MCP?](#what-is-mcp)\n*   [Project Structure](#project-structure)\n*   [Prerequisites](#prerequisites)\n*   [Building the Project](#building-the-project)\n*   [Running Part 1](#running-part-1)\n*   [How Part 1 Works](#how-part-1-works)\n*   [Troubleshooting](#troubleshooting)\n*   [Further Learning](#further-learning)\n\n## What is MCP?\n\nThe 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.\n\nThink 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.\n\n## Project Structure\n\nThis project has a multi-module Gradle setup. For this tutorial, we focus on the `part1` module:\n\n```\nmcp-tutorials/\n├── build.gradle.kts          // Root Gradle configuration\n├── settings.gradle.kts       // Gradle settings for multi-module project\n├── part1/                    // The first tutorial module (our client)\n│   ├── build.gradle.kts\n│   └── src/main/kotlin/eu/torvian/mcp/tutorials/part1/\n│       ├── Main.kt           // Entry point for the generic client\n│       ├── ServerConfig.kt   // Data class for subprocess configuration\n│       └── StdioMcpClient.kt // The generalized MCP client implementation\n├── docs/                     // (Future) Additional documentation specific to these tutorials\n└── gradle/\n    ├── libs.versions.toml    // Version catalog for dependencies\n    └── wrapper/              // Gradle wrapper files\n```\n\n**Note:** The `HelloWorldServer` used in this tutorial is from a separate project. You will need to build its JAR separately.\n\n## Prerequisites\n\n*   **Java 17 or later**: Required for running Kotlin applications on the JVM.\n*   **Gradle**: (Optional) If you don't use the provided Gradle wrapper, ensure you have Gradle installed.\n*   **Basic understanding of Kotlin**: Familiarity with Kotlin syntax and concepts will be helpful.\n*   **`mcp-hello-world-server.jar`**: The server JAR from the `mcp-hello-world` project. You can obtain it by cloning and building that repository:\n    ```bash\n    git clone https://github.com/rwachters/mcp-hello-world.git\n    cd mcp-hello-world\n    ./gradlew :server:jar\n    # The server JAR will be at server/build/libs/mcp-hello-world-server.jar\n    cd .. # Return to mcp-tutorials root\n    ```\n\n## Building the Project\n\nThis project uses Gradle to build a \"fat JAR\" for the `part1` client, which includes all necessary dependencies to run independently.\n\n1.  **Clone this repository:**\n    ```bash\n    git clone https://github.com/rwachters/mcp-tutorials.git\n    cd mcp-tutorials\n    ```\n2.  **Build the `part1` client JAR:**\n    ```bash\n    ./gradlew :part1:jar\n    ```\n    This will produce `part1/build/libs/part1.jar` (or similar, depending on your build configuration).\n\n## Running Part 1\n\nTo 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.\n\n1.  **Ensure you have built the `part1` client JAR** (as per [Building the Project](#building-the-project)).\n2.  **Ensure you have the `mcp-hello-world-server.jar`** (as per [Prerequisites](#prerequisites)). For simplicity in the example below, we assume you've copied it to the root of the `mcp-tutorials` project (or you can provide its full path).\n3.  **Execute the `part1` client application:**\n    ```bash\n    # Assuming mcp-hello-world-server.jar is in the root of mcp-tutorials\n    java -jar part1/build/libs/part1.jar java -jar mcp-hello-world-server.jar\n    ```\n    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:\n    ```\n    Client: Starting server process: java -jar mcp-hello-world-server.jar\n    Client: Server process started with PID: 12345\n    Starting Hello World MCP Server...\n    Client: Successfully connected to MCP server.\n    Client: Discovered tools from server: greet\n    --- Interactive Tool Caller ---\n    Type a tool name to call it, or 'quit' to exit.\n    \u003e Enter tool name: greet\n    \u003e Enter value for 'name' (string, REQUIRED): Alice\n    Client: Calling tool 'greet' with arguments: {name=Alice}\n    Server: Called 'greet' with name='Alice'. Responding with: 'Hello, Alice!'\n    Server Response: Hello, Alice!\n    \u003e Enter tool name: quit\n    Client: MCP connection closed.\n    Client: Server subprocess terminated.\n    ```\n\n### Other Server Examples (Conceptual)\n\nYou 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.\n\n*   **Generic Docker Server:**\n    ```bash\n    # Set your API key in the shell first (e.g., export MY_API_KEY=\"your_secret\")\n    java -jar part1/build/libs/part1.jar docker run -i --rm -e MY_API_KEY my/mcp-server-image\n    ```\n    (Note: `-i` is for interactive mode, `-e MY_API_KEY` tells Docker to pass the host's `MY_API_KEY` env var into the container.)\n\n*   **UV Server Example:**\n    ```bash\n    # Set Python path or virtual environment activation in the shell if needed\n    java -jar part1/build/libs/part1.jar uv python -m my_uv_mcp_server_module\n    ```\n\n## How Part 1 Works\n\n*   **`mcp-hello-world-server.jar` (from `mcp-hello-world` project)**:\n    *   This is a separate MCP server application.\n    *   It initializes an `MCP Server` instance with basic capabilities.\n    *   Defines a `Tool` named `greet` with a `name` argument, using the MCP Kotlin SDK's schema definition.\n    *   Registers a lambda function for the `greet` tool that extracts the `name` argument and returns a \"Hello, \\[name]!\" `TextContent` as a `CallToolResult`.\n    *   Connects to a `StdioServerTransport`, which allows it to communicate via standard input/output streams with a client.\n    *   **Important:** Its initial `println(\"Starting...\")` message is written to `System.err` to avoid interfering with the client's MCP protocol parsing on `System.out`.\n\n*   **`StdioMcpClient.kt` (in this `part1` module)**:\n    *   Initializes an `MCP Client` instance.\n    *   In `connectToServer()`, it launches the specified MCP server command (e.g., `java -jar mcp-hello-world-server.jar`) as a separate child process using `ProcessBuilder`.\n    *   It then sets up a `StdioClientTransport` to 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.\n    *   After connecting, it calls `mcp.listTools()` to dynamically discover all tools offered by the server.\n    *   The `interactiveToolLoop()` allows the user to input a tool name and, based on the tool's `inputSchema`, prompts for necessary arguments. It then calls `mcp.callTool()` to execute the server's chosen tool.\n    *   The structured `CallToolResult` from the server is then processed, and its `TextContent` (or error messages) is printed to the console.\n\n*   **`build.gradle.kts` (Fat JARs)**:\n    *   The Gradle build script for the `part1` module is configured to create a \"fat JAR\". This means all dependencies (like `kotlin-stdlib`, `kotlinx-coroutines-core`, and the MCP Kotlin SDK itself) are bundled directly into the `part1.jar` file. This makes it easily runnable with `java -jar`.\n    *   Special `exclude` rules are applied in the `jar` task to prevent `Duplicate entry` errors during the build, particularly for `META-INF` files and `module-info.class` files that often cause conflicts when merging JARs.\n\n## Troubleshooting\n\n*   **`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 in `part1/build.gradle.kts` and using `./gradlew :part1:jar`.\n*   **`Duplicate entry` during build**: Check your `part1/build.gradle.kts` `jar` task for the `from(configurations.runtimeClasspath.get()...)` block and ensure the `exclude` rules for `META-INF` files are present and correct.\n*   **Client fails to connect (hangs or errors)**:\n    *   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.\n    *   Verify there are no unexpected issues starting the subprocess (e.g., permissions, `java`, `docker`, `uv` commands not in your system's PATH).\n    *   **Crucially**: The `StdioMcpClient` expects *only* valid MCP JSON-RPC messages on the server's `stdout`. If the server prints *any* plain text (e.g., startup logs, warnings) to `stdout` before or during the MCP handshake, the client's MCP parser will likely fail or hang. Ensure such output is directed to `stderr` by the server. (Our `HelloWorldServer` does this correctly by writing startup messages to `System.err`).\n*   **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 like `slf4j-simple` to your `part1/build.gradle.kts` dependencies (e.g., `implementation(\"org.slf4j:slf4j-simple:2.0.13\")`). This is purely for logging output and doesn't affect the core MCP functionality.\n\n## Further Learning\n\n*   **Model Context Protocol Documentation**: [https://modelcontextprotocol.io/introduction](https://modelcontextprotocol.io/introduction)\n*   **MCP Kotlin SDK GitHub**: [https://github.com/modelcontextprotocol/kotlin-sdk](https://github.com/modelcontextprotocol/kotlin-sdk)\n*   **Kotlin Coroutines Documentation**: [https://kotlinlang.org/docs/coroutines-guide.html](https://kotlinlang.org/docs/coroutines-guide.html)\n*   **Related Project: MCP Hello World (Kotlin)**: For the source code of the `HelloWorldServer` used in this tutorial, and a simpler client/server example, visit [https://github.com/rwachters/mcp-hello-world](https://github.com/rwachters/mcp-hello-world).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frwachters%2Fmcp-tutorials","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frwachters%2Fmcp-tutorials","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frwachters%2Fmcp-tutorials/lists"}