EmDash는 /_emdash/api/mcp에 내장 Model Context Protocol(MCP) 서버를 제공합니다. MCP 클라이언트는 콘텐츠, 바이라인, 스키마, 미디어, 분류체계, 메뉴, 리비전, 설정을 읽고 관리하고 전체 사이트를 내보내거나 가져오는 데 사용합니다.
인증
MCP 엔드포인트에는 Bearer 토큰이 필요합니다. EmDash는 다음 토큰 흐름을 지원합니다.
| 방식 | 용도 |
|---|---|
| OAuth 2.1 Authorization Code with Proof Key for Code Exchange (PKCE) | 대화형 MCP 클라이언트. 사용자가 브라우저에서 요청된 스코프를 승인합니다. |
| Personal access token | 클라이언트 또는 자동화용 장기 액세스. 토큰은 ec_pat_ 접두사를 사용하며 관리자에서 생성합니다. |
| OAuth 2.0 Device Authorization Grant | 사용자에게 브라우저에서 코드 승인을 요청하는 CLI 클라이언트. emdash login이 이 흐름을 사용합니다. |
세션 쿠키는 MCP 엔드포인트 인증에 사용되지 않습니다.
Scopes
OAuth 및 개인 액세스 토큰은 클라이언트가 호출할 수 있는 도구를 제한합니다. 사용자 역할은 별도로 확인되므로 스코프는 사용자가 갖지 않은 권한을 부여하지 않습니다.
| Scope | 액세스 |
|---|---|
content:read | 콘텐츠, 바이라인, 분류체계, 용어, 메뉴, 리비전 읽기 및 검색. 초안 유형 콘텐츠는 사용자의 content:read_drafts 권한도 필요합니다. |
content:write | 콘텐츠, 바이라인, 리비전 생성 및 변경. 기존 토큰 호환을 위해 taxonomies:manage 및 menus:manage도 부여합니다. |
media:read | 미디어 레코드 읽기. |
media:write | 미디어 업로드, 등록, 업데이트, 삭제. |
schema:read | 컬렉션 및 필드 읽기. |
schema:write | 컬렉션 및 필드 생성, 업데이트, 삭제. |
taxonomies:manage | 분류체계 정의 및 용어 생성, 업데이트, 삭제. |
menus:manage | 메뉴 및 메뉴 항목 생성, 업데이트, 삭제. |
settings:read | 사이트 설정 읽기. |
settings:manage | 사이트 설정 업데이트. |
mcp:tools | 활성화된 모든 플러그인이 노출하는 MCP 도구 호출. |
mcp:tools:<pluginId> | 활성화된 한 플러그인이 노출하는 MCP 도구 호출. |
transfer:export | 전체 사이트를 사이트 패키지로 내보내고 다운로드. |
transfer:analyze | 사이트 패키지를 업로드하고 가져오기용으로 분석. |
transfer:execute | 사이트 가져오기 시작, 진행, 취소, 포기. |
admin | 사이트 전송 도구를 포함한 모든 코어 도구 호출. 플러그인 도구는 여전히 mcp:tools 또는 플러그인별 스코프가 필요합니다. |
admin 스코프에는 transfer:export, transfer:analyze, transfer:execute가 포함됩니다. 각 전송 스코프는 자체 동작만 부여하며 관리자 역할이 필요합니다. 에이전트 등이 내보내기/가져오기 없이 사이트 패키지만 분석하도록 하려면 admin 대신 transfer:analyze를 부여하세요.
인가 코드 동의 페이지에서 사용자는 요청된 스코프를 제거할 수 있습니다. EmDash는 요청을 클라이언트 등록 스코프 및 사용자 역할과 교차하고 빈 부여를 거부합니다.
역할 요구 사항
다음 표는 넓은 기능별 최소 역할입니다. 다른 사용자 콘텐츠에 대해 작업할 때 소유권 검사로 더 높은 역할이 필요할 수 있습니다.
| 기능 | 최소 역할 |
|---|---|
| 게시된 콘텐츠, 미디어, 분류체계, 용어, 메뉴 읽기 | Subscriber |
| 초안, 예약 콘텐츠, 휴지통, 비교, 리비전 읽기 | Contributor |
| 콘텐츠 생성 또는 미디어 업로드 | Contributor |
| 소유 콘텐츠 편집/게시 및 미디어 등록 | Author |
| 바이라인, 분류체계, 메뉴 또는 모든 사용자 콘텐츠 관리 | Editor |
| 스키마 또는 설정 읽기 | Editor |
| 스키마/설정 변경, 콘텐츠 영구 삭제, 미디어 사용 복구 | Admin |
| 전체 사이트 내보내기 또는 가져오기 | Admin |
전체 역할 정의는 사용자 역할을 참조하세요.
전송
서버는 stateless Streamable HTTP를 사용합니다. 각 요청은 독립적이며 MCP 세션이나 Server-Sent Events 연결을 유지하지 않습니다.
| 메서드 | 엔드포인트 | 동작 |
|---|---|---|
POST | /_emdash/api/mcp | JSON-RPC 초기화, 도구 목록, 도구 호출을 수락합니다. |
GET | /_emdash/api/mcp | 405 Method Not Allowed를 반환합니다. |
DELETE | /_emdash/api/mcp | 405 Method Not Allowed를 반환합니다. |
응답은 JSON-RPC 2.0을 사용합니다. 도구 요청을 구성하기 전에 tools/list를 호출해 현재 입력 스키마와 MCP 주석을 얻으세요.
도구 목록
다음 목록은 tools/list가 반환하는 정적 도구와 일치합니다. 클라이언트가 도구 이름 대신 표시할 수 있어 등록 제목을 포함합니다.
콘텐츠 도구
| Tool | Registered title | Required scope |
|---|---|---|
content_list | List Content | content:read |
content_get | Get Content | content:read |
content_create | Create Content | content:write |
content_update | Update Content | content:write |
content_delete | Delete Content (Trash) | content:write |
content_restore | Restore Content | content:write |
content_permanent_delete | Permanently Delete Content | content:write |
content_publish | Publish Content | content:write |
content_unpublish | Unpublish Content | content:write |
content_schedule | Schedule Content | content:write |
content_unschedule | Cancel Scheduled Publication | content:write |
content_compare | Compare Live vs Draft | content:read |
content_discard_draft | Discard Draft | content:write |
content_list_trashed | List Trashed Content | content:read |
content_duplicate | Duplicate Content | content:write |
content_translations | Get Content Translations | content:read |
바이라인 도구
| Tool | Registered title | Required scope |
|---|---|---|
byline_list | List Bylines | content:read |
byline_get | Get Byline | content:read |
byline_create | Create Byline | content:write |
byline_update | Update Byline | content:write |
byline_delete | Delete Byline | content:write |
byline_translations | List Byline Translations | content:read |
스키마 도구
| Tool | Registered title | Required scope |
|---|---|---|
schema_list_collections | List Collections | schema:read |
schema_get_collection | Get Collection Schema | schema:read |
schema_list_block_types | List Block Types | schema:read |
schema_get_block_type | Get Block Type | schema:read |
schema_create_block_type | Create Block Type | schema:write |
schema_update_block_type | Update Block Type | schema:write |
schema_activate_block_type_version | Activate Block Type Version | schema:write |
schema_create_collection | Create Collection | schema:write |
schema_delete_collection | Delete Collection | schema:write |
schema_update_collection | Update Collection | schema:write |
schema_create_field | Add Field to Collection | schema:write |
schema_delete_field | Remove Field from Collection | schema:write |
schema_update_field | Update Field | schema:write |
미디어 도구
| Tool | Registered title | Required scope |
|---|---|---|
media_list | List Media | media:read |
media_create | Confirm Signed Media Upload | media:write |
media_upload | Upload Media | media:write |
media_get | Get Media Item | media:read |
media_update | Update Media Metadata | media:write |
media_delete | Delete Media | media:write |
media_usage_repair | Repair Media Usage Index | admin |
검색 도구
| Tool | Registered title | Required scope |
|---|---|---|
search | Search Content | content:read |
분류체계 도구
| Tool | Registered title | Required scope |
|---|---|---|
taxonomy_list | List Taxonomies | content:read |
taxonomy_get | Get Taxonomy Definition | content:read |
taxonomy_create | Create Taxonomy Definition | taxonomies:manage |
taxonomy_update | Update Taxonomy Definition | taxonomies:manage |
taxonomy_delete | Delete Taxonomy Definition | taxonomies:manage |
taxonomy_list_terms | List Taxonomy Terms | content:read |
taxonomy_create_term | Create Taxonomy Term | taxonomies:manage |
taxonomy_update_term | Update Taxonomy Term | taxonomies:manage |
taxonomy_delete_term | Delete Taxonomy Term | taxonomies:manage |
taxonomy_term_translations | List Term Translations | content:read |
메뉴 도구
| Tool | Registered title | Required scope |
|---|---|---|
menu_list | List Menus | content:read |
menu_get | Get Menu with Items | content:read |
menu_translations | List Menu Translations | content:read |
menu_create | Create Menu | menus:manage |
menu_update | Update Menu | menus:manage |
menu_delete | Delete Menu | menus:manage |
menu_set_items | Set Menu Items | menus:manage |
리비전 도구
| Tool | Registered title | Required scope |
|---|---|---|
revision_list | List Revisions | content:read |
revision_restore | Restore Revision | content:write |
설정 도구
| Tool | Registered title | Required scope |
|---|---|---|
settings_get | Get Site Settings | settings:read |
settings_update | Update Site Settings | settings:manage |
사이트 전송 도구
transfer:*는 transfer:export, transfer:analyze, transfer:execute 중 하나를 의미합니다. admin 스코프는 이 표의 모든 요구를 충족합니다.
| Tool | Registered title | Required scope |
|---|---|---|
site_transfer_capabilities | Get Site Transfer Capabilities | transfer:* |
site_export_start | Start Site Export | transfer:export |
site_export_status | Get Site Export Status | transfer:export |
site_import_analyze | Analyze Site Import | transfer:analyze |
site_import_start | Start Site Import | transfer:execute |
site_import_status | Get Site Import Status | transfer:* |
site_import_resume | Resume Site Import | transfer:execute |
site_import_receipt | Get Site Import Receipt | transfer:* |
관리자가 요청을 승인하면 site_export_start와 site_import_start는 스코프 없는 토큰도 수락합니다. 승인된 요청이 시작하는 작업에 대해 site_export_status, site_import_status, site_import_resume, site_import_receipt도 동일 토큰을 스코프 없이 수락합니다.
도구 스키마 사용
tools/list는 각 도구의 설명, JSON 입력 스키마, 주석을 반환합니다. 설치된 EmDash 버전이 지원하는 필드, 허용 값, 제한을 사용하도록 호출 전 메타데이터를 읽으세요.
예: 기사를 업데이트하는 클라이언트는 먼저 content_get을 호출하고 반환된 _rev를 유지합니다. 그다음 다음 JSON-RPC 요청을 보낼 수 있습니다.
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "content_update",
"arguments": {
"collection": "articles",
"id": "01JARTICLE0000000000000000",
"data": { "title": "Updated title" },
"_rev": "opaque-revision-token"
}
}
}
결과는 첫 번째 콘텐츠 블록의 JSON 텍스트로 반환됩니다. 출력 스키마가 있는 도구는 같은 값을 structuredContent에도 반환할 수 있습니다.
콘텐츠 수명 주기 및 바이라인
콘텐츠 수명 주기 참조는 MCP, REST, CLI, 관리 패널이 공유하는 상태, 리비전, 권한, 충돌, 훅 동작을 정의합니다.
content_get은 불투명한 _rev 값을 반환합니다. content_update, content_publish, content_unpublish, content_schedule, content_discard_draft에 전달하세요. 오래된 값은 충돌을 반환하므로 재시도 전에 항목을 다시 읽으세요.
content_update는 부분 업데이트입니다. 생략한 필드는 현재 값을 유지합니다. 게시된 항목 업데이트는 라이브 버전은 그대로 두고 초안을 준비합니다. content_compare로 라이브와 초안을 검토한 뒤 content_publish로 게시하거나 content_discard_draft로 제거하세요. content_delete는 휴지통으로 이동합니다. 영구 삭제는 content_permanent_delete만 가능합니다.
바이라인은 재사용 가능한 저자/기고 크레딧입니다. byline_create는 게스트 크레딧을 만들거나 CMS 사용자에 연결할 수 있습니다. 반환된 바이라인 ID를 content_create와 content_update가 받는 bylines 입력에 전달하세요. 바이라인 삭제는 콘텐츠에서 해당 크레딧을 제거하고 기본 바이라인을 지웁니다.
MCP 쓰기는 관리자 항목 편집 잠금에 참여하지 않습니다. _rev 검사는 이를 받는 작업을 보호하지만, 편집자가 열어 둔 동안 다른 쓰기 도구가 항목을 변경할 수 있습니다.
번역
콘텐츠, 바이라인, 분류 용어, 메뉴 번역 도구는 해당 번역 그룹의 모든 로케일 변형을 반환합니다. 스키마에 translationOf가 있으면 생성 도구 입력을 사용하세요. 필수 필드는 tools/list가 기준입니다.
content_translations는 컬렉션과 콘텐츠 ID 또는 슬러그를 받습니다. 바이라인, 용어, 메뉴 번역 도구는 한 레코드 ID 또는 공유 번역 그룹 ID를 받습니다. 초안 액세스가 없는 사용자는 게시된 콘텐츠 번역만 봅니다.
스키마, 미디어, 분류체계, 메뉴
스키마 도구는 데이터베이스 구조를 변경합니다. 콘텐츠 생성 또는 필드 변경 전에 schema_get_collection을 사용해 사용 가능한 필드 이름, 유형, 제약, 검증 규칙을 얻으세요. 컬렉션 및 필드 삭제는 저장된 콘텐츠나 필드 값을 제거하며 되돌릴 수 없습니다.
media_upload로 base64 인코딩된 바이트를 보냅니다. 업로드는 구성된 크기 및 MIME 제한의 대상입니다. 동일 바이트는 deduplicated: true로 기존 미디어를 반환할 수 있습니다.
media_create는 POST /_emdash/api/media/upload-url로 만든 보류 업로드를 확인합니다. 반환된 서명 URL로 파일을 업로드한 뒤 같은 사용자 계정에서 반환된 storageKey로 media_create를 호출하세요. 도구는 미디어 라이브러리에 제공하기 전에 저장 파일 존재 및 업로드 URL 요청 시 제공한 크기 일치를 확인합니다.
분류체계 정의는 분류와 적용 컬렉션을 설명합니다. 용어는 콘텐츠에 할당되는 개별 값입니다. 계층 용어는 parentId를 사용할 수 있으나 부모는 같은 분류체계에 속해야 하며 순환을 만들 수 없습니다. 비계층 분류체계에서 parentId로 용어를 만들거나 업데이트하면 VALIDATION_ERROR입니다. 자식이 있는 용어는 삭제 전 자식을 제거하거나 이동해야 합니다.
menu_set_items는 메뉴의 전체 항목 목록을 하나의 원자적 작업으로 교체합니다. 배열 순서가 메뉴 순서가 됩니다. 중첩 항목의 parentIndex는 같은 배열의 이전 항목을 가리키므로 부모를 자식 앞에 두세요.
media_usage_repair는 한 컬렉션 또는 모든 컬렉션을 처리할 수 있으며 큰 사이트에서 오래 걸릴 수 있습니다. complete, partial, failed, stale 상태는 성공한 도구 응답입니다. isError 대신 반환된 상태와 개수를 확인하세요. 인증, 검증, 예기치 않은 실행 실패는 isError: true입니다.
사이트 전송
site_* 도구는 전체 사이트를 사이트 패키지로 내보내고 빈 사이트에 패키지를 가져옵니다. 작업을 시작·진행하고 제한된 요약을 반환합니다. 패키지 바이트, 미디어, 레코드 내용, principal 이메일, 다운로드 URL은 전달하지 않습니다. CLI 또는 REST API로 내보내기를 다운로드하고 가져오기용 패키지를 업로드한 뒤 작업 ID로 참조하세요.
모든 전송 도구는 Admin 역할이 필요합니다. 역할은 스코프보다 먼저 확인되며, 비관리자는 INSUFFICIENT_PERMISSIONS를 받고 승인 요청이 생성되지 않습니다.
내보내기
site_export_start는 comments(기본 true)를 받고 새 작업을 반환합니다. site_export_status는 호출마다 하나의 제한된 내보내기 단계를 실행하고 작업과 nextRequestInMs를 보고합니다. nextRequestInMs가 null이 될 때까지 해당 지연 후 다시 호출하세요. advance: false로 단계 없이 상태만 읽을 수 있습니다. 내보내기가 완료되면 결과에 totals(종류별 레코드 수, 미디어 수 및 바이트, 패키지 파일 수 및 바이트)도 포함됩니다.
가져오기
먼저 패키지를 업로드하세요. CLI의 emdash site import <file> --analyze가 업로드·분석하고 작업 ID를 출력합니다.
site_import_analyze는 호출마다 하나의 제한된 분석 단계를 실행합니다. nextRequestInMs가 null이 될 때까지 반복하면 packageDigest, planDigest, executable, 개수, 크기, principal, decisions, transformations, warnings, blockers가 포함된 계획 요약을 얻습니다. 각 transformation은 code, 레코드 kind(있을 때), count로 나열되며 적용 ID나 값은 없습니다. principal, warnings, blockers는 각 최대 50개, 전체 개수는 total에 있습니다. principal은 이메일 없이 제안 및 현재 매핑된 대상 사용자 ID로 나열됩니다. decisions로 principal을 대상 사용자 ID(또는 null)에 매핑하고 패키지 또는 대상 제목·태그라인을 선택하세요. 변경마다 새 planDigest가 생성됩니다.
site_import_start는 작업 ID와 최신 계획의 packageDigest, planDigest를 받습니다. 계획에 blocker가 없어야 합니다. 도구는 destructiveHint: true입니다. 시작 후 가져오기는 사이트에 쓰고 완료되거나 관리자가 포기할 때까지 다른 쓰기를 차단합니다. 호출 전 계획을 사용자에게 보여 확인을 받으세요.
site_import_resume은 하나의 제한된 가져오기 단계를 실행하고 작업과 nextRequestInMs를 보고합니다. nextRequestInMs가 null이 될 때까지 호출하세요. 연결 끊김 후 반복해도 안전합니다. site_import_status는 가져오기를 진행하지 않고 작업과 업로드된 파일 수를 보고합니다. site_import_receipt는 가져오기 완료 후 receiptDigest를 포함한 전체 영수증을 반환합니다.
가져오기 실행 중 및 실패·취소 후 포기 전까지 쓰기 가능한 다른 모든 도구는 TRANSFER_IMPORT_IN_PROGRESS로 실패합니다(플러그인 도구 포함). readOnlyHint: true 도구와 8개의 site_* 도구는 계속 동작하며 initialize와 tools/list는 차단되지 않습니다. 미디어 사용 활성화 중에는 쓰기 도구가 동일하게 MEDIA_USAGE_ACTIVATION_IN_PROGRESS로 실패합니다.
작업 요약에는 id, kind, state, stage, progress, packageDigest, planDigest, error({ code } 또는 null), 타임스탬프가 포함됩니다. progress는 { done, total } 단계와 내보내기가 지금까지 쓴 records, 알려지면 bytesDone 및 bytesTotal입니다. MCP 도구는 가져오기 취소·포기를 할 수 없습니다. REST API를 사용하세요.
승인
admin 또는 필요한 전송 스코프가 있는 토큰은 승인을 요청하지 않습니다. 둘 다 없는 토큰(예: transfer:analyze만 있는 에이전트)에서는 관리자가 승인하면 site_export_start와 site_import_start가 실행됩니다.
- 스코프 없는 첫 호출은 보류 승인 요청을 만들고
TRANSFER_APPROVAL_REQUIRED로 실패합니다. 메시지와_meta.details에approvalId와expiresAt이 있습니다. 같은 인수로approvalId없이 다시 호출하면 같은 열린 요청이 반환됩니다. - 관리자는 설정 → Transfer의 Approval requests 또는 REST API의 세션 전용 승인 엔드포인트에서 승인합니다. API 토큰은 요청을 승인할 수 없습니다.
- 클라이언트는 같은 인수와
approvalId로 다시 호출합니다. 승인은 해당 호출이 작업을 시작할 때 소비됩니다. 시작에 실패하면 만료 전까지 같은approvalId로 재시도할 수 있습니다.
요청은 사용자, 토큰, 동작, 정확한 인수(내보내기 옵션 또는 가져오기 작업 ID와 두 digest)에 묶입니다. 보류 요청은 생성 후 15분, 승인된 요청은 승인 후 15분에 만료됩니다. 다른 인수·토큰이거나 거부·만료·사용된 승인이면 TRANSFER_APPROVAL_INVALID입니다.
site_import_start는 요청 생성 전 digest, 작업 상태, 계획 blocker를 확인하므로 관리자는 실행 가능한 가져오기만 승인합니다. 승인에는 토큰 ID가 필요하며 없으면 INSUFFICIENT_SCOPE입니다.
승인된 호출으로 작업이 시작된 후 같은 사용자와 토큰은 해당 작업에 대해 스코프 없이 site_export_status 또는 site_import_status, site_import_resume, site_import_receipt를 호출할 수 있습니다.
플러그인 도구
관리자는 각 플러그인의 MCP 표면을 활성화해야 합니다. 활성화된 도구는 tools/list에 <pluginId>__<localName>으로 나타나며 토큰 인증 호출에 mcp:tools 또는 mcp:tools:<pluginId>가 필요합니다. EmDash는 플러그인 경로가 선언한 권한도 확인하고 감사 로그에 플러그인, 도구, 경로, 행위자를 기록합니다.
플러그인 도구는 설치별이므로 위 정적 목록에 포함되지 않습니다.
OAuth 디스커버리
MCP 클라이언트는 보호 리소스 메타데이터에서 인가 서버를 발견합니다.
GET /.well-known/oauth-protected-resource
응답은 /_emdash/api/mcp를 보호 리소스로 식별하고 인가 서버로 연결합니다. 클라이언트는 다음에서 메타데이터를 읽습니다.
GET /.well-known/oauth-authorization-server/_emdash
이 문서는 현재 인가, 토큰, 등록, 디바이스 인가 엔드포인트, 지원 스코프, grant 유형, PKCE 방법 S256을 제공합니다. OAuth 프로토콜 경로를 하드코딩하지 말고 발견한 값을 사용하세요.
인증되지 않은 MCP 요청은 디스커버리 URL과 함께 401을 반환합니다.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"
오류
도구 실패는 isError: true입니다. 첫 텍스트 블록은 안정적인 코드로 시작하고 _meta.code가 구조화 메타데이터를 읽는 클라이언트용으로 반복합니다.
{
"content": [{ "type": "text", "text": "[NOT_FOUND] Collection 'articles' not found" }],
"isError": true,
"_meta": { "code": "NOT_FOUND" }
}
인증 실패는 INSUFFICIENT_SCOPE, INSUFFICIENT_PERMISSIONS 등의 코드를 사용합니다. 전송 실패는 JSON-RPC 내부 오류 -32603을 사용하며 기본 예외는 노출하지 않습니다.