TnsAI
MCP

MCP Client

The `McpClient` connects to any MCP-compatible server and exposes its tools, resources, and prompts.

Quick Start

The simplest way to get started is to connect to an existing MCP server, discover its tools, and call one. This example connects to a filesystem server that lets you read and write files.

// Connect to a filesystem MCP server
McpClient client = McpClient.builder()
    .transport(StdioTransport.builder()
        .command("npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp")
        .build())
    .clientName("MyApp")
    .clientVersion("1.0.0")
    .build();

client.connect();
client.initialize();

// Discover and use tools
List<McpToolDefinition> tools = client.listTools();
JsonNode result = client.executeTool("read_file", Map.of("path", "/tmp/test.txt"));

client.disconnect();

Builder

The builder lets you configure all aspects of the client before connecting. You must provide a transport (how the client talks to the server), and can optionally set timeouts, project roots, and a handler for server-initiated LLM requests.

McpClient client = McpClient.builder()
    .transport(transport)                          // Required: StdioTransport, HttpTransport, SSETransport
    .clientName("MyApp")                           // Client identification
    .clientVersion("1.0.0")
    .requestTimeout(Duration.ofSeconds(30))
    .roots(List.of(new Root("/project", "Project")))
    .samplingRequestHandler(req -> response)       // Handle server-initiated LLM requests
    .build();

Tools

Tools are functions that an MCP server exposes for clients to call. You can list all available tools and then execute any of them by name, passing arguments as a map.

List<McpToolDefinition> tools = client.listTools();
JsonNode result = client.executeTool("tool_name", Map.of("key", "value"));

Resources

Resources represent data that the server makes available for reading, such as files, database entries, or API responses. You can browse and read them by URI.

List<Resource> resources = client.listResources();
ResourceContent content = client.readResource("file:///path/to/file");

Prompts

Prompts are reusable prompt templates that the server defines. You can list them and render a specific prompt with arguments, which is useful for standardizing how you ask an LLM to process certain types of data.

List<Prompt> prompts = client.listPrompts();
String rendered = client.getPrompt("summarize", Map.of("text", longText));

Sampling (Server-Initiated LLM Calls)

MCP servers can request LLM completions from the client via the sampling protocol. The client registers a samplingRequestHandler that the server invokes when it needs text generation.

SamplingRequest

A SamplingRequest describes what the server wants the LLM to generate. It is built with a fluent builder and requires at least one message and a maxTokens limit.

SamplingRequest request = SamplingRequest.builder()
    .addUserMessage("What is the capital of France?")
    .systemPrompt("You are a geography expert.")
    .maxTokens(100)
    .temperature(0.7)
    .stopSequences(List.of("\n\n"))
    .includeContext("thisServer")        // "none", "thisServer", or "allServers"
    .modelPreferences(ModelPreferences.builder()
        .intelligencePriority(0.8)
        .speedPriority(0.5)
        .costPriority(0.3)
        .hint("claude-sonnet-4-20250514")
        .build())
    .metadata(Map.of("requestSource", "agent"))
    .build();

Builder methods: addUserMessage(text), addAssistantMessage(text), addMessage(SamplingMessage), messages(list), systemPrompt(str), includeContext(str), temperature(double), maxTokens(int), stopSequences(list), modelPreferences(prefs), metadata(map).

SamplingResponse

The SamplingResponse is what your handler returns to the server after generating a completion. It contains the generated text, which model was used, and why generation stopped.

SamplingResponse response = ...;
String text = response.getTextContent();  // extracts text from content (handles string, object, or array)
String model = response.getModel();       // which model actually responded
String reason = response.getStopReason(); // "endTurn", "stopSequence", "maxTokens"

Registering the Handler

You wire up the sampling handler when building the client. When the MCP server needs an LLM completion, your handler is called with the request and must return a response.

McpClient client = McpClient.builder()
    .transport(transport)
    .samplingRequestHandler(request -> {
        // Forward to your LLM
        String answer = myLlm.complete(request.getMessages(), request.getMaxTokens());
        SamplingResponse resp = new SamplingResponse();
        resp.setRole("assistant");
        resp.setModel("my-model");
        resp.setContent(new TextNode(answer));
        resp.setStopReason("endTurn");
        return resp;
    })
    .build();

Change Notifications

MCP servers can notify clients when their available tools or resources change. Register callbacks to react to these changes, for example by refreshing your cached tool list.

client.setOnToolsListChanged(notification -> refreshTools());
client.setOnResourcesListChanged(notification -> refreshResources());

Agent Integration

Using MCP Tools in Agents

The McpToolBridge converts tools from any MCP server into native TnsAI Tool objects, so you can register them directly with an agent. The agent can then call these tools during its LLM reasoning loop.

// Bridge any MCP server's tools into TnsAI
McpToolBridge bridge = McpToolBridge.stdio(
    "npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp"
);
bridge.connect();

// Convert to TnsAI tools
List<Tool> tools = bridge.toTnsAITools();

// Register with agent
Agent agent = AgentBuilder.create()
    .llm(llm)
    .role(role)
    .tools(tools)
    .build();

MCP Server Configuration

For agents that connect to remote MCP servers. Immutable after construction.

Factory methods for common cases:

// Endpoint + API key (sends Authorization: Bearer <key>)
McpServerConfig config = McpServerConfig.of("https://mcp.firecrawl.dev/v1", "fc-xxx");

// Endpoint only, no auth
McpServerConfig config = McpServerConfig.of("https://localhost:8080/mcp");

Builder for full control:

McpServerConfig config = McpServerConfig.builder()
    .name("firecrawl")                              // optional: auto-derived from endpoint host if omitted
    .endpoint("https://mcp.firecrawl.dev/v1")       // required
    .apiKey("fc-xxx")                               // sets Authorization: Bearer header
    .build();

// Custom API key header (non-Authorization)
McpServerConfig config = McpServerConfig.builder()
    .endpoint("https://api.example.com/mcp")
    .apiKey("xxx", "X-API-Key")                     // sends X-API-Key: xxx
    .header("X-Workspace", "my-workspace")           // additional headers
    .timeout(Duration.ofSeconds(60))                 // default: 30s
    .build();

// Shorthand timeout
McpServerConfig config = McpServerConfig.builder()
    .endpoint("https://api.example.com/mcp")
    .timeoutSeconds(60)
    .build();

Auto-derived name: When name is omitted, it is extracted from the endpoint host (e.g., https://mcp.firecrawl.dev becomes "firecrawl"). Falls back to "mcp-server".

Accessors: getName(), getEndpoint(), getHeaders() (unmodifiable map), getTimeout(), hasApiKey().

Agent usage:

protected List<McpServerConfig> getMcpServers() {
    return List.of(
        McpServerConfig.of("https://mcp.firecrawl.dev", "fc-xxx"),
        McpServerConfig.builder()
            .endpoint("https://my-internal-mcp.local:9090/mcp")
            .apiKey("internal-key", "X-Internal-Auth")
            .timeoutSeconds(120)
            .build()
    );
}

On this page