SDK
54e8b61 기준입니다.java-sdk는 Java 애플리케이션이 MCP 서버와 클라이언트가 되도록 만드는 라이브러리입니다. 서버는 “내가 제공하는 도구는 이것이고, 입력은 이런 JSON이다”라고 공개합니다. 클라이언트는 목록을 확인하고 도구를 호출한 뒤 결과를 받습니다. 이 저장소가 해결하는 문제는 모델의 지능이 아니라, 모델 주변 프로그램과 도구 사이의 일관된 통신입니다.
예를 들어 사내 재고 조회 함수를 lookup_stock이라는 MCP 도구로 등록할 수 있습니다.
AI를 포함한 호스트 애플리케이션은 그 도구를 발견하고 {"sku":"BOOK-001"} 같은 인자를 보냅니다.
Java 서버가 데이터베이스를 조회하는 코드는 개발자가 작성하고, SDK는 메시지의 해석·전송·응답 연결을 맡습니다.
모델 제공자의 추론 API 키나 모델 가중치가 SDK 자체에 필요한 것은 아닙니다.
식당마다 주문 방식이 다르면 손님이 매번 새 양식을 배워야 합니다. MCP는 “메뉴 보기”와 “메뉴 주문하기”의 양식을 맞춥니다. java-sdk는 자바로 그 접수 창구를 만들고 이용하는 데 필요한 부품입니다.
창구가 요리까지 대신하지는 않습니다. 재고 조회, 파일 읽기, 계산 등 실제 업무는 도구 핸들러가 수행합니다. 누가 어떤 메뉴를 주문해도 되는지도 애플리케이션이 결정해야 합니다.
| MCP 기능 | 쉬운 뜻과 예 | 대표 메서드 |
|---|---|---|
| Tool | 실행할 수 있는 기능. 재고 조회, 환율 계산, 문서 검색 | tools/list, tools/call |
| Resource | URI로 읽을 수 있는 정보. 안내문, 파일 내용, 상태 데이터 | resources/list, resources/read |
| Prompt | 서버가 제공하는 재사용 메시지 틀. 리뷰 요청문, 요약 지시문 | prompts/list, prompts/get |
이 이름들은 mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java의 메서드 상수와 레코드에 대응합니다.
서버에서 클라이언트에 모델 생성을 요청하는 sampling, 사용자 추가 입력을 요청하는 elicitation도 있지만,
첫 학습에서는 도구 한 개의 목록 조회와 호출을 끝까지 추적하는 편이 좋습니다.
// 도구 호출의 메시지 모양: SDK가 이 봉투를 만들고 읽는다.
{
"jsonrpc": "2.0",
"id": "study-1",
"method": "tools/call",
"params": {
"name": "lookup_stock",
"arguments": { "sku": "BOOK-001" }
}
}
이 저장소에는 완성된 채팅 UI나 모델 학습 파이프라인이 없습니다. 대신 클라이언트·서버, 직렬화, 비동기 흐름, 테스트, 배포 모듈이 있습니다. 작은 도구 하나에서 출발해 범용 SDK가 갖춰야 할 경계를 배우기에 알맞습니다.
2026-10-23 GitHub Trending Java Weekly #5에 오른 저장소입니다. 스타 3,736과 포크 1,112는 관심의 규모를 보여주지만, 특정 기능이 순위를 만들었다는 인과관계까지 설명하지는 않습니다. 아래 세 가지는 공식 설명·릴리스·소스 구조에서 읽을 수 있는 주목할 기술적 배경입니다.
공식 저장소 설명은 Spring AI와의 협력 유지보수를 명시합니다. Java로 작성한 업무 함수를 유지하면서 MCP 계약을 덧붙일 수 있다는 점이 실용적입니다. 모든 기능을 별도 언어로 옮기기보다, 이미 있는 서비스와 라이브러리를 재사용할 수 있습니다. 이는 도입 경로에 대한 해석이며, 이 저장소만으로 회사 시스템 연결이 자동 완성된다는 뜻은 아닙니다.
공개 배포판의 변경 내역은 v2.1.0 릴리스 노트에서 볼 수 있습니다.
커밋 54e8b61에는 도구 목록 필터링, JDK HTTP 전송의 Publisher 기반 응답 처리, 조합 가능한 요청 커스터마이저가 구현되어 있습니다.
STDIO 출력 쓰기의 동기화, Streamable HTTP 세션 정리와 keep-alive도 살펴볼 수 있습니다.
초보자에게는 기능 목록보다 동시 실행·연결 종료·오류 응답 같은 세부가 SDK 품질을 좌우한다는 사례입니다.
이 SDK의 장점은 도구를 등록하는 편의 API와, 통신 구현을 교체할 수 있는 경계를 함께 제공한다는 점입니다.
편리한 McpServer.sync(...)로 시작하더라도, 아래에는 독립된 전송·JSON 인터페이스가 있습니다.
| 선택지 | 잘 맞는 상황 | java-sdk와 비교할 점 |
|---|---|---|
| 직접 만든 REST API | 고정된 클라이언트에 업무 API를 제공 | REST 자체가 MCP의 도구 발견·capabilities·메시지 계약을 구현하지는 않음. 범위가 좁으면 직접 API가 더 단순할 수 있음 |
| 직접 만든 JSON-RPC | 프로토콜 구현을 통제하거나 학습 | 요청 ID, 오류, 초기화, 전송별 차이, 적합성 검증을 직접 책임져야 함 |
| 다른 언어의 MCP SDK | 그 언어로 이미 서비스를 운영 | 프로토콜은 같아도 호스트 언어와 생태계가 다름. Java 코드·JDK HttpClient를 재사용하는 것이 이 SDK의 이점 |
| Spring AI MCP | Spring Boot 설정·starter·보안 통합이 필요 | README 기준으로 이 SDK를 확장하는 상위 통합. 경쟁 관계만으로 보기 어려움 |
| java-sdk 직접 사용 | Spring 없이 쓰거나 내부 통신 구조를 학습 | 기본 STDIO·JDK HTTP·Servlet 전송을 제공. 서버 실행 환경과 업무 권한 정책은 따로 구성 |
java-sdk는 통신 부품 상자, Spring AI는 그 부품을 Spring 애플리케이션에 설치하는 조립 장치에 가깝습니다. 어느 쪽이 항상 우월한지보다, 이미 사용하는 실행 환경과 필요한 제어 범위로 선택하면 됩니다.
conformance-tests/VALIDATION_RESULTS.md는 2026-10-02 검증 결과와 알려진 실패를 함께 기록합니다.
이런 문서는 표준 SDK를 평가할 때 중요한 단서입니다.
“공식”이라는 이름만으로 모든 규격·전송·실패 복구가 완전하다고 가정하지 않고, 어떤 조건을 검증했는지 확인하는 습관을 배울 수 있습니다.
루트 pom.xml은 io.modelcontextprotocol.sdk:mcp-parent:2.2.0-SNAPSHOT을 선언합니다.
반면 최신 공개 릴리스는 2026-10-16의 v2.1.0입니다.
배포판을 사용하는 방법과 개발 브랜치의 구현을 읽는 방법은 버전 기준을 분리해야 합니다.
아래 의존성 버전을 v2.1.0의 구성으로 그대로 옮기면 안 됩니다.
| 기술 | 실제 선언 | 맡는 역할과 근거 |
|---|---|---|
| Java | 컴파일 기준 17 | 루트 POM의 java.version, compiler의 release. 레코드와 제네릭으로 메시지 모델링 |
| Project Reactor | BOM 2026.0.0-RC1 | 루트 dependencyManagement. mcp-core/pom.xml의 reactor-core는 개별 버전을 쓰지 않음 |
| Jackson 3 | 3.1.8 | mcp-json-jackson3/pom.xml의 tools.jackson.core:jackson-databind. 기본 편의 모듈이 선택 |
| Jackson 2 | 2.22.4 | mcp-json-jackson2/pom.xml의 com.fasterxml.jackson.core:jackson-databind. 대체 구현 |
| Jackson annotations | 2.22 | mcp-core의 직접 의존성. JSON 바인딩을 추상화해도 스키마 어노테이션 의존성은 존재 |
| JSON Schema 검증 | 2.0.8 / 3.0.8 | 각 JSON 모듈의 com.networknt:json-schema-validator. Jackson 2 / 3에 맞춰 분리 |
| JDK HttpClient | JDK 내장 API | mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java. 별도 HTTP 클라이언트 라이브러리 없이 원격 통신 |
| Jakarta Servlet | 6.1.0 | mcp-core/pom.xml의 provided 의존성. 실제 컨테이너는 실행 애플리케이션 측이 제공 |
| SLF4J | 2.0.20 | 로그 API. Logback 1.6.5는 core에서 테스트 범위이며 운영 로그 백엔드는 별도 선택 |
scope=test는 테스트용, scope=provided는 실행 환경이 공급한다는 뜻입니다.
SNAPSHOT은 계속 바뀔 수 있는 개발 버전입니다.
Reactor의 개별 버전은 BOM이 관리하며, 표의 값은 reactor-core 버전이 아니라 BOM 버전입니다.제품용 프론트엔드 앱은 없습니다. 추적 파일에 package.json, React·Vue 코드, 프론트엔드 빌드 설정이 보이지 않습니다.
문서 사이트는 mkdocs.yml의 Material 테마와 Markdown으로 구성됩니다.
.github/workflows/docs.yml은 Python 3.x에서 mkdocs-material과 mike를 설치하지만,
두 패키지의 버전은 고정하지 않습니다. 문서 도구가 런타임 Java 의존성인 것은 아닙니다.
Spring은 core의 필수 웹 프레임워크가 아닙니다.
conformance-tests/client-spring-http-client/pom.xml은 Spring Boot 4.1.2, Spring AI 2.0.2,
MCP 보안 통합 0.1.14를 선언합니다.
이 값은 Spring 인증 검증 애플리케이션의 구성입니다.
README는 WebClient·WebFlux·WebMVC 통합을 Spring AI 2.0+의 영역으로 안내합니다.
| 도구 | 버전·설정 | 배울 점 |
|---|---|---|
| Maven Wrapper | Maven 3.9.16, wrapper 3.3.4 | .mvn/wrapper/maven-wrapper.properties로 빌드 도구 버전을 맞춤 |
| 컴파일·테스트 플러그인 | compiler 3.16.0, surefire 3.6.0 | 루트 POM에서 공통 실행 정책을 관리 |
| 코드 포맷 | spring-javaformat 0.0.48 | validate 단계에 연결. 테스트 전에 서식 오류로 빌드가 멈출 수 있음 |
| 단위 검증 | JUnit 6.1.3, AssertJ 3.27.7, Mockito 5.24.0 | 결과 검증과 가짜 의존성으로 조건 분리 |
| 통합 검증 | Testcontainers 2.0.5, Tomcat 11.0.27 | 컨테이너·실제 Servlet 전송을 쓰는 테스트 구성 |
| GitHub Actions | ubuntu-latest, Temurin 17 | .github/workflows/ci.yml의 기본 검증 및 Jackson 2 별도 작업 |
| MCP 적합성 도구 | 0.2.0-alpha.12, Node.js 22 | .github/workflows/conformance.yml의 일반 서버·클라이언트 작업. 인증 작업은 별도 action 설정 |
추적 파일에는 Dockerfile이나 Kubernetes 배포 명세가 없습니다. Docker는 README가 안내하는 전체 테스트 준비물이지, SDK를 사용하는 모든 Java 프로세스의 필수 런타임은 아닙니다. 데이터베이스나 벡터 저장소도 SDK의 필수 구성요소가 아닙니다.
<!-- Maven 의존성 예: 공개 배포판 2.1.0 사용 -->
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>2.1.0</version>
</dependency>
mcp/pom.xml은 mcp-core와 mcp-json-jackson3를 묶습니다.
커밋 기준 packaging은 jar이며 mcp/src/main/java/module-info.java도 있습니다.
업무 기능을 잔뜩 넣은 앱이라기보다, 기본 선택을 전달하는 편의 모듈입니다.
버전을 일괄 관리하려면 mcp-bom을 사용합니다.
Servlet은 제공 범위, Logback은 core의 테스트 범위, Spring은 별도 검증 모듈에 있습니다. POM의 버전만 수집하지 말고 모듈과 scope까지 읽어야 실제 설치 부담과 책임을 구분할 수 있습니다.
아래는 io.modelcontextprotocol.client와 io.modelcontextprotocol.server의 기존 API 경로입니다.
모델과 도구 선택을 담당하는 호스트는 SDK 바깥에 있고, Java SDK의 클라이언트는 그 호스트 안에서 통신을 맡을 수 있습니다.
표준 입출력과 HTTP는 같은 기능 호출을 서로 다른 통로로 전달합니다.
핵심 구현은 mcp-core/src/main/java/io/modelcontextprotocol/ 아래에 있습니다.
다이어그램의 클래스는 그 아래 client/, spec/, server/에서 찾을 수 있습니다.
전송별 세션 구현은 다르므로, 모든 경로가 정확히 같은 서버 세션 클래스를 거친다고 읽으면 안 됩니다.
mcp-core/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java의 callTool()은 별도의 통신 엔진을 만들지 않습니다.
McpAsyncClient인 delegate에 호출을 넘기고, 반환된 Mono에서 결과를 기다립니다.
// McpSyncClient.callTool()의 실제 위임 구조
public McpSchema.CallToolResult callTool(
McpSchema.CallToolRequest callToolRequest) {
return withProvidedContext(
this.delegate.callTool(callToolRequest)
).block();
}
Mono<T>는 나중에 결과 하나 또는 완료·오류를 전달하는 비동기 흐름입니다.
block()은 호출한 스레드를 기다리게 하므로, 비동기 이벤트 처리 스레드에서 무심코 사용하면 안 됩니다.서버 방향에도 같은 경계가 있습니다.
mcp-core/src/main/java/io/modelcontextprotocol/server/McpServerFeatures.java의 AsyncToolSpecification.fromSync()는
동기 핸들러를 Mono.fromCallable()로 감싸며, 일반 경로에서는 Schedulers.boundedElastic()에 실행을 맡깁니다.
immediate 옵션이 참인 경로는 스케줄러 전환을 생략합니다.
“동기 API를 지원한다”는 말 뒤에 실행 스레드 선택까지 있다는 점이 학습 포인트입니다.
mcp-core/src/main/java/io/modelcontextprotocol/spec/McpTransport.java는 sendMessage(), closeGracefully(),
unmarshalFrom() 같은 계약을 정의합니다.
StdioClientTransport.connect()는 ProcessBuilder로 서버 프로세스를 시작하고,
HTTP 구현은 JDK HttpClient를 사용합니다.
호출자는 “도구를 실행한다”는 의도를 유지하면서 전송 전략을 바꿀 수 있습니다.
JSON 쪽에서는 mcp-core/src/main/java/io/modelcontextprotocol/json/McpJsonMapper.java가 readValue(), convertValue(),
writeValueAsString() 등을 정의합니다.
mcp-json-jackson3/src/main/java/io/modelcontextprotocol/json/jackson3/JacksonMcpJsonMapper.java는
Jackson의 JsonMapper 호출을 이 인터페이스에 맞추는 Adapter입니다.
기본 구현은 McpJsonDefaults.getMapper()에서 가져옵니다.
내부의 McpServiceLoader.getDefault()는 일반 Java 환경에서
ServiceLoader.load(...).findFirst()로 공급자를 찾고 결과를 저장합니다.
Jackson 3 공급자 이름은 mcp-json-jackson3/src/main/resources/META-INF/services/io.modelcontextprotocol.json.McpJsonMapperSupplier에 적혀 있습니다.
가전제품은 콘센트 규격만 알고, 벽 안의 발전 방식을 몰라도 됩니다. 인터페이스가 콘센트라면 Jackson 구현은 어댑터입니다. 다만 이 코드의 기본 탐색은 첫 공급자를 고르므로, 여러 구현을 동시에 넣을 때 선택 순서를 당연하게 가정하면 안 됩니다.
mcp-core/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java의 generateRequestId()는 세션 접두어와
AtomicLong 카운터를 합쳐 ID를 만듭니다.
sendRequest()는 그 ID를 키로 pendingResponses에 응답 대기자를 저장합니다.
handle()에 응답이 들어오면 같은 ID의 대기자를 꺼내 완료합니다.
자료구조는 ConcurrentHashMap<Object, MonoSink<McpSchema.JSONRPCResponse>>입니다.
이 구조에서 “동시성”은 단순히 스레드를 많이 만드는 문제가 아니라,
어느 응답이 누구의 결과인지 안전하게 연결하는 문제라는 사실이 드러납니다.
sendRequest()의 timeout(requestTimeout)은 응답을 무한정 기다리지 않게 합니다.
메시지를 보내기 전에는 LifecycleInitializer.withInitialization()가 관문 역할을 합니다.
AtomicReference.compareAndExchange()로 첫 초기화 작업을 정하고,
동시에 들어온 호출은 기존 초기화 결과를 기다립니다.
doInitialize()는 initialize 요청, 버전 검사, notifications/initialized 통지를 수행합니다.
McpAsyncClient.callTool()은 이 관문 안에서 서버의 tools capability를 확인한 뒤 요청을 보냅니다.
명시적으로 initialize()를 호출해 흐름을 배울 수도 있지만, 소스에는 일반 작업 시 초기화를 보장하는 경로도 있습니다.
기존 API의 이 초기화 절차를 뒤에서 설명할 modern 서버에 그대로 적용해서는 안 됩니다.
분석 대상 커밋의 제목은 “Notify resources list_changed when removing a resource template (#1266)”입니다.
현재 McpAsyncServer.removeResourceTemplate()를 읽으면
실제 템플릿이 제거되고 resources.listChanged가 참일 때 notifyResourcesListChanged()로 이어집니다.
아래는 조건을 드러내기 위해 축약한 흐름입니다.
// removeResourceTemplate(): 조건 흐름을 보이기 위한 축약
return Mono.defer(() -> {
var removed = this.resourceTemplates.remove(uriTemplate);
if (removed != null
&& Boolean.TRUE.equals(
this.serverCapabilities.resources().listChanged())) {
return notifyResourcesListChanged();
}
return Mono.empty();
});
통지 메서드는 전송 Provider의 notifyClients()에
notifications/resources/list_changed를 전달합니다.
템플릿이 없거나 resources.listChanged가 true가 아니면 알리지 않습니다.
resources capability 자체가 없으면 삭제 요청은 IllegalStateException으로 실패합니다.
자원 내용 변경 알림인 notifications/resources/updated와도 구분해야 합니다.
서버의 자료구조를 바꾸는 일과, 클라이언트가 가진 목록을 갱신하게 만드는 일은 별개의 책임입니다. 이 짧은 메서드 하나로 캐시·관찰자·기능 협상이 만나는 지점을 공부할 수 있습니다.
mcp-core/src/main/java/io/modelcontextprotocol/modern/McpSchema.java는 LATEST_PROTOCOL_VERSION = "2026-07-28"을 선언합니다.
mcp-core/src/main/java/io/modelcontextprotocol/modern/server/McpServer.java는 요청마다 _meta의 버전과 capabilities를 검사하고,
dispatch()에서 해당 McpFeature로 보냅니다.
ToolsFeature.handle()는 repository의 list(), find(), call()을 사용합니다.
이 경로는 stateless 수명주기와 server/discover를 다룹니다.
기존 패키지의 McpServer와 클래스 이름이 같아도 계약이 다르므로 import를 확인해야 합니다.
ROADMAP.md는 이 규격 지원을 3.x 계획으로 설명하지만, 현재 POM은 2.2.0-SNAPSHOT입니다.
코드가 존재한다는 사실과 안정된 배포 API로 약속되었다는 사실을 구분하는 사례입니다.
README는 공개 API를 Reactive Streams 인터페이스 중심으로 바꿀 계획을 설명합니다.
그러나 현재 McpAsyncClient.callTool()과 McpTransport.sendMessage()의 반환형은 Mono입니다.
HTTP 내부에서 Publisher를 사용한다는 사실을 SDK 전체의 Reactor 의존성 제거로 해석하면 안 됩니다.
mcp-json-jackson3가 core에 의존해야 SDK 인터페이스를 구현할 수 있습니다.
그런데 core 테스트가 다시 Jackson 3 모듈에 의존하면 모듈 간 순환이 생깁니다.
README는 JSON 구현이 필요한 통합 테스트를 mcp-test로 옮긴 이유를 이 의존 관계로 설명합니다.
테스트 폴더 배치는 실행 속도뿐 아니라 모듈 의존 방향의 결과이기도 합니다.
| 궁금한 문제 | 처음 열 파일 | 이어 볼 곳 |
|---|---|---|
| 동기 호출은 어떻게 동작할까 | mcp-core/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java | McpAsyncClient.callTool() |
| 응답이 뒤섞이면 어떻게 찾을까 | mcp-core/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java | sendRequest()와 handle() |
| 도구 인자가 틀리면 어떻게 될까 | mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java | mcp-core/src/main/java/io/modelcontextprotocol/util/ToolInputValidator.java |
| JSON 구현은 누가 선택할까 | mcp-core/src/main/java/io/modelcontextprotocol/json/McpJsonDefaults.java | mcp-core/src/main/java/io/modelcontextprotocol/util/McpServiceLoader.java와 공급자 등록 파일 |
| 새 규격의 요청 처리는 어떨까 | mcp-core/src/main/java/io/modelcontextprotocol/modern/server/McpServer.java | mcp-core/src/main/java/io/modelcontextprotocol/modern/server/feature/ToolsFeature.java |
표의 소스 파일은 모두 mcp-core/src/main/java/io/modelcontextprotocol/ 아래입니다.
처음부터 6,000줄이 넘는 스키마 파일을 정독하기보다, 호출 지점에서 필요한 레코드로 이동하는 편이 이해하기 쉽습니다.
| 분류 | 파일 수·물리적 줄 수 | 해석 |
|---|---|---|
| Java 전체 | 302개 · 74,017줄 | SDK·검증 앱·테스트·공용 테스트 부품 포함 |
| Java main 경로 | 173개 · 44,647줄 | 운영 코드 외에 conformance 앱과 테스트 지원 main 코드도 포함 |
| Java test 경로 | 129개 · 29,370줄 | 전체 Java 물리적 줄 수의 약 40% |
| core 운영 경로 | 142개 · 35,282줄 | mcp-core/src/main/java만 집계 |
| XML / YAML | 22개 · 1,662줄 / 10개 · 561줄 | POM·서비스 설정·워크플로·문서 설정 |
| Markdown | 29개 · 4,548줄 | 문서·가이드·검증 결과·정책 |
집계 기준은 커밋의 추적 파일이며, 빈 줄과 주석, 마지막 개행이 없는 줄까지 포함한 물리적 줄 수입니다.
생성된 target/ 출력이나 Git 내부 파일은 포함하지 않습니다.
따라서 74,017줄을 전부 업무 로직이라고 해석하거나, 테스트 줄 수를 기능 커버리지 비율로 바꿔 말할 수는 없습니다.
McpSchema.CallToolRequest는 name, arguments, meta를 가진 레코드입니다.
builder(String name)은 빈 이름을 막지만, JSON을 읽는 fromJson()은 누락된 이름을 빈 문자열로 보정하고 경고합니다.
“직접 생성할 때의 검증”과 “외부 메시지를 받아들일 때의 호환성 처리”가 같은 규칙은 아닙니다.
// 기존 spec 패키지의 요청 객체를 만드는 학습용 예
var request = McpSchema.CallToolRequest.builder("hello")
.arguments(Map.of("name", "민수"))
.build();
// arguments()는 Map<String, Object>를 돌려준다.
Object name = request.arguments().get("name");
작은 실습: builder(""), 직접 생성, 이름이 빠진 JSON 역직렬화를 각각 비교해 보세요.
어느 층에서 예외가 나고, 어느 층에서 보정되는지 표로 정리하면
MIGRATION-2.0.md가 설명하는 required-first Builder와 wire 호환성의 의미가 보입니다.
Tool.inputSchema의 타입은 좁은 전용 레코드가 아닌 Map<String, Object>입니다.
JSON Schema 키워드를 보존하기 위한 선택입니다.
다음 예시는 이름 문자열 하나를 받는 도구를 구성하는 메서드 안의 코드이며, 웹 서버 실행 전체를 포함하지 않습니다.
import java.util.List;
import java.util.Map;
import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema;
// 아래 구문은 애플리케이션의 서버 구성 메서드 안에 둔다.
Map<String, Object> inputSchema = Map.of(
"type", "object",
"properties", Map.of(
"name", Map.of("type", "string", "minLength", 1)),
"required", List.of("name"),
"additionalProperties", false
);
var hello = McpServerFeatures.SyncToolSpecification.builder()
.tool(McpSchema.Tool.builder("hello", inputSchema)
.description("이름을 받아 한국어 인사말을 반환합니다.")
.build())
.callHandler((exchange, request) -> {
String name = (String) request.arguments().get("name");
return McpSchema.CallToolResult.builder()
.content(List.of(McpSchema.TextContent
.builder("안녕하세요, " + name + "님!").build()))
.isError(false)
.build();
})
.build();
var transport = new StdioServerTransportProvider(
McpJsonDefaults.getMapper());
var server = McpServer.sync(transport)
.serverInfo("study-server", "1.0.0")
.capabilities(McpSchema.ServerCapabilities.builder()
.tools(false).build())
.tools(hello)
.build();
tools(false)의 false는 도구 기능을 끈다는 뜻이 아닙니다.
tools capability를 만들되 목록 변경 알림을 선언하지 않는 값입니다.
서버 구성의 validateToolInputs 기본값은 true이며,
ToolInputValidator.validate()가 핸들러 실행 전에 인자를 검사합니다. 검증이 비활성화되었거나 입력 스키마가 비어 있거나 검증기가 없으면 검사를 건너뜁니다.
서버의 종료는 애플리케이션 수명주기에 server.closeGracefully()를 연결해 처리해야 합니다.
작은 실습: 빈 이름, 숫자 이름, 추가 필드, 이름 누락을 각각 보내 보세요.
현재 검증 경로는 잘못된 입력을 CallToolResult의 isError=true로 돌려줍니다.
존재하지 않는 도구 이름의 JSON-RPC 오류와 구분하면 프로토콜 오류와 업무 결과 오류를 함께 배울 수 있습니다.
Mono를 반환받았다는 사실만으로 그 작업이 끝난 것은 아닙니다.
Mono.defer()는 구독 시점에 작업을 구성합니다.
McpAsyncServer.removeResourceTemplate()가 그 안에서 Map을 수정하는 이유를 살펴보세요.
다만 이 SDK 내부에는 생성 시 연결을 구독하는 코드도 있으므로, “모든 동작이 항상 지연된다”고 일반화하면 안 됩니다.
// 비동기 호출을 연결하는 예: 완료·오류도 흐름의 일부다.
asyncClient.callTool(request)
.map(result -> result.isError())
.subscribe(
isError -> logger.info("도구 오류 여부: {}", isError),
error -> logger.error("통신 실패", error)
);
작은 실습: mock 전송으로 응답을 늦추고 타임아웃을 짧게 설정해 결과를 관찰하세요.
McpClient.SyncSpec와 AsyncSpec의 요청·초기화 제한 시간은 각각 기본 20초입니다.
타임아웃은 대기 정책이며, 이미 수행된 외부 작업을 되돌리는 트랜잭션은 아닙니다.
McpAsyncClient.listTools()는 expand()로 nextCursor를 따라가고
reduceWith()로 도구를 모읍니다.
반면 listTools(String cursor)는 지정한 페이지를 조회합니다.
목록이 크면 전체를 한 번에 모으는 편의성과 메모리 사용 사이의 선택이 생깁니다.
작은 실습: 두 페이지를 반환하는 mock 서버를 만들고 두 메서드의 요청 횟수를 비교하세요. 잘못된 서버가 같은 커서를 반복할 때를 별도 실패 사례로 설계해 보는 것도 좋습니다.
HttpServletStreamableServerTransportProvider.doPost()는 요청 크기, 헤더,
Content-Type 등을 먼저 확인합니다.
Accept에 application/json과 text/event-stream이 필요한 경로도 있습니다.
JSON 문법이 맞더라도 전송 계층에서 거절될 수 있습니다.
STDIO에서는 System.out이 프로토콜 메시지 통로입니다.
학습 중 진단 문자열을 임의로 출력하면 상대가 JSON으로 읽지 못할 수 있습니다.
로그를 stderr 쪽으로 보내도록 구성하고, UTF-8 메시지가 여러 바이트 조각으로 나뉘는 경우도 살펴보세요.
mcp-core/src/test/java/io/modelcontextprotocol/util/ToolInputValidatorTests.java는
검증기 부재·비활성화·잘못된 입력·null 인자를 나눕니다.
mcp-core/src/test/java/io/modelcontextprotocol/client/transport/Utf8LineDecoderBoundTests.java는 바이트 수가 아닌 디코딩된 문자 수에 따른 줄 길이 경계를,
mcp-test/src/test/java/io/modelcontextprotocol/server/DerivedCapabilitiesRuntimeChangesTests.java는
자동으로 파생된 capabilities의 목록 변경 알림 값이 null인 상태에서 런타임 변경을 다룹니다.
통신 라이브러리에서 배울 핵심은 성공 예제보다, 입력·상태·시간·종료 경계를 나누어 검증하는 방법입니다. 작은 실습: 테스트 하나를 읽고 “이 테스트가 없으면 어떤 사용자 문제가 생기는가”를 한 문장으로 적어 보세요.
SDK 자체는 GPU 계산을 하지 않습니다. 모델을 별도로 실행하거나 이미지·오디오를 처리하는 도구를 붙이면 그 기능의 요구사항이 더해집니다. 실행 부담은 MCP 라이브러리보다 연결한 도구와 모델, 동시 요청 수에 더 크게 좌우됩니다.
| 상황 | 필요한 소프트웨어 | 하드웨어 판단 |
|---|---|---|
| 소스 읽기 | 텍스트 편집기 또는 Java IDE | 일반 개발용 PC. 빌드·Docker·GPU 불필요 |
| 작은 STDIO 도구 | 기준 Java 17+, JSON 구현 모듈 | 단순 텍스트 도구라면 보통 CPU 환경으로 시작 가능. 공식 최소 RAM 수치는 없음 |
| HTTP 서버 | Servlet 6.1 API에 맞는 컨테이너 또는 외부 Spring 통합 | 동시 연결·스트림·도구 응답 크기를 기준으로 측정 |
| 소스 빌드 | JDK 17, Wrapper의 Maven 3.9.16 | 의존성 캐시·컴파일 출력 공간 필요. 첫 다운로드에는 네트워크 필요 |
| 전체 테스트 | README 기준 Docker·npx, CI의 적합성 작업은 Node.js 22 | 컨테이너와 다중 JVM을 고려해 여유 확보 |
학습용 권장 출발점: 2~4코어 CPU, RAM 8GB 이상, 남은 디스크 수 GB를 잡을 수 있습니다. IDE와 Docker를 함께 사용하면 RAM 16GB가 더 여유롭습니다. 이는 공식 보장 사양이나 실측 결과가 아닌 일반 개발 환경을 위한 추정입니다. 큰 모델 가중치 저장 공간은 이 SDK의 크기와 별도로 계산해야 합니다.
Servlet 전송 객체를 만들었다고 포트가 열리지는 않습니다.
conformance-tests/server-servlet/src/main/java/io/modelcontextprotocol/conformance/server/ConformanceServlet.java의
main()은 전송 Provider를 만든 뒤 Tomcat에 Servlet을 등록하고 서버를 시작합니다.
그 검증 앱은 8080 포트와 /mcp 경로를 사용합니다.
이 값은 SDK 전체에 강제되는 포트가 아닙니다.
기존 Streamable HTTP 서버 Builder의 기본값은 mcpEndpoint="/mcp",
keepAliveInterval=30분, sessionSweepInterval=30분입니다.
열린 스트림이 있는 세션의 keep-alive와, 열린 스트림도 최근 요청도 없는 세션의 정리는 다른 목적입니다.
지연 시간뿐 아니라 열린 연결 수, 메모리, 타임아웃, 종료 후 남는 프로세스를 관찰해야 합니다.
기존 Servlet 전송 Builder는 ServerHttpHeaderValidator.NOOP를 기본으로 둡니다.
Origin·Host 검증이 필요하면 명시적으로 구성해야 합니다.
이 검증은 사용자 인증이나 “어떤 재고를 조회할 수 있는가”라는 업무 권한 정책을 대신하지 않습니다.
STDIO 도구도 실행한 사용자 권한을 자동으로 제한하지 않습니다. 학습 도구는 먼저 정해진 문자열·메모리 데이터로 구성하면 통신 구조를 이해하기 쉽습니다.
실습용 앱은 SDK 소스와 별도 프로젝트로 구성하고, 공개 배포판을 쓸 때는 의존성을 2.1.0으로 고정하세요.
아래 소스 탐색과 회귀 조건은 커밋 54e8b61 기준입니다.
modern API까지 실험하려면 그 코드가 포함된 버전과 별도의 프로토콜 계약을 먼저 확인해야 합니다.
만들 것: 6절의 hello 도구를 STDIO 서버에 등록합니다.
클라이언트는 ServerParameters로 Java 실행 명령을 정하고 StdioClientTransport로 연결합니다.
McpClient.sync(transport).build()로 생성한 뒤 초기화·목록 조회·호출 순서를 관찰합니다.
완료 조건: 목록에 hello가 나타나고, 이름을 보내면 인사말이 반환됩니다.
서버 stdout에는 임의 로그가 없어야 하며, 클라이언트 종료 후 자식 프로세스 정리도 확인합니다.
읽을 곳: StdioClientTransport.connect(),
McpSyncClient.listTools(), callTool().
배울 것: 메서드 호출과 프로세스 간 메시지 교환의 연결.
만들 것: 정상 이름, 누락, 숫자, 빈 문자열, 추가 필드를 보내는 표를 작성합니다.
각 경우의 isError, 메시지, 핸들러 실행 여부를 기록합니다.
없는 도구 이름도 한 번 호출해 JSON-RPC 오류와 비교합니다.
완료 조건: 잘못된 인자가 핸들러에 도달하지 않는지 확인하고,
required와 minLength, additionalProperties가 각각 막는 입력을 설명할 수 있어야 합니다.
읽을 곳: McpAsyncServer.toolsCallRequestHandler(),
ToolInputValidator.validate().
배울 것: JSON 구문 검증, 스키마 검증, 업무 검증을 분리하는 법.
만들 것: 업무 핸들러를 유지하고 서버 전송을 Servlet Streamable HTTP로 교체합니다.
Servlet 등록과 컨테이너 시작은 ConformanceServlet.main()의 구성을 참고합니다.
클라이언트는 아래와 같이 JDK HTTP 전송을 선택합니다.
완료 조건: 도구의 입력·출력은 같고, 두 전송의 설정·프로세스 구성·종료 방식은 표로 비교됩니다. 잘못된 Content-Type 요청과 정상 JSON-RPC 요청도 구분해서 관찰합니다.
배울 것: 인터페이스가 같아도 배포와 네트워크 조건은 달라진다는 점.
// HTTP 실습 앱 안의 클라이언트 구성: 기존 client/spec API
var transport = HttpClientStreamableHttpTransport
.builder("http://localhost:8080")
.endpoint("/mcp")
.build();
try (var client = McpClient.sync(transport).build()) {
client.initialize();
var tools = client.listTools();
var result = client.callTool(
McpSchema.CallToolRequest.builder("hello")
.arguments(Map.of("name", "민수"))
.build());
// tools와 result를 실습 앱의 로그·검증 코드에서 확인한다.
}
만들 것: study://notes/{id} 템플릿을 등록합니다.
resources.listChanged가 true·false·null인 구성과,
삭제 대상이 존재·부재인 경우를 조합해 알림 횟수를 기록합니다.
완료 조건: “실제 삭제가 있었고 listChanged=true일 때”를 구분하고,
없는 템플릿을 반복 삭제해도 추가 알림이 생기지 않는지 확인합니다.
비동기 API에서는 반환된 Mono가 구독되는 시점도 기록합니다.
읽을 곳: McpAsyncServer.removeResourceTemplate(),
notifyResourcesListChanged(), mcp-test/src/test/java/io/modelcontextprotocol/server/DerivedCapabilitiesRuntimeChangesTests.java.
배울 것: 상태 변경·구독·캐시 갱신·선택 capability의 관계.
만들 것: 실제 외부 서비스 대신 mock 전송을 사용합니다. 요청 A와 B를 보내고 B 응답을 먼저 돌려주며, C는 응답하지 않게 합니다. 요청 ID와 각 구독자의 결과·오류를 기록합니다.
완료 조건: A와 B가 서로 다른 결과를 정확히 받고, C는 설정한 제한 시간 뒤 실패해야 합니다. 이후 늦은 응답·세션 종료를 추가해 대기 중인 요청과 Map의 변화를 관찰합니다. 호출자 타임아웃과 원격 작업 취소가 같은지 여부를 설명합니다.
읽을 곳: McpClientSession.sendRequest(), handle(),
dismissPendingResponses(), mcp-core/src/test/java/io/modelcontextprotocol/spec/McpClientSessionTests.java.
배울 것: 순서 독립성, 시간 제한, 자원 정리를 분리해 검증하는 법.
실습의 완료 기준은 실행 화면 한 장보다, 정상·실패·종료 조건을 다른 사람이 재현할 수 있게 설명하는 것입니다. 전체 빌드나 적합성 검증은 Docker·npx 등 준비 조건을 갖춘 뒤 공식 개발 문서의 절차로 진행하세요.
주당 3~5시간 정도를 가정한 학습 제안입니다. Java 경험이 있다면 1주차를 줄이고 테스트·네트워크에 시간을 더 쓸 수 있습니다. 각 주차마다 설명 문서 하나보다 작은 산출물 하나를 남기는 방식입니다.
| 주차 | 공부할 주제 | 이 저장소에서 읽을 곳 | 완료 산출물 |
|---|---|---|---|
| 1주 | Java 17 레코드·제네릭·Map·람다 | McpSchema.CallToolRequest, Tool, TypeRef | 입력 JSON과 Java 필드의 대응표, 객체 생성 예 |
| 2주 | JSON-RPC·MCP의 도구와 리소스 | McpSchema 상수, LifecycleInitializer | 초기화→목록→호출의 메시지 순서도 |
| 3주 | Mono·Flux·구독·스케줄러·오류 | McpAsyncClient, McpServerFeatures.AsyncToolSpecification.fromSync() | 동기·비동기 호출 비교, 타임아웃 관찰 기록 |
| 4주 | HTTP·SSE·Servlet·STDIO | 클라이언트·서버의 transport/, ConformanceServlet | 같은 도구를 두 전송으로 연결한 구성도 |
| 5주 | JSON Schema·SPI·모듈 의존성 | ToolInputValidator, McpJsonDefaults, 각 POM | 잘못된 입력 조건표, JSON 구현 의존 그래프 |
| 6주 | 회귀 테스트·적합성·버전 호환성 | mcp-test, VALIDATION_RESULTS.md, MIGRATION-2.0.md | 선택한 기능 하나의 검증 범위와 알려진 한계 |
Mono를 만드는 시점과 그 안의 작업이 수행되는 시점은 어떻게 다른가?block()은 어느 스레드를 기다리게 하며, 그 스레드는 다른 요청을 처리해야 하는가?Mono<Void>는 무엇을 표현하는가?다음 단계에서는 resources 구독, 출력 스키마 검증, 요청 커스터마이저, modern의 repository 중 하나만 고르세요. 인터페이스, 기본 구현, 정상 테스트, 실패 테스트, 버전 변경 안내를 한 묶음으로 읽으면 공개 API가 유지해야 할 약속을 이해할 수 있습니다.
프로젝트 전체를 한 번 훑는 것보다, 입력부터 테스트까지 한 경로를 끝까지 설명할 수 있는 것이 더 확실한 학습입니다. 마지막에는 도구 하나의 사용법을 넘어, 그 도구를 안전하게 호출하게 하는 계약까지 자신의 말로 설명해 보세요.
“호출자 → 초기화 → 요청 ID → 전송 → 스키마 검증 → 핸들러 → 응답”를 그리세요. 화살표마다 실제 파일과 메서드 이름을 하나씩 붙이면, 다음 SDK를 읽을 때도 사용할 수 있는 분석 방법이 됩니다.
| 용어 | 쉬운 설명 |
|---|---|
| SDK | 특정 규격이나 서비스를 이용하는 데 필요한 코드 부품 묶음. 여기서는 MCP 클라이언트·서버 구현을 제공 |
| Host / Client / Server | Host는 AI 기능을 포함한 애플리케이션, Client는 서버와 통신하는 구성요소, Server는 도구·정보를 제공하는 상대 |
| JSON-RPC | method·params로 호출을 표현하고 id로 결과를 연결하는 메시지 규칙 |
| Notification | 응답을 요구하지 않는 통지. 목록 변경이나 준비 완료를 알릴 때 사용 |
| Capability | 상대가 제공하는 기능의 선언. tools 유무와 목록 변경 통지 지원 여부처럼 세부 값도 구분 |
| Transport | 메시지가 이동하는 통로. 여기서는 STDIO·SSE·Streamable HTTP 구현을 구분 |
| STDIO | 프로세스의 표준 입력·출력. 로컬 서버를 자식 프로세스로 실행하고 파이프로 통신할 때 사용 |
| SSE | HTTP 응답을 열어 두고 서버가 이벤트를 연속 전달하는 형식. 과거 SSE 전송과 Streamable HTTP의 SSE 응답은 같은 API가 아님 |
| Streamable HTTP | HTTP로 MCP 메시지를 주고받으며 필요에 따라 JSON 응답 또는 SSE 스트림을 사용하는 전송 방식 |
| Serialization | Java 객체를 전송할 JSON으로 바꾸는 일. 역방향은 역직렬화 |
| JSON Schema | JSON 값의 필드·타입·필수 조건을 설명하는 규칙. 도구 입력과 구조화 출력의 계약으로 사용 |
| Record / Builder | Record는 데이터를 표현하는 Java 문법, Builder는 필수값과 선택값을 조립해 객체를 만드는 방식 |
| Mono / Flux | Reactor의 비동기 흐름. Mono는 최대 하나의 값, Flux는 여러 값을 전달하며 완료·오류도 표현 |
| Backpressure | 소비자가 처리할 수 있는 양에 맞춰 생산 속도를 조절하는 원리. 모든 버퍼가 자동으로 제한된다는 뜻은 아님 |
| Scheduler | 비동기 작업을 어느 실행 자원에서 처리할지 정하는 구성요소. boundedElastic은 블로킹 작업을 옮길 때 사용 |
| Correlation ID | 요청과 응답을 연결하는 식별자. 이 코드에서는 세션 접두어와 카운터로 요청 ID 생성 |
| SPI / ServiceLoader | 구현을 추가하는 확장 계약과 Java의 공급자 탐색 기능. 기본 JSON 구현을 찾는 데 사용 |
| Facade / Adapter | 복잡한 내부를 간단한 창구로 감싸거나, 다른 라이브러리의 API를 원하는 계약에 맞추는 설계 |
| BOM / SNAPSHOT | BOM은 의존성 버전 조합표, SNAPSHOT은 변경 가능한 개발 빌드 버전 |
| Stateless | 요청 처리에 이전 연결의 초기화 상태를 전제로 삼지 않는 방식. 업무 데이터나 서버 객체가 전혀 없다는 뜻은 아님 |
| Conformance / SEP | Conformance는 규격에 맞는지 확인하는 검증, SEP는 MCP 규격 변경 제안의 식별 체계 |
| MRTR | 여러 차례 입력과 응답을 주고받으며 작업을 이어 가는 흐름. modern 경로의 입력 요청·requestState와 관련 |
| 항목 | 상태 | 기준 |
|---|---|---|
| 저장소·선정 | modelcontextprotocol/java-sdk · GitHub Trending Java Weekly #5 | 2026-10-23 집계 |
| 스타 / 포크 | 3,736 / 1,112 | 2026-10-23 GitHub 기준 |
| 주 언어·생성일 | Java · 2025-01-20 | 2026-10-23 GitHub 기준 |
| 최근 push | 2026-10-23 | 2026-10-23 GitHub 기준 |
| 최신 릴리스 | v2.1.0 · 2026-10-16 | 2026-10-23 GitHub 기준 |
| 분석 커밋 | 54e8b61 · 2026-10-23 Notify resources list_changed when removing a resource template (#1266) | 소스·경로·기본값의 기준점 |
| 개발 버전 | 2.2.0-SNAPSHOT | 루트 pom.xml |
| 코드 규모 | Java 302개·74,017줄, XML 1,662줄, YAML 561줄 | 추적 파일의 빈 줄·주석 포함 |
| 핵심 Java 규모 | core main 35,282줄 / 전체 Java test 29,370줄 | 각 경로별 물리적 줄 수 |
| 프로토콜 경로 | 기존 API의 2025-11-25 지원 경로와 modern의 2026-07-28 경로 공존 | spec·modern 패키지와 검증 문서 |
| 라이선스 | MIT | LICENSE |
검증 숫자: README는 conformance 0.1.15와 서버 40/40, 인증 12/14를 소개합니다.
더 구체적인 VALIDATION_RESULTS.md는 2026-10-02 기준 일반 서버 73/73 checks,
modern 필수 37/37 scenarios, 인증 14/14 scenarios·193 checks를 기록합니다.
도구 버전과 집계 단위가 달라 숫자를 직접 합치거나 같은 시험으로 비교할 수 없습니다.
알려진 실패: 그 보고서는 클라이언트 sse-retry의 retry 시간·Last-Event-ID 문제와,
modern의 SEP-2243 사용자 헤더 검증 및 tasks 확장 미구현을 명시합니다.
필수 시나리오 통과 숫자는 미채점 항목까지 모두 구현했다는 보장이 아닙니다.
인증 결과는 Spring 보안 통합을 사용하는 검증 앱의 결과이지 core 단독 인증 기능의 증거가 아닙니다.
문서와 API: ROADMAP.md에는 2026년 9월 milestone 계획과 2.0.1 중심 설명이 남아 있습니다.
일정을 현재의 완료 상태로 해석하지 말고, 배포 태그와 패키지·규격을 함께 확인해야 합니다.
latest 문서는 이후 바뀔 수 있으므로 이 글의 커밋 고정 링크와 차이가 생길 수 있습니다.
예제와 성능: 코드는 학습용 발췌이며 전체 실행 앱·컨테이너 설정을 모두 포함하지 않습니다. 사양표의 RAM 권장은 추정이고 처리량·지연 시간의 실측 보장이 아닙니다. 업무 권한·모델 제공자 연결·실제 데이터 처리는 사용하는 애플리케이션에서 별도로 설계해야 합니다.