v1.11.7
Relio v1.11.7 — 끝에 슬래시가 붙은 MCP 주소에서 나던 failed to parse json 제거
Qwen Code와 OpenCode에서 도구 목록을 가져올 때 failed to parse json이 발생했습니다. 전송 구현이나 인증이 아니라, MCP 클라이언트가 보낸 요청이 MCP 핸들러에 도달하지 못하고 화면(SPA)으로 넘어가 HTML을 돌려받은 것이 원인이었습니다.
1. 원인
Relio는 브라우저 라우팅을 위해 알려지지 않은 경로를 모두 index.html로 응답합니다. 이 Fallback이 제외하던 경로는 /api/ 접두사와 정확히 /mcp 하나뿐이었습니다.
실제 서버에서 확인한 응답입니다.
| 요청 | 수정 전 | 수정 후 |
|---|---|---|
POST /mcp |
200 application/json |
200 application/json |
POST /mcp/ |
200 text/html — 화면 HTML |
200 application/json |
GET /.well-known/oauth-protected-resource |
200 text/html |
404 application/json |
GET /.well-known/oauth-authorization-server |
200 text/html |
404 application/json |
DELETE /mcp |
200, 본문 없음 |
204 No Content |
POST /mcp 잘못된 본문 |
200 |
400 |
POST /app 등 화면 경로 |
200 text/html |
405 application/json |
주소 끝에 /를 붙였거나 클라이언트가 정규화 과정에서 붙인 경우, 인증은 통과한 것처럼 보이지만 첫 JSON-RPC 응답이 HTML이므로 클라이언트는 failed to parse json을 보고했습니다. OAuth 탐색 경로도 마찬가지로 HTML을 돌려주어, 인증 방식을 탐색하던 클라이언트가 존재하지 않는 OAuth 메타데이터를 파싱하려 했습니다.
2. 수정
/mcp와/mcp/모두 동일한 MCP 핸들러로 연결/mcp/그외경로는 화면 HTML이 아니라 JSON404/.well-known/경로는 JSON404— Relio는 개인 키 Bearer 인증만 사용하므로 클라이언트가 OAuth 시도를 중단하고 보유한 키를 그대로 씁니다- 화면 Fallback은
GET·HEAD에만 응답하고 그 외 Method는 JSON405— 잘못 라우팅된 API 호출이 HTML로 둔갑하지 않습니다 DELETE /mcp는 본문 없는204- 파싱 불가능한 본문은
400— 응답을 짝지을id가 없는 상태에서200을 주면 클라이언트가 오지 않을 응답을 기다립니다
3. 인증 탐색과 브라우저 클라이언트
- MCP
401응답에WWW-Authenticate: Bearer realm="Relio MCP"추가. 스킴을 명시하지 않으면 클라이언트가 OAuth 메타데이터를 찾아 나섭니다 OPTIONS /mcpCORS Preflight를 인증 이전 단계에서 처리. MCP Inspector처럼 브라우저에서 동작하는 클라이언트는 Authorization을 보내기 전에 Preflight를 먼저 보냅니다- 허용된 Origin에만
Access-Control-Allow-Origin을 부여하며, 허용되지 않은 Origin의 실제 요청은 기존과 동일하게403
4. prompts/list
접속 직후 모든 Method를 탐색하는 클라이언트가 정상 서버를 상대로 Method not found를 기록했습니다. prompts Capability를 선언하고 빈 목록으로 응답합니다.
검증
- Go 전체 Test/Vet,
gofmt, TypeScript Typecheck, React Production Build - 실제 Qwen Code:
http://…/mcp/와http://…/mcp모두Connected - 실제 OpenCode: 두 주소 모두
connected - 공식
@modelcontextprotocol/sdk클라이언트: 두 주소에서initialize→tools/list77개 →tools/call성공 - 22가지 전송 케이스 전수 점검에서
text/html응답 0건 - 단위 Test: 화면 Fallback이 기계 경로와 비 GET Method를 처리하지 않음, 파싱 실패의 HTTP 상태
- 오프라인 통합 Test에
/mcp/·OAuth 탐색·DELETE·prompts/list검사 추가 - 실제 브라우저: 로그인, 6개 화면, 딥링크 새로고침, MCP 사용 안내 모달 정상
- 완전 격리 Network 신규 설치 Smoke Test와 자격증명 연속성 Test
업그레이드
DB Migration은 없습니다. 기존 개인 키의 Secret, Scope, Channel, 만료일이 그대로 유지됩니다.
기존 설정에서 주소 끝의 /를 빼는 우회를 적용했다면 되돌릴 필요가 없으며, 두 표기 모두 동작합니다.
오프라인 이미지
| 항목 | 값 |
|---|---|
| Asset | relio-v1.11.7.tar.gz |
| Docker Image | relio:v1.11.7 |
| SHA-256 | 6000f0dcbd7a2cd99482247ae510ebfb0bfd327a01feeb60fe9f94b5eb626aff |
| 압축 크기 | 36M |
| Architecture | linux/amd64 |
이 Release가 직접 제공하는 Asset은 위 Docker Image tar.gz 하나뿐입니다.
오프라인 서버에서 로드
gunzip -c relio-v1.11.7.tar.gz | docker load
docker image inspect relio:v1.11.7실행
# 최초 1회만 생성하고 비밀번호 관리 도구에 보관하세요.
ENCRYPTION_KEY="$(openssl rand -hex 32)"
docker run -d \
--name relio \
-p 8080:8080 \
-e POSTGRES_DSN="postgres://relio:password@postgres:5432/relio" \
-e BOOTSTRAP_ADMIN="admin" \
-e BOOTSTRAP_ADMIN_PASSWORD="ChangeMe-Immediately" \
-e ENCRYPTION_KEY="$ENCRYPTION_KEY" \
-v relio-data:/var/lib/relio \
relio:v1.11.7Relio Application이 받는 환경변수는 필수 3개(POSTGRES_DSN, BOOTSTRAP_ADMIN, BOOTSTRAP_ADMIN_PASSWORD)와 선택 1개(ENCRYPTION_KEY)뿐입니다.
ENCRYPTION_KEY를 설정하면 Personal Key와 SSO Client Secret이 재기동, 이미지 교체, relio-data Volume 재생성 후에도 유지됩니다. 설정하지 않으면 자격증명을 여는 Key가 Volume 안에만 존재하므로 /var/lib/relio를 PostgreSQL과 같은 복구 시점으로 반드시 보존해야 합니다.
검증
Release 전에 Source Test, TypeScript Typecheck, Frontend Build, Go Vet, 환경변수 계약, 외부 정적 Asset 차단, 비 Root 실행, 완전 격리 Network 신규 설치 Smoke Test, 이전 Release 업그레이드 Test, Docker Save/Load 재검증을 통과합니다.