DEV Community

Sebastiao Gazolla Jr
Sebastiao Gazolla Jr

Posted on

MCP Fundamentals: Your First Java Client in 30 Lines of Code

In the previous post I talked about what MCP is conceptually. Today I want to show you exactly how it works by walking through the simplest possible MCP client in Java. We're going to create a file using just 30 lines of code, but more importantly, understand what each line does and why.

The Complete Code First

Let me show you the entire working example, then we'll break it down piece by piece:

package com.example.mcp;
import java.time.Duration;
import java.util.Map;

import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;

public class SimpleClientMCP {
    public static void main(String[] args) {
        int requestTimeoutSeconds = 30;
        String basePath = System.getProperty("user.home") + "/Documents";

        StdioClientTransport stdioTransport = new StdioClientTransport(
            ServerParameters.builder("cmd.exe")
                .args("/c", "npx @modelcontextprotocol/server-filesystem " + basePath)
                .build());

        McpSyncClient client = McpClient.sync(stdioTransport)
            .requestTimeout(Duration.ofSeconds(requestTimeoutSeconds))
            .build();

        client.initialize();

        CallToolRequest request = new CallToolRequest("write_file", Map.of(
            "path", basePath + "\\test.txt",
            "content", "Olá!\n\nThis is a sample using Model Context Protocol."
        ));

        CallToolResult result = client.callTool(request);
        System.out.println(result.toString());
    }
}
Enter fullscreen mode Exit fullscreen mode

When you run this, it creates a file called test.txt in your Documents folder with some content. Simple, right? But there's a lot happening under the hood.

The MCP Architecture in This Example

Before diving into the code, let's understand what's actually happening:

  1. Your Java Application (the client, called host application in the MCP documentation)
  2. MCP Protocol (the communication layer, the MCP Client)
  3. Filesystem Server (the tool provider)
[Java App] ←→ [MCP Protocol] ←→ [Filesystem Server] ←→ [Your Files]
Enter fullscreen mode Exit fullscreen mode

Your Java code doesn't directly touch the filesystem. It talks to an MCP server that provides filesystem tools, and that server does the actual file operations.

Breaking Down Each Section

The Imports: What You Need

import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
Enter fullscreen mode Exit fullscreen mode

These imports give you the core MCP functionality:

  • McpClient/McpSyncClient: The main client that handles MCP communication.
  • ServerParameters/StdioClientTransport: How to connect to MCP servers
  • CallToolRequest/CallToolResult: How to call tools and get results

Setting Up Basic Configuration

int requestTimeoutSeconds = 30;
String basePath = System.getProperty("user.home") + "/Documents";
Enter fullscreen mode Exit fullscreen mode

This is straightforward setup. The timeout prevents your application from hanging if the MCP server doesn't respond. The basePath determines where the filesystem server can operate - it's both a working directory and a security boundary.

Creating the Transport: How to Talk to the Server

StdioClientTransport stdioTransport = new StdioClientTransport(
    ServerParameters.builder("cmd.exe")
        .args("/c", "npx @modelcontextprotocol/server-filesystem " + basePath)
        .build());
Enter fullscreen mode Exit fullscreen mode

This is where the magic starts. Let's break it down:

STDIO Transport means we're communicating with a subprocess through standard input/output. It's like having a conversation through a pipe.

ServerParameters.builder("cmd.exe") tells the transport to run a Windows command. On Linux/Mac, you'd use "sh" or "bash".

.args("/c", "npx @modelcontextprotocol/server-filesystem " + basePath) specifies the actual command to run. This starts the filesystem MCP server and tells it to operate in your Documents folder.

What happens here is:

  1. Your Java app starts a subprocess: cmd.exe /c npx @modelcontextprotocol/server-filesystem C:\Users\YourName\Documents
  2. That subprocess runs the Node.js filesystem server
  3. The server starts up and waits for MCP protocol messages
  4. Your Java app can now talk to it through stdin/stdout

Building the Client: The MCP Connection

McpSyncClient client = McpClient.sync(stdioTransport)
    .requestTimeout(Duration.ofSeconds(requestTimeoutSeconds))
    .build();
Enter fullscreen mode Exit fullscreen mode
  • McpClient.sync() creates a synchronous client. There's also an async version, but sync is easier to understand and debug.
  • .requestTimeout() sets how long to wait for responses. MCP operations can involve file I/O or network calls, so timeouts are important.
  • .build() creates the actual client instance. At this point, the subprocess is running but the MCP handshake hasn't happened yet.

Initializing the Connection: The MCP Handshake

client.initialize();
Enter fullscreen mode Exit fullscreen mode

This single line does a lot:

  1. Protocol Negotiation: Client and server agree on MCP version
  2. Capability Exchange: They tell each other what features they support
  3. Authentication (if needed): Some servers require credentials
  4. Tool Discovery: The server tells the client what tools are available

After initialize() completes, your client knows exactly what the server can do.

Making the Tool Call: The Actual Work

CallToolRequest request = new CallToolRequest("write_file", Map.of(
    "path", basePath + "\\test.txt",
    "content", "Olá!\n\nThis is a sample using Model Context Protocol."
));

CallToolResult result = client.callTool(request);
Enter fullscreen mode Exit fullscreen mode

CallToolRequest specifies:

  • Tool name: "write_file" (this must match exactly what the server provides)
  • Parameters: A map of parameter names to values

The filesystem server expects these specific parameters for write_file:

  • path: Where to create the file
  • content: What to put in the file

client.callTool() sends this request to the server and waits for the response.

Understanding the Result

System.out.println(result.toString());
Enter fullscreen mode Exit fullscreen mode

The result contains:

  • Success/failure status
  • Content: What the tool returned (usually a success message)
  • Error details (if something went wrong)

For a successful file write, you'll see something like:

CallToolResult{content=[TextContent{text=File written successfully}], isError=false}
Enter fullscreen mode Exit fullscreen mode

What's Happening Under the Hood

When you call client.callTool(request), here's the actual flow:

  1. Your Java app serializes the request to JSON
  2. Transport layer sends JSON over stdin to the subprocess
  3. Filesystem server receives and parses the JSON
  4. Server validates the parameters (path exists? content is valid?)
  5. Server performs the actual file operation
  6. Server sends response back as JSON over stdout
  7. Transport layer receives the JSON response
  8. Your Java app deserializes it to CallToolResult

All of this happens transparently. You just see the high-level tool call.

Why This Architecture Matters

Security: The filesystem server only operates in the directory you specify. It can't access your entire system.

Isolation: If the server crashes, it doesn't take down your Java application.

Language Independence: The server is written in Node.js, but your client is Java. MCP bridges different technologies.

Tool Reusability: Other applications can use the same filesystem server. You're not writing filesystem code; you're using a standard tool.

Common Issues and Solutions

"Command not found" errors: Make sure you have Node.js and npm installed. The npx command needs to be in your PATH.

Permission errors: The server can only access the directory you specify in basePath. Make sure that directory exists and is writable.

Timeout errors: Some operations (especially on network drives) can be slow. Increase requestTimeoutSeconds if needed.

Path separator issues: On Windows, use \\ or / in paths. The File.separator constant is helpful for cross-platform code.

Extending This Example

Want to try more tools? The filesystem server provides several:

// Read a file
CallToolRequest readRequest = new CallToolRequest("read_file", Map.of(
    "path", basePath + "\\test.txt"
));

// List directory contents
CallToolRequest listRequest = new CallToolRequest("list_files", Map.of(
    "path", basePath
));

// Move a file
CallToolRequest moveRequest = new CallToolRequest("move_file", Map.of(
    "source", basePath + "\\test.txt",
    "destination", basePath + "\\renamed.txt"
));
Enter fullscreen mode Exit fullscreen mode

Each tool has different parameters, but the calling pattern is the same.

What We've Learned

This simple example demonstrates the core MCP concepts:

  • Transport: How client and server communicate (STDIO in this case)
  • Initialization: The protocol handshake that establishes the connection
  • Tool Calls: The request/response pattern for using server functionality
  • Error Handling: How failures are communicated back to the client

Next post, I'll show you how to connect to multiple MCP servers simultaneously and how to discover tools dynamically. We'll build on this foundation to create more sophisticated applications.

The key insight is that MCP turns tools into network services, but with a standardized protocol that any client can use. Your Java application doesn't need to know how to write files, it just needs to know how to talk MCP.


Try running this code and experimenting with different file operations. What other MCP servers are you curious about? Let me know in the comments!

Top comments (0)