콘텐츠로 이동

MCP 프록시 및 디스커버리 API

브라우저용 MCP 디스커버리, HTTP 리버스 프록시, 서버 테스트, 그리고 MCP 이벤트 WebSocket.

Revka는 메인 데몬 내부에서 Model Context Protocol(MCP) 서버를 백그라운드 태스크로 실행합니다. 이 서버는 임시 포트에 바인딩되며, 디스커버리 파일을 읽는 외부 CLI 도구(Claude Code, Codex, Claude Desktop)가 직접 접속하도록 설계되어 있습니다. 그러나 브라우저 대시보드는 게이트웨이의 오리진에서 동작하므로 CORS 문제 없이 다른 포트와 통신할 수 없습니다. 이를 위해 게이트웨이는 동일 오리진 기반의 /api/mcp/* 리버스 프록시 라우트와 /ws/mcp/events WebSocket을 제공합니다.

이 페이지에서는 브라우저에서 사용하는 인터페이스를 다룹니다. MCP 서버 가동 여부 확인, 프록시를 통한 세션 생성 및 도구 호출, 저장 전 외부 MCP 서버 설정 테스트, WebSocket을 통한 도구 진행 이벤트 스트리밍, 그리고 ~/.revka/mcp.json 디스커버리 파일 형식에 대해 설명합니다.

MCP 서버 자체(인프로세스 데몬, 네이티브 엔드포인트, 도구 레지스트리)에 대해서는 Revka를 MCP 서버로 사용하기를 참조하세요. Revka를 외부 MCP 서버의 클라이언트로 설정하려면 외부 MCP 서버 연결을 참조하세요. 이 페이지의 모든 라우트는 페어링 흐름에서 발급된 베어러 토큰이 필요합니다.

모든 /api/* 라우트는 요청 본문 64 KiB 제한과 설정 가능한 타임아웃(기본값 30초, 환경 변수 REVKA_GATEWAY_TIMEOUT_SECS)을 공유하며, 라우트별 재정의 값은 아래에 명시되어 있습니다.

프록시와 디스커버리 파일 중 선택 기준

섹션 제목: “프록시와 디스커버리 파일 중 선택 기준”

MCP 서버에 접근하는 방법은 두 가지이며, 서로 호환되지 않습니다.

  • 외부 CLI 및 데스크톱 클라이언트(Claude Code, Codex, Claude Desktop)는 ~/.revka/mcp.json을 읽고 MCP 포트에 직접 연결합니다. /api/mcp/* 라우트는 사용하지 않습니다.
  • 브라우저 대시보드는 게이트웨이의 /api/mcp/* 리버스 프록시를 사용하여 동일 오리진을 유지하고 게이트웨이의 베어러 인증 미들웨어를 재사용합니다. 프록시는 각 요청을 인프로세스 MCP 서버의 임시 포트로 전달합니다.

두 방법은 동일한 백엔드를 공유하므로, 프록시를 통해 생성된 세션과 직접 생성된 세션은 동일하게 동작합니다.

GET /api/mcp/discovery는 인프로세스 MCP 서버가 실행 중이고 접근 가능한지 보고합니다. ~/.revka/mcp.json 디스커버리 파일을 읽은 후 서버의 /health 엔드포인트를 500ms 타임아웃으로 프로빙하여, 대시보드의 MCP 상태 배지를 구동하는 일관된 응답 형식을 반환합니다.

GET /api/mcp/discovery
Authorization: Bearer <token>

서버가 정상적으로 실행 중인 경우:

{
"available": true,
"url": "http://127.0.0.1:54500/mcp",
"health": {
"status": "ok",
"pid": 12345,
"uptime_seconds": 3600,
"started_at": "2026-06-18T00:00:00Z",
"protocol_version": "2024-11-05"
}
}

데몬이 디스커버리 파일을 아직 작성하지 않았거나 헬스 프로브가 실패한 경우, available: false와 함께 reason이 반환됩니다.

{ "available": false, "reason": "discovery file missing" }
{ "available": false, "reason": "health check failed" }

health 객체는 MCP 서버 자체의 /health 엔드포인트가 반환하는 내용과 동일합니다. protocol_version2024-11-05입니다. 디스커버리 엔드포인트는 항상 200 OK를 반환하므로, MCP 사용 가능 여부는 available 필드를 확인하세요.

이 라우트들은 브라우저가 동일 오리진을 유지할 수 있도록 HTTP 요청을 인프로세스 MCP 서버로 프록시합니다. 각 라우트는 게이트웨이 베어러 토큰이 필요합니다. 데몬 시작 시 MCP 서버가 바인딩에 실패한 경우, 모든 프록시 라우트는 503 Service Unavailable과 함께 다음 형식의 응답을 반환합니다.

{ "available": false, "reason": "mcp server not bound" }

MCP용 게이트웨이 프록시 라우트

섹션 제목: “MCP용 게이트웨이 프록시 라우트”
메서드경로프록시 대상비고
GET/api/mcp/healthMCP GET /health편의 패스스루; 5초 타임아웃
POST/api/mcp/sessionMCP POST /session본문 그대로 전달; 10초 타임아웃
POST/api/mcp/callMCP POST /mcp (JSON-RPC 2.0)Authorization + X-Revka-Session 전달; 120초 타임아웃
GET/api/mcp/session/{session_id}/eventsMCP GET /session/{id}/eventsSSE 패스스루; 장기 연결, 요청 타임아웃 없음
POST/api/mcp/servers/test(핸드셰이크, 패스스루 아님)저장 전 외부 MCP 서버 설정 테스트; 10초 상한

모든 라우트는 Authorization: Bearer <token>이 필요합니다. 업스트림 MCP 서버의 상태 코드, Content-Type, 본문이 그대로 반환됩니다.

POST /api/mcp/session은 본문을 그대로 MCP 서버의 POST /session으로 전달하고 응답을 반환합니다. 성공 시 세션 ID와 세션별 토큰이 반환됩니다.

POST /api/mcp/session
Authorization: Bearer <gateway-token>
Content-Type: application/json
{ "cwd": "/home/user/project", "label": "code-tab" }
{ "session_id": "<session-id>", "token": "<mcp-session-token>", "cwd": "/home/user/project" }

두 값을 모두 보관하세요. session_idtoken은 MCP 서버가 발급하며, 게이트웨이에는 불투명한 값으로, 아래의 JSON-RPC 및 SSE 라우트에 필요합니다.

POST /api/mcp/call은 MCP 서버의 JSON-RPC 2.0 엔드포인트(POST /mcp)로 프록시합니다. MCP 서버가 지원하는 메서드는 initialize, tools/list, tools/call입니다.

POST /api/mcp/call
Authorization: Bearer <gateway-token>
X-Revka-Session: <session_id>
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}

120초 타임아웃은 장시간 실행되는 tools/call 호출에 여유를 주면서, 무한 실행으로 인해 워커가 점유되는 것을 방지합니다.

GET /api/mcp/session/{session_id}/events는 MCP 서버의 세션별 진행 스트림으로의 SSE 패스스루입니다. 의도적으로 장기 연결로 설계되어 있습니다. 프록시는 요청 수준 타임아웃을 비활성화하여 서버 푸시 스트림이 중간에 끊기지 않도록 하며, x-accel-buffering: no를 설정하여 nginx 등 리버스 프록시가 스트림을 버퍼링하지 않도록 합니다.

GET /api/mcp/session/<session_id>/events
Authorization: Bearer <gateway-token>
X-Revka-Session: <session_id>
Accept: text/event-stream

응답은 Content-Type: text/event-streamCache-Control: no-cache를 포함합니다. 각 이벤트의 data: 페이로드는 해당 세션에서 진행 중인 도구 호출이 내보내는 ProgressEvent입니다. 브라우저 클라이언트의 경우 WebSocket MCP 이벤트 프록시에서 설명하는 WebSocket 방식을 권장합니다. 동일한 데이터를 단일 인증 경로로 제공합니다.

POST /api/mcp/servers/test는 사용자가 제공한 외부 MCP 서버 설정을 저장하기 전에 테스트 연결합니다. 실제 클라이언트가 수행하는 것과 동일한 initialize + tools/list 핸드셰이크를 실행하고, 성공 여부, 발견된 도구 수 및 이름, 왕복 지연 시간을 보고합니다. 이 엔드포인트는 설정 편집기 MCP 섹션의 “테스트” 버튼의 백엔드입니다.

POST /api/mcp/servers/test
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "filesystem",
"transport": "stdio",
"command": "/usr/local/bin/mcp-filesystem",
"args": ["--read-only"],
"env": { "MCP_LOG": "debug" },
"url": null,
"headers": {},
"timeout_ms": 30000
}
필드타입비고
namestring필수; 비어 있으면 안 됨
transportstring"stdio", "http", 또는 "sse"
commandstringtransport"stdio"일 때 필수
argsstring[]선택적 stdio 인수
envobjectstdio 자식 프로세스의 선택적 환경 변수
urlstringtransport"http" 또는 "sse"일 때 필수
headersobjecthttp/sse용 선택적 헤더
timeout_msnumber도구별 타임아웃 힌트; 정수 초로 변환됨

핸드셰이크 성공 시:

{ "ok": true, "tool_count": 5, "tools": ["read_file", "write_file", "..."], "latency_ms": 234 }

유효성 검사 또는 연결 실패 시, 엔드포인트는 ok: false와 함께 200 OK를 반환합니다.

{ "ok": false, "error": "command is required for stdio transport", "latency_ms": 0 }
{ "ok": false, "error": "timed out after 10s", "latency_ms": 10000 }

동작 관련 참고사항:

  • 전체 핸드셰이크는 10초 상한(TEST_HANDSHAKE_TIMEOUT_SECS)으로 제한됩니다. 잘못 설정된 서버가 요청 스레드를 점유할 수 없습니다.
  • 유효성 검사 오류는 HTTP 오류가 아닌 error 필드로 반환됩니다. name이 비어 있거나, transport가 알 수 없거나, stdio에 command가 없거나, http/sse에 url이 없는 경우 모두 ok: false로 반환됩니다.
  • timeout_ms는 정수 초로 변환되며 최소 1로 제한됩니다. 500과 같이 1초 미만의 값은 1초 도구별 타임아웃으로 처리되어 다운스트림 클라이언트가 0을 받는 일이 없습니다.

GET /ws/mcp/events는 MCP 서버의 세션별 진행 SSE 스트림을 브라우저로 프록시하는 WebSocket입니다. 대시보드의 코드 탭은 CLI 코딩 에이전트가 터미널에서 실행되는 동안 이 WebSocket을 열어 에이전트 작업 중 실시간 도구 진행 상황을 표시합니다. 게이트웨이 측 프록시를 사용하면 /ws/terminal과 일관되게 모든 브라우저 트래픽이 CORS 없이 단일 인증 경로를 유지합니다.

ws://host:port/ws/mcp/events?session_id=<mcp-session>&mcp_token=<mcp-token>&token=<bearer>
쿼리 파라미터용도
token게이트웨이 베어러 토큰 (/ws/terminal과 동일한 인증)
session_idPOST /api/mcp/session이 반환한 MCP 세션 ID
mcp_tokenPOST /api/mcp/session이 반환한 MCP 세션 토큰

게이트웨이는 자체 베어러를 독립적으로 검증합니다(Authorization: Bearer, bearer.<token> WebSocket 서브프로토콜, 또는 ?token= 쿼리 파라미터 중 하나 사용). mcp_token은 인프로세스 MCP 서버의 이벤트 스트림을 여는 데만 사용되며, 게이트웨이 자격증명으로는 검증되지 않습니다.

각 이벤트는 서버의 ProgressEvent 직렬화 형식과 일치하는 단일 JSON 텍스트 프레임으로 전달됩니다.

{
"token": 7,
"progress": 4,
"total": 10,
"message": "...",
"tool": "notion",
"timestamp": "2026-04-17T10:20:33+00:00"
}

인프로세스 MCP 서버에 접근할 수 없는 경우, 프록시는 오류 프레임을 전송하고 연결을 닫습니다.

{ "error": "daemon-unreachable", "detail": "..." }

스트림은 읽기 전용입니다. 닫기 외의 클라이언트 프레임은 무시됩니다. 단일 SSE 이벤트 내의 여러 data: 줄은 SSE 스펙에 따라 \n으로 연결됩니다.

일반적인 브라우저 수명 주기는 다음과 같습니다. POST /api/mcp/session으로 세션 생성 → 해당 session_idmcp_token으로 /ws/mcp/events 열기 → POST /api/mcp/call로 도구 호출 → WebSocket에서 진행 프레임 수신. 다른 WebSocket 엔드포인트에 대해서는 실시간: WebSocket, SSE & 라이브 캔버스를 참조하세요.

데몬은 시작 시 ~/.revka/mcp.json을 작성하여 호출자가 MCP 서버의 임시 포트를 하드코딩 없이 찾을 수 있도록 합니다. 게이트웨이는 프록시 및 WebSocket 이벤트 라우트에서 이 파일을 읽으며, 외부 CLI도 동일한 파일을 직접 읽습니다.

페이로드 형식은 고정된 계약입니다.

{ "url": "http://127.0.0.1:54500/mcp", "pid": 12345, "started_at": "2026-06-18T00:00:00Z" }
필드타입의미
urlstring전체 MCP 엔드포인트, 예: http://127.0.0.1:<port>/mcp
pidnumber (선택적)데몬 프로세스 ID
started_atstring (선택적)RFC 3339 시작 타임스탬프

신뢰할 수 있는 동작:

  • 바인딩 전까지 파일 없음. 데몬이 MCP 태스크를 완전히 시작할 때까지 파일이 존재하지 않습니다. 호출자는 파일 부재를 적절히 처리해야 합니다. 해당 시간대에 GET /api/mcp/discovery{ "available": false, "reason": "discovery file missing" }을 반환합니다.
  • mtime 캐시. 게이트웨이는 수정 시간을 키로 파싱된 파일을 캐시합니다. 데몬이 요청 사이에 재시작될 수 있으므로(임시 포트가 변경됨), mtime이 변경된 후 다음 요청 시 캐시가 자동으로 재읽기되어 재시작 후에도 오래된 포트를 제공하지 않습니다.
  • 안정적인 형식. url, pid, started_at만 포함됩니다. url만 존재가 보장되며, pidstarted_at은 생략될 수 있습니다.