23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
저희 팀은 내부적으로 ‘대화 요약 데이터’, ‘사용자 프로파일’, ‘서비스 사용 이력’ 등 다양한 도메인의 REST API를 이미 구현하여 여러 시스템에서 활용하고 있었습니다.
그러나 이러한 API를 LLM 기반 어플리케이션에서 직접 호출하여 사용하도록 하다 보니 몇 가지 문제점이 드러났습니다.
API를 사용하는 각 어플리케이션이 API 명세와 파라미터 정보를 별도로 관리하게 되어, 명세의 일관성이 떨어졌습니다.
LLM에서 사용할 Tool로 추상화하는 작업이 반복적으로 발생했습니다.
Tool 구성과 연동이 복잡하고 시간이 많이 소요되어 개발 속도와 효율성에도 부정적인 영향을 끼쳤습니다.
이러한 문제를 해결하기 위해, 저희는 기존 API들을 Model Context Protocol(MCP) 기반의 표준 구조로 빠르고 유연하게 통합할 수 있는 방안을 설계하고 구현하게 되었습니다.
MCP(Model Context Protocol)는 LLM 기반 애플리케이션과 외부의 데이터 또는 도구를 연결해주는 표준화된 프로토콜입니다.
이를 통해 LLM이 다양한 외부 기능을 일관된 방식으로 호출할 수 있도록 지원합니다.
MCP는 세 가지 주요 구성 요소로 이루어져 있습니다
Host
LLM이 포함된 애플리케이션으로, Client를 통해 필요한 기능을 요청합니다.
Client
Host와 Server 사이의 통신을 중개하는 역할을 하며, 요청과 응답을 관리합니다.
Server
기능(capability)을 선언하고, 요청된 Tool, Prompt, Resource 등을 실제로 실행 및 반환합니다.
이러한 구조를 기반으로 MCP는 다양한 기능을 안정적이고 확장 가능한 방식으로 연결할 수 있으며, 이를 위해 Client와 Server는 공통의 통신 규약(JSON-RPC 2.0)을 따릅니다.
MCP에서 이루어지는 모든 통신은 JSON-RPC 2.0 포맷을 기반으로 하며, 이때 사용되는 요청(Request)과 응답(Response)의 형식은 아래와 같습니다.
Request
{
jsonrpc: "2.0";
id: string | number;
method: string;
params?: {
[key: string]: unknown;
};
}Response
{
jsonrpc: "2.0";
id: string | number;
result?: {
[key: string]: unknown;
}
error?: {
code: number;
message: string;
data?: unknown;
}
}Client와 Server는 초기화부터 종료까지의 명확한 생명주기(lifecycle)를 따라 상호작용하며, 아래와 같은 3단계로 구성됩니다.
Initialization Phase
Client는 Server에 초기화를 요청합니다. (method: initialize)
Server는 해당 요청에 대한 응답으로, 지원 가능한 capabilities 정보와 protocolVersion을 제공합니다.
이후 Client는 초기화 응답을 정상적으로 수신했음을 알리는 알림 메시지를 Server로 전달합니다. (method: notifications/initialized)
Operation Phase
Tool 정보 조회 및 Tool 실행 요청 등 다양한 상호작용을 반복적으로 처리합니다.
Shutdown Phase
Client와 Server 간의 연결을 정리하고, 통신 세션을 정상적으로 종료합니다.
또한, MCP는 다양한 통신 환경을 고려해 다음과 같은 Transport 방식을 지원합니다:
stdio
sse (Server-Sent Events)
streamable http (도입일: 2025-03-26)
MCP의 규격을 충족하기 위해 Python, TypeScript, Java 등 다양한 언어용 SDK가 제공되며, 이 중에서 MCP Java SDK를 선택 했습니다.
이 SDK는 Server, Session, Transport 등 MCP의 핵심 계층을 모두 구현할 수 있도록 구성되어 있습니다.
Java SDK를 통해 MCP Server를 구성하면, Tool의 정의부터 등록, 실행까지 모두 표준화된 방식으로 처리할 수 있습니다.
MCP Server
MCP Server는 제공 가능한 기능(capabilities)과 세션(session)을 관리합니다.
Client의 요청은 MCP Session을 통해 처리됩니다.
MCP Session
초기화 단계(initialization phase)와 운영 단계(operation phase)에 해당하는 로직과 처리를 담당합니다.
MCP Transport
JSON-RPC 메시지의 직렬화/역직렬화 처리를 포함하여, stdio, sse, streamable-http와 같은 Transport 스펙을 구현합니다.
MCP Server는 생성 시, 제공 가능한 기능(capabilities)과 함께 이를 처리할 Tool, Prompt, Resource 등의 구현체를 함께 등록해야 합니다.
아래는 Spring 기반 프로젝트에서 MCP Tool을 구성하는 예시입니다.
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public List<ToolCallback> weatherTools(WeatherService weatherService) {
return List.of(ToolCallbacks.from(weatherService));
}
}@Service
public class WeatherService {
@Tool(description = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(double latitude, double longitude) {
// Implementation using weather.gov API
}
@Tool(description = "Get weather alerts for a US state. Input is Two-letter US state code (e.g., CA, NY)")
public String getAlerts(String state) {
// Implementation using weather.gov API
}
}@Tool 어노테이션은 MCP Server에 ToolCallback 형태로 등록되며,
이는 Tool 구현 메서드에 대한 핸들러(handler) 역할과 함께, Tool의 설명(description) 으로도 활용됩니다.
MCP Client와 Server 간의 Tool 실행 흐름은 아래와 같습니다
MCP 통합 API 서버는 기존에 운영 중이던 다양한 API들을 Model Context Protocol(MCP) 표준에 맞춰 손쉽고 빠르게 통합 제공하는 것을 목표로 설계 및 구현되었습니다.
주요 특징은 다음과 같습니다.
Transport 다중 지원
SSE 및 Streamable HTTP 기반의 Transport 방식을 지원하여 다양한 통신 환경에 대응합니다.
유연한 API 연동 구조
기존의 REST API, DSL 기반 명세, MCP STDIO, MCP SSE 방식 등 다양한 연동 방식을 지원합니다.
MCP API Group 기능
관련된 Tool들을 논리적으로 묶어 그룹화할 수 있으며, 이를 McpServerGroup을 통해 하나의 MCP 서버 엔드포인트로 각각 제공할 수 있습니다.
이러한 구조 덕분에 복잡한 API 연동도 단일한 프로토콜로 손쉽게 해결할 수 있습니다.
SSE(Server-Side Emitter) 방식은 연결 유지 기반의 실시간 통신 모델로 아래와 같은 구조로 메시지를 주고받습니다:
Client는 SSE 기반 통신을 위해 두 개의 endpoint를 사용합니다
하나는 SSE 연결용, 다른 하나는 메시지 전달용입니다.
Client의 operation 요청은 메시지 전달용 endpoint를 통해 전송되며, Server의 응답은 SSE 연결을 통해 실시간으로 전달됩니다.
이 방식은 stateful 통신 모델로, Server는 SSE connection 상태를 지속적으로 관리해야 합니다.
따라서 클라이언트 요청은 동일한 SSE connection을 유지하고 있는 MCP Server로 정확하게 라우팅되어야 합니다.
Streamable HTTP는 SSE 방식의 한계를 보완하기 위해 도입된 신규 스펙이며, MCP 공식 사양 2025-03-26에 정의되어 있습니다.
이 방식은 Stateful과 Stateless 양쪽 방식을 모두 지원하며, MCP Server는 필요에 따라 상태 관리를 선택적으로 구현할 수 있습니다.
Streamable HTTP(Stateless)의 주요 특징
MCP Client와 Server는 단일 endpoint를 통해 전체 Lifecycle을 처리합니다.
SSE와 달리 지속적인 연결이 유지되지 않으며, 각 요청은 독립적으로 처리됩니다.
인증 및 세션 관리는 요청 단위로 수행되며, Server는 상태를 저장하지 않습니다.
Server는 MCP Client가 지정한 connection을 통해 결과를 즉시 반환합니다.
MCP 통합 API 서버에서는 단일 endpoint를 기반으로 한 Streamable HTTP 방식을 구현함으로써, 보다 확장성 있는 구조를 구성할 수 있었습니다.
MCP Server에서 Tool을 제공하기 위해서는 두 가지 구성 요소가 필요합니다
Tool Specification: LLM에게 기능의 구조와 파라미터를 설명합니다.
Tool 구현체(Adapter): 실제 API 호출 또는 비즈니스 로직을 수행하는 핸들러입니다.
Tool Specification은 MCP에서 LLM에게 기능을 설명하는 역할을 하며, 아래와 같은 JSON 스키마 구조를 따릅니다
{
name: string; // Unique identifier for the tool
description?: string; // Human-readable description
inputSchema: { // JSON Schema for the tool's parameters
type: "object",
properties: { ... } // Tool-specific parameters
},
annotations?: { // Optional hints about tool behavior
title?: string; // Human-readable title for the tool
readOnlyHint?: boolean; // If true, the tool does not modify its environment
destructiveHint?: boolean; // If true, the tool may perform destructive updates
idempotentHint?: boolean; // If true, repeated calls with same args have no additional effect
openWorldHint?: boolean; // If true, tool interacts with external entities
}
}참고: API 명세서는 LLM을 통해 JSON 기반 Adapter 파일로 변경되어 사용됩니다.
Tool 구현체는 실제 API 호출 로직을 담당하며, 입력을 DSL(Domain Specific Language)을 통해 매핑하고, 결과를 JSON-RPC 형식으로 반환하는 구조입니다.
ToolCallback 인터페이스를 상속받아 구현되며, 실제 API 호출은 call 메서드 내에서 수행됩니다.
이를 통해 MCP Client의 요청에 따라 MCP Server 또는 Session 내에서 유연하게 실행될 수 있습니다.
package org.springframework.ai.tool;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.metadata.ToolMetadata;
import org.springframework.lang.Nullable;
public interface ToolCallback extends FunctionCallback {
ToolDefinition getToolDefinition();
default ToolMetadata getToolMetadata() {
return ToolMetadata.builder().build();
}
String call(String toolInput);
default String call(String toolInput, @Nullable ToolContext tooContext) {
if (tooContext != null && !tooContext.getContext().isEmpty()) {
throw new UnsupportedOperationException("Tool context is not supported!");
} else {
return this.call(toolInput);
}
}
}Tool Specification과 실제 Tool 구현체가 호출할 API에 대한 명세는 아래와 같은 Adapter JSON 형식으로 관리하고 있습니다.
[
{
"api" : {
"type" : "HTTP",
"http" : {
"url" : "http://localhost:5000/weather/{userId}",
"method": "GET",
"timeout" : "2s"
},
"rest-api" : "/v1/weather",
"requestExpression" : "{ #.userId : #.variables.userId }",
"responseExpression" : "{#:#}"
},
"tool" : {
"name": "get_weather",
"description": "Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY)",
"inputSchema":
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"state": {
"type": "string",
"description": "US state code"
}
},
"required": [
"state"
]
}
}
}
]
`Adapter JSON은 MCP에서 외부 API를 Tool 형태로 추상화하기 위한 메타 정보이며, 다음과 같은 구조를 가집니다
api
실제 호출할 API에 대한 정보를 포함하며, DSL(Domain Specific Language) 기반으로 파라미터를 매핑합니다.
DSL은 ANTLR4 기반으로 구성되어 런타임 시점에 계산 및 변환을 수행합니다.
tool
MCP Server가 MCP Client에 제공할 기능을 설명하는 스펙 정보로, tool의 이름(name), 설명(description), 입력 파라미터(inputSchema) 등의 정의를 포함합니다.
Adapter JSON으로 정의된 Tool은 실제 MCP 통합 시스템에서 여러 개의 논리적인 그룹으로 묶어 운영합니다.
각 그룹은 고유한 endpoint를 갖고 있으며, MCP Client는 이를 통해 필요한 기능만 선택적으로 호출할 수 있습니다.
[{
"name" : "toolbox",
"tools" : ["get_weather"]
},
{
"name" : "weather",
"tools" : ["get_weather"]
}]아래와 같이 논리적 그룹으로 여러 Tool이 재사용 되고 이를 그룹 단위로 Endpoint를 노출하게 됩니다.
이러한 구조를 통해 시스템은 관리가 쉬워지고, 기능별로 적절한 접근 제어와 버전 관리를 수행할 수 있습니다.
지금까지 설명한 구조를 기반으로, MCP Client는 sse 및 streamable http 방식을 통해 MCP 통합 API서버와 연결되어 유연하게 Tool을 사용할 수 있게 되었습니다.
LangChain MCP Adapters를 사용하면, LangGraph 기반의 에이전트에서도 MCP로 제공되는 API를 손쉽게 통합할 수 있습니다.
아래는 MultiServerMCPClient를 사용해 MCP 통합 API서버에 Group별로 Tool을 호출하는 예제입니다
import os
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
import asyncio
model = ChatOpenAI()
async def main():
async with MultiServerMCPClient(
{
"toolbox" :{
"url" : "http://{mcp-server}/mcp/toolbox/streamable",
"transport" : "streamable_http", # sse or streamable_http
},
"weather" :{
"url" : "http://{mcp-server}/mcp/weather/streamable",
"transport" : "streamable_http", # sse or streamable_http
}
}
) as client:
tools = client.get_tools()
agent = create_react_agent(model, tools=client.get_tools(), debug=True)
response = await agent.ainvoke({"messages": "what is the weather in nyc?"})
messages = response["messages"]
final_message = messages[-1]
print(final_message.content)
if __name__ == "__main__":
asyncio.run(main())이처럼 LangChain 기반 워크플로우에서도 MCP를 통해 다양한 API를 표준화된 Tool로 활용할 수 있습니다.
MCP 통합 API 서버를 도입함으로써, 기존에 REST API로만 제공되던 데이터를LLM 어플리케이션에서 보다 빠르고 유연하게 통합할 수 있게 되었습니다.
Tool 추상화, Group 기반 Endpoint 관리, 다양한 Transport 지원 등은에이전트 개발 효율성과 유지보수성을 크게 향상시켰습니다.
이 글이 유사한 고민을 하고 있는 분들께 실용적인 인사이트와 영감이 되길 바랍니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.