CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-mcp

Spring Framework integration for Model Context Protocol (MCP), providing Spring AI function calling capabilities and Spring-friendly abstractions for MCP clients and MCP servers

Overview
Eval results
Files

context-metadata-conversion.mddocs/reference/

Context and Metadata Conversion

Convert Spring AI tool context to MCP metadata for passing additional information during tool execution.

Import

import org.springframework.ai.mcp.ToolContextToMcpMetaConverter;
import org.springframework.ai.chat.model.ToolContext;
import java.util.Map;

ToolContextToMcpMetaConverter Interface

@FunctionalInterface
public interface ToolContextToMcpMetaConverter {
    Map<String, Object> convert(ToolContext toolContext);

    static ToolContextToMcpMetaConverter defaultConverter();
    static ToolContextToMcpMetaConverter noOp();
}

Strategy interface for converting a ToolContext to a map of metadata sent as part of an MCP tool call.

Default Converter

static ToolContextToMcpMetaConverter defaultConverter()

Returns the default converter implementation that:

  • Returns empty map if toolContext is null or has no context
  • Filters out the McpToolUtils.TOOL_CONTEXT_MCP_EXCHANGE_KEY entry
  • Filters out entries with null values
  • Returns all other context entries as metadata

Usage Example

ToolContextToMcpMetaConverter defaultConverter =
    ToolContextToMcpMetaConverter.defaultConverter();

// Use with tool callback builders
SyncMcpToolCallback callback = SyncMcpToolCallback.builder()
    .mcpClient(mcpClient)
    .tool(tool)
    .toolContextToMcpMetaConverter(defaultConverter)
    .build();

No-Op Converter

static ToolContextToMcpMetaConverter noOp()

Returns a converter that always returns an empty map, ignoring all tool context.

Usage Example

ToolContextToMcpMetaConverter noOpConverter =
    ToolContextToMcpMetaConverter.noOp();

// Use when you don't want to pass any context metadata
AsyncMcpToolCallback callback = AsyncMcpToolCallback.builder()
    .mcpClient(asyncClient)
    .tool(tool)
    .toolContextToMcpMetaConverter(noOpConverter)
    .build();

Custom Converters

Simple Custom Converter

// Convert only specific context keys
ToolContextToMcpMetaConverter selectiveConverter = toolContext -> {
    if (toolContext == null) return Map.of();

    Map<String, Object> context = toolContext.getContext();
    Map<String, Object> metadata = new HashMap<>();

    // Only pass userId and requestId
    if (context.containsKey("userId")) {
        metadata.put("userId", context.get("userId"));
    }
    if (context.containsKey("requestId")) {
        metadata.put("requestId", context.get("requestId"));
    }

    return metadata;
};

Transform Context Values

// Transform values before passing to MCP
ToolContextToMcpMetaConverter transformingConverter = toolContext -> {
    if (toolContext == null) return Map.of();

    Map<String, Object> metadata = new HashMap<>();

    for (Map.Entry<String, Object> entry : toolContext.getContext().entrySet()) {
        String key = entry.getKey();
        Object value = entry.getValue();

        // Skip reserved keys
        if (key.equals(McpToolUtils.TOOL_CONTEXT_MCP_EXCHANGE_KEY)) {
            continue;
        }

        // Transform timestamp to ISO string
        if (value instanceof java.time.Instant) {
            metadata.put(key, ((java.time.Instant) value).toString());
        }
        // Convert complex objects to JSON strings
        else if (value != null && !isPrimitive(value)) {
            metadata.put(key, ModelOptionsUtils.toJsonString(value));
        }
        // Pass primitives as-is
        else if (value != null) {
            metadata.put(key, value);
        }
    }

    return metadata;
};

boolean isPrimitive(Object value) {
    return value instanceof String ||
           value instanceof Number ||
           value instanceof Boolean;
}

Add Fixed Metadata

// Add application-level metadata to all tool calls
ToolContextToMcpMetaConverter enrichingConverter = toolContext -> {
    Map<String, Object> metadata = new HashMap<>();

    // Add fixed application metadata
    metadata.put("appName", "MyApplication");
    metadata.put("appVersion", "1.0.0");
    metadata.put("environment", System.getenv("ENVIRONMENT"));

    // Add context if available
    if (toolContext != null && toolContext.getContext() != null) {
        for (Map.Entry<String, Object> entry : toolContext.getContext().entrySet()) {
            if (!entry.getKey().equals(McpToolUtils.TOOL_CONTEXT_MCP_EXCHANGE_KEY)
                && entry.getValue() != null) {
                metadata.put(entry.getKey(), entry.getValue());
            }
        }
    }

    return metadata;
};

Using Converters with Providers

// With SyncMcpToolCallbackProvider
ToolContextToMcpMetaConverter converter = // ... your converter
SyncMcpToolCallbackProvider provider = SyncMcpToolCallbackProvider.builder()
    .mcpClients(clients)
    .toolContextToMcpMetaConverter(converter)
    .build();

// With AsyncMcpToolCallbackProvider
AsyncMcpToolCallbackProvider asyncProvider = AsyncMcpToolCallbackProvider.builder()
    .mcpClients(asyncClients)
    .toolContextToMcpMetaConverter(converter)
    .build();

Complete Example

import org.springframework.ai.mcp.*;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.chat.client.ChatClient;
import java.util.Map;

// Define custom converter
ToolContextToMcpMetaConverter customConverter = toolContext -> {
    if (toolContext == null) return Map.of();

    Map<String, Object> metadata = new HashMap<>();
    Map<String, Object> context = toolContext.getContext();

    // Pass user information
    if (context.containsKey("userId")) {
        metadata.put("user_id", context.get("userId"));
    }

    // Pass request tracing
    if (context.containsKey("traceId")) {
        metadata.put("trace_id", context.get("traceId"));
    }

    // Pass permissions
    if (context.containsKey("permissions")) {
        metadata.put("permissions", context.get("permissions"));
    }

    // Add timestamp
    metadata.put("timestamp", System.currentTimeMillis());

    return metadata;
};

// Create provider with custom converter
SyncMcpToolCallbackProvider provider = SyncMcpToolCallbackProvider.builder()
    .mcpClients(mcpClient)
    .toolContextToMcpMetaConverter(customConverter)
    .build();

// Use with Spring AI ChatClient
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultFunctions(provider.getToolCallbacks())
    .build();

// Provide context when calling
Map<String, Object> userContext = Map.of(
    "userId", "user-123",
    "traceId", "trace-456",
    "permissions", List.of("read", "write")
);

String response = chatClient.prompt()
    .user("List my files")
    .toolContext(userContext)
    .call()
    .content();

Integration with MCP Metadata

The converted metadata is passed to the MCP server as part of the CallToolRequest:

// Inside tool callback implementation
var mcpMeta = toolContext != null
    ? this.toolContextToMcpMetaConverter.convert(toolContext)
    : null;

var request = CallToolRequest.builder()
    .name(this.tool.name())
    .arguments(arguments)
    .meta(mcpMeta)  // Converted metadata passed here
    .build();

Security Considerations

// Filter sensitive information
ToolContextToMcpMetaConverter secureConverter = toolContext -> {
    if (toolContext == null) return Map.of();

    Map<String, Object> metadata = new HashMap<>();
    List<String> sensitiveKeys = List.of("password", "token", "secret", "apiKey");

    for (Map.Entry<String, Object> entry : toolContext.getContext().entrySet()) {
        String key = entry.getKey();

        // Skip sensitive keys
        if (sensitiveKeys.stream().anyMatch(key::toLowerCase()::contains)) {
            continue;
        }

        // Skip reserved keys
        if (key.equals(McpToolUtils.TOOL_CONTEXT_MCP_EXCHANGE_KEY)) {
            continue;
        }

        if (entry.getValue() != null) {
            metadata.put(key, entry.getValue());
        }
    }

    return metadata;
};

Related Components

  • Synchronous Tool Callbacks - Using converters with sync callbacks
  • Asynchronous Tool Callbacks - Using converters with async callbacks
  • Synchronous Tool Discovery - Using converters with sync providers
  • Asynchronous Tool Discovery - Using converters with async providers

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-ai--spring-ai-mcp

docs

index.md

tile.json