지난 화요일은 Stateless MCP의 날이었다. MCP 2.0, 공식 명칭으로는 2026-07-28 Model Context Protocol 명세가 공개된 것이다. 외우기 쉬운 이름은 아니지만, 내용만큼은 MCP 명세가 처음 등장한 이후 가장 큰 변화라 할 만하다. 이번 업데이트는 필자가 이 프로토콜에 다시 관심을 갖게 된 계기가 되기도 했다.
배경 설명을 덧붙이자면, MCP(Model Context Protocol)는 LLM 기반 에이전트 프레임워크에 새로운 도구를 제공하는 표준 방식을 정의한 프로토콜이다. Anthropic이 2024년 11월에 처음 선보인 이후 2025년 내내 폭발적인 관심을 받았지만, 이후 Skills(역시 Anthropic이 만든 개념)에 다소 가려지게 됐다. 터미널과 curl에 접근할 수 있는 에이전트 환경이 MCP가 제공하던 기능을 더 유연하게 대부분 대체할 수 있다는 사실이 드러났기 때문이다. 이 이야기는 필자의 2025년 회고에서 다룬 바 있다.
그런데 최근 들어 MCP에 다시 관심이 생겼다. 인터넷 접근이 가능한 셸 환경을 에이전트에 통째로 넘기는 방식은 보안 위험이 상당하고, 그런 환경을 효과적으로 다룰 수 있는 강력한 모델이 필요하다. 반면 MCP 도구는 감사(audit)와 제어가 훨씬 쉽고, 구조도 단순해서 노트북에서 구동되는 소형 모델도 무리 없이 활용할 수 있다.
새 Stateless MCP 명세는 클라이언트와 서버 구현의 복잡도도 크게 낮췄다. 이번 주에만 관련 프로젝트를 세 개나 만들었다!
Stateful과 Stateless MCP의 차이를 가장 잘 보여주는 예시는 새 명세의 RC를 소개한 5월 21일 블로그 포스트에 담겨 있다. 명확한 전후 비교 예시가 포함돼 있다.
기존 Stateful MCP(이하 '레거시 MCP')는 두 번의 HTTP 요청이 필요했다. 첫 번째 요청으로 세션을 초기화하고 Mcp-Session-Id를 받아온 뒤, 두 번째 요청으로 실제 도구를 호출하는 방식이었다.
POST /mcp HTTP/1.1
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {
},
"clientInfo": {
"name": "my-app",
"version": "1.0"
}
}
}
POST /mcp HTTP/1.1
Mcp-Session-Id: 1868a90c-3a3f-4f5b
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "search",
"arguments": {
"q": "otters"
}
}
}
새로운 Stateless 방식은 단 한 번의 HTTP 요청으로 처리된다.
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search",
"arguments": {
"q": "otters"
},
"_meta": {
"io.modelcontextprotocol/clientInfo": {
"name": "my-app",
"version": "1.0"
}
}
}
}
클라이언트와 서버 양쪽 구현 모두 훨씬 깔끔해졌다. 세션 ID를 추적하기 위한 서버 측 상태를 유지할 필요도 없고, 동일한 세션을 동일한 백엔드 서버로 라우팅하는 문제도 신경 쓸 필요가 없어, 확장 가능한 웹 애플리케이션을 구축하기에도 훨씬 적합하다.
MCP 서버를 대화식으로 탐색할 수 있는 마땅한 CLI 도구를 찾지 못해, Codex의 도움을 받아 직접 만들기로 했다.
그렇게 탄생한 것이 mcp-explorer다. Stateless 방식으로 구현된 Python CLI 도구로, 별도 설치 없이 uvx로 바로 실행할 수 있다.
uvx mcp-explorer list https://agentic-mermaid.dev/mcp위 명령은 Ade Oshineye의 agentic-mermaid.dev 데모 MCP 서버를 조회한다. 실행하면 다음과 같은 도구 목록이 반환된다.
execute(code: string, timeoutMs?: integer) - Execute Mermaid SDK code
Run JavaScript in an isolated sandbox; return a value.
describe_sdk(family: string, detail?: string) - Describe Mermaid SDK operations
Return version-matched mutation operations for one diagram family.
render_svg(source: string, options?: object) - Render Mermaid as SVG
Render a Mermaid source string to themeable SVG. Returns { ok, svg }.
render_ascii(source: string, useAscii?: boolean, targetWidth?: integer, options?: object) - Render Mermaid as text
Render a Mermaid source string to text. Returns { ok, text }.
render_png(source: string, scale?: number, background?: string, fitTo?: object, options?: object) - Render Mermaid as PNG
Rasterize a Mermaid source string to PNG. Returns { ok, png_base64 }.
...
특정 도구를 자세히 살펴보려면
uvx mcp-explorer inspect render_svg입력·출력의 JSON 스키마를 포함한 상세 정보가 출력된다.
해당 도구를 호출하고 인수를 전달하려면
uvx mcp-explorer call \
https://agentic-mermaid.dev/mcp \
render_svg \
-a source 'graph TD; A-->B' \
-a options '{"padding":24}'반환 결과
{"ok":true,"svg":"<svg xmlns=\"http://www.w3.org/2000/svg\" width=...
순수 SVG만 받고 싶다면 명령어에 | jq .svg -r을 추가하면 된다. 필자가 받은 결과는 이 이미지와 같다.
README에는 몇 가지 명령어가 더 있지만, 대략적인 사용 방식은 이 정도면 충분히 파악할 수 있을 것이다. 실제 코드의 대부분을 에이전트가 작성하더라도, 이런 CLI 도구를 직접 만들어보는 것은 명세를 익히는 데 매우 효과적인 방법이라고 생각한다.
두 번째 프로젝트는 datasette-mcp다. Datasette 인스턴스에 /-/mcp 엔드포인트를 추가해주는 Datasette 플러그인이다.
아마 네 번째 도전일 텐데, 새 Stateless MCP 명세 덕분에 드디어 공개할 만한 버전을 완성했다.
제공하는 도구는 딱 세 가지다: list_databases(), get_database_schema(database_name), execute_sql(database_name, sql). 이름만 봐도 무슨 역할을 하는지 알 수 있다. 다만 execute_sql()는 현재 읽기 전용으로만 동작한다.
에이전트나 ChatGPT, Claude 같은 채팅 도구에 연결하면, 호스팅 중인 Datasette 인스턴스에 SQL 쿼리를 실행할 수 있게 된다.
현재 필자의 블로그 Datasette 미러인 datasette.simonwillison.net/-/mcp에서 운영 중이다. ChatGPT와 Claude에 연결하는 방법을 파악하는 데 약간의 시행착오가 있었지만 결국 해냈다. 정확한 방법은 새 TIL 포스트에 정리해뒀다.
실제로 사용해본 공유 Claude 세션이 있다. 다음과 같이 물어봤다.
list tables in simonwillison.net
그리고 이어서
what has Simon said recently about MCP?
답을 구하기 위해 SQL 쿼리를 총 7번 실행했다.
필자의 LLM 도구에는 진작에 공식 MCP 연동이 추가됐어야 했다. 새 알파 버전 플러그인 llm-mcp-client가 바로 그 시도다.
llm install llm-mcp-client
llm -T 'MCP("https://datasette.simonwillison.net/-/mcp")' 'count the notes'출력 결과(추론 트레이스 포함, LLM 0.32rc2 사용):
노트 수 파악 중
"count the notes"라는 질문은 블로그 노트를 세어달라는 뜻일 것이다. 게시된 노트와 초안 중 어느 쪽인지 다소 애매하다. 명확한 답변을 위해 게시 노트와 초안 각각의 수를 쿼리해보겠다. 바로 실행해보자!
노트는 총 151개다.
해당 프롬프트에 대한 llm logs 출력 결과도 확인할 수 있다.
완성도가 충분히 높아지면 LLM 코어에 직접 통합하는 것도 고려 중이다. Datasette Agent와 llm-coding-agent에서도 MCP를 실험해볼 계획이다.
MCP가 처음 공개된 지 몇 달 후, 필자는 「Model Context Protocol has prompt injection security problems」라는 글을 썼다. 사용자가 직접 도구를 조합하는 방식이 데이터 유출 공격에 대한 책임을 결국 사용자 본인에게 떠넘긴다는 점을 지적한 글이었다. 당시에는 아직 Lethal Trifecta라는 개념을 만들기 전이었지만, 머릿속에는 바로 그 문제가 있었다.
그 이후로 셸과 curl에 자유롭게 접근할 수 있는 범용 에이전트가 등장했는데, 이쪽은 보안 유지가 훨씬 더 어렵다!
MCP에 대해 새삼 깨달은 점이 있다. 에이전트의 기능 범위와 잠재적 위험을 파악하는 일이, 오늘날 대부분의 범용·코딩 에이전트 도구처럼 개방된 네트워크 환경에서 임의의 명령어를 실행하는 방식보다 훨씬 쉽다는 것이다.
앞으로 민감한 LLM 기반 애플리케이션을 구축할 때는 MCP를 훨씬 더 적극적으로 활용할 계획이다.
현재 이 블로그의 장문 아티클만 보고 계신 겁니다. /atom/everything/을 구독하면 모든 포스트를 받아볼 수 있고, 다른 구독 옵션도 확인해보세요.