MCP 어노테이션 특수 파라미터

MCP 어노테이션 특수 파라미터 (MCP Annotations Special Parameters)

MCP 어노테이션은 어노테이션된 메서드에 추가 컨텍스트와 기능을 제공하는 여러 특수 파라미터 타입을 지원해요. 이 파라미터들은 프레임워크가 자동으로 주입하며 JSON 스키마 생성에서 제외돼요. 이 글에서는 McpMeta, @McpProgressToken, 요청 컨텍스트 같은 특수 파라미터들을 하나씩 살펴볼게요.

출처: 문서

본문

MCP 어노테이션 특수 파라미터 (MCP Annotations Special Parameters)

MCP Annotations는 어노테이션된 메서드에 추가 컨텍스트와 기능을 제공하는 여러 특수 파라미터 타입을 지원해요. 이 파라미터들은 프레임워크가 자동으로 주입하고, JSON 스키마 생성에서는 제외돼요.

특수 파라미터 타입 (Special Parameter Types)

MetaProvider

MetaProvider 인터페이스는 도구, 프롬프트, 리소스 선언의 _meta 필드에 데이터를 공급해요.

개요 (Overview)

  • @McpTool(metaProvider = …), @McpPrompt(metaProvider = …), @McpResource(metaProvider = …)에서 참조하는 클래스로 구현돼요.
  • 시작 시 도구/프롬프트/리소스 명세에 정적 또는 계산된 메타데이터를 붙일 수 있게 해 줘요.
  • 기본 DefaultMetaProvider는 빈 맵을 반환해요 (_meta가 추가되지 않아요).

커스텀 MetaProvider (Custom MetaProvider)

public class MyToolMetaProvider implements MetaProvider {

    @Override
    public Map<String, Object> getMeta() {
        return Map.of(
            "version", "1.0",
            "team", "platform",
            "experimental", false
        );
    }
}

@McpTool(name = "my-tool",
         description = "Tool with metadata",
         metaProvider = MyToolMetaProvider.class)
public String myTool(@McpToolParam(description = "Input") String input) {
    return "Processed: " + input;
}

같은 패턴이 @McpPrompt와 @McpResource에도 적용돼요.

McpMeta

McpMeta 클래스는 MCP 요청, 알림, 결과의 메타데이터에 접근을 제공해요.

개요 (Overview)

  • 메서드 파라미터로 사용하면 자동 주입돼요.
  • 파라미터 개수 제한과 JSON 스키마 생성에서 제외돼요.
  • get(String key) 메서드를 통해 메타데이터에 편리하게 접근할 수 있어요.
  • 요청에 메타데이터가 없으면 빈 McpMeta 객체가 주입돼요.

도구에서 사용 (Usage in Tools)

@McpTool(name = "contextual-tool", description = "Tool with metadata access")
public String processWithContext(
        @McpToolParam(description = "Input data", required = true) String data,
        McpMeta meta) {

    // Access metadata from the request
    String userId = (String) meta.get("userId");
    String sessionId = (String) meta.get("sessionId");
    String userRole = (String) meta.get("userRole");

    // Use metadata to customize behavior
    if ("admin".equals(userRole)) {
        return processAsAdmin(data, userId);
    } else {
        return processAsUser(data, userId);
    }
}

리소스에서 사용 (Usage in Resources)

@McpResource(uri = "secure-data://{id}", name = "Secure Data")
public ReadResourceResult getSecureData(String id, McpMeta meta) {

    String requestingUser = (String) meta.get("requestingUser");
    String accessLevel = (String) meta.get("accessLevel");

    // Check access permissions using metadata
    if (!"admin".equals(accessLevel)) {
        return ReadResourceResult.builder(List.of(
            TextResourceContents.builder("secure-data://" + id,
                "Access denied").mimeType("text/plain").build()
        )).build();
    }

    String data = loadSecureData(id);
    return ReadResourceResult.builder(List.of(
        TextResourceContents.builder("secure-data://" + id,
            data).mimeType("text/plain").build()
    )).build();
}

프롬프트에서 사용 (Usage in Prompts)

@McpPrompt(name = "localized-prompt", description = "Localized prompt generation")
public GetPromptResult localizedPrompt(
        @McpArg(name = "topic", required = true) String topic,
        McpMeta meta) {

    String language = (String) meta.get("language");
    String region = (String) meta.get("region");

    // Generate localized content based on metadata
    String message = generateLocalizedMessage(topic, language, region);

    return GetPromptResult.builder(List.of(new PromptMessage(Role.ASSISTANT, TextContent.builder(message).build())))
        .description("Localized Prompt")
        .build();
}

@McpProgressToken

@McpProgressToken 어노테이션은 MCP 요청에서 진행 토큰을 받을 파라미터를 표시해요.

개요 (Overview)

  • 파라미터 타입은 String이어야 해요.
  • 요청에서 진행 토큰 값을 자동으로 받아요.
  • 생성된 JSON 스키마에서 제외돼요.
  • 진행 토큰이 없으면 null이 주입돼요.
  • 장기 실행 작업 추적에 사용돼요.

도구에서 사용 (Usage in Tools)

@McpTool(name = "long-operation", description = "Long-running operation with progress")
public String performLongOperation(
        @McpProgressToken String progressToken,
        @McpToolParam(description = "Operation name", required = true) String operation,
        @McpToolParam(description = "Duration in seconds", required = true) int duration,
        McpSyncServerExchange exchange) {

    if (progressToken != null) {
        // Send initial progress
        exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
            .total(1.0).message("Starting " + operation).build());

        // Simulate work with progress updates
        for (int i = 1; i <= duration; i++) {
            Thread.sleep(1000);
            double progress = (double) i / duration;

            exchange.progressNotification(ProgressNotification.builder(progressToken, progress)
                .total(1.0).message(String.format("Processing... %d%%", (int)(progress * 100))).build());
        }
    }

    return "Operation " + operation + " completed";
}

리소스에서 사용 (Usage in Resources)

@McpResource(uri = "large-file://{path}", name = "Large File Resource")
public ReadResourceResult getLargeFile(
        @McpProgressToken String progressToken,
        String path,
        McpSyncServerExchange exchange) {

    File file = new File(path);
    long fileSize = file.length();

    if (progressToken != null) {
        // Track file reading progress
        exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
            .total(fileSize).message("Reading file").build());
    }

    String content = readFileWithProgress(file, progressToken, exchange);

    if (progressToken != null) {
        exchange.progressNotification(ProgressNotification.builder(progressToken, fileSize)
            .total(fileSize).message("File read complete").build());
    }

    return ReadResourceResult.builder(List.of(
        TextResourceContents.builder("large-file://" + path, content).mimeType("text/plain").build()
    )).build();
}

McpSyncRequestContext / McpAsyncRequestContext

요청 컨텍스트 객체는 MCP 요청 정보와 서버 측 작업에 대한 통합 접근을 제공해요.

개요 (Overview)

  • 상태 유지(stateful)와 상태 없는(stateless) 작업 모두를 위한 통합 인터페이스를 제공해요.
  • 파라미터로 사용하면 자동 주입돼요.
  • JSON 스키마 생성에서 제외돼요.
  • 로깅, 진행 알림, 샘플링, elicitation, roots 접근 같은 고급 기능을 가능하게 해 줘요.
  • 상태 유지(서버 exchange)와 상태 없는(전송 컨텍스트) 모드 모두에서 동작해요.

컨텍스트 Getter (Context Getters)

McpSyncRequestContext와 McpAsyncRequestContext 모두 다음 읽기 전용 컨텍스트를 노출해요:

Method Description
request() 원본 MCP 요청 (예: CallToolRequest, ReadResourceRequest). 진행 토큰에 접근하려면 request().progressToken() 사용.
exchange() 내부 서버 exchange (McpSyncServerExchange / McpAsyncServerExchange). 상태 유지 모드에서만 사용 가능하며 상태 없는 모드에서는 null.
sessionId() 현재 세션 식별자.
clientInfo() 클라이언트 구현 정보 (Implementation).
clientCapabilities() 클라이언트가 선언한 기능(capabilities).
requestMeta() 요청의 _meta 필드에서 온 메타데이터 맵. 컨텍스트 객체를 이미 사용하고 있다면 McpMeta 주입보다 이걸 선호해요.
transportContext() 전송 수준 컨텍스트 (McpTransportContext).

McpSyncRequestContext 기능 (McpSyncRequestContext Features)

public record UserInfo(String name, String email, int age) {}

@McpTool(name = "advanced-tool", description = "Tool with full server capabilities")
public String advancedTool(
        McpSyncRequestContext context,
        @McpToolParam(description = "Input", required = true) String input) {

    // Send logging notification
    context.info("Processing: " + input);

    // Ping the client
    context.ping();

    // Send progress updates
    context.progress(50); // 50% complete

    // Check if elicitation is supported before using it
    if (context.elicitEnabled()) {
        // Request additional information from user
        StructuredElicitResult<UserInfo> elicitResult = context.elicit(
            e -> e.message("Need additional information"),
            UserInfo.class
        );

        if (elicitResult.action() == ElicitResult.Action.ACCEPT) {
            UserInfo userInfo = elicitResult.structuredContent();
            // Use the user information
        }
    }

    // Check if sampling is supported before using it
    if (context.sampleEnabled()) {
        // Request LLM sampling
        CreateMessageResult samplingResult = context.sample(
            s -> s.message("Process: " + input)
                .modelPreferences(pref -> pref.modelHints("gpt-4"))
        );
    }

    // Access client root directories (only available in stateful mode)
    if (context.rootsEnabled()) {
        ListRootsResult roots = context.roots();
        roots.roots().forEach(root -> context.info("Client root: " + root.uri()));
    }

    return "Processed with advanced features";
}

McpAsyncRequestContext 기능 (McpAsyncRequestContext Features)

public record UserInfo(String name, String email, int age) {}

@McpTool(name = "async-advanced-tool", description = "Async tool with server capabilities")
public Mono<String> asyncAdvancedTool(
        McpAsyncRequestContext context,
        @McpToolParam(description = "Input", required = true) String input) {

    return context.info("Async processing: " + input)
        .then(context.progress(25))
        .then(context.ping())
        .flatMap(v -> {
            // Perform elicitation if supported
            if (context.elicitEnabled()) {
                return context.elicitation(UserInfo.class)
                    .map(userInfo -> "Processing for user: " + userInfo.name());
            }
            return Mono.just("Processing...");
        })
        .flatMap(msg -> {
            // Perform sampling if supported
            if (context.sampleEnabled()) {
                return context.sampling("Process: " + input)
                    .map(result -> "Completed: " + result);
            }
            return Mono.just("Completed: " + msg);
        });
}

McpTransportContext

상태 없는 작업을 위한 가벼운 컨텍스트예요.

개요 (Overview)

  • 전체 서버 exchange 없이 최소한의 컨텍스트를 제공해요.
  • 상태 없는 구현에서 사용돼요.
  • 파라미터로 사용하면 자동 주입돼요.
  • JSON 스키마 생성에서 제외돼요.

사용 예제 (Usage Example)

@McpTool(name = "stateless-tool", description = "Stateless tool with context")
public String statelessTool(
        McpTransportContext context,
        @McpToolParam(description = "Input", required = true) String input) {

    // Limited context access
    // Useful for transport-level operations

    return "Processed in stateless mode: " + input;
}

@McpResource(uri = "stateless://{id}", name = "Stateless Resource")
public ReadResourceResult statelessResource(
        McpTransportContext context,
        String id) {

    // Access transport context if needed
    String data = loadData(id);

    return ReadResourceResult.builder(List.of(
        TextResourceContents.builder("stateless://" + id, data).mimeType("text/plain").build()
    )).build();
}

CallToolRequest

동적 스키마와 함께 전체 요청에 접근해야 하는 도구를 위한 특수 파라미터예요.

개요 (Overview)

  • 전체 도구 요청에 대한 접근을 제공해요.
  • 런타임에 동적 스키마 처리를 가능하게 해 줘요.
  • 자동 주입되며 스키마 생성에서 제외돼요.
  • 서로 다른 입력 스키마에 적응하는 유연한 도구에 유용해요.

사용 예제 (Usage Examples)

@McpTool(name = "dynamic-tool", description = "Tool with dynamic schema support")
public CallToolResult processDynamicSchema(CallToolRequest request) {
    Map<String, Object> args = request.arguments();

    // Process based on whatever schema was provided at runtime
    StringBuilder result = new StringBuilder("Processed:\n");

    for (Map.Entry<String, Object> entry : args.entrySet()) {
        result.append("  ").append(entry.getKey())
              .append(": ").append(entry.getValue()).append("\n");
    }

    return CallToolResult.builder()
        .addTextContent(result.toString())
        .build();
}

혼합 파라미터 (Mixed Parameters)

@McpTool(name = "hybrid-tool", description = "Tool with typed and dynamic parameters")
public String processHybrid(
        @McpToolParam(description = "Operation", required = true) String operation,
        @McpToolParam(description = "Priority", required = false) Integer priority,
        CallToolRequest request) {

    // Use typed parameters for known fields
    String result = "Operation: " + operation;
    if (priority != null) {
        result += " (Priority: " + priority + ")";
    }

    // Access additional dynamic arguments
    Map<String, Object> allArgs = request.arguments();

    // Remove known parameters to get only additional ones
    Map<String, Object> additionalArgs = new HashMap<>(allArgs);
    additionalArgs.remove("operation");
    additionalArgs.remove("priority");

    if (!additionalArgs.isEmpty()) {
        result += " with " + additionalArgs.size() + " additional parameters";
    }

    return result;
}

진행 토큰과 함께 (With Progress Token)

@McpTool(name = "flexible-with-progress", description = "Flexible tool with progress")
public CallToolResult flexibleWithProgress(
        @McpProgressToken String progressToken,
        CallToolRequest request,
        McpSyncServerExchange exchange) {

    Map<String, Object> args = request.arguments();

    if (progressToken != null) {
        exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
            .total(1.0).message("Processing dynamic request").build());
    }

    // Process dynamic arguments
    String result = processDynamicArgs(args);

    if (progressToken != null) {
        exchange.progressNotification(ProgressNotification.builder(progressToken, 1.0)
            .total(1.0).message("Complete").build());
    }

    return CallToolResult.builder()
        .addTextContent(result)
        .build();
}

파라미터 주입 규칙 (Parameter Injection Rules)

자동 주입 (Automatic Injection)

다음 파라미터는 프레임워크가 자동으로 주입해요:

  1. McpMeta - 요청의 _meta 필드에서 온 메타데이터.
  2. @McpProgressToken String - 사용 가능하면 진행 토큰.
  3. McpSyncRequestContext / McpAsyncRequestContext - 통합 요청 컨텍스트 (권장).
  4. McpSyncServerExchange / McpAsyncServerExchange - 저수준 서버 exchange 컨텍스트 (상태 유지 전용).
  5. McpTransportContext - 상태 없는 작업용 전송 컨텍스트.
  6. CallToolRequest - 동적 스키마용 전체 도구 요청 (도구 전용).

스키마 생성 (Schema Generation)

특수 파라미터는 JSON 스키마 생성에서 제외돼요:

  • 도구의 입력 스키마에 나타나지 않아요.
  • 파라미터 개수 제한에 포함되지 않아요.
  • MCP 클라이언트에 보이지 않아요.

Null 처리 (Null Handling)

  • McpMeta - 절대 null이 아니에요. 메타데이터가 없으면 빈 객체.
  • @McpProgressToken - 토큰이 없으면 null일 수 있어요.
  • 서버 exchange - 올바르게 구성되면 절대 null이 아니에요.
  • CallToolRequest - 도구 메서드에서는 절대 null이 아니에요.

모범 사례 (Best Practices)

컨텍스트에 McpMeta 사용

@McpTool(name = "context-aware", description = "Context-aware tool")
public String contextAware(
        @McpToolParam(description = "Data", required = true) String data,
        McpMeta meta) {

    // Always check for null values in metadata
    String userId = (String) meta.get("userId");
    if (userId == null) {
        userId = "anonymous";
    }

    return processForUser(data, userId);
}

진행 토큰 null 검사 (Progress Token Null Checks)

@McpTool(name = "safe-progress", description = "Safe progress handling")
public String safeProgress(
        @McpProgressToken String progressToken,
        @McpToolParam(description = "Task", required = true) String task,
        McpSyncServerExchange exchange) {

    // Always check if progress token is available
    if (progressToken != null) {
        exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
            .total(1.0).message("Starting").build());
    }

    // Perform work...

    if (progressToken != null) {
        exchange.progressNotification(ProgressNotification.builder(progressToken, 1.0)
            .total(1.0).message("Complete").build());
    }

    return "Task completed";
}

올바른 컨텍스트 고르기 (Choose the Right Context)

  • 상태 유지·상태 없는 작업 모두를 지원하고 편리한 헬퍼 메서드를 제공하는 통합 요청 컨텍스트가 필요하면 McpSyncRequestContext / McpAsyncRequestContext를 사용하세요.
  • 전송 수준 컨텍스트만 필요한 단순한 상태 없는 작업에는 McpTransportContext를 사용하세요.
  • 가장 단순한 경우에는 컨텍스트 파라미터를 아예 생략하세요.

기능 검사 (Capability Checking)

클라이언트 기능을 사용하기 전에 항상 기능 지원 여부를 검사하세요:

@McpTool(name = "capability-aware", description = "Tool that checks capabilities")
public String capabilityAware(
        McpSyncRequestContext context,
        @McpToolParam(description = "Data", required = true) String data) {

    // Check if elicitation is supported before using it
    if (context.elicitEnabled()) {
        // Safe to use elicitation
        var result = context.elicit(UserInfo.class);
        // Process result...
    }

    // Check if sampling is supported before using it
    if (context.sampleEnabled()) {
        // Safe to use sampling
        var samplingResult = context.sample("Process: " + data);
        // Process result...
    }

    // Note: Stateless servers do not support bidirectional operations
    // (roots, elicitation, sampling) and will return false for these checks

    return "Processed with capability awareness";
}

추가 자료 (Additional Resources)

더 알아보기 (Learn more)