
MCP 서버를 직접 만들어보자 — 프로토콜부터 구현까지 (Part 2)
MCP의 내부 구조를 JSON-RPC 프로토콜부터 파헤치고, Python FastMCP로 메모 서버를 직접 만들어 Claude Desktop에 연결해봅니다.

MCP의 내부 구조를 JSON-RPC 프로토콜부터 파헤치고, Python FastMCP로 메모 서버를 직접 만들어 Claude Desktop에 연결해봅니다.
Part 1에서 MCP가 AI 세계의 USB-C라는 것을 살펴봤다. AI가 외부 도구와 소통하기 위한 표준 프로토콜이 왜 필요한지, 그리고 MCP가 그 문제를 어떻게 해결하는지를 개념 수준에서 이해했다. Part 1을 마무리하며 약속한 것이 있다 — JSON-RPC 기반의 프로토콜 구조, Transport 레이어의 작동 원리, 그리고 MCP 서버를 직접 구현하는 과정을 다루겠다고.
이제 후드를 열고 엔진을 직접 들여다볼 차례다.
이 글에서 다루는 내용은 다음과 같다.
개념을 이해한 사람이 직접 코드를 작성하고, 실행하고, 결과를 확인하는 것. 그것이 이 글의 목표다. 준비가 됐다면 시작하자.
MCP의 내부를 이해하려면 먼저 그 위에서 돌아가는 통신 프로토콜을 알아야 한다. MCP는 JSON-RPC 2.0을 사용한다. JSON-RPC는 JSON 포맷으로 원격 프로시저 호출(Remote Procedure Call)을 수행하는 경량 프로토콜이다. 2010년에 사양이 확정되었고, 단순하면서도 견고한 구조 덕분에 다양한 시스템에서 널리 쓰이고 있다.
핵심 아이디어는 간단하다. "어떤 함수를 어떤 인자로 호출하고, 그 결과를 돌려받는다." 이것을 JSON으로 표현한 것이 전부다.
요청(Request) 메시지는 네 가지 필드로 구성된다.
jsonrpc — 프로토콜 버전. 항상 "2.0"이다.id — 요청 식별자. 서버가 응답할 때 이 값을 그대로 돌려주어, 어떤 요청에 대한 응답인지 매칭할 수 있게 한다.method — 호출할 메서드 이름. 무엇을 할 것인지를 나타낸다.params — 메서드에 전달할 인자. 생략 가능하다.응답(Response) 메시지는 성공과 실패 두 가지 형태가 있다. 성공하면 result 필드에 결과가 담기고, 실패하면 error 필드에 오류 코드와 메시지가 담긴다. 요청의 id와 동일한 값이 응답에도 포함되므로, 비동기 환경에서도 요청-응답 쌍을 정확히 추적할 수 있다.
JSON-RPC는 MCP의 언어다. 한국어에 주어-목적어-서술어 문법이 있듯, JSON-RPC에는 method-params-result 문법이 있다. 클라이언트가 "이 메서드를 이 인자로 실행해줘"라고 말하면, 서버가 "결과는 이거야" 혹은 "이런 에러가 발생했어"라고 대답한다. MCP의 모든 상호작용은 이 문법 위에서 이루어진다.
MCP는 JSON-RPC 위에 자체적인 메서드 체계를 정의한다. 클라이언트(예: Claude Desktop)와 서버(예: 메모 서버) 사이에 오가는 주요 메서드를 정리하면 다음과 같다.
| 메서드 | 방향 | 설명 |
|---|---|---|
initialize | Client → Server | 연결 시작, 기능 협상 |
tools/list | Client → Server | 사용 가능한 도구 목록 요청 |
tools/call | Client → Server | 특정 도구 실행 |
resources/list | Client → Server | 사용 가능한 리소스 목록 요청 |
resources/read | Client → Server | 특정 리소스 읽기 |
prompts/list | Client → Server | 사용 가능한 프롬프트 목록 요청 |
prompts/get | Client → Server | 특정 프롬프트 가져오기 |
tools는 서버가 제공하는 실행 가능한 기능이고, resources는 읽기 전용 데이터이며, prompts는 미리 정의된 프롬프트 템플릿이다. 이 세 가지가 MCP 서버가 외부에 노출하는 핵심 기능이다.
구체적으로 어떻게 생겼는지 보자. 클라이언트가 save_memo라는 도구를 호출하는 상황이다.
요청:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "save_memo",
"arguments": {
"title": "회의록",
"content": "다음 스프린트 목표 논의"
}
}
}
응답:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{ "type": "text", "text": "메모 '회의록'이 저장되었습니다." }
]
}
}
구조를 하나씩 뜯어보자. 요청에서 method가 "tools/call"이므로 도구 실행 요청이다. params.name이 "save_memo"이므로 어떤 도구를 실행할지 지정하고, params.arguments에 해당 도구에 전달할 인자가 담겨 있다. 응답에서 id가 요청과 동일한 1이므로 이 응답이 위의 요청에 대한 것임을 알 수 있다. result.content 배열에 실행 결과가 텍스트 형태로 담겨 돌아온다.
이것이 MCP 통신의 전부다. 겉보기엔 단순한 JSON 주고받기이지만, 이 단순함이 MCP의 강점이다. 어떤 프로그래밍 언어든, 어떤 플랫폼이든 JSON을 다룰 수 있으면 MCP 서버를 만들 수 있다. 복잡한 바이너리 프로토콜도, 특별한 라이브러리도 필요 없다. 이것이 바로 Part 1에서 이야기한 "USB-C의 단순함"이 프로토콜 수준에서 구현된 모습이다.
JSON-RPC가 MCP의 언어라면, Transport는 그 언어를 전달하는 통로다. 아무리 문법이 완벽해도 상대방에게 전달할 수단이 없으면 의미가 없다. MCP는 두 가지 공식 Transport를 정의한다 — stdio와 Streamable HTTP.
가장 단순하고 가장 널리 쓰이는 방식이다. 클라이언트(예: Claude Desktop)가 MCP 서버를 서브프로세스로 직접 실행한다. 그 후 통신은 운영체제의 표준 입출력 스트림을 통해 이루어진다.
직관적이다. 프로세스를 띄우고, 파이프로 대화하고, 프로세스가 종료되면 세션도 끝난다. 네트워크 설정도 필요 없고, 포트 충돌 걱정도 없다. Claude Desktop, Cursor 같은 로컬 AI 도구들이 모두 이 방식을 사용하는 이유다.
2025년에 기존 SSE(Server-Sent Events) Transport를 대체하며 도입된 방식이다. 서버가 하나의 HTTP 엔드포인트를 노출하고, 클라이언트가 이 엔드포인트에 HTTP 요청을 보내는 구조다.
Mcp-Session-Id 헤더를 통해 이루어진다| 항목 | stdio | Streamable HTTP |
|---|---|---|
| 연결 방식 | 서브프로세스 | HTTP 엔드포인트 |
| 적합한 환경 | 로컬 (같은 머신) | 원격 / 클라우드 |
| 세션 관리 | 프로세스 수명 = 세션 | Mcp-Session-Id 헤더 |
| 보안 | OS 프로세스 격리 | Origin 검증, DNS rebinding 보호 |
| 복잡도 | 낮음 | 중간~높음 |
| 사용 예시 | Claude Desktop, Cursor | 웹 기반 AI 서비스 |
이 글에서는 가장 일반적인 stdio Transport를 사용한다. 로컬 환경에서 MCP 서버를 직접 실행하고 테스트하는 것이 학습에 가장 효과적이기 때문이다.
Transport 연결이 수립되면, 클라이언트와 서버는 바로 도구를 호출하지 않는다. 먼저 초기화 핸드셰이크를 거친다. 서로의 능력(capabilities)을 확인하고, 프로토콜 버전에 합의하는 과정이다.
Part 1의 레스토랑 비유를 다시 꺼내보자. 웨이터가 처음 오면 "저희 주방에서는 한식, 양식, 디저트를 제공합니다"라고 메뉴 카테고리를 알려주는 것과 같다. 손님이 갑자기 "스시 주세요"라고 하기 전에, 이 식당이 무엇을 할 수 있는지부터 파악하는 것이다.
초기화는 정확히 세 단계로 이루어진다.
initialize 요청을 보낸다. 자신이 지원하는 프로토콜 버전과 클라이언트 능력(capabilities)을 알린다.notifications/initialized 알림을 보낸다. "확인했다, 이제 시작하자"는 신호다.실제 JSON-RPC 메시지를 보면 이 과정이 더 명확해진다.
1단계 — 클라이언트가 보내는 initialize 요청:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": { "roots": { "listChanged": true } },
"clientInfo": { "name": "Claude Desktop", "version": "1.0.0" }
}
}
클라이언트는 자신이 사용할 프로토콜 버전(protocolVersion)과 자신의 능력(capabilities), 그리고 자기 소개(clientInfo)를 보낸다. roots.listChanged가 true라는 것은 클라이언트가 작업 디렉토리 변경 알림을 지원한다는 의미다.
2단계 — 서버의 응답:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-11-25",
"capabilities": {
"tools": { "listChanged": true },
"resources": { "listChanged": true },
"prompts": { "listChanged": true }
},
"serverInfo": { "name": "MemoServer", "version": "1.0.0" }
}
}
서버는 동일한 프로토콜 버전에 합의하고, 자신이 제공하는 기능을 알린다. 이 서버는 tools, resources, prompts를 모두 지원하며, 각각의 목록이 변경될 수 있음(listChanged: true)을 선언한다. serverInfo로 자기 소개도 한다 — 이 서버의 이름은 "MemoServer"이고 버전은 1.0.0이다.
3단계 — 클라이언트의 확인 알림:
이 마지막 단계는 JSON-RPC의 notification 형식이다. id 필드가 없으므로 응답을 기대하지 않는다. 단순히 "준비 완료"를 알리는 신호일 뿐이다. 이 알림이 전송된 후부터 클라이언트는 tools/list, tools/call 등 실제 메서드를 호출할 수 있다.
이 핸드셰이크가 중요한 이유는 하위 호환성 때문이다. 프로토콜 버전이 다른 클라이언트와 서버가 만나도 초기화 단계에서 합의할 수 있고, 서버가 지원하지 않는 기능을 클라이언트가 무작정 호출하는 상황을 방지할 수 있다.
이론은 충분하다. 이제 직접 코드를 작성할 차례다. Python의 FastMCP 라이브러리를 사용해서, AI가 메모를 저장하고 검색할 수 있는 MCP 서버를 만들어보겠다.
먼저 FastMCP를 설치한다.
pip install fastmcp
프로젝트 구조는 복잡할 필요가 없다. memo_server.py 파일 하나면 충분하다. 별도의 디렉토리 구조도, 설정 파일도 필요 없다. FastMCP가 MCP 프로토콜의 복잡한 부분을 전부 추상화해주기 때문에, 우리는 비즈니스 로직에만 집중하면 된다.
서버의 뼈대와 첫 번째 기능을 한 번에 만들어보자.
from fastmcp import FastMCP
mcp = FastMCP(name="MemoServer")
memos: dict[str, str] = {}
@mcp.tool
def save_memo(title: str, content: str) -> str:
"""메모를 저장합니다."""
memos[title] = content
return f"메모 '{title}'이 저장되었습니다."
코드를 한 줄씩 살펴보자.
FastMCP(name="MemoServer")는 MCP 서버 인스턴스를 생성한다. 여기서 지정한 name은 초기화 핸드셰이크에서 serverInfo.name으로 클라이언트에 전달된다. 앞 섹션에서 본 초기화 응답의 "name": "MemoServer" 부분이 바로 이 값이다.
memos: dict[str, str] = {}는 메모를 저장할 인메모리 딕셔너리다. 튜토리얼이므로 메모리에 저장하지만, 실제 프로덕션 환경에서는 데이터베이스를 사용해야 한다. 서버가 종료되면 데이터도 사라지기 때문이다.
@mcp.tool 데코레이터가 핵심이다. 이 데코레이터를 붙이면 일반 Python 함수가 MCP Tool로 등록된다. FastMCP는 함수의 타입 힌트(title: str, content: str)를 분석해서 JSON Schema 형식의 inputSchema를 자동 생성하고, docstring("메모를 저장합니다.")을 Tool의 description으로 사용한다. 개발자가 별도의 스키마를 수동으로 작성할 필요가 없다.
함수의 반환값 str은 MCP 응답 형식인 content: [{ type: "text", text: "..." }] JSON-RPC 응답으로 자동 변환된다. 섹션 1에서 살펴본 tools/call 응답 예시의 result.content 배열이 바로 이 반환값으로부터 만들어지는 것이다.
클라이언트가 tools/list를 호출하면 이 Tool이 목록에 나타나고, tools/call로 "save_memo"를 지정하면 이 함수가 실행된다.
저장만 할 수 있으면 반쪽짜리다. 키워드로 메모를 검색하는 기능을 추가하자.
@mcp.tool
def search_memo(keyword: str) -> str:
"""키워드로 메모를 검색합니다."""
results = {k: v for k, v in memos.items() if keyword in k or keyword in v}
if not results:
return "일치하는 메모가 없습니다."
return "\n".join(f"- {k}: {v}" for k, v in results.items())
구조는 save_memo와 동일하다. @mcp.tool 데코레이터를 붙이고, 타입 힌트와 docstring을 작성하면 끝이다. 이 함수는 제목과 내용 양쪽에서 키워드를 검색하고, 일치하는 메모가 있으면 목록 형태로 반환한다. 없으면 "일치하는 메모가 없습니다"라는 텍스트를 돌려준다.
이번에는 Tool이 아닌 Resource를 만들어보자.
@mcp.resource("memo://list")
def list_memos() -> str:
"""저장된 모든 메모 목록을 반환합니다."""
if not memos:
return "저장된 메모가 없습니다."
return "\n".join(f"- {title}" for title in memos.keys())
Tool과 Resource의 차이를 명확히 이해해야 한다.
"memo://list"는 이 Resource의 URI다. MCP에서 Resource는 URI로 식별된다. http://나 file:// 같은 표준 스킴뿐 아니라 memo://처럼 커스텀 스킴도 자유롭게 정의할 수 있다. 클라이언트가 resources/read를 호출할 때 이 URI를 지정하면 해당 Resource의 데이터를 받아볼 수 있다.
마지막으로 Prompt를 추가한다.
@mcp.prompt
def summarize_memos() -> str:
"""저장된 모든 메모를 요약해달라는 프롬프트 템플릿."""
memo_text = "\n".join(f"- {k}: {v}" for k, v in memos.items())
return f"다음 메모들을 간결하게 요약해주세요:\n\n{memo_text}"
Prompt는 반복적인 AI 작업을 위한 표준화된 **"레시피 카드"**다. 사용자가 매번 "메모를 요약해줘"라는 프롬프트를 직접 작성하는 대신, 서버가 미리 만들어둔 템플릿을 제공하는 것이다. 클라이언트가 prompts/get으로 이 템플릿을 가져와서 AI 모델에 전달하면, 일관된 형식으로 메모 요약을 요청할 수 있다.
마지막으로 서버를 실행하는 코드를 추가한다.
if __name__ == "__main__":
mcp.run()
mcp.run()은 stdio Transport를 시작한다. 섹션 2에서 설명한 대로, 서버는 stdin에서 JSON-RPC 메시지를 읽고 stdout으로 응답을 보내는 방식으로 동작한다. 이 한 줄이 프로토콜 핸드셰이크, 메시지 파싱, 라우팅을 모두 처리해준다.
이것이 MCP 서버의 전부다. 약 30줄의 Python 코드로 AI가 메모를 저장하고, 검색하고, 목록을 읽고, 요약 프롬프트를 사용할 수 있게 됐다.
서버 코드를 완성했으니 이제 실제로 사용해볼 차례다. 만든 메모 서버를 Claude Desktop에 연결해서, 자연어 대화만으로 메모를 저장하고 검색하는 경험을 직접 해보겠다.
Claude Desktop은 JSON 설정 파일을 통해 MCP 서버를 등록한다. 운영체제별 설정 파일 경로는 다음과 같다.
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.json설정 파일을 여는 가장 간편한 방법은 Claude Desktop 앱에서 Claude 메뉴 → Settings → Developer → Edit Config 순서로 접근하는 것이다. 이 버튼을 누르면 기본 텍스트 에디터에서 설정 파일이 열린다.
설정 파일에 다음 내용을 작성한다.
{
"mcpServers": {
"memo-server": {
"command": "python",
"args": ["/absolute/path/to/memo_server.py"]
}
}
}
각 필드의 의미를 살펴보자. "memo-server"는 이 서버의 식별 이름으로, 원하는 대로 지정할 수 있다. "command"는 서버를 실행할 실행 파일이다 — Python 스크립트이므로 "python"을 지정한다. "args"는 command에 전달할 인자 배열로, 여기에 memo_server.py의 절대 경로를 넣어야 한다. 상대 경로를 사용하면 Claude Desktop이 서버를 찾지 못할 수 있으므로 반드시 절대 경로를 사용하자.
설정 파일을 저장한 뒤, Claude Desktop을 완전히 종료하고 다시 시작한다. 단순히 창을 닫는 것이 아니라, macOS에서는 Dock 아이콘을 우클릭해서 Quit, Windows에서는 시스템 트레이에서 종료해야 한다. 프로세스가 완전히 종료되어야 설정 파일을 다시 읽기 때문이다.
재시작 후 Claude Desktop의 입력창 하단을 확인하자. MCP 서버가 정상적으로 연결되었다면 망치 아이콘이 나타난다. 이 아이콘을 클릭하면 연결된 서버와 사용 가능한 Tool 목록을 확인할 수 있다.
이제 자연어로 대화해보자. 다음과 같은 요청을 시도해볼 수 있다.
save_memo Tool이 호출된다memo://list Resource가 읽힌다search_memo Tool이 호출된다summarize_memos Prompt가 사용된다Claude가 각 요청을 이해하고, 적절한 MCP 기능을 자동으로 선택해서 실행하는 것을 확인할 수 있다. 사용자는 Tool이니 Resource니 Prompt니 하는 내부 개념을 전혀 알 필요가 없다. 그냥 자연어로 말하면 된다.
사용자가 "새 메모를 저장해줘"라고 입력했을 때, 보이지 않는 곳에서는 정교한 프로토콜 교환이 일어나고 있다. 앞서 배운 JSON-RPC 메시지들이 실제로 오가는 것이다.
먼저 Claude Desktop은 서버에 tools/list를 요청해서 사용 가능한 도구를 파악한다. 서버가 save_memo와 search_memo를 제공한다는 것을 알게 되면, Claude는 사용자의 자연어 요청을 분석해서 어떤 도구를 어떤 인자로 호출할지 결정한다. 그 후 tools/call로 해당 도구를 실행하고, 결과를 받아 사용자에게 자연어로 응답한다.
첫 번째 단계에서 Claude는 서버가 어떤 도구를 가지고 있는지 목록을 요청한다. 두 번째 단계에서 사용자의 의도에 맞는 도구를 선택해서 호출한다. 세 번째 단계에서 필요에 따라 리소스를 읽어 추가 정보를 가져온다. 이 모든 과정이 섹션 1에서 살펴본 JSON-RPC 메시지 형식 그대로 이루어진다.
프로토콜은 보이지 않는 곳에서 일하고, 사용자는 자연어로 대화하기만 하면 된다. 이것이 MCP의 아름다움이다.
MCP는 특정 언어에 종속되지 않는 프로토콜이다. 이를 직접 확인하기 위해, 앞에서 Python으로 만든 메모 서버를 TypeScript로 다시 구현해보겠다. 동일한 기능을 다른 언어로 작성하면서 MCP의 언어 중립적 특성을 체감할 수 있다.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "memo-server", version: "1.0.0" });
const memos = new Map<string, string>();
// Tool: 메모 저장
server.tool(
"save_memo",
"메모를 저장합니다",
{ title: z.string(), content: z.string() },
async ({ title, content }) => {
memos.set(title, content);
return { content: [{ type: "text", text: `메모 '${title}'이 저장되었습니다.` }] };
}
);
// Tool: 메모 검색
server.tool(
"search_memo",
"키워드로 메모를 검색합니다",
{ keyword: z.string() },
async ({ keyword }) => {
const results = [...memos.entries()]
.filter(([k, v]) => k.includes(keyword) || v.includes(keyword));
const text = results.length > 0
? results.map(([k, v]) => `- ${k}: ${v}`).join("\n")
: "일치하는 메모가 없습니다.";
return { content: [{ type: "text", text }] };
}
);
// Resource: 메모 목록
server.resource(
"memo-list",
"memo://list",
{ description: "저장된 모든 메모 목록", mimeType: "text/plain" },
async (uri) => ({
contents: [{
uri: uri.href,
text: memos.size > 0
? [...memos.keys()].map(t => `- ${t}`).join("\n")
: "저장된 메모가 없습니다.",
}],
})
);
// Prompt: 메모 요약
server.prompt(
"summarize_memos",
"저장된 모든 메모를 요약해달라는 프롬프트",
() => ({
messages: [{
role: "user" as const,
content: {
type: "text" as const,
text: `다음 메모들을 간결하게 요약해주세요:\n\n${
[...memos.entries()].map(([k, v]) => `- ${k}: ${v}`).join("\n")
}`,
},
}],
})
);
const transport = new StdioServerTransport();
await server.connect(transport);
TypeScript MCP SDK는 server.tool(), server.resource(), server.prompt() 모두 위치 기반 인자(positional arguments)를 사용한다. 또한 Zod 스키마를 정의할 때 z.object()로 감싸지 않고 { key: z.string() } 형태의 raw shape을 직접 전달한다는 점에 주의하자.
Python 버전과 TypeScript 버전을 나란히 비교하면 MCP 프로토콜의 일관성이 더 잘 보인다.
| 항목 | Python (FastMCP) | TypeScript (MCP SDK) |
|---|---|---|
| 서버 생성 | FastMCP(name="...") | new McpServer({name, version}) |
| Tool 등록 | @mcp.tool 데코레이터 | server.tool(name, desc, shape, handler) |
| Resource 등록 | @mcp.resource(uri) | server.resource(name, uri, meta, handler) |
| Prompt 등록 | @mcp.prompt | server.prompt(name, desc, handler) |
| 스키마 정의 | 타입 힌트 자동 생성 | Zod raw shape 명시 |
| Transport | mcp.run() | server.connect(transport) |
Python은 데코레이터와 타입 힌트로 더 간결하고, TypeScript는 명시적인 스키마 정의로 더 엄격하다. 어떤 언어를 선택하든 MCP 프로토콜 위에서 동일하게 작동한다. 클라이언트 입장에서는 서버가 Python으로 작성됐는지 TypeScript로 작성됐는지 알 수 없고, 알 필요도 없다. 프로토콜이 그 차이를 추상화해주기 때문이다.
TypeScript 서버를 Claude Desktop에 연결할 때는 설정 파일에 다음과 같이 추가하면 된다.
{
"mcpServers": {
"memo-server-ts": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/memo_server.ts"]
}
}
}
npx tsx는 TypeScript 파일을 별도의 빌드 과정 없이 바로 실행해주는 도구다. Python 버전과 마찬가지로 절대 경로를 사용해야 한다.
약 30줄의 Python 코드로 AI가 외부 세계와 대화하는 창구를 만들었다. 메모를 저장하고, 검색하고, 목록을 읽고, 요약을 요청하는 — 이 모든 것이 표준 프로토콜 위에서 자연어 대화만으로 이루어졌다. TypeScript로도 동일한 서버를 구현하면서, MCP가 특정 언어나 플랫폼에 종속되지 않는다는 것도 확인했다.
이제 메모 서버를 넘어, 실제 업무에 MCP를 적용할 차례다. 몇 가지 아이디어를 던져본다.
더 깊이 탐구하고 싶다면, MCP 공식 스펙에서 프로토콜의 모든 세부 사항을 확인할 수 있다. Python 개발자라면 FastMCP 문서가 가장 빠른 시작점이고, TypeScript 개발자라면 npm의 @modelcontextprotocol/sdk 패키지를 참고하면 된다. 이미 커뮤니티에서 1,000개 이상의 MCP 서버가 공개되어 있으니, 다른 사람들이 어떤 서버를 만들었는지 둘러보는 것도 좋은 출발점이다.
MCP는 AI와 세상을 잇는 다리다. Part 1에서 그 다리의 설계도를 읽었고, Part 2에서 직접 다리를 놓아봤다. 이제 그 다리 위로 무엇을 보낼지는 여러분의 상상력에 달려 있다.