# MCP

> API 키로 인증하는 MCP 서버 엔드포인트와 클라이언트 설정

## 개요

theseeker CRM은 MCP(Model Context Protocol) 서버를 제공합니다. MCP를 지원하는 AI 코딩 에이전트는 이 서버를 붙이면
프로젝트, 모니터, 인시던트, 상태 페이지, 애널리틱스, 오류, 피드백, 서버와 문서 사이트 리소스를 60개 도구로 직접 읽고 바꿀 수 있습니다.

대상 클라이언트는 opencode, Claude Code, Cursor처럼 원격 MCP 서버를 지원하는 도구이며,
opencode-dashboard의 "CRM MCP" 탭에서도 같은 엔드포인트를 사용합니다.
REST API와 기능 범위는 같고, 인증도 [API 키](/getting-started/api-keys) 하나를 그대로 씁니다.

## 인증

MCP 요청은 REST API와 같은 Bearer 형식을 사용합니다.

```http
Authorization: Bearer <keyId>.<secret>
```

키에 부여된 scope가 도구의 요구 scope 중 하나라도 만족하면 호출이 승인됩니다.

| Scope | 의미 |
| --- | --- |
| `status:read` | 상태 데이터 조회 |
| `status:write` | 상태 이벤트 전송 |
| `feedback:read` | 피드백 조회 |
| `feedback:write` | 피드백 전송 |
| `analytics:write` | 레거시 분석 이벤트 전송 |
| `admin:read` | 관리 리소스 조회 |
| `admin:write` | 관리 리소스 변경 |
| `servers:read` | 서버 목록과 상세 조회 |
| `servers:write` | 서버 등록, 수정, 삭제, 토큰 재발급 |
| `docs:read` | 문서 사이트와 페이지 조회 |
| `docs:write` | 문서 사이트 작성·게시 |

키 종류에 따라 대상 범위가 달라집니다.

- **프로젝트 키**(ingest 계열): 발급된 프로젝트에 고정됩니다. 다른 프로젝트를 지정하면 거부되고, 목록 도구는 자기 프로젝트만 돌려줍니다.
- **조직 관리 API 키**: 조직 전체 프로젝트를 대상으로 합니다. 아래 표에서 "조직 키 전용"이 `O`인 도구는 조직 키로만 호출할 수 있습니다.

secret은 발급 직후 한 번만 표시됩니다. 키는 mode-600 파일이나 secret manager에 두고 설정 파일에 평문으로 넣지 마세요.

## 엔드포인트

```text
POST https://crm.theseeker.io/api/mcp
```

- 무상태(stateless) Streamable HTTP 전송이며 세션 ID를 유지하지 않습니다. 응답은 항상 JSON입니다.
- `POST`만 허용합니다. `GET`과 `DELETE`는 `405`입니다.
- 요청에는 `Content-Type: application/json`과 `Accept: application/json, text/event-stream` 헤더가 모두 필요합니다. 빠지면 `406`입니다.
- 표준 MCP 흐름(`initialize` → `tools/list` → `tools/call`)을 그대로 따릅니다.

## 도구 목록

도구 입력에서 프로젝트는 `projectId` 또는 `projectSlug`로 지정합니다(프로젝트 키는 생략 가능).
결과는 사람이 읽는 텍스트와 함께 `structuredContent`에 같은 데이터를 JSON으로 담아 돌려줍니다.

| 도구 | 필요 스코프 (OR) | 조직 키 전용 | 설명 |
| --- | --- | :---: | --- |
| `get_api_key_info` | (없음) | X | 현재 키의 스코프·프로젝트·사용 가능 도구 표 |
| `list_projects` | `admin:read`, `status:read` | X | 프로젝트 목록 (프로젝트 키는 자기 프로젝트만) |
| `create_project` | `admin:write` | O | 프로젝트 생성 |
| `list_monitors` | `admin:read`, `status:read` | X | 모니터 목록 + 최신 상태 |
| `get_monitoring_snapshot` | `admin:read`, `status:read` | X | 업타임·인시던트·하트비트 스냅샷 (`monitorId?`, `uptimeDays?` 1..90) |
| `create_monitor` | `admin:write` | X | 모니터 생성 |
| `update_monitor` | `admin:write` | X | 모니터 수정 |
| `delete_monitor` | `admin:write` | X | 모니터 삭제 |
| `list_incidents` | `admin:read`, `status:read` | X | 인시던트 목록 (`projectId?`, `projectSlug?`, `status?`, `limit` 1..100) |
| `get_incident` | `admin:read`, `status:read` | X | 인시던트 상세·업데이트 조회 (`incidentId`) |
| `create_incident` | `admin:write` | X | 모니터 인시던트 생성 (`title`, `severity`, `monitorIds`, `initialBody`) |
| `post_incident_update` | `admin:write` | X | 인시던트 상태·메시지 게시 (`incidentId`, `status`, `body`) |
| `resolve_incident` | `admin:write` | X | 인시던트 해결 (`incidentId`, `body?`) |
| `get_status_page` | `admin:read`, `status:read` | O | 상태 페이지 설정 조회 |
| `get_public_status_snapshot` | `admin:read`, `status:read` | O | 공개 상태 페이지 스냅샷 |
| `update_status_page` | `admin:write` | O | 상태 페이지 설정 수정 |
| `list_analytics_properties` | `admin:read` | O | 애널리틱스 속성 설정 조회 |
| `list_analytics_goals` | `admin:read` | O | 애널리틱스 목표 설정 조회 |
| `get_analytics_snapshot` | `admin:read` | O | 애널리틱스 값 스냅샷 (`propertyId`, `period` 7d/30d/90d) |
| `list_analytics_funnels` | `admin:read` | O | 속성의 퍼널 목록 (`propertyId`) |
| `get_analytics_funnel_result` | `admin:read` | O | 퍼널 전환 결과 (`funnelId`, `period` 기본 30d) |
| `get_web_vitals` | `admin:read` | O | 웹 바이탈 (`propertyId`, `period` 기본 30d) |
| `create_analytics_property` | `admin:write` | O | 애널리틱스 속성 생성 |
| `update_analytics_property` | `admin:write` | O | 애널리틱스 속성 수정 |
| `delete_analytics_property` | `admin:write` | O | 애널리틱스 속성 삭제 |
| `create_analytics_goal` | `admin:write` | O | 애널리틱스 목표 생성 |
| `update_analytics_goal` | `admin:write` | O | 애널리틱스 목표 수정 |
| `delete_analytics_goal` | `admin:write` | O | 애널리틱스 목표 삭제 |
| `list_error_issues` | `admin:read` | O | 오류 이슈 목록 |
| `get_error_issue` | `admin:read` | O | 오류 이슈 상세 |
| `get_error_fix_context` | `admin:read` | O | 오류 수정 브리프(요약·기호화 스택·브레드크럼·관련 피드백·세션 타임라인·markdown), `issueId`, `sampleLimit` 1..5 |
| `list_error_types` | `admin:read` | O | 오류 유형 목록 |
| `update_error_issue_status` | `admin:write` | O | 오류 이슈 resolve/ignore/reopen |
| `get_feedback_fields` | `admin:read`, `feedback:read` | X | 피드백 필드 설정 조회 |
| `list_feedback` | `admin:read`, `feedback:read` | X | 피드백 목록 |
| `get_feedback` | `admin:read`, `feedback:read` | X | 피드백 상세·연관 오류 (`feedbackId`) |
| `update_feedback_fields` | `admin:write` | X | 피드백 필드 설정 변경 |
| `update_feedback_triage` | `admin:write` | X | 피드백 상태·태그·연관 오류 수정 (`feedbackId`, `status?`, `tags?`, `relatedIssueIds?` 중 하나 이상) |
| `list_servers` | `admin:read`, `servers:read` | X | 서버 목록 |
| `get_server` | `admin:read`, `servers:read` | X | 서버 상세 |
| `create_server` | `admin:write`, `servers:write` | X | 서버 등록 |
| `update_server` | `admin:write`, `servers:write` | X | 서버 수정 |
| `delete_server` | `admin:write`, `servers:write` | X | 서버 삭제 |
| `rotate_server_token` | `admin:write`, `servers:write` | X | 서버 토큰 회전 |
| `tail_server_logs` | `admin:write`, `servers:write` | X | 서버 에이전트 로그 요청 (`serverId`, `sourceId`, `lines` 100/200/500) |
| `get_doc_authoring_guide` | `docs:read`, `admin:read` | O | 작성 가이드와 컴포넌트 목록 |
| `list_doc_sites` | `docs:read`, `admin:read` | O | 조직의 문서 사이트 목록 |
| `get_doc_site` | `docs:read`, `admin:read` | O | 사이트 설정, 내비게이션과 게시 정보 |
| `list_doc_pages` | `docs:read`, `admin:read` | O | 사이트 페이지 목록 |
| `get_doc_page` | `docs:read`, `admin:read` | O | 페이지 Markdown과 선택적 JSON |
| `list_doc_releases` | `docs:read`, `admin:read` | O | 게시 릴리스 목록 |
| `create_doc_site` | `docs:write`, `admin:write` | O | 문서 사이트 생성 |
| `update_doc_site` | `docs:write`, `admin:write` | O | 사이트 이름, 설정과 도메인 변경 |
| `delete_doc_site` | `docs:write`, `admin:write` | O | 확인 slug를 지정해 사이트 삭제 |
| `set_doc_navigation` | `docs:write`, `admin:write` | O | 탭·그룹·페이지 내비게이션 교체 |
| `create_doc_page` | `docs:write`, `admin:write` | O | Markdown 또는 JSON 페이지 생성 |
| `update_doc_page` | `docs:write`, `admin:write` | O | revision 기반 페이지 수정 |
| `delete_doc_page` | `docs:write`, `admin:write` | O | 페이지 삭제 |
| `publish_doc_site` | `docs:write`, `admin:write` | O | 새 불변 릴리스 게시 |
| `restore_doc_release` | `docs:write`, `admin:write` | O | 이전 릴리스 복원 |

문서 페이지의 `markdown`은 Mintlify 형식 MDX입니다. 먼저 `get_doc_authoring_guide`에서 지원 컴포넌트와 예제를 확인하세요. `get_doc_page`에는 `pageId`와 `path` 중 정확히 하나를 지정하고, 홈 페이지 경로는 빈 문자열(`""`)을 사용합니다. `update_doc_page`는 `baseRevision`으로 동시 수정 충돌을 감지합니다. 게시 취소는 관리 UI에서만 가능합니다.

현재 키로 어떤 도구를 쓸 수 있는지 모르겠다면 `get_api_key_info`를 먼저 호출하세요. 이 도구는 scope 없이도 호출할 수 있습니다.

## 프롬프트

`prompts/list`에서 다음 두 프롬프트를 조회하고 `prompts/get`으로 지침을 받을 수 있습니다. 프롬프트는 도구를 직접 실행하지 않습니다.

| 프롬프트 | 인자 | 안내하는 도구와 순서 |
| --- | --- | --- |
| `triage_todays_errors` | `propertyId` 필수 | 최근 24시간 `list_error_issues` → 상위 이슈의 `get_error_fix_context` → 사용자 확인 후에만 `update_error_issue_status` |
| `weekly_summary` | `projectSlug?` | 최근 7일 `list_projects` → `get_monitoring_snapshot` → `list_incidents` → `get_analytics_snapshot` → `list_error_issues` → `list_feedback` |

## 클라이언트 설정

아래 설정은 앱의 `조직 설정 → MCP 연결`(`/<org>/admin/mcp`) 화면에서도 복사 버튼과 함께 그대로 제공됩니다.
`<keyId>.<secret>` 자리에는 발급받은 키를 넣으세요.

### opencode

`opencode.json`의 `mcp` 항목에 다음을 추가합니다.

```json
{
  "mcp": {
    "theseeker-crm": {
      "type": "remote",
      "url": "https://crm.theseeker.io/api/mcp",
      "oauth": false,
      "headers": {
        "Authorization": "Bearer {file:~/.config/theseeker/crm-mcp-key}"
      },
      "enabled": true
    }
  }
}
```

opencode는 `{file:<경로>}`를 읽어 그 파일의 내용으로 치환하므로 설정 파일에 키를 평문으로 두지 않아도 됩니다.
`~/.config/theseeker/crm-mcp-key`에는 `<keyId>.<secret>` 한 줄만 저장하고 권한을 `600`으로 두세요.

### Claude Code

```bash
claude mcp add --transport http theseeker-crm https://crm.theseeker.io/api/mcp --header "Authorization: Bearer <keyId>.<secret>"
```

### Cursor

`~/.cursor/mcp.json` 또는 프로젝트의 `.cursor/mcp.json`에 다음을 추가합니다.

```json
{
  "mcpServers": {
    "theseeker-crm": {
      "url": "https://crm.theseeker.io/api/mcp",
      "headers": {
        "Authorization": "Bearer <keyId>.<secret>"
      }
    }
  }
}
```

### curl

설정 없이 연결만 확인하려면 `initialize`와 `tools/list`를 직접 호출하세요.

```bash
curl -sS -X POST https://crm.theseeker.io/api/mcp \
  -H "Authorization: Bearer <keyId>.<secret>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

curl -sS -X POST https://crm.theseeker.io/api/mcp \
  -H "Authorization: Bearer <keyId>.<secret>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
```

## 오류와 Rate limit

전송 계층 오류는 HTTP status로 돌아옵니다.

| HTTP | 의미 |
| ---: | --- |
| 401 | 토큰 누락, 형식 오류, 폐기된 키 |
| 403 | IP allowlist 밖의 요청 |
| 405 | `POST` 외의 메서드 |
| 406 | `Content-Type` 또는 `Accept` 헤더 누락 |
| 429 | 요청 한도 초과 |

MCP endpoint는 키마다 분당 120회를 기본으로 허용합니다. 조직의 **API 키** 페이지에서 키별 한도를 분당 1~600회로 설정할 수 있습니다.
`429` 응답에는 `Retry-After`, `X-RateLimit-Limit`,
`X-RateLimit-Remaining` 헤더가 포함되며 `Retry-After`만큼 기다린 뒤 재시도하세요.

일일 도구 호출 예산은 UTC 날짜 기준으로 계산하며 `tools/call` 요청만 셉니다. 예산을 초과하면 HTTP `429`와
`{"error":{"code":"BUDGET_EXCEEDED",...}}` 본문을 반환하고, `Retry-After`는 다음 UTC 자정(00:00)까지 남은 초입니다.
거부된 호출은 사용량에 포함되지 않습니다. `get_api_key_info` 응답에는 유효 분당 한도(`rateLimitPerMinute`),
일일 예산(`dailyCallBudget`, `null`이면 무제한), 오늘 사용한 호출 수(`usedToday`)가 포함됩니다.

변경 작업 도구 그룹의 모든 결과는 성공·실패·거부 여부와 함께 감사 로그에 기록됩니다. scope, 프로젝트 범위,
일일 예산 때문에 거부된 도구 호출도 기록하며, `servers.logs` 호출 결과도 기록합니다. 사람의 UI 변경도 기록됩니다.
감사 로그는 `/<org>/admin/audit`에서 조직 관리자만 볼 수 있습니다.

도구 실행 중 발생한 오류는 HTTP `200`과 함께 MCP 결과의 `isError: true`로 돌아오고, 본문에 다음 JSON이 들어갑니다.

```json
{"error":{"code":"FORBIDDEN_SCOPE","message":"설명"}}
```

| Code | 의미 |
| --- | --- |
| `UNAUTHORIZED` | 인증 실패 |
| `FORBIDDEN_SCOPE` | 도구가 요구하는 scope 없음 |
| `FORBIDDEN_PROJECT` | 키 범위 밖의 프로젝트 지정 |
| `NOT_FOUND` | 대상 리소스 없음 |
| `VALIDATION` | 입력 검증 실패 |
| `CONFLICT` | slug 등 충돌 |
| `RATE_LIMITED` | 요청 한도 초과 |
| `UPSTREAM_ERROR` | 에이전트·게이트웨이가 요청을 처리하지 못함 |
| `INTERNAL` | 서버 내부 오류 |

REST API의 오류 규약은 [Errors와 Rate Limits](/api-reference/errors-and-rate-limits)를 참고하세요.
