EmDash에는 /_emdash/api/mcp에 내장된 Model Context Protocol (MCP) 서버가 포함되어 있으며, AI 어시스턴트를 위한 콘텐츠 관리 작업을 도구로 노출합니다.
이 페이지는 프로토콜 세부사항을 다룹니다: 인증, 트랜스포트, 도구 명세, OAuth 검색, 오류 처리.
인증
MCP 서버는 세 가지 인증 방법을 지원합니다:
| 방법 | 작동 방식 |
|---|---|
| OAuth 2.1 Authorization Code + PKCE | MCP 클라이언트를 위한 표준 플로우. 사용자가 브라우저에서 범위를 승인합니다. |
| 개인 액세스 토큰 (PAT) | 관리 패널에서 생성된 장기 ec_pat_* 토큰. |
| Device Flow | 브라우저에서 코드를 승인하는 CLI 스타일 플로우. emdash login에서 사용됩니다. |
세션 쿠키(관리 UI에서)도 작동하지만 외부 MCP 클라이언트에는 실용적이지 않습니다.
범위
토큰은 클라이언트가 수행할 수 있는 작업을 제한하기 위해 범위가 지정됩니다. 범위는 OAuth 인증 중에 요청되며 모든 도구 호출에서 적용됩니다.
| 범위 | 접근 가능 |
|---|---|
content:read | 콘텐츠 목록, 가져오기, 비교, 검색. 택소노미 용어와 메뉴 목록. |
content:write | 콘텐츠 생성, 업데이트, 삭제, 게시, 게시 취소, 예약, 복제, 복원. 택소노미 용어 생성. |
media:read | 미디어 항목 목록 및 가져오기. |
media:write | 미디어 메타데이터 업데이트 및 삭제. |
schema:read | 컬렉션 목록 및 컬렉션 스키마 가져오기. |
schema:write | 컬렉션과 필드 생성 및 삭제. |
admin | 모든 작업에 대한 전체 접근. |
admin 범위는 모든 것에 대한 접근을 부여합니다. 세션 기반 인증(토큰 없음)도 사용자의 역할에 따라 전체 접근이 가능합니다.
역할 요구사항
범위 외에도 일부 도구는 최소 RBAC 역할을 요구합니다:
| 작업 | 최소 역할 |
|---|---|
| 콘텐츠 작업 | 최소 없음 (범위가 접근 제어) |
| 스키마 읽기 | 편집자 (40) |
| 스키마 쓰기 | 관리자 (50) |
역할 정의는 인증 가이드를 참조하세요.
트랜스포트
서버는 스테이트리스 모드에서 Streamable HTTP 트랜스포트를 사용합니다. 각 요청은 독립적이며 — 세션이나 장기 연결이 없습니다.
POST /_emdash/api/mcp— JSON-RPC 도구 호출 전송GET /_emdash/api/mcp— 405 반환 (스테이트리스 모드에서는 SSE 없음)DELETE /_emdash/api/mcp— 405 반환 (닫을 세션 없음)
응답은 JSON-RPC 2.0 형식을 따릅니다. 오류는 범위 및 권한 실패를 위한 MCP 전용 코드와 함께 표준 JSON-RPC 오류 코드를 사용합니다.
도구
서버는 7개 도메인에 걸쳐 33개의 도구를 노출합니다. 각 도구는 JSON 텍스트 콘텐츠로 결과를 반환하거나, 실패 시 isError: true와 함께 오류 메시지를 반환합니다.
콘텐츠 도구
content_list
선택적 필터링과 페이지네이션으로 컬렉션의 콘텐츠 항목을 나열합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 (예: posts, pages) |
status | string | 아니오 | 필터: draft, published 또는 scheduled |
limit | integer | 아니오 | 반환할 최대 항목 수 (1-100, 기본값 50) |
cursor | string | 아니오 | 이전 응답의 페이지네이션 커서 |
orderBy | string | 아니오 | 정렬 필드 (예: created_at, updated_at) |
order | string | 아니오 | 정렬 방향: asc 또는 desc (기본값 desc) |
locale | string | 아니오 | 로캘별 필터 (예: en, fr). i18n에서만 관련됨. |
범위: content:read | 읽기 전용: 예
content_get
ID 또는 슬러그로 단일 콘텐츠 항목을 가져옵니다. 모든 필드 값, 메타데이터, 낙관적 동시성을 위한 _rev 토큰을 반환합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 콘텐츠 항목 ID (ULID) 또는 슬러그 |
locale | string | 아니오 | 슬러그 조회용 로캘. ID는 전역적으로 고유합니다. |
범위: content:read | 읽기 전용: 예
content_create
새 콘텐츠 항목을 생성합니다. data 객체에는 컬렉션의 스키마와 일치하는 필드 값이 포함되어야 합니다 — schema_get_collection을 사용하여 사용 가능한 필드를 확인하세요. 항목은 기본적으로 draft로 생성됩니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
data | object | 예 | 키-값 쌍으로 된 필드 값 |
slug | string | 아니오 | URL 슬러그 (생략 시 제목에서 자동 생성) |
status | string | 아니오 | 초기 상태: draft 또는 published (기본값 draft) |
locale | string | 아니오 | 콘텐츠의 로캘 (기본값: 사이트 기본값) |
translationOf | string | 아니오 | 이것이 번역인 항목의 ID |
범위: content:write
content_update
기존 콘텐츠 항목을 업데이트합니다. 변경하려는 필드만 포함하세요 — 지정하지 않은 필드는 변경되지 않습니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 콘텐츠 항목 ID 또는 슬러그 |
data | object | 아니오 | 업데이트할 필드 값 |
slug | string | 아니오 | 새 URL 슬러그 |
status | string | 아니오 | 새 상태: draft 또는 published |
_rev | string | 아니오 | 충돌 감지를 위한 content_get의 리비전 토큰 |
범위: content:write
content_delete
콘텐츠 항목을 휴지통으로 이동하여 소프트 삭제합니다. content_restore로 취소하거나 content_permanent_delete로 영구 삭제합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 콘텐츠 항목 ID 또는 슬러그 |
범위: content:write | 파괴적: 예
content_restore
휴지통에서 소프트 삭제된 콘텐츠 항목을 복원합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 콘텐츠 항목 ID 또는 슬러그 |
범위: content:write
content_permanent_delete
휴지통에 있는 콘텐츠 항목을 영구적이고 비가역적으로 삭제합니다. 항목이 먼저 휴지통에 있어야 합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 콘텐츠 항목 ID 또는 슬러그 |
범위: content:write | 파괴적: 예
content_publish
콘텐츠 항목을 게시하여 사이트에서 라이브로 만듭니다. 현재 초안에서 게시된 리비전을 생성합니다. 이후 편집은 재게시할 때까지 라이브 버전에 영향을 주지 않는 새 초안을 생성합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 콘텐츠 항목 ID 또는 슬러그 |
범위: content:write
content_unpublish
게시된 항목을 초안 상태로 되돌립니다. 라이브 사이트에서 더 이상 보이지 않지만 콘텐츠는 보존됩니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 콘텐츠 항목 ID 또는 슬러그 |
범위: content:write
content_schedule
콘텐츠 항목을 미래 게시 예약합니다. 지정된 날짜/시간에 자동으로 게시됩니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 콘텐츠 항목 ID 또는 슬러그 |
scheduledAt | string | 예 | ISO 8601 날짜/시간 (예: 2026-06-01T09:00:00Z) |
범위: content:write
content_compare
콘텐츠 항목의 게시된(라이브) 버전과 현재 초안을 비교합니다. 두 버전과 변경 사항 여부를 나타내는 플래그를 반환합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 콘텐츠 항목 ID 또는 슬러그 |
범위: content:read | 읽기 전용: 예
content_discard_draft
현재 초안을 폐기하고 마지막 게시된 버전으로 되돌립니다. 한 번 이상 게시된 항목에서만 작동합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 콘텐츠 항목 ID 또는 슬러그 |
범위: content:write | 파괴적: 예
content_list_trashed
컬렉션의 휴지통에 있는 소프트 삭제된 콘텐츠 항목을 나열합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
limit | integer | 아니오 | 최대 항목 수 (1-100, 기본값 50) |
cursor | string | 아니오 | 페이지네이션 커서 |
범위: content:read | 읽기 전용: 예
content_duplicate
기존 콘텐츠 항목의 사본을 생성합니다. 복제본은 제목에 “(Copy)“가 추가되고 자동 생성된 슬러그로 초안으로 생성됩니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 복제할 콘텐츠 항목 ID 또는 슬러그 |
범위: content:write
content_translations
콘텐츠 항목의 모든 로캘 변형을 가져옵니다. 번역 그룹과 각 로캘 버전의 요약을 반환합니다. i18n이 활성화된 경우에만 관련됩니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 콘텐츠 항목 ID 또는 슬러그 |
범위: content:read | 읽기 전용: 예
스키마 도구
schema_list_collections
CMS에 정의된 모든 콘텐츠 컬렉션을 나열합니다. 슬러그, 라벨, 지원 기능, 타임스탬프를 반환합니다.
매개변수 없음.
범위: schema:read | 최소 역할: 편집자 | 읽기 전용: 예
schema_get_collection
모든 필드 정의를 포함한 컬렉션의 상세 정보를 가져옵니다. 필드는 데이터 모델을 설명합니다: 이름, 타입, 제약 조건, 유효성 검사 규칙. content_create와 content_update가 기대하는 것을 이해하는 데 사용합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
slug | string | 예 | 컬렉션 슬러그 (예: posts) |
범위: schema:read | 최소 역할: 편집자 | 읽기 전용: 예
schema_create_collection
새 콘텐츠 컬렉션을 생성합니다. 데이터베이스 테이블과 스키마 정의를 생성합니다. 슬러그는 문자로 시작하는 소문자 영숫자와 밑줄이어야 합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
slug | string | 예 | 고유 식별자 (/^[a-z][a-z0-9_]*$/) |
label | string | 예 | 표시 이름 (복수형, 예: “Blog Posts”) |
labelSingular | string | 아니오 | 단수형 표시 이름 |
description | string | 아니오 | 이 컬렉션의 설명 |
icon | string | 아니오 | 관리 UI의 아이콘 이름 |
supports | string[] | 아니오 | 기능: drafts, revisions, preview, scheduling, search (기본값: ['drafts', 'revisions']) |
범위: schema:write | 최소 역할: 관리자
schema_delete_collection
컬렉션과 데이터베이스 테이블을 삭제합니다. 비가역적이며 컬렉션의 모든 콘텐츠를 삭제합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
slug | string | 예 | 삭제할 컬렉션 슬러그 |
force | boolean | 아니오 | 콘텐츠가 있어도 강제 삭제 |
범위: schema:write | 최소 역할: 관리자 | 파괴적: 예
schema_create_field
컬렉션의 스키마에 새 필드를 추가합니다. 데이터베이스 테이블에 열을 추가합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
slug | string | 예 | 필드 식별자 (/^[a-z][a-z0-9_]*$/) |
label | string | 예 | 표시 이름 |
type | string | 예 | 데이터 타입 (아래 참조) |
required | boolean | 아니오 | 필드 필수 여부 |
unique | boolean | 아니오 | 값이 고유해야 하는지 여부 |
defaultValue | any | 아니오 | 새 항목의 기본값 |
validation | object | 아니오 | 제약 조건: min, max, minLength, maxLength, pattern, options |
options | object | 아니오 | 위젯 구성: collection (참조용), rows (textarea용) |
searchable | boolean | 아니오 | 전문 검색 색인에 포함 |
translatable | boolean | 아니오 | 이 필드가 번역 가능한지 여부 (기본값 true) |
필드 타입: string, text, number, integer, boolean, datetime, select, multiSelect, portableText, image, file, reference, json, slug.
select와 multiSelect 타입의 경우, validation.options에 허용된 값을 제공합니다.
범위: schema:write | 최소 역할: 관리자
schema_delete_field
컬렉션에서 필드를 제거합니다. 열을 삭제하고 해당 필드의 모든 데이터를 삭제합니다. 비가역적.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
fieldSlug | string | 예 | 제거할 필드 슬러그 |
범위: schema:write | 최소 역할: 관리자 | 파괴적: 예
미디어 도구
media_list
선택적 MIME 타입 필터링과 페이지네이션으로 업로드된 미디어 파일을 나열합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
mimeType | string | 아니오 | MIME 타입 접두사로 필터 (예: image/, application/pdf) |
limit | integer | 아니오 | 최대 항목 수 (1-100, 기본값 50) |
cursor | string | 아니오 | 페이지네이션 커서 |
범위: media:read | 읽기 전용: 예
media_get
ID로 단일 미디어 파일의 상세 정보를 가져옵니다. 파일명, MIME 타입, 크기, 치수, 대체 텍스트, URL을 포함한 메타데이터를 반환합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
id | string | 예 | 미디어 항목 ID |
범위: media:read | 읽기 전용: 예
media_update
업로드된 미디어 파일의 메타데이터를 업데이트합니다. 파일 자체는 변경할 수 없습니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
id | string | 예 | 미디어 항목 ID |
alt | string | 아니오 | 접근성을 위한 대체 텍스트 |
caption | string | 아니오 | 캡션 텍스트 |
width | integer | 아니오 | 이미지 너비(픽셀) |
height | integer | 아니오 | 이미지 높이(픽셀) |
범위: media:write
media_delete
미디어 파일을 영구 삭제합니다. 데이터베이스 레코드와 스토리지의 파일을 제거합니다. 이 미디어를 참조하는 콘텐츠는 깨진 참조가 됩니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
id | string | 예 | 미디어 항목 ID |
범위: media:write | 파괴적: 예
검색 도구
search
콘텐츠 컬렉션 전체에서 전문 검색. 컬렉션의 supports 목록에 search가 있어야 하고 필드가 searchable로 표시되어야 합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
query | string | 예 | 검색 쿼리 텍스트 |
collections | string[] | 아니오 | 특정 컬렉션 슬러그로 검색 제한 |
locale | string | 아니오 | 로캘별 결과 필터 |
limit | integer | 아니오 | 최대 결과 수 (1-50, 기본값 20) |
범위: content:read | 읽기 전용: 예
택소노미 도구
taxonomy_list
모든 택소노미 정의를 나열합니다 (예: 카테고리, 태그). 이름, 라벨, 계층적 여부, 연관된 컬렉션을 반환합니다.
매개변수 없음.
범위: content:read | 읽기 전용: 예
taxonomy_list_terms
페이지네이션으로 택소노미의 용어를 나열합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
taxonomy | string | 예 | 택소노미 이름 (예: categories, tags) |
limit | integer | 아니오 | 최대 항목 수 (1-100, 기본값 50) |
cursor | string | 아니오 | 페이지네이션 커서 |
범위: content:read | 읽기 전용: 예
taxonomy_create_term
택소노미에 새 용어를 생성합니다. 계층적 택소노미의 경우, parentId를 지정하여 하위 용어를 생성합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
taxonomy | string | 예 | 택소노미 이름 |
slug | string | 예 | URL에 안전한 식별자 |
label | string | 예 | 표시 이름 |
parentId | string | 아니오 | 부모 용어 ID (계층적 택소노미용) |
description | string | 아니오 | 용어 설명 |
범위: content:write
메뉴 도구
menu_list
모든 내비게이션 메뉴를 나열합니다. 이름, 라벨, 타임스탬프를 반환합니다.
매개변수 없음.
범위: content:read | 읽기 전용: 예
menu_get
이름으로 메뉴를 가져오며 모든 항목을 순서대로 포함합니다. 항목에는 라벨, URL, 타입, 중첩을 위한 선택적 부모가 있습니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
name | string | 예 | 메뉴 이름 (예: main, footer) |
범위: content:read | 읽기 전용: 예
리비전 도구
revision_list
콘텐츠 항목의 리비전 이력을 최신순으로 나열합니다. 컬렉션이 revisions를 지원해야 합니다.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
collection | string | 예 | 컬렉션 슬러그 |
id | string | 예 | 콘텐츠 항목 ID 또는 슬러그 |
limit | integer | 아니오 | 최대 리비전 수 (1-50, 기본값 20) |
범위: content:read | 읽기 전용: 예
revision_restore
콘텐츠 항목을 이전 리비전으로 복원합니다. 지정된 리비전의 데이터로 현재 초안을 대체합니다. 자동으로 게시되지 않습니다 — 필요한 경우 이후에 content_publish를 사용하세요.
| 매개변수 | 타입 | 필수 | 설명 |
|---|---|---|---|
revisionId | string | 예 | 복원할 리비전 ID |
범위: content:write
OAuth 검색
OAuth 2.1을 지원하는 MCP 클라이언트는 인증 방법을 자동으로 검색할 수 있습니다. 서버는 두 개의 메타데이터 문서를 게시합니다:
Protected Resource 메타데이터
GET /.well-known/oauth-protected-resource
{
"resource": "https://example.com/_emdash/api/mcp",
"authorization_servers": ["https://example.com/_emdash"],
"scopes_supported": [
"content:read", "content:write",
"media:read", "media:write",
"schema:read", "schema:write",
"admin"
],
"bearer_methods_supported": ["header"]
}
Authorization Server 메타데이터
GET /_emdash/.well-known/oauth-authorization-server
{
"issuer": "https://example.com/_emdash",
"authorization_endpoint": "https://example.com/_emdash/oauth/authorize",
"token_endpoint": "https://example.com/_emdash/api/oauth/token",
"scopes_supported": ["content:read", "content:write", "..."],
"response_types_supported": ["code"],
"grant_types_supported": [
"authorization_code",
"refresh_token",
"urn:ietf:params:oauth:grant-type:device_code"
],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"],
"device_authorization_endpoint": "https://example.com/_emdash/api/oauth/device/code"
}
인증되지 않은 요청이 MCP 엔드포인트에 도달하면 서버가 반환합니다:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"
이것은 표준 MCP 클라이언트 검색 플로우를 트리거합니다.
오류 처리
도구 오류는 isError: true와 함께 텍스트 콘텐츠로 반환됩니다:
{
"content": [{ "type": "text", "text": "Collection 'nonexistent' not found" }],
"isError": true
}
범위 및 권한 오류는 MCP 프로토콜 오류를 발생시킵니다:
{
"jsonrpc": "2.0",
"error": {
"code": -32600,
"message": "Insufficient scope: requires content:write"
},
"id": 1
}
트랜스포트 수준 오류(서버 잘못된 구성, 처리되지 않은 예외)는 구현 세부사항을 누출하지 않고 JSON-RPC 오류 코드 -32603(내부 오류)을 반환합니다.