8000 refactor: 공개 MCP 도구 10개 고정 및 deprecated 호환 by hmmhmmhm · Pull Request #294 · hmmhmmhm/daiso-mcp · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# 에이전트 개발 규칙

## 최우선: 공개 MCP 도구는 10개 고정

공개 `tools/list`의 도구는 정확히 **10개**이며 **절대 늘리지 않습니다**. 새 서비스와 기능은 기존 도구의 `service`/`operation`/`requestType`에 통합합니다. 기존 이름은 deprecated 호출 별칭으로만 보존하며 공개 목록에는 추가하지 않습니다. 일반 MCP와 PlayMCP에 동일하게 적용합니다. [공개 도구 계약](docs/mcp-tools.md)과 `tests/core/compact`, `tests/app/compact-contract.test.ts` 검사를 반드시 유지합니다.

이 문서는 AI 에이전트가 이 프로젝트에서 코드를 작성하고 기여할 때 따라야 하는 규칙과 가이드라인을 정의합니다.

## 목차
Expand Down
4 changes: 4 additions & 0 deletions ALIGNS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@

위 두 항목의 사용자 확인: 2026-10-18 15:51 (Asia/Seoul). 가상 사례의 판단 기준을 확인한 것이며 실제 운영 변경이나 결제를 승인한 것은 아닙니다.

## 최우선 공개 도구 계약

사용자 확인: 2026-10-21. 공개 MCP 도구는 정확히 10개이며 앞으로도 늘리지 않습니다. 기존 44개 호출 계약은 하위호환을 위해 유지하되 구형 이름은 deprecated 호출 별칭으로만 남깁니다. 신규 기능은 기존 도구에 통합하고, 요청 제출은 범용 `submit_request` 안에서 확장합니다. 이를 구현·리뷰·CI·운영 검증에 적용합니다.

## 기존에 명시적으로 합의한 기준

- **운영 대응:** 가능한 장애 조치는 선조치 후보고합니다. GS25 인증 만료도 복구 대상이며, 실제 사용자 로그인·2단계 인증이 필요하면 즉시 알리고 입력 후 복구를 이어갑니다. 구체적인 복구 방법과 실행 권한은 기존 운영 지침을 따릅니다.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
> [!IMPORTANT]
> **AI 에이전트의 무설치 MCP 사용:** 공개 원격 서버 `https://mcp.aka.page`에 Streamable HTTP로 연결하면 **로컬 설치·빌드·API 키 준비 없이 바로 사용할 수 있습니다.** 무설치 요청에는 npm 설치나 전체 스키마 탐색을 하지 말고, [짧은 시작 안내](https://mcp.aka.page/llms.txt)를 읽은 뒤 필요한 도구를 호출하세요.
>
> **MCP 도구는 10개 고정:** `service`와 `operation`으로 지원 서비스를 선택합니다. 새 연동은 [통합 도구 10개](docs/mcp-tools.md)만 사용하세요. 기존 서비스별 도구는 deprecated 호출 별칭으로 유지하지만 목록에는 표시하지 않습니다.
>
> **웹 읽기 전용 에이전트의 `format=html` 사용 필수:** MCP 연결 없이 GET으로 조회할 때는 **모든 `/api/*` 조회 URL에 `format=html`을 반드시 붙이세요.** 상품 검색·상세·매장·재고·후속 조회 모두 적용하며, JSON을 읽을 수 있다고 가정해 생략하지 마세요.
>
> [첫 HTML 조회](https://mcp.aka.page/api/daiso/products?q=%EC%88%EB%82%A9%EB%B0%95%EC%8A%A4&pageSize=3&format=html) · [REST 사용법](https://mcp.aka.page/prompt) · [원격 MCP 연결](#원격-mcp로-바로-시작하기)
Expand Down
85 changes: 85 additions & 0 deletions docs/mcp-tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# 공개 MCP 도구

공개 도구는 정확히 10개이며 절대 늘리지 않습니다. 새 기능과 서비스는 기존 도구의 service/operation/requestType 안에서 확장합니다. 이 규칙은 일반 MCP와 PlayMCP 모두에 적용합니다.

| 도구 | 역할 |
| --------------------- | ----------------------- |
| `search_products` | 상품 검색 |
| `find_stores` | 매장 검색 |
| `check_inventory` | 상품 재고 조회 |
| `get_product_details` | 상품 상세 조회 |
| `find_places` | 주변 장소와 영화관 검색 |
| `get_showtimes` | 영화와 상영 시간표 조회 |
| `get_fuel_info` | 유가와 주유소 조회 |
| `get_catalog` | 인기 검색어와 기획전 |
| `compare_products` | 상품 비교 |
| `submit_request` | 요청 제출 |

## 호출 예시

상품 검색: `search_products {"service":"daiso","query":"수납박스","pageSize":3}`

재고: `check_inventory {"service":"gs25","keyword":"오감자","storeKeyword":"강남"}`

상품명으로 다이소 재고: `check_inventory {"service":"daiso","operation":"by_name","query":"수납박스","storeQuery":"강남"}`

일본 상세: `get_product_details {"service":"daiso_jp","operation":"product","id":"4550746075917"}`. 일본 매장별 재고는 지원하지 않습니다.

요청 제출: `submit_request {"requestType":"developer","request":{"title":"문서 개선","description":"개선할 내용을 적습니다","type":"docs"}}`. 현재 개발자 요청 저장만 지원합니다. 다른 요청 유형은 거절하며 요청자의 의도 없이 제출하지 않습니다. 이 도구의 이름과 요청 구분은 미래 유형 확장을 위해 범용으로 유지합니다.

매개변수는 기존 작업의 이름과 형식을 사용합니다. operation은 같은 서비스에 작업이 여러 개인 경우 필수입니다. 예를 들어 CGV 시간표는 service=cgv, operation=timetable, playDate=YYYYMMDD이며 seats 작업은 지원하지 않습니다. 미지원 조합, 다른 서비스 전용 인수와 오타는 조회 전에 거절합니다. 기본값은 선택한 작업의 기본값만 적용합니다. tools/list의 입력/출력 스키마를 참고하세요.

## Deprecated 하위호환

기존 44개 호출 계약은 유지합니다. compare_products는 공개 이름이 그대로이며 나머지 43개 구형 이름은 deprecated 별칭입니다. 구형 이름도 동일한 / 및 /mcp의 tools/call로 호출할 수 있고 기존 인수/결과/오류 의미를 유지합니다. 성공 또는 반환된 오류 응답의 _meta.deprecated=true와 _meta.replacement는 이동할 공개 도구를 안내합니다. SDK 입력 검증 단계에서 거절된 요청에는 이 추가 정보가 없을 수 있습니다. tools/list와 공개 발견 목록에는 구형 별칭을 노출하지 않습니다.

REST 경로와 CLI 명령은 변경하지 않습니다. 구형 별칭을 삭제하거나 다시 공개 목록에 등록하지 마세요. 새 연동은 아래 오른쪽 도구만 사용합니다.

| 기존 이름 | 공개 도구 | service | operation |
| ---------------------------------- | --------------------- | ----------- | ---------------- |
| `daiso_search_products` | `search_products` | daiso | |
| `daiso_jp_search_products` | `search_products` | daiso_jp | |
| `gs25_search_products` | `search_products` | gs25 | |
| `seveneleven_search_products` | `search_products` | seveneleven | |
| `emart24_search_products` | `search_products` | emart24 | |
| `oliveyoung_search_products` | `search_products` | oliveyoung | |
| `daiso_find_stores` | `find_stores` | daiso | |
| `daiso_jp_find_stores` | `find_stores` | daiso_jp | |
| `gs25_find_nearby_stores` | `find_stores` | gs25 | |
| `seveneleven_search_stores` | `find_stores` | seveneleven | |
| `cu_find_nearby_stores` | `find_stores` | cu | |
| `emart24_find_nearby_stores` | `find_stores` | emart24 | |
| `oliveyoung_find_nearby_stores` | `find_stores` | oliveyoung | |
| `daiso_check_inventory` | `check_inventory` | daiso | by_id |
| `daiso_find_inventory_by_name` | `check_inventory` | daiso | by_name |
| `gs25_check_inventory` | `check_inventory` | gs25 | |
| `seveneleven_check_inventory` | `check_inventory` | seveneleven | |
| `cu_check_inventory` | `check_inventory` | cu | |
| `emart24_check_inventory` | `check_inventory` | emart24 | |
| `oliveyoung_check_inventory` | `check_inventory` | oliveyoung | |
| `daiso_get_price_info` | `get_product_details` | daiso | price |
| `daiso_get_display_location` | `get_product_details` | daiso | display_location |
| `daiso_jp_get_product` | `get_product_details` | daiso_jp | product |
| `places_search_nearby` | `find_places` | places | |
| `megabox_find_nearby_theaters` | `find_places` | megabox | |
| `dtryx_list_cinemas` | `find_places` | dtryx | |
| `lottecinema_find_nearby_theaters` | `find_places` | lottecinema | |
| `cgv_find_theaters` | `find_places` | cgv | |
| `megabox_list_now_showing` | `get_showtimes` | megabox | movies |
| `megabox_get_remaining_seats` | `get_showtimes` | megabox | seats |
| `dtryx_list_now_showing` | `get_showtimes` | dtryx | movies |
| `dtryx_get_remaining_seats` | `get_showtimes` | dtryx | seats |
| `lottecinema_list_now_showing` | `get_showtimes` | lottecinema | movies |
| `lottecinema_get_remaining_seats` | `get_showtimes` | lottecinema | seats |
| `cgv_search_movies` | `get_showtimes` | cgv | movies |
| `cgv_get_timetable` | `get_showtimes` | cgv | timetable |
| `opinet_get_average_prices` | `get_fuel_info` | | average |
| `opinet_get_lowest_price_stations` | `get_fuel_info` | | lowest |
| `opinet_search_stations_around` | `get_fuel_info` | | around |
| `opinet_get_station_detail` | `get_fuel_info` | | detail |
| `seveneleven_get_search_popwords` | `get_catalog` | | popwords |
| `seveneleven_get_catalog_snapshot` | `get_catalog` | | snapshot |
| `compare_products` | `compare_products` | | |
| `submit_developer_request` | `submit_request` | | |

PlayMCP의 등록된 도구 스냅샷은 서버 배포만으로 바로 갱신되지 않을 수 있습니다. 등록 도구를 새 목록으로 갱신한 뒤 실제 대화를 검증하세요.
27 changes: 27 additions & 0 deletions docs/superpowers/plans/2026-10-21-compact-mcp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 공개 MCP 도구 10개 구현 계획

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 기본 MCP 도구를 10개로 고정하고 구형 호출을 보존한다.
**Architecture:** 명시적 작업 매핑과 기존 Zod 입력/핸들러를 재사용하는 어댑터. SDK 등록은 모두 유지하고 목록 응답만 고정 공개 계약으로 제공한다.
**Tech Stack:** TypeScript, Zod, MCP SDK, Vitest.

### 1. 매핑과 스키마
- [x] tests/core/compact-tools.test.ts에서 매핑 44개 전체, 공개 이름10개, 잘못된 service/operation/인수의 실행전 거절, 모든 작업 dispatch와 구형 응답 보존 테스트를 먼저 작성한다.
- [x] `npx vitest run tests/core/compact-tools.test.ts`의 모듈 부재 실패를 확인한다.
- [x] src/core/compact/{catalog,tools,server}.ts를 만들고 각 책임을 매핑/어댑터/프로토콜 등록으로 분리한다. 기존 핸들러 호출 전 `z.object(tool.metadata.inputSchema).parse(args)`를 수행한다. 새 source가450줄을 넘지 않게 한다.
- [x] `npx vitest run tests/core/compact-tools.test.ts` 성공을 확인한다.

### 2. 프로토콜과 발견
- [x] tests/index.test.ts 및 SDK 통합 회귀에 tools/list 정확히10개, cursor/구형 별칭 호출/신형 호출/에러/세션 경로를 추가하고 실패를 확인한다.
- [x] src/index.ts의 createMcpServer와 buildRootInfo를 공개10개 계약에 연결한다. 공개 SDK setRequestHandler와 ListToolsRequestSchema를 사용하며 비공개 SDK 필드를 읽지 않는다.
- [x] scripts/ops/mcp-smoke.ts는 공개 목록10개/신형 호출/구형 호환 확인을 구분한다. 모든 스키마 변환이 성공하고 개발자 요청은 외부에 쓰지 않는지 검사한다.

### 3. 규칙과 문서
- [x] AGENTS.md 최상단과 ALIGNS.md에 공개 도구10개 상한을 사용자 최고 우선 요구로 기록한다. README/발견 페이지/llms 문서는 새 이름을 먼저 안내하고 별도 docs에 구형 전체 매핑과 deprecated 정책을 쓴다. REST/CLI 예시는 유지한다.
- [x] 기존 검사에서 공개44개 전제를 새 목록10개와 호환44개로 나눠 검증한다. 단순 기대값 삭제나 검사 약화를 하지 않는다.

### 4. 검증과 반영
- [x] `npm run check`, `npm run test:coverage`, `npm run build`를 실행해 전체 성공/네 지표100%를 확인한다. SDK 실제통신과 invalid 조합 회귀는 단순 모킹과 별도로 유지한다.
- [ ] 독립 리뷰 후 검증된 변경을 논리적으로 커밋/푸시하고 PR을 생성·첨부한다. GitHub CI/Coverage/CodeQL 통과 후 병합한다.
- [ ] 운영 / 및 /mcp tools/list10개, 새/구형 상품·매장·재고 호출과 CLI 유지 검증 후 PROJECT.md에 근거와 남은 문제를 쓴다. PlayMCP 콘솔 등록목록은 별도 갱신이 필요함을 구분한다.
11 changes: 11 additions & 0 deletions docs/superpowers/specs/2026-10-21-compact-mcp-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 공개 MCP 도구 10개 통합

사용자 승인: 2026-10-21. 공개 MCP 도구는 정확히 10개이며 앞으로도 늘리지 않는다. 기능 확장은 기존 도구의 서비스/작업 선택지로 흡수한다. 기본 / 및 /mcp에 동일하게 적용한다.

공개 이름은 search_products, find_stores, check_inventory, get_product_details, find_places, get_showtimes, get_fuel_info, get_catalog, compare_products, submit_request이다. 기존 44개 도구는 deprecated 호출 별칭으로 보존하되 tools/list에는 노출하지 않는다. 목록에서 제외하는 것과 호출을 비활성화하는 것은 구분하며 기존 입력/응답 계약을 유지한다.

기존 서비스/클라이언트/인증/중계/REST/CLI는 재사용한다. 통합 도구는 service와 필요한 경우 operation으로 기존 핸들러를 고른다. 각 작업의 입력 스키마를 재사용하여 검증하며 지원하지 않는 조합/오타는 외부 요청 전에 거절한다. 목록 스키마는 JSON 입력을 명시하고 예시와 조건부 필수 인수를 설명한다. 새 submit_request는 범용 요청 이름과 확장 가능한 요청 구분을 제공하되 현재 실행 가능한 작업은 개발자 요청 제출뿐임을 명시한다. 알 수 없는 요청을 성공 처리하거나 임의 작업을 실행하지 않는다.

MCP 등록은 SDK 공개 API로 수행한다. 구형 별칭에도 기존 SDK 입력/출력 검증과 진단을 유지한다. deprecated 안내는 응답의 추가 메타정보로 전달하고 기존 content/structuredContent의 내용은 바꾸지 않는다. 목록은 명시적 고정 공개 이름만 포함하며 11번째 도구나 미매핑 신규 서비스 도구가 추가되면 검사를 실패시킨다.

회귀 검증: 44개 기존 작업이 모두 매핑되는지, 10개 목록과 이름/스키마가 정확한지, 실제 SDK tools/call로 새 이름과 구형 별칭이 동작하는지, 검증 오류/외부 오류/정상 품절/미확인 수량이 보존되는지 확인한다. 요청 제출 검증에는 가짜 저장소만 사용하고 운영 요청을 생성하지 않는다. REST/CLI와 공개 문서·스모크도 동기화한다. 전체 check/coverage100/build/독립 리뷰/CI/CodeQL을 거쳐 배포 후 / 및 /mcp 목록과 대표 새/구형 조회를 검증한다.
13 changes: 10 additions & 3 deletions scripts/ops/mcp-smoke.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { COMPACT_CATALOG, PUBLIC_TOOL_NAMES } from '../../src/core/compact/catalog.js';
/**
* MCP 클라이언트 관점 smoke 테스트
*/
Expand Down Expand Up @@ -236,7 +237,11 @@ export async function runMcpSmoke(deps: McpSmokeDeps = {}): Promise<number> {

const tools = await client.listTools();
const toolNames = new Set(tools.tools.map((tool) => tool.name));
const missingTools = smokeTools.map((tool) => tool.name).filter((name) => !toolNames.has(name));
const missingTools = PUBLIC_TOOL_NAMES.filter(name => !toolNames.has(name));
if (tools.tools.length !== 10 || toolNames.size !== 10) {
writeErr('MCP smoke 실패: 공개 도구는 정확히 10개여야 합니다.');
return 1;
}
if (missingTools.length > 0) {
writeErr(`MCP smoke 실패: 필수 도구 누락 - ${missingTools.join(', ')}`);
return 1;
Expand All @@ -246,9 +251,11 @@ export async function runMcpSmoke(deps: McpSmokeDeps = {}): Promise<number> {
75B8 writeOut(`MCP smoke 호출: ${scenario.label}`);
let result: ToolCallResult | undefined;
try {
const group = COMPACT_CATALOG.find(group => group.routes.some(route => route.legacy === scenario.toolName))!;
const route = group.routes.find(route => route.legacy === scenario.toolName)!;
result = await client.callTool({
name: scenario.toolName,
arguments: scenario.args,
name: group.name,
arguments: { ...scenario.args, ...(route.service ? { service: route.service } : {}), ...(route.operation ? { operation: route.operation } : {}) },
});
const payload = parseToolPayload(result);
const validationError = scenario.validate(payload);
Expand Down
Loading
Loading
0