# MCP Server

The `McpServer` exposes TnsAI tools, resources, and prompts to MCP clients.

## Building a Server

To create an MCP server, use the builder to register your tool, resource, and prompt providers, then call `start()`. By default the server communicates over stdio, making it easy to launch as a subprocess from any MCP client.

```java
McpServer server = McpServer.builder()
    .serverName("My-TnsAI-Server")
    .serverVersion("1.0.0")
    .addToolProvider(myToolProvider)
    .addResourceProvider(myResourceProvider)
    .addPromptProvider(myPromptProvider)
    .build();

server.start();  // Stdio transport (blocking)
```

### Expose TnsAI Tools via MCP

`TnsAIToolProvider` adapts TnsAI POJO toolkits and runtime tools into MCP tool definitions. Choose the factory that matches your registration pattern:

```java
import com.tnsai.enums.BuiltInTool;
import com.tnsai.mcp.server.TnsAIToolProvider;

// From an explicit set of POJO toolkit instances
McpServer server = McpServer.builder()
    .serverName("TnsAI-Tools")
    .addToolProvider(TnsAIToolProvider.fromPojos(
        BuiltInTool.WEB_SEARCH_TOOLS.instantiate(),
        BuiltInTool.UTILITY_TOOLS.instantiate(),
        new MyDomainTools()
    ))
    .build();

server.start();  // Listens on stdio
```

Other factory methods on `TnsAIToolProvider`:

| Factory | Input | Use when |
|---|---|---|
| `from(Object... pojos)` | One or more `@Tool`-bearing POJOs | Quick start with a known set of toolkits |
| `fromPojos(Object... pojos)` | Same as `from`, alternative name | Same |
| `fromDynamic(DynamicToolMethod...)` | Runtime-defined tools | Bridging MCP-proxy or plugin tools to MCP |
| `from(Collection<Object> pojos, Collection<DynamicToolMethod> dynamics)` | Both sources combined | Mixing POJOs with runtime tools in one provider |

## Custom Tool Provider

To expose your own custom tools via MCP, implement the `ToolProvider` interface. You define which tools are available and how each one executes when called by a client.

```java
public class MyToolProvider implements ToolProvider {

    @Override
    public List<McpToolDefinition> listTools() {
        return List.of(
            McpToolDefinition.builder()
                .name("analyze")
                .description("Analyze text for sentiment")
                .inputSchema(schemaNode)
                .build()
        );
    }

    @Override
    public ToolResult executeTool(String name, JsonNode args) {
        if ("analyze".equals(name)) {
            String text = args.get("text").asText();
            return ToolResult.text(analyzeSentiment(text));
        }
        return ToolResult.error("Unknown tool: " + name);
    }
}
```

## HTTP Server

For production deployments where clients connect over the network, use `HttpMcpServer`. It serves MCP over Streamable HTTP transport, supporting JSON-RPC requests via POST, server-sent event notifications via GET, and session termination via DELETE.

```java
HttpMcpServer httpServer = HttpMcpServer.builder()
    .port(8080)
    .path("/mcp")
    .addToolProvider(myProvider)
    .build();

httpServer.start();
// POST /mcp  — JSON-RPC requests
// GET  /mcp  — SSE notifications
// DELETE /mcp — Session termination
```

## Error Handling

Structured error events emitted when MCP tool execution fails. Register a `ToolErrorListener` to receive notifications and decide on recovery strategy.

### ToolErrorCategory

Each error is classified into a category that tells clients whether they should retry the operation or fix the request. This prevents pointless retries for permanent failures like invalid parameters.

| Category | Code | Retriable | Guidance |
|----------|------|-----------|----------|
| `INVALID_PARAMS` | `invalid_params` | No | Request malformed, do not retry with same params |
| `TOOL_NOT_FOUND` | `tool_not_found` | No | Tool does not exist, check tool registry |
| `TIMEOUT` | `timeout` | Yes | Execution timed out, may retry |
| `PERMISSION_DENIED` | `permission_denied` | No | Access denied, do not retry |
| `INTERNAL` | `internal` | Yes | Unexpected error, may retry |

### ToolErrorEvent

A `ToolErrorEvent` is a structured record emitted whenever a tool execution fails. It contains the tool name, error category, a safe error message (no stack traces), a timestamp, and an optional request ID for correlation.

```java
// Factory methods
ToolErrorEvent event = ToolErrorEvent.of("my_tool", ToolErrorCategory.TIMEOUT, "Timed out after 30s");
ToolErrorEvent event = ToolErrorEvent.of("my_tool", ToolErrorCategory.INTERNAL, "Unexpected error", requestId);

// Check retriability (delegates to category)
if (event.isRetriable()) {
    // retry logic
}
```

### ToolErrorListener

Register a `ToolErrorListener` to get notified whenever a tool call fails. This is useful for logging, metrics, or implementing retry logic based on the error category.

```java
// Lambda listener
server.getRequestHandler().addToolErrorListener(event -> {
    logger.warn("Tool '{}' failed: {} [{}]",
        event.toolName(), event.message(), event.category().code());
    if (event.isRetriable()) {
        retryQueue.add(event);
    }
});
```
