TnsAI
MCP

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.

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

If you already have TnsAI tools registered through the SPI mechanism, you can expose all of them to MCP clients with a single line. The TnsAIToolProvider.fromRegistry() call discovers all tools on the classpath automatically.

McpServer server = McpServer.builder()
    .serverName("TnsAI-Tools")
    .addToolProvider(TnsAIToolProvider.fromRegistry())  // All SPI-discovered tools
    .build();

server.start();  // Listens on stdio

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.

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.

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.

CategoryCodeRetriableGuidance
INVALID_PARAMSinvalid_paramsNoRequest malformed, do not retry with same params
TOOL_NOT_FOUNDtool_not_foundNoTool does not exist, check tool registry
TIMEOUTtimeoutYesExecution timed out, may retry
PERMISSION_DENIEDpermission_deniedNoAccess denied, do not retry
INTERNALinternalYesUnexpected 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.

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

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

On this page