
Appwrite MCP 서버를 구축하며 대부분의 기능을 숨기기로 결정한 이유
요약
Appwrite가 Anthropic의 Model Context Protocol(MCP)을 활용하여 MCP 서버를 구축하며 겪은 기술적 도전과 설계 결정을 다룹니다. 특히 stdio 방식의 한계와 권한 부여(Authorization) 구현 과정에서의 복잡성을 설명합니다.
핵심 포인트
- MCP는 스키마와 핸드셰이크가 결합된 JSON-RPC 기반 프로토콜임
- stdio 전송 방식은 API 키 범위 제한으로 인해 프로젝트 전환이 어려움
- 호스팅 버전 구현 시 사용자 토큰 기반의 권한 부여 설계가 핵심 과제임
- MCP 명세상 권한 부여는 선택 사항이지만 보안을 위해 필수적임
Anthropic이 2024년 11월 25일에 Model Context Protocol (MCP)을 발표했을 때, 당시 Appwrite의 엔지니어링 리드(Engineering Lead)였던 Christy를 포함해 모든 이의 이목이 집중되었습니다. 저는 막 "엔지니어링 인턴 (Engineering Intern)" 역할을 시작했을 뿐이었고, 완전히 새로운 프로토콜이 무엇을 의미하는지, 혹은 왜 그렇게 큰 화제인지 전혀 알지 못했습니다.
겉으로 보기에는 제 생각이 완전히 틀린 것은 아니었습니다. MCP는 스키마(schema)와 핸드셰이크(handshake)가 결합된 JSON-RPC입니다. 우리가 16개월 동안 매달렸던 것은 그 주변에 결합된 모든 것들이었습니다.
MCP가 출시되었을 당시에는 Streamable HTTP가 존재하지 않았습니다. 이는 2025-03-26 개정판에서 HTTP+SSE를 대체했습니다.
stdio의 시대
Christy는 2025년 2월 26일까지 리포지토리(repo)에 작동하는 stdio 서버를 구현해 두었습니다. 우리는 이미 API 키를 보유하고 있었기에 연결 과정은 간단했습니다:
claude mcp add appwrite \
--env APPWRITE_PROJECT_ID=<YOUR_PROJECT_ID> \
--env APPWRITE_API_KEY=<YOUR_API_KEY> \
...
API 키는 설계상 정확히 하나의 프로젝트에만 범위(scoped)가 지정되므로, 한계점은 자격 증명(credential) 자체에 내장되어 있었습니다. 프로젝트를 전환하려면 에디터 설정(editor config)을 수정해야 했습니다. 프로젝트를 생성하는 것은 불가능했습니다. 조직(organization) 수준에서의 작업도 마찬가지였습니다.
자격 증명은 두 가지 전송 방식(transports) 사이의 결정적인 차이점이며, 호스팅 버전(hosted version)에서 어려운 모든 점은 프로젝트가 아닌 사용자에게 속한 토큰(token)으로 이를 교체하는 과정에서 발생합니다.
권한 부여(Authorization)가 일정을 잡아먹다
명세(spec)에 따르면, 권한 부여(authorization)는 진정으로 선택 사항입니다:
MCP 구현에서 권한 부여(Authorization)는 선택 사항(OPTIONAL)입니다. [...] HTTP 기반 전송 방식을 사용하는 구현체는 이 명세를 준수해야 합니다(SHOULD).
단 한 번의 도구 호출(tool call)로 데이터베이스를 날려버릴 수 있는 서비스의 경우, 이를 선택 사항(OPTIONAL)으로 취급하는 것이 편치 않았습니다. 만약 Auth0나 WorkOS를 사용한다면, 이는 설정 화면(config screen)의 문제입니다. Appwrite는 모든 것을 자체적으로(in-house) 유지하므로, Matej는 인증 서버(authorization server)를 직접 구축했고, 저는 리소스 서버(resource server)와 실제 클라이언트가 작동하기 전까지 Cloud에 여전히 부족했던 나머지 기능들을 구축했습니다.
단계 2부터 6까지가 "이 URL을 붙여넣기만 하면 됩니다"를 가능하게 만드는 부분입니다. 아무것도 사전 프로비저닝(pre-provisioned)되지 않습니다.
세 가지 RFC가 해당 흐름을 담당합니다. 보호된 리소스 메타데이터(Protected Resource Metadata, RFC 9728)는 전체 인증 명세에서 유일한 실질적인 필수 사항(MUST)입니다:
{
"resource": "https://mcp.appwrite.io/",
"authorization_servers": ["https://cloud.appwrite.io/v1/oauth2/console"],
...
리소스 인디케이터(Resource Indicators, RFC 8707)는 우리의 정식 URI를 토큰의 aud에 포함시켜, 다른 서비스를 위해 발행된 토큰이 우리에게 재사용(replayed)될 수 없도록 합니다. 동적 클라이언트 등록(Dynamic Client Registration, RFC 7591)은 클라이언트가 스스로 등록할 수 있게 해주는 기능입니다. 여기에 S256을 사용하는 PKCE, RFC 8414 discovery를 추가하면 그 형태가 갖춰집니다.
RFC들은 문서화되어 있습니다. 문서화되지 않은 것은 모든 클라이언트가 이를 다르게 읽는다는 점이며, 이는 운영 환경(production)에 도달해서야 알게 됩니다:
- Raycast는
2025-03-26권한 부여 사양(authorization spec)을 따르고 있었는데, 이는 보호된 리소스(protected-resource) 경로 대신/.well-known/oauth-authorization-server를 찾습니다. 저는 서버 앞에 로깅 프록시(logging proxy)를 두고 실제로 무엇을 요청하는지 관찰한 끝에 겨우 이를 찾아냈습니다. - Claude Code는 매 실행마다 재인증(re-authenticate)을 수행했습니다. 이 도구는 일시적인 루프백 포트(ephemeral loopback port)에서 대기하기 때문에 리다이렉트 URI(redirect URI)가 일치하지 않았습니다. OAuth의 네이티브 앱 BCP (RFC 8252 §7.3)는
127.0.0.1의 모든 포트를 허용해야 한다고 명시하고 있습니다. 저희는 그렇게 하지 않았습니다. - **저희 자체 스코프 카탈로그(scope catalog)**가 흐름을 끊어놓았습니다. 약 118개의 세분화된 스코프(granular scopes)가 생성하는
scope파라미터는 약 2,680자였는데, 검증기(validator)의 제한은 2,048자였습니다. 그 누구도 동의 화면(consent screen)에 도달하지 못했습니다.
이것을 구축하려는 분들께 한 가지 경고를 드리자면, RFC 7591은 2025-11-25에 SHOULD에서 MAY로 변경되었으며, 2026-07-28부로 폐기(deprecated)되어 Client ID 메타데이터 문서(Client ID Metadata Documents)로 대체됩니다. 저희도 그것을 출시했습니다. 이 사양의 이 부분은 여전히 변동 중입니다.
세션, 그리고 세션이 없는 상태
2025-06-18 사양에서는 서버가 InitializeResult와 함께 Mcp-Session-Id를 제공할 수 있었고, 종료를 위한 DELETE와 재개 가능성(resumability)을 위한 Last-Event-ID를 지원했습니다. 저희는 이 모든 것을 건너뛰었습니다:
StreamableHTTPSessionManager(app=server, json_response=False, stateless=True)
모든 요청은 베어러 토큰(bearer token)을 포함합니다. 이를 검증하고, 이를 통해 클라이언트를 구축하며, 호출을 처리합니다. 저장할 것도 없고, 재시작 시 잃을 것도 없으며, 복제본(replicas) 간에 스티키(sticky)하게 유지할 것도 없습니다.
결과적으로 이는 제가 공을 세울 수 없는 어떤 이유 덕분에 옳은 선택이 되었습니다. 2026-07-28 개정판에서는 프로토콜에서 세션을 완전히 제거했습니다. Mcp-Session-Id, initialize 핸드셰이크(handshake), GET SSE 스트림까지 모두 사라졌습니다. 저희가 유지하는 것은 버전 협상(version negotiation)뿐인데, 이는 클라이언트의 프로토콜 버전을 직접 선택할 수 없기 때문입니다.
주제를 벗어난 이야기지만, 왜 REST가 아닌가요?
저와 같은 많은 지식인들은 MCP가 왜 존재하는지 궁금해했을 것입니다. 예를 들어 REST를 사용한다면 100배는 더 간단해지지 않을까요?
모델은 오직 학습 데이터와 런타임(runtime) 시점에 당신이 전달하는 정보만을 알고 있습니다. 만약 모델이 Appwrite를 본 적이 없다면, 다음과 같은 내용을 절대 추측할 수 없을 것입니다:
POST https://<REGION>.cloud.appwrite.io/v1/tablesdb
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <API_KEY>
...
엔드포인트(endpoint), 헤더 이름(header names), 그리고 unique()가 마법 같은 값(magic value)이라는 사실까지 말이죠. 하지만 MCP를 사용하면 동일한 작업이 자기 기술적(self-describing)인 형태로 나타납니다:
{
"name": "tables_db_create",
"description": "Create a database in an Appwrite project",
...
AI가 (지금으로서는) 당신보다 훨씬 더 많은 가이드(handholding)를 필요로 한다는 사실을 알면 안심이 될 것입니다.
도구 선택 (Tool choice)
제가 살펴본 모든 MCP 서버는 작고 엄선된 세트(curated set)를 제공합니다. Appwrite는 SDK 메서드당 하나의 도구를 생성하며, 이는 38개 서비스에 걸쳐 총 981개의 메서드에 달합니다.
2026년 8월 기준 집계. GitHub의 90개는 22개의 도구 세트로 그룹화되어 있으며, 그중 5개는 기본적으로 활성화되어 있음.
서로 관련 없는 두 가지 이유 때문에, "모두 노출하기" 방식은 통하지 않습니다.
클라이언트가 이를 수용하지 않습니다. 2025년 초, Cursor는 "에이전트(Agent)에게 처음 40개의 도구만 전송할 것"이라고 문서화했으며, 그 이후는 조용히 잘라냈습니다(truncated). Windsurf는 50개가 넘어가자 아예 거부했습니다.

Discord, liviu74, 2025년 3월 14일. Windsurf는 거부했고, Cursor는 수락했으나 41번 이후의 도구들을 조용히 누락시킴.
이것은 이미 서비스별 플래그(per-service flags)가 마련되어 있는 상황이었음에도 발생한 일이며, 바로 이 점이 뼈아픈 부분입니다. 한 커뮤니티 사용자가 issue #17, "도구의 수를 줄여주세요"라는 이슈를 제기했습니다:
Cursor는 사용할 수 있는 MCP 도구가 40개로 제한되어 있지만, Appwrite는 단독으로만 195개의 도구를 가지고 있습니다. 따라서 다른 도구와 함께 사용할 수도 없고, Appwrite의 모든 도구를 사용할 수도 없습니다.
저는 --databases 옵션을 통해 범위를 좁힐 수 있다고 지적했습니다. 이에 대한 답변은 다음과 같았습니다:
그건 말도 안 됩니다. 그럼 다른 작업을 할 때마다 매번 MCP 파라미터 설정을 수정해야 하나요...? 게다가
--databases만 해도 도구가 42개나 되는데, 이는 이미 Cursor의 권장 제한(40개)을 초과합니다.
품질은 제한치를 훨씬 밑도는 수준으로 떨어집니다. 이 수치들은 서로 다른 관점에서도 일치합니다. Anthropic은 "사용 가능한 도구가 30~50개를 초과하는 시점"부터 성능 저하가 발생한다고 명시합니다. OpenAI는 "턴(turn) 시작 시 함수가 20개 미만이어야 한다"고 말합니다. Block의 Goose는 50개 이하를 권장합니다. 그리고 해결책은 수치로 증명됩니다. Anthropic의 고급 도구 사용 (advanced tool use) 연구에 따르면, 검색 도구가 활성화된 MCP 도구 사용 평가(evals)에서 Opus 4의 성능은 49%에서 74%로, Opus 4.5는 79.5%에서 88.1%로 향상되었으며, 정의(definitions)에 사용되는 토큰은 85% 감소했습니다. RAG-MCP는 선택 정확도를 3배 이상 높였습니다 (13.62% 대비 43.13%).
제 의견과 상충하는 주의 사항도 있습니다. MCPVerse에 따르면 일부 에이전트 모델(agentic models)은 큰 액션 공간(action spaces)을 잘 처리하는 것으로 나타났습니다. Claude-4-Sonnet은 오라클 도구 세트(oracle tool set)에서 62.3점을, 약 220개의 도구가 있을 때 62.4점을 기록했습니다. 방대한 카탈로그가 치명적인 것은 아닙니다. 다만 에이전트가 981개의 도구 중 단 3개만 필요할 때, 아무런 이득 없이 지불해야 하는 세금과 같습니다.
981개 대신 4개의 도구
appwrite_get_context: 현재 위치와 볼 수 있는 프로젝트를 알려줍니다.appwrite_search_tools: 자연어로 숨겨진 카탈로그를 검색합니다.appwrite_call_tool: 이름으로 특정 도구를 호출합니다.appwrite_search_docs: Appwrite 문서를 의미론적(semantically)으로 검색합니다.
검색은 요청 시점에 좁혀지기 때문에, 서비스별 플래그(per-service flags)를 완전히 삭제할 수 있었습니다. 변경(Mutations) 작업에는 confirm_write: true가 필요하며, 대화에 담기에 너무 큰 결과물은 MCP 리소스(resources)로 전환됩니다.
appwrite_search_tools 뒤에 숨겨진 스코어링(scoring) 방식은 의도적으로 단순합니다. 도구(tool)의 이름, 설명, 서비스 및 리소스에 대한 토큰(token) 및 서브스트링(substring) 매칭을 수행하며, 쿼리에서 추론된 동사가 도구와 일치하면 보너스를 주고, 일치하지 않으면 페널티를 부여합니다. 임베딩 (embeddings)도 없고, 다시 구축할 인덱스 (index)도 없으며, 핫 패스 (hot path)에서 추론 (inference) 호출도 발생하지 않습니다.
클라이언트가 보는 모습은 다음과 같습니다:

4개의 도구와 1개의 리소스 뒤에 981개의 메서드 (methods)가 있습니다. "Logout" 링크는 OAuth 세션입니다.
제가 설계에 대해 의구심을 갖는 것을 멈추게 된 이유는 우리만 그런 것이 아니었기 때문입니다. Stripe는 전체 API를 stripe_api_search 뒤에 배치했습니다. Sentry는 46개 중 9개를 search_sentry_tools를 통해 노출합니다. GitHub는 동적 도구 세트 (dynamic toolset) 도구들을 제거했으며 검색 대체제를 구축하려는 것으로 보입니다. 서로 협력할 이유가 없는 세 회사가 같은 시기에 '검색 후 실행 (search-then-execute)' 방식에 도달했습니다.
최종 결과물
claude mcp add --transport http appwrite https://mcp.appwrite.io/
API 키 (API key)도, 프로젝트 ID (project ID)도, 프로젝트를 전환하기 위한 설정 편집도 필요하지 않습니다. 이제 프로젝트와 조직 (organizations)은 자격 증명 (credential)의 속성이 아니라 호출 시의 파라미터 (parameters)입니다.
stdio가 사라진 것은 아닙니다. 호스팅 방식의 리팩터링 (refactor) 과정에서 이를 제거했다가 이틀 만에 다시 추가했는데, 셀프 호스팅 (self-hosted) 사용자들이 이를 필요로 하기 때문입니다. 이는 프로젝트 API 키로 실행되며, 프로젝트 키는 어차피 콘솔 레벨의 작업에 접근할 수 없으므로 981개의 메서드 중 647개를 가져옵니다.
해당 URL 뒤에는 OpenTelemetry, Sentry, Grafana 대시보드 (dashboards), 그리고 다른 클라우드 리전 (Cloud region)의 프로젝트가 general_access_forbidden을 반환하지 않도록 하는 리전 라우팅 (region routing)도 갖춰져 있습니다. 결국 패키지를 게시하는 것이 아니라 서비스를 운영하게 되는 것입니다. 이것이 제가 가장 과소평가했던 부분입니다.
이를 구축하려는 누구에게나 해주고 싶은 말
전송 (transport) 방식에 시간을 쏟는 것이 아닙니다. 권한 부여 (authorization) 명세와 그로 인해 파생되는 모든 것들이 수개월의 시간을 잡아먹는 주범입니다.
초기에 실제 클라이언트(client)를 대상으로 테스트를 진행하고, 그들이 (당신의 설계와) 의견이 다를 것을 예상하십시오. 서버 앞에 로깅 프록시 (logging proxy)를 두는 것이 문서를 한 번 더 훑어보는 것보다 저에게는 훨씬 더 가치 있는 일이었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

