본문으로 건너뛰기

Workspace MCP Tool 마켓플레이스 (게시 / 구독) QA 가이드 (#2047)

이 문서는 Workspace MCP Tool 마켓플레이스(게시 / 구독, Epic B P3.2)QA가 화면에서 직접 따라 검증할 수 있도록 단계별 스크린샷과 함께 정리한 것입니다. 모든 화면은 실제 GenD prod 환경의 데모 워크스페이스에서 캡처했습니다.

개요

마켓플레이스는 워크스페이스 간(workspace-to-workspace) MCP 도구 공유 채널입니다.

  • 게시(Publish) — 워크스페이스 admin 이 자기 워크스페이스의 활성(active) 도구를 마켓플레이스에 올립니다. 공개 범위는 두 가지입니다.
    • 테넌트(tenant) — 같은 테넌트 안의 다른 워크스페이스에만 보임.
    • 공개(public) — 모든 테넌트에 보임.
  • 구독(Subscribe)다른 워크스페이스가 게시된 도구를 import 합니다. 구독자는 결과(RESULTS)만 전달받으며, 도구의 쿼리(query_spec)는 노출되지 않습니다. 실행은 출처(source) 워크스페이스에서 일어납니다.
  • 게시 취소(Unpublish) — admin 이 게시를 내리면, 그 도구를 구독한 워크스페이스의 import 가 즉시 비활성화(구독 revoke) 됩니다.

★ Q5-A 보안 — "공유 가능한 소스"만 게시 가능 (private 데이터 차단)

마켓플레이스의 핵심 안전장치는 무엇을 게시할 수 있는가 입니다.

  • 게시 가능: 도구의 타깃이 워크스페이스 간 공유 소스(cross-workspace-shared) 일 때만 게시됩니다.
    • 전사(platform-wide) 공유 ORM 모델 — 예: IntelSource(전사 회사·공시 카탈로그, workspace_id IS NULL).
    • 허용된 공유 Trino 카탈로그 — 기본 allowlist: iceberg, gendpg.
  • 게시 불가 (422): 타깃이 워크스페이스 private 데이터(workspace_id 테넌트 펜스를 가진 모델, 예: DataMart)면 게시가 422로 거부됩니다 — 게시하면 한 테넌트의 비공개 행이 구독자에게 새기 때문입니다(Q5-A safe default). UI 는 이 거부를 명확한 사유 토스트("이 도구는 게시할 수 없습니다: …")로 보여줍니다.
  • raw_sql 도구는 절대 게시 불가 (422): 고급(raw SQL) 도구의 target_table_fqn메타데이터(감사/카탈로그 라우팅)일 뿐, 실제 실행되는 SQL 은 query_spec_jsonb['sql'] 이라 어떤 private 테이블이든 읽을 수 있습니다. 따라서 wizard 타깃 검사를 우회하지 못하도록 게시 시점에 raw_sql 을 먼저 차단합니다. 게시하려면 공유 모델을 타깃으로 하는 wizard 도구여야 합니다.

즉, "no-private-leak" 불변식: 게시 가능한 것은 (1) 전사 공유 모델, (2) 허용 공유 카탈로그 — 이 둘뿐입니다. 그 외 모든 private 타깃과 모든 raw_sql 도구는 422 로 막힙니다.

실행 컨텍스트 — 구독자는 결과만, 실행은 출처에서

구독은 출처 워크스페이스의 도구를 그대로 import 합니다(쿼리 사본을 받는 게 아닙니다). 구독자의 호출자는 tools/list 에서 이 도구를 보고 호출할 수 있지만, 실행은 출처 워크스페이스에서 일어나고 구독자는 결과만 받습니다. 구독 시 출처 도구에 구독자 그룹용 guest 그랜트가 upsert 되고(#2046 그랜트 모델 재사용), 게시 취소 시 이 그랜트와 구독이 함께 revoke 됩니다(잔존 그랜트로 인한 재-grant 위험 차단).

권한: 게시 / 게시 취소 / 구독은 모두 워크스페이스 admin(글로벌 admin 역할 또는 Keycloak /tenants/<slug>/admins 멤버) 만 가능합니다. 비-admin 은 마켓플레이스 카드는 보되 구독 버튼은 숨겨지고 읽기 전용 배너가 뜹니다. 비-admin 의 게시/구독 차단(403)은 화면에서 재현할 수 없어(아래 참고) API/단위 테스트로 검증합니다.


사전 준비 / 시드

별도 시드 스크립트는 필요 없습니다. E2E 스펙(ui/tests/mcp-tool-marketplace-e2e.spec.ts)이 각 시나리오에서 zz_e2e_mkt_* 도구를 API 로 직접 시드하고(공유 모델 타깃 = 게시 가능 / private 모델 타깃 = 게시 422), 게시/게시취소 흐름은 브라우저로 구동합니다. 끝나면 API 로 (게시 취소 후) 소프트 삭제합니다.

  • 계정: 워크스페이스 admin 계정(글로벌 admin 역할이면 충족).
  • 게시 가능 타깃(시드): wizard 스펙, target_model_ref = "gend_api.db.models.IntelSource"(전사 공유 모델).
  • 게시 불가 타깃(시드): wizard 스펙, target_model_ref = "gend_api.db.models.DataMart"(워크스페이스 private) → 게시 시 422.
  • 검증 후 만든 도구는 목록에서 ⋯ 삭제(소프트 삭제) 로 정리합니다.

★ 단일 워크스페이스 E2E 한계 (브라우저 가능 vs API 검증): 백엔드 GET /marketplace자기 워크스페이스 도구를 제외하고, POST /{id}/subscribe자기 도구 구독을 409 로 거부합니다(워크스페이스는 자기 자신을 구독할 수 없음). 단일 E2E admin 은 하나의 워크스페이스 안에서만 동작하므로 — 자기가 방금 게시한 도구는 자기 마켓플레이스에 안 보이고 자기가 구독할 수도 없습니다. 따라서:

  • 브라우저로 검증 가능: 공유 도구 게시(배지)·private 도구 게시 거부(422 토스트)·게시 취소(배지 제거)·마켓플레이스 페이지 렌더링.
  • API/단위 테스트로 검증(다른 구독자 워크스페이스가 필요): 방금 게시한 카드가 다른 워크스페이스 마켓플레이스에 뜨는 것, 구독 → "구독됨" 전환, 목록의 "import(출처: …)" 배지 — apps/api/tests/test_workspace_mcp_tool_marketplace.py(공유→200 / private→422 / 자기도구 구독→409 / 크로스-ws 구독→201 + import / 게시취소→revoke) 와 ui/src/components/workspace/mcp-tools/McpToolMarketplace.test.tsx·WorkspaceMcpToolList.test.tsx(구독 버튼→"구독됨" 비활성, import 배지) 가 검증합니다.

1. 게시 — 공유 모델 도구(OK) / private 도구(422) (S1)

좌측 사이드바 MCP Tools 목록에서 활성(active) 도구 행의 게시(Publish) 아이콘을 누르면 게시 대화상자가 뜹니다. 공개 범위(테넌트 / 공개) 를 고르고 게시 를 누릅니다.

게시 대화상자 — 공개 범위 선택

타깃이 공유 모델(IntelSource) 인 도구는 게시에 성공하고, 목록의 도구 이름 옆에 게시됨(Published) 배지가 붙습니다.

게시 성공 — 게시됨 배지

반대로 타깃이 워크스페이스 private 데이터(DataMart) 인 도구를 게시하려 하면, 백엔드 Q5-A 게이트가 422 로 거부하고 UI 는 사유 토스트("이 도구는 게시할 수 없습니다: …")를 띄웁니다. 이 도구에는 게시됨 배지가 붙지 않습니다.

private 도구 게시 거부 — 422 사유 토스트

확인 포인트(QA): 공유 모델 도구는 게시 후 게시됨 배지가 뜨고, private 모델 도구는 422 사유 토스트 가 뜨며 배지가 붙지 않는지 확인합니다. (raw_sql 도구는 애초에 게시 버튼 노출 대상이 아니며, 백엔드에서도 422 로 막힙니다.)


2. 마켓플레이스 페이지 — 게시된 도구 카드 (S2)

헤더의 마켓플레이스 버튼(또는 /workspace/mcp-tools/marketplace)으로 들어가면 마켓플레이스 페이지가 열립니다. 다른 워크스페이스가 게시한 도구가 카드 그리드로 나타나며, 각 카드는 도구 이름·설명·출처 워크스페이스(출처: …)·공개 범위 배지(테넌트 / 공개) 를 보여줍니다.

마켓플레이스 페이지 — 게시된 도구 카드

확인 포인트(QA): 마켓플레이스 페이지가 렌더되고, 카드에 출처 워크스페이스 + 공개 범위 배지가 보이는지 확인합니다. 단일 워크스페이스 세션에서는 자기가 방금 게시한 도구가 자기 마켓플레이스에 보이지 않습니다(자기 워크스페이스 제외) — 다른 워크스페이스가 게시한 카드가 없으면 "아직 게시된 도구가 없습니다" 빈 상태가 정상입니다.


3. 구독 → "구독됨" + import 배지 (S3)

마켓플레이스 카드의 구독(Subscribe) 을 누르면 도구가 import 되고, 같은 카드의 버튼이 구독됨(Subscribed) (비활성) 으로 바뀝니다. 그리고 내 도구 목록(/workspace/mcp-tools)에서 그 도구에 "다른 워크스페이스에서 import (출처: …)" 배지가 붙습니다.

마켓플레이스 구독 상태 — 이 캡처는 prod 실행 당시 이 테넌트의 실제 마켓플레이스 상태(빈 상태이거나, 다른 워크스페이스가 게시한 카드)를 그대로 보여줍니다

확인 포인트(QA): 다른 워크스페이스가 게시한 카드가 있으면 구독(Subscribe) 을 눌러 버튼이 구독됨(Subscribed) 비활성으로 바뀌고, 내 도구 목록에 "다른 워크스페이스에서 import (출처: …)" 배지가 붙는지 확인합니다.

★ 단일 워크스페이스 한계 (캡처 정직성): 구독은 다른 워크스페이스가 게시한 카드에서만 가능합니다(자기 도구는 마켓플레이스에서 제외 + 409). 단일 E2E 세션(하나의 워크스페이스)에는 자기가 방금 게시한 카드가 보이지 않으므로 구독 → "구독됨" 전환·import 배지는 브라우저로 안정적으로 재현할 수 없습니다. 따라서 위 스크린샷은 prod 실행 당시 이 테넌트의 실제 마켓플레이스 상태(빈 상태이거나 다른 워크스페이스의 카드)를 그대로 캡처한 것입니다. 구독 → "구독됨" 전환과 import(출처: …) 배지 자체는 크로스-워크스페이스 시나리오가 필요해 API/단위 테스트로 결정적으로 검증합니다 — apps/api/tests/test_workspace_mcp_tool_marketplace.py(공유→200 / private→422 / 자기도구 구독→409 / 크로스-ws 구독→201 + import / 게시취소→revoke) 와 ui/src/components/workspace/mcp-tools/McpToolMarketplace.test.tsx·WorkspaceMcpToolList.test.tsx(구독 버튼→"구독됨" 비활성, import 배지). 환경에 다른 워크스페이스의 공유 카드가 있으면 그 카드로 구독 흐름을 직접 검증할 수 있습니다.


4. 게시 취소 → 마켓플레이스에서 제거 + 구독 revoke (S4)

게시된 도구 행의 게시 취소(Unpublish) 아이콘을 누르면 확인 대화상자가 뜨고, 확인하면 게시가 내려갑니다.

게시 취소 확인 대화상자

게시 취소가 성공하면 게시됨 배지가 사라지고, 그 도구를 구독했던 워크스페이스의 import 가 revoke 됩니다(구독자의 tools/list · dispatch 에서 도구가 빠지고, 출처 도구의 구독자 guest 그랜트도 함께 삭제). 마켓플레이스에서도 더 이상 보이지 않습니다.

게시 취소 후 — 게시됨 배지 제거

게시 취소 후 — 마켓플레이스 갱신

확인 포인트(QA): 게시 취소 후 게시됨 배지가 제거되고 마켓플레이스 페이지가 깨끗하게 렌더되는지 확인합니다. 구독자 측 revoke(구독 비활성)는 다른 워크스페이스가 필요하므로 API/단위 테스트로 검증합니다.


QA 체크리스트 요약

#시나리오기대 결과검증 경로
S1a공유 모델 도구 게시성공 토스트 + 게시됨 배지브라우저
S1bprivate 모델 도구 게시422 사유 토스트, 배지 없음브라우저
S2마켓플레이스 페이지카드(출처 + 공개 범위 배지) 또는 빈 상태브라우저
S3구독 → 구독됨 + import 배지버튼 구독됨 비활성 + 목록 import 배지크로스-ws 필요 → API/단위
S4게시 취소게시됨 배지 제거 + 구독 revoke게시취소=브라우저 / revoke=API
비-admin 게시/구독 403(화면 불가)API/단위 테스트

비-admin 403·크로스-워크스페이스 구독은 화면으로 검증 불가: E2E 계정이 admin 이고 단일 워크스페이스에서만 동작하므로 비-admin 차단(403)과 다른 워크스페이스에서의 구독·import 배지는 브라우저로 재현할 수 없습니다. 이는 apps/api/tests/test_workspace_mcp_tool_marketplace.py 와 UI 단위 테스트(McpToolMarketplace.test.tsx, WorkspaceMcpToolList.test.tsx) 가 검증합니다.


알아두기

  • no-private-leak 불변식: 게시 가능한 것은 전사 공유 모델(예: IntelSource) 또는 허용 공유 카탈로그(iceberg/gendpg) 뿐입니다. private 타깃·모든 raw_sql 도구는 422 로 막힙니다(Q5-A safe default).
  • 결과만 공유: 구독자는 결과만 받고 쿼리(query_spec_jsonb)는 마켓플레이스 카드에 노출되지 않습니다(invoke-only).
  • 출처 실행: 구독한 도구는 출처 워크스페이스에서 실행됩니다(공유되는 것은 결과뿐).
  • 게시 취소 = 즉시 revoke: 게시를 내리면 구독자 import 가 즉시 비활성화되고, 잔존 guest 그랜트도 함께 삭제됩니다(재-grant 위험 차단).
  • active 만 게시 가능: 검토 대기/비활성 도구는 게시할 수 없습니다(409). 게시 전 활성화가 필요합니다.
  • 소프트 삭제: 삭제는 status=deleted 소프트 삭제이며, 같은 이름을 나중에 다시 등록할 수 있습니다.