채팅창에서 내 코드가 실행된다
Claude Desktop 채팅에 "방금 한 얘기 노트에 저장해줘"라고 적는다. 모델은 답을 지어내는 대신 noteadd라는 도구를 호출하고, 잠시 뒤 "저장했다"는 결과가 답변에 박힌다. 다음 대화에서 "내 노트 뭐 있지?"라고 물으면 notelist가 돌아 아까 저장한 메모가 그대로 나온다.
그 noteadd·notelist를 실행한 것은 내 노트북에서 도는 몇십 줄짜리 서버다. 플러그인 스토어에 올린 것도, 어딘가에 배포한 것도 아니다. 로컬 파일 하나를 dart run으로 띄우고, Claude Desktop 설정에 경로 한 줄을 등록했을 뿐이다.
이 글은 그 서버를 처음부터 만든다. 끝에 가면 위 캡처가 당신 화면에서 재현된다.
왜 서버인가 — 데모가 아니라 어댑터
"LLM으로 무언가를 한다"고 하면 보통 프롬프트를 떠올린다. 하지만 모델이 내 데이터를 읽거나 내 기능을 실행해야 하는 순간, 프롬프트만으로는 닿지 않는다. 그때마다 우리는 클라이언트마다 제각각인 연동 코드를 새로 붙여 왔다.
MCP(Model Context Protocol)는 그 연결을 규약 하나로 통일한다. 서버는 자기가 가진 기능을 이름 붙은 도구로 노출하고, 모델은 그 도구를 표준 방식으로 호출한다. 한 번 만든 서버는 Claude Desktop이든 다른 MCP 클라이언트든 같은 규약으로 붙는다.
그래서 이 글이 만드는 서버의 정체는 "온도 알려주는 데모"가 아니라 내 기능·데이터를 LLM에 표준으로 잇는 한 줄 어댑터다. 예제로 고른 기능은 영속 노트장이다. 대화가 끝나도 남는 메모를 네 개의 도구로 다룬다.
| 도구 | 하는 일 |
|---|---|
note_add | 노트를 저장한다(대화가 끝나도 남는다). |
note_list | 저장한 노트를 최신순으로 보여준다(태그 필터 가능). |
note_search | 본문에 특정 문자열이 든 노트를 찾는다(대소문자 무시). |
note_delete | id로 노트를 지운다. |
모델은 대화의 맥락에 따라 이 넷 중 무엇을 부를지 스스로 고른다. 우리가 하는 일은 넷의 의미를 정의하는 것뿐이다.
이 서버가 앉는 자리
만들 것을 전체 그림 안에 먼저 앉혀 두면, 지금 짜는 코드가 어디까지 책임지는지가 분명해진다.
[ LLM ]
│
[ MCP 클라이언트 ] ← Claude Desktop (그리고 다음 글의 mcp_client)
│ JSON-RPC over stdio
▼
[ MCP 서버 ] ← 이번 글이 만드는 것
│ · 서버 기동(stdio)
│ · 도구 4개 등록(이름·입력 스키마·핸들러)
▼
[ notes.json ] ← 영속 저장
이번 글의 범위는 점선 박스, 곧 서버 + 도구 정의 + 저장까지다. 클라이언트 쪽(Claude Desktop)은 이미 있는 것을 등록해서 쓴다. 직접 만드는 클라이언트는 다음 편의 몫이다.