# 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.

```java
// 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.

```java
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.

```java
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.

```java
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.

```java
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.

```java
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.

```java
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.

```java
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.

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

## Agent Integration

### Using MCP Tools in Agents

The `McpToolBridge` converts tools from any MCP server into `DynamicToolMethod` instances — runtime-defined tools that share the same registry as POJO `@Tool` methods. The agent can then call these tools during its LLM reasoning loop without distinguishing them from local toolkits.

```java
import com.tnsai.tools.method.DynamicToolMethod;

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

// Convert remote MCP tools to runtime-defined TnsAI tools
List<DynamicToolMethod> tools = bridge.toTnsAITools();

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

### MCP Server Configuration

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

**Factory methods** for common cases:

```java
// 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:

```java
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:**

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