Protobuf를 이용한 MCP 서버 구축 (3부) – Proto 주석으로 AI 상호작용 향상시키기

Charlie Zhang

2025년 9월 30일

이 블로그 시리즈에서는 유용한 도구들로 가득 찬 MCP (Model Context Protocol) 서버를 구축하는 방법을 보여드립니다. 처음부터 시작하는 대신, 기존의 프로토콜 버퍼와 Google의 gRPC 트랜스코딩을 활용할 것입니다. 맞춤형 protoc (프로토콜 버퍼 컴파일러) 플러그인을 생성하여 MCP 서버를 자동으로 생성할 수 있습니다. 이 통합된 접근 방식을 통해 gRPC 서비스, OpenAPI 사양, REST API 및 MCP 서버를 모두 동일한 소스에서 생성할 수 있습니다.

이 블로그 시리즈는 4개의 기사로 구성되어 있습니다:


구축할 내용

이 튜토리얼을 마치면 다음을 갖게 됩니다:

  • proto 주석을 통해 MCP 도구를 정확하게 이해하는 AI 에이전트
  • proto 파일 주석에서 자동으로 생성된 풍부한 도구 설명
  • AI 에이전트 행동 개선을 검증하기 위한 테스트 프레임워크
  • AI 친화적인 proto 주석 작성에 대한 모범 사례

이 기사에 언급된 모든 코드는 GitHub 저장소 zhangcz828/proto-to-mcp-tutorial에서 찾을 수 있습니다.


전제 조건

시작하기 전에, 1부와 2부를 완료했으며 다음을 갖추고 있는지 확인하세요:

  • 1부와 2부의 서점 튜토리얼 프로젝트
  • AI 에이전트 상호작용 테스트를 위한 OpenAI API 키


우리가 해결하는 문제

1부와 2부에서는 하나의 proto 파일이 gRPC 서비스, REST 엔드포인트, OpenAPI 사양, 그리고 MCP 서버를 생성하는 통합 생성 파이프라인을 구축했습니다. 기술적으로는 모든 것이 작동했고, 도구는 존재했으며 호출될 수 있었습니다. 하지만 AI 에이전트로 테스트를 시작했을 때, AI가 올바른 도구를 선택하거나 필요한 매개변수를 올바르게 수집하는 데 자주 실패하는 치명적인 병목 현상을 발견했습니다.

문제는 우리 코드에 있는 것이 아니라, 누락된 문맥적 문서에 있었습니다. 이 기사에서는 여러분이 이미 proto 파일에 작성하고 있는 주석을 풍부하고 구조화된 MCP 도구 설명으로 변환하는 방법을 보여줄 것입니다. 이 접근 방식은 설명을 자동으로 동기화 상태로 유지하며 AI 도구 선택, 프롬프팅 및 요청 구성을 극적으로 개선합니다.


Proto 주석이 도구 설명이 되는 방법

우리의 protoc-gen-mcp 플러그인은 이미 proto 파일에서 MCP 도구를 생성합니다. 도구 설명이 생성되는 과정은 다음과 같습니다:

  • RPC 메서드 주석 $\rightarrow$ AI 에이전트를 안내하는 도구 설명
  • 요청 메시지 및 필드 주석 $\rightarrow$ 매개변수 문서
  • HTTP 어노테이션 (이미 REST/OpenAPI에 사용됨) $\rightarrow$ 도구 도움말의 메서드, 경로, 본문 매핑

이는 코드를 재생성할 때마다 도구 설명이 API 정의와 완벽하게 일치한다는 것을 의미합니다.


테스트를 위한 AI 에이전트 설정

최소한의 LangGraph 기반 에이전트를 사용하여 이전/이후 동작을 보여드리겠습니다.

종속성 설치

.env 파일에 OpenAI (또는 호환되는) 키를 넣습니다:

OPENAI_API_KEY=sk-xxx

종속성을 설치합니다:

uv pip install langchain_mcp_adapters
uv pip install langgraph
uv pip install langchain_openai
uv pip install langchain

또는 requirements.txt에 통합하고 실행합니다:

uv pip install -r requirements.txt

agent/langGraph.py 생성

from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import os, asyncio

load_dotenv()

model = ChatOpenAI(
model="gpt-4o",
api_key=os.getenv("OPENAI_API_KEY"),
)

client = MultiServerMCPClient(
{
"bookstore": {
"command": "uv",
"args": ["--directory", "./generated/mcp", "run", "mcp_server.py"],
"transport": "stdio",
}
}
)

async def main():
tools = await client.get_tools()

agent = create_react_agent(model=model, tools=tools)

print("Agent is ready. Type 'exit' or 'quit' to end the session.")

while True:
try:
user_input = input("You: ")

if user_input.lower() in ["exit", "quit"]:
break

response = await agent.ainvoke({"messages": [{"role": "user", "content": user_input}]})

print("Output: ", response_stream["messages"][-1].content)
except Exception as e:
print(f"An error occurred: {e}")

print("\nExiting agent.")

if __name__ == "__main__":
asyncio.run(main())

실행

python3 agent/langGraph.py

그리고 다음을 질문합니다: create a new book. (새 book을 생성해줘)


기준선: 간단한 Proto 주석

최소한의 주석이 있는 proto 파일은 다음과 같습니다:

syntax = "proto3";
package bookstore.v1;
import "google/api/annotations.proto";
import "mcp/protobuf/annotations.proto";
option go_package = "generated/go/bookstore/v1";

service BookstoreService {
 // Get a book by ID
 rpc GetBook(GetBookRequest) returns (Book) {
   option (google.api.http) = {
     get: "/v1/books/{book_id}"
   };
   option (mcp.v1.tool) = {
     enabled: true
   };
 }
 
 // Create a new book in the system.
 rpc CreateBook(CreateBookRequest) returns (Book) {
   option (google.api.http) = {
     post: "/v1/books"
     body: "*"
   };
   option (mcp.v1.tool) = {
     enabled: true
   };
 }
}
message Book {
 string book_id = 1;
 string title = 2;
 string author = 3;
 int32 pages = 4;
}
message GetBookRequest {
 // The ID of the book to retrieve
 string book_id = 1;
}
message CreateBookRequest {
 // The book object to create.
 Book book = 1;
}

에이전트를 실행하고 관찰합니다:

✗ python agent/langGraph.py
[09/30/25 14:40:34] INFO     Processing request of type ListToolsRequest                                                                                                                     server.py:623
Agent is ready. Type 'exit' or 'quit' to end the session.
You: create a new book
[09/30/25 14:40:45] INFO     Processing request of type CallToolRequest                                                                                                                      server.py:623
Output: It looks like some mandatory details for creating a new book are missing. Could you please provide more information about the book, such as the title, author, genre, or any other relevant details?

코드에서 title, author, pages필수라는 것을 알지만, LLM은 해당 지침이 부족하여 일반적인 프롬프트를 생성합니다. 제목과 저자만 제공하면:

You: the author is "Charlie" and the title is "Test book"
[09/30/25 14:45:34] INFO     Processing request of type CallToolRequest                                                                                                                      server.py:623
Output: It seems that I made an error when attempting to create a new book. However, I don't have the necessary details to proceed. Could you please provide a description for the book you want to create, so I can assist you properly?

에이전트는 실패를 감지하지만, 도구 정의가 제약 조건을 노출하지 않았기 때문에 누락된 필드를 명확하게 설명할 수 없습니다.


개선된 방법: 풍부한 Proto 주석

RPC 주석을 업데이트하여 명시적인 지침을 제공합니다:

 // Create a new book in the system.
 //
 // INSTRUCTIONS:
 //   1. For each required field:
 //      - If the user has not provided a value , prompt the user to supply it (otherwise the request will fail).
 //   2. For optional fields:
 //      - If not set by the user, do not set the field in the request and omit them.
 //
 // Example payload for creating a book:
 // {
 //   "book": {
 //     "bookId": "string", // optional
 //     "title": "string", // required
 //     "author": "string", // required
 //     "pages": int // required
 //   }
 // }
 rpc CreateBook(CreateBookRequest) returns (Book) {
   option (google.api.http) = {
     post: "/v1/books"
     body: "*"
   };
   option (mcp.v1.tool) = {
     enabled: true
   };
 }

재생성합니다:

go build -o protoc-gen-mcp plugins/protoc-gen-mcp/main.go
./generate.sh
python3 agent/langGraph.py

다시 쿼리합니다: create a new book: (새 책을 생성해줘)

✗ python3 agent/langGraph.py
[09/30/25 14:50:07] INFO     Processing request of type ListToolsRequest                                                                                                                     server.py:623
Agent is ready. Type 'exit' or 'quit' to end the session.
You: create a new book
Output: Before we create a new book, I'll need some details from you. Please provide the following information:
1. Title of the book (required)
2. Author of the book (required)
3. Number of pages in the book (required)
Feel free to provide any optional details as well if you have them.

이제 에이전트는 누락된 필수 필드만 사전에 수집하며, 설명적인 proto 주석이 실행 가능한 도구 지침이 됨을 보여줍니다.

부분적인 입력을 제공하면:

You: the author is "Charlie" and the title is "Test book"
Output: Could you please provide the number of pages for the book "Test book" by Charlie? This information is required to create the book.

에이전트는 여전히 하나의 누락된 필수 필드가 있음을 알고, 이에 대한 프롬프트를 표시합니다.


Proto 주석 작성을 위한 모범 사례

AI 도구 설명이 될 proto 주석을 작성할 때:

  • 명령형 동사로 시작: “새 책 생성”, “인보이스 삭제”, “사용자 프로필 업데이트”
  • 요약과 지침 분리: 한 줄 요약과 상세 지침 사이에 빈 줄 사용
  • 필수 vs. 선택 필드 표시: 어떤 매개변수가 필수이고 선택 사항인지 명시적으로 서술
  • 예시 페이로드 제공: 중첩된 객체가 있을 때, 간결한 JSON 예시 포함
  • 제약 조건 포함: 필드 형식, 범위 또는 유효성 검사 규칙 문서화
  • 출력 설명: 도구가 반환하는 내용과 일반적인 실패 모드 설명


결론

이제 proto 주석이 지능적인 AI 도구 설명이 되는 완벽한 파이프라인을 갖게 되었습니다. 이제 동일한 proto 파일에 gRPC 서비스, REST 엔드포인트, OpenAPI 사양 및 AI 에이전트를 훨씬 더 효과적으로 만드는 설명이 포함된 MCP 도구를 생성하는 데 필요한 모든 것이 포함되어 있습니다.

다음 단계

이제 AI 에이전트가 proto 주석을 통해 MCP 도구를 정확하게 해석하고 사용할 수 있으므로, 4부에서는 proto에서 생성된 MCP 도구를 실제로 운영하면서 얻은 운영 교훈을 공유할 것입니다.

메일: salesinquiry@enterprisedb.com