diff --git a/README.md b/README.md index 54783d90..c694d477 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,18 @@ AI 에이전트 사용 안내 > [!IMPORTANT] -> **최신 안내:** [현재 버전](https://mcp.aka.page/guide/2026-10-22.1) · [항상 최신 버전으로 이동](https://mcp.aka.page/guide/latest) +> **최신 안내:** [현재 버전](https://mcp.aka.page/guide/2026-10-26.1) · [항상 최신 버전으로 이동](https://mcp.aka.page/guide/latest) > -> **안내 버전: 2026-10-22.1 · 갱신일: 2026-10-22** +> **안내 버전: 2026-10-26.1 · 갱신일: 2026-10-26** > -> **ChatGPT 웹 읽기:** 연결된 MCP 도구가 없으면 [HTML GET으로 바로 조회](https://mcp.aka.page/browse/daiso/products/%EC%88%98%EB%82%A9%EB%B0%95%EC%8A%A4/pageSize/3)하세요. 설치·API 키·MCP 연결 설정은 필요 없습니다. +> **ChatGPT 웹 조회:** 연결된 MCP 도구가 없으면 사용 가능한 실제 브라우저로, 브라우저 도구도 없으면 open/WebFetch로 [HTML GET으로 바로 조회](https://mcp.aka.page/browse/daiso/products/%EC%88%98%EB%82%A9%EB%B0%95%EC%8A%A4/pageSize/3)하세요. 설치·API 키·MCP 연결 설정은 필요 없습니다. > > **로컬 설치·빌드·API 키 준비 없이 실제 조회부터 실행하세요.** 패키지 설치나 전체 스키마 탐색은 필요 없습니다. > > 1. 연결된 MCP 도구가 있으면 `search_products`에 `{"service":"daiso","query":"수납박스","pageSize":3}`를 전달합니다. -> 2. 웹 읽기(open/WebFetch)가 가능하면 [첫 HTML 조회](https://mcp.aka.page/browse/daiso/products/%EC%88%98%EB%82%A9%EB%B0%95%EC%8A%A4/pageSize/3)를 바로 엽니다. **셸이 있어도 연결된 MCP 도구가 없으면 HTML GET이 우선입니다.** 경로의 검색어를 사용자 요청으로 바꾸세요. 웹 검색은 URL 열기를 대신하지 않습니다. -> 3. 위 두 수단이 없을 때만 사용 가능한 조회 도구가 없음을 알립니다. 터미널·셸 호출로 우회하지 마세요. 셸 DNS 실패를 웹 GET 실패로 확대하거나 실행하지 않은 요청의 실패를 만들지 마세요. +> 2. 실제 브라우저 도구가 있으면 /browse/ HTML GET 주소를 열고 실제 상품·매장·재고를 읽습니다. Work의 내장 브라우저도 사용 가능합니다. +> 3. 웹 읽기(open/WebFetch)만 가능하면 같은 HTML GET을 실행합니다. 접근 오류 시 문서의 링크를 따라갈 수 있으나 성공을 보장하지 않습니다. Crawled가 과거인 수집본을 현재 재고로 단정하지 마세요. +> 4. 위 세 수단이 없거나 모두 실패하면 실제 조회 제약과 오류를 알립니다. 셸이 있어도 설치 없는 웹 조회가 우선이며 터미널·셸로 우회하지 않습니다. 셸 DNS 오류를 웹 GET 실패로 확대하거나 실행하지 않은 요청의 실패를 만들지 마세요. > > **검색 링크:** [핫식스 상품 검색](https://mcp.aka.page/browse/daiso/products/%ED%95%AB%EC%8B%9D%EC%8A%A4/pageSize/10) · [강남 매장 검색](https://mcp.aka.page/browse/daiso/stores/%EA%B0%95%EB%82%A8). 웹 읽기는 링크를 직접 열고, 접근 오류만으로 상품 없음이나 재고 0을 판단하지 마세요. > @@ -127,7 +128,7 @@ 2. 서버 URL을 입력하고, 전송 방식을 묻는 경우 **HTTP / Streamable HTTP**를 선택합니다. 3. 도구를 활성화하고 `다이소 MCP로 수납박스 검색해줘`라고 요청합니다. -[앱별 연결 방법](docs/client-guide.md#원격-mcp-연결) · [WebFetch로 첫 조회](docs/service-reference.md?guide=2026-10-22.1#설치-없이-첫-조회) +[앱별 연결 방법](docs/client-guide.md#원격-mcp-연결) · [WebFetch로 첫 조회](docs/service-reference.md?guide=2026-10-26.1#설치-없이-첫-조회)

스마트폰과 노트북을 클라우드로 연결하는 두 햄스터 @@ -205,7 +206,7 @@ MCP 연결 없이 웹 읽기와 GET 요청을 지원하는 에이전트는 [REST | 문서 | 내용 | | :-------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | | [연결·사용 가이드](docs/client-guide.md) | 앱별 MCP 연결, CLI, Skill, OpenAPI | -| [서비스 레퍼런스](docs/service-reference.md?guide=2026-10-22.1) | 서비스별 도구·REST API·응답 모델·가격 비교 | +| [서비스 레퍼런스](docs/service-reference.md?guide=2026-10-26.1) | 서비스별 도구·REST API·응답 모델·가격 비교 | | [일본 다이소](docs/daiso-jp-network-analysis.md) | 일본 상품·매장 검색과 지원 범위 | | [독립·예술영화관](docs/dtryx.md) | 디트릭스 지원 극장과 조회 방법 | | [AI 조회 가이드](docs/ai-instruction.md) | 도구 선택과 상품·매장·재고 조회 순서 | diff --git a/docs/ai-instruction.md b/docs/ai-instruction.md index 8ae244d9..d461a4cf 100644 --- a/docs/ai-instruction.md +++ b/docs/ai-instruction.md @@ -5,8 +5,9 @@ 최신 지침은 https://mcp.aka.page/guide/latest 를 열어 현재 버전 주소로 이동합니다. 오래된 버전 경로도 최신 지침으로 연결됩니다. 첫 조회에는 지침의 guide 버전을 유지하세요. 1. 연결된 MCP 도구가 있으면 `search_products`에 `{"service":"daiso","query":"수납박스","pageSize":3}`를 전달합니다. -2. 웹 읽기(open/WebFetch)가 가능하면 아래 HTML GET을 바로 엽니다. 셸이 있어도 연결된 MCP 도구가 없으면 HTML GET이 우선입니다. 웹 검색(search)은 URL 열기(open)를 대신하지 않습니다. -3. 위 두 수단이 없을 때만 사용 가능한 조회 도구가 없음을 알립니다. 셸 호출로 우회하지 말고 실제 실행하지 않은 요청을 실패라고 설명하지 않습니다. 셸 DNS 오류는 웹 GET 실패가 아닙니다. +2. 실제 브라우저 도구가 있으면 /browse/ HTML GET 주소를 열고 실제 상품·매장·재고를 읽습니다. Work의 내장 브라우저도 사용 가능합니다. +3. 웹 읽기(open/WebFetch)만 가능하면 같은 HTML GET을 실행합니다. 접근 오류 시 문서의 링크를 따라갈 수 있으나 성공을 보장하지 않습니다. Crawled가 과거인 수집본을 현재 재고로 단정하지 마세요. +4. 위 세 수단이 없거나 모두 실패하면 실제 조회 제약과 오류를 알립니다. 셸이 있어도 설치 없는 웹 조회가 우선이며 터미널·셸로 우회하지 않습니다. 셸 DNS 오류를 웹 GET 실패로 확대하거나 실행하지 않은 요청의 실패를 만들지 마세요. https://mcp.aka.page/browse/daiso/products/%EC%88%98%EB%82%A9%EB%B0%95%EC%8A%A4/pageSize/3 diff --git a/docs/client-guide.md b/docs/client-guide.md index 0587b76c..da60dec5 100644 --- a/docs/client-guide.md +++ b/docs/client-guide.md @@ -1,5 +1,7 @@ # 앱 연결·CLI·Skill 가이드 +실제 브라우저 도구가 있으면 /browse/ HTML을 그 도구로 여세요. 웹 읽기(open/WebFetch)의 접근 오류 시 사용 가능한 브라우저로 전환합니다. Crawled가 과거인 수집본은 현재 재고 확인이 아닙니다. 응답 생성 시각도 함께 확인하세요. + 연결된 MCP 도구가 있으면 [원격 MCP 안내](../README.md#원격-mcp로-바로-시작하기)를 따릅니다. 웹 읽기만 가능하면 설치·연결 설정 없이 [첫 상품 조회](https://mcp.aka.page/browse/daiso/products/%EC%88%98%EB%82%A9%EB%B0%95%EC%8A%A4/pageSize/3)를 open/WebFetch로 바로 여세요. 모든 후속 조회도 쿼리 없는 `/browse/` 경로를 사용하며 자동 HTML입니다. 원격 서버는 `https://mcp.aka.page`, 전송 방식은 Streamable HTTP, 사용자 인증은 없습니다. @@ -253,3 +255,5 @@ npx daiso products 수납박스 --json 앱의 원격 MCP 지원 여부는 해당 앱 문서에서 확인하세요. 웹 GET 요청을 실행할 수 있다면 [REST 사용법 페이지](https://mcp.aka.page/prompt)를 읽고 조회할 수 있습니다. REST GET 조회는 MCP 프로토콜 연결이 아닙니다. + +웹 읽기 접근 오류와 오래된 결과 구별은 [진단 안내](web-reading-troubleshooting.md)를 참고하세요. diff --git a/docs/service-reference.md b/docs/service-reference.md index 382439e9..decd176a 100644 --- a/docs/service-reference.md +++ b/docs/service-reference.md @@ -1,5 +1,7 @@ # DAISO MCP 서비스 레퍼런스 +실제 브라우저 도구가 있으면 /browse/ HTML을 그 도구로 여세요. 웹 읽기(open/WebFetch)의 접근 오류 시 사용 가능한 브라우저로 전환합니다. Crawled가 과거인 수집본은 현재 재고 확인이 아닙니다. 응답 생성 시각도 함께 확인하세요. + README 하단에 있던 기능, REST API, 개발, 아키텍처 설명을 분리한 문서입니다. ## 설치 없이 첫 조회 @@ -14,7 +16,7 @@ README 하단에 있던 기능, REST API, 개발, 아키텍처 설명을 분리 모든 후속 웹 GET도 `/browse/`를 사용하세요. 자동 HTML이며 쿼리 문자열이나 format 지정은 필요 없습니다. 상품 검색 결과의 실제 ID로 재고를 조회하고 검색 성공만으로 매장 재고를 단정하지 않습니다. 이미지는 응답 imageUrl과 카드의 원본 Markdown만 사용하고 이미지 검색으로 대체하지 않습니다. -현재 안내는 [버전 안내](https://mcp.aka.page/guide/2026-10-22.1), 다음 버전도 따라가려면 [항상 최신 안내](https://mcp.aka.page/guide/latest)를 여세요. 이 상세 레퍼런스를 끝까지 읽기 전에 첫 조회를 실행하세요. +현재 안내는 [버전 안내](https://mcp.aka.page/guide/2026-10-26.1), 다음 버전도 따라가려면 [항상 최신 안내](https://mcp.aka.page/guide/latest)를 여세요. 이 상세 레퍼런스를 끝까지 읽기 전에 첫 조회를 실행하세요. 이미 연결된 MCP 도구가 있는 환경만 `search_products`에 `service=daiso`, `query=수납박스`를 전달합니다. 서버는 https://mcp.aka.page 이며 Streamable HTTP, 인증 없음입니다. 실제로 실행한 도구·URL·오류만 보고하고 실행하지 않은 요청의 성공·실패를 만들지 않습니다. @@ -811,16 +813,16 @@ GET /api/cgv/timetable?playDate={YYYYMMDD}&theaterCode={극장코드}&movieCode= 웹 읽기(open/WebFetch)는 아래 경로로 실행합니다. `{...}`는 앞선 실제 응답 또는 사용자가 알려 준 조건으로 바꾸고 각 값을 한 번 URL 인코딩합니다. 모든 결과는 자동 HTML입니다. -| 사용자 요청 | 웹 조회 경로 | -| --- | --- | -| 수납박스 검색 | `/browse/daiso/products/수납박스/pageSize/3` | -| 선택한 상품의 안산 중앙역 재고 | `/browse/daiso/inventory/{실제상품ID}/안산중앙역` | -| 강남역 다이소 매장 | `/browse/daiso/stores/강남역` | -| 선택한 매장의 진열 위치 | `/browse/daiso/display-location/{실제상품ID}/{재고응답의storeCode}` | -| 명동 올리브영 매장 | `/browse/oliveyoung/stores/명동` | -| 강남 CU 과자 재고 | `/browse/cu/inventory/과자/강남` | -| 잠실 롯데시네마 극장 | `/browse/lottecinema/theaters/잠실` | -| 선택한 CGV 극장 시간표 | `/browse/cgv/timetable/playDate/{YYYYMMDD}/theaterCode/{실제극장코드}` | +| 사용자 요청 | 웹 조회 경로 | +| ------------------------------ | ---------------------------------------------------------------------- | +| 수납박스 검색 | `/browse/daiso/products/수납박스/pageSize/3` | +| 선택한 상품의 안산 중앙역 재고 | `/browse/daiso/inventory/{실제상품ID}/안산중앙역` | +| 강남역 다이소 매장 | `/browse/daiso/stores/강남역` | +| 선택한 매장의 진열 위치 | `/browse/daiso/display-location/{실제상품ID}/{재고응답의storeCode}` | +| 명동 올리브영 매장 | `/browse/oliveyoung/stores/명동` | +| 강남 CU 과자 재고 | `/browse/cu/inventory/과자/강남` | +| 잠실 롯데시네마 극장 | `/browse/lottecinema/theaters/잠실` | +| 선택한 CGV 극장 시간표 | `/browse/cgv/timetable/playDate/{YYYYMMDD}/theaterCode/{실제극장코드}` | 이미 연결된 MCP 도구가 있으면 웹 GET 대신 [공개 통합 도구](mcp-tools.md)를 사용합니다. 예를 들어 상품 검색은 `search_products(service=daiso, query=수납박스)`, 실제 상품 ID로 재고 조회는 `check_inventory(service=daiso, operation=by_id, productId=실제ID, keyword=사용자지역)`입니다. 위 상세 레퍼런스의 deprecated 별칭을 새 호출 예제로 사용하지 않습니다. diff --git a/docs/web-discovery.md b/docs/web-discovery.md index 5195c291..3bde216a 100644 --- a/docs/web-discovery.md +++ b/docs/web-discovery.md @@ -1,6 +1,8 @@ # 웹에서 다이소 MCP 발견하기 -[공개 첫 화면](https://mcp.aka.page/)은 설치 없는 사용 방법과 첫 실제 조회 링크를 HTML로 제공합니다. 웹 읽기 기능만으로 MCP 도구가 자동 등록되는 것은 아닙니다. 연결된 MCP 도구를 먼저 사용하고, 없으면 셸 유무와 무관하게 웹 읽기의 HTML GET을 바로 실행합니다. 두 수단이 없으면 실제 조회 도구 제약을 알리며 셸 호출로 우회하지 않습니다. WebFetch 또는 open으로 HTML 링크를 직접 엽니다. +실제 브라우저 도구가 있으면 /browse/ HTML을 그 도구로 여세요. 웹 읽기(open/WebFetch)의 접근 오류 시 사용 가능한 브라우저로 전환합니다. Crawled가 과거인 수집본은 현재 재고 확인이 아닙니다. 응답 생성 시각도 함께 확인하세요. + +[공개 첫 화면](https://mcp.aka.page/)은 설치 없는 사용 방법과 첫 실제 조회 링크를 HTML로 제공합니다. 웹 읽기 기능만으로 MCP 도구가 자동 등록되는 것은 아닙니다. 연결된 MCP 도구를 먼저 사용하고, 없으면 실제 브라우저, 그다음 웹 읽기의 HTML GET을 사용합니다. 모든 수단이 없거나 실패하면 실제 조회 도구 제약을 알리며 셸 호출로 우회하지 않습니다. WebFetch 또는 open으로 HTML 링크를 직접 엽니다. - `/`: HTML 안내. `Accept: application/json`이면 기존 서버 정보 JSON. - `/root.json`: 현재 서비스·도구 목록 JSON. API 클라이언트는 이 명시적 주소를 권장합니다. @@ -53,13 +55,12 @@ HTML은 원래 응답의 성공·오류와 HTTP 상태를 보존합니다. 상 ## 안내 버전과 상품 이미지 카드 -현재 안내 버전은 `2026-10-22.1`, 갱신일은 `2026-10-22`입니다. README의 AI 안내, 첫 화면, llms.txt, prompt와 HTML 조회 결과에 표시합니다. 버전이 없거나 다르면 예전 검색 사본일 수 있으므로 실제 읽은 URL과 문구를 확인하고 최신 버전을 읽었다고 추정하지 않습니다. 검색 엔진의 재수집·캐시 반영 시각은 이 배포로 보장하지 않습니다. +현재 안내 버전은 `2026-10-26.1`, 갱신일은 `2026-10-26`입니다. README의 AI 안내, 첫 화면, llms.txt, prompt와 HTML 조회 결과에 표시합니다. 버전이 없거나 다르면 예전 검색 사본일 수 있으므로 실제 읽은 URL과 문구를 확인하고 최신 버전을 읽었다고 추정하지 않습니다. 검색 엔진의 재수집·캐시 반영 시각은 이 배포로 보장하지 않습니다. 상품 HTML은 이름·ID·원본 필드와 응답 imageUrl의 이미지를 같은 카드에 표시합니다. 이미지 링크는 원본 URL의 쿼리까지 유지하며 HTTPS이고 인증정보가 없는 주소만 사용합니다. imageUrl이 없거나 안전한 이미지 주소가 아니면 이미지 없음으로 표시합니다. 검색 이미지로 채우지 않습니다. JSON API 필드와 오류 상태는 유지합니다. - ## 최신 안내의 연결 계약 -`/guide/latest` 및 이전 `/guide/버전`은 307과 Cache-Control: no-store로 현재 `/guide/2026-10-22.1`에 연결합니다. 새 버전 배포 시 공통 상수와 README·스킬 안내를 함께 갱신합니다. 첫 화면·robots·sitemap도 같은 상수의 현재 경로를 안내합니다. 외부 캐시가 과거 응답이나 리다이렉트를 재사용하는 것까지 강제할 수는 없습니다. +`/guide/latest` 및 이전 `/guide/버전`은 307과 Cache-Control: no-store로 현재 `/guide/2026-10-26.1`에 연결합니다. 새 버전 배포 시 공통 상수와 README·스킬 안내를 함께 갱신합니다. 첫 화면·robots·sitemap도 같은 상수의 현재 경로를 안내합니다. 외부 캐시가 과거 응답이나 리다이렉트를 재사용하는 것까지 강제할 수는 없습니다. 첫 HTML 조회와 상세·재고·페이지 이동 링크는 쿼리 없는 `/browse/` 경로입니다. 프롬프트의 웹 GET 예제도 같은 경로를 사용하며 안내 버전은 `/guide/latest`에서 확인합니다. 안내와 HTML 결과는 no-store를 보내며 기존 JSON/API 원본 캐시 정책은 유지합니다. 이미지 카드에는 원본 imageUrl로 생성한 Markdown을 제공하고 이미지 검색/image_query/캐러셀 대체를 금지합니다. 표시가 불가능하면 원본 링크와 텍스트만 제공합니다. diff --git a/docs/web-reading-troubleshooting.md b/docs/web-reading-troubleshooting.md new file mode 100644 index 00000000..879c6b3e --- /dev/null +++ b/docs/web-reading-troubleshooting.md @@ -0,0 +1,24 @@ +# 웹 읽기 접근 오류 진단 + +2026-10-26 실험에서는 같은 재고 주소가 일반 HTTPS, MCP SDK, Work 내장 브라우저에서는 정상 동작했지만 웹 읽기 도구에서는 접근 오류가 발생했습니다. 일부 기존 링크는 웹 읽기로 열렸으나 새 주소나 동일 주소의 직접 열기는 실패했습니다. 과거 수집 시점의 결과가 반환되는 경우도 확인했습니다. + +Cloudflare 관측 구간에는 정상 페이지의 OpenAI 요청에서 200과 클라이언트 연결 종료인 499가 관측됐습니다. 403 차단은 관측되지 않았습니다. 로그의 표본 추출과 수집 지연 때문에 요청이 보이지 않는다는 이유만으로 서버에 도달하지 않았다고 확정할 수는 없습니다. 웹 읽기 내부의 실패 원인이나 시간 제한은 확인되지 않았으며 서버 변경으로 해결됐다고 주장하지 않습니다. + +## 조회 수단 선택 + +1. 연결된 MCP 도구가 있으면 직접 호출합니다. +2. 실제 브라우저 도구가 있으면 `/browse/` 주소를 열고 데이터를 읽습니다. 설치나 MCP 등록은 필요하지 않습니다. +3. 웹 읽기만 가능하면 같은 HTML 주소를 엽니다. 접근 오류가 나면 이미 공개된 링크를 따라가는 방법을 시도할 수 있지만 성공을 보장하지 않습니다. +4. 가능한 수단이 모두 실패하면 실행한 도구, URL, 오류를 그대로 보고합니다. 접근 오류는 품절이나 로그인 필요를 뜻하지 않습니다. + +브라우저 제공 여부는 환경마다 다릅니다. 브라우저가 없는 환경에 있다고 가정하거나 실행하지 않은 셸 명령과 DNS 오류를 만들어 보고하지 마세요. + +## 오래된 결과 구별 + +웹 읽기의 `Crawled` 시점과 페이지의 **응답 생성 시각**을 확인하세요. 오래된 수집본을 현재 재고로 단정하면 안 됩니다. 응답 생성 시각은 HTML을 만든 시각이며 원본 재고 갱신 시각이나 원본 캐시 우회를 증명하지 않습니다. 상품 검색 성공도 특정 매장의 재고 보유를 뜻하지 않습니다. + +## 재현 자료 + +문제 보고에는 실제 도구 이름, 정확한 공개 URL, 오류 원문, 실행 시각, 수집 시각을 포함해 주세요. 같은 주소를 브라우저로 열었을 때의 결과도 도움이 됩니다. 인증 토큰과 쿠키는 포함하지 마세요. + +관련 안내: [클라이언트 사용법](client-guide.md), [웹 검색과 발견](web-discovery.md). diff --git a/skills/daiso-cli/SKILL.md b/skills/daiso-cli/SKILL.md index 4d0397d9..3a7d0e58 100644 --- a/skills/daiso-cli/SKILL.md +++ b/skills/daiso-cli/SKILL.md @@ -1,7 +1,7 @@ --- name: daiso-cli -description: Use this when a user wants to search Daiso/다이소, compare product price candidates, nearby restaurants/cafes/음식점/카페, convenience stores/편의점, marts, Olive Young/올리브영, Megabox/메가박스, Lotte Cinema/롯데시네마, or CGV data through the Daiso project. Honor explicit no-install requests with connected MCP tools first, then HTML GET via web reading even when a shell is available; otherwise prefer the daiso CLI for direct execution, use the MCP endpoint when the host app supports remote MCP, and choose commands for products, stores, inventory/재고, compare, places, movies, showtimes, seats, health checks, and raw JSON output. -version: 1.0.8 +description: Use this when a user wants to search Daiso/다이소, compare product price candidates, nearby restaurants/cafes/음식점/카페, convenience stores/편의점, marts, Olive Young/올리브영, Megabox/메가박스, Lotte Cinema/롯데시네마, or CGV data through the Daiso project. Honor explicit no-install requests with connected MCP tools first, then HTML GET via an available browser, then web reading even when a shell is available; otherwise prefer the daiso CLI for direct execution, use the MCP endpoint when the host app supports remote MCP, and choose commands for products, stores, inventory/재고, compare, places, movies, showtimes, seats, health checks, and raw JSON output. +version: 1.0.9 metadata: openclaw: requires: @@ -24,8 +24,9 @@ Use this skill to operate the Daiso MCP project through `npx daiso` and the publ 최신 지침은 https://mcp.aka.page/guide/latest 를 열어 현재 버전 주소로 이동합니다. 오래된 버전 경로도 최신 지침으로 연결됩니다. 첫 조회에는 지침의 guide 버전을 유지하세요. 1. 연결된 MCP 도구가 있으면 `search_products`에 `{"service":"daiso","query":"수납박스","pageSize":3}`를 전달합니다. -2. 웹 읽기(open/WebFetch)가 가능하면 아래 HTML GET을 바로 엽니다. 셸이 있어도 연결된 MCP 도구가 없으면 이 경로가 우선입니다. 검색(search)은 URL 열기(open)를 대신하지 않습니다. -3. 위 두 수단이 없을 때만 사용 가능한 조회 도구가 없음을 알립니다. 셸 호출로 우회하지 않으며, 셸 DNS 오류를 웹 GET 실패로 확대하지 않습니다. 실제로 실행하지 않은 요청의 성공·실패를 만들지 않습니다. +2. 실제 브라우저 도구가 있으면 /browse/ HTML GET 주소를 열고 실제 상품·매장·재고를 읽습니다. Work의 내장 브라우저도 사용 가능합니다. +3. 웹 읽기(open/WebFetch)만 가능하면 같은 HTML GET을 실행합니다. 접근 오류 시 문서의 링크를 따라갈 수 있으나 성공을 보장하지 않습니다. Crawled가 과거인 수집본을 현재 재고로 단정하지 마세요. +4. 위 세 수단이 없거나 모두 실패하면 실제 조회 제약과 오류를 알립니다. 셸이 있어도 설치 없는 웹 조회가 우선이며 터미널·셸로 우회하지 않습니다. 셸 DNS 오류를 웹 GET 실패로 확대하거나 실행하지 않은 요청의 실패를 만들지 마세요. https://mcp.aka.page/browse/daiso/products/%EC%88%98%EB%82%A9%EB%B0%95%EC%8A%A4/pageSize/3 diff --git a/src/pages/agentStart.ts b/src/pages/agentStart.ts index db41fca4..c21866fd 100644 --- a/src/pages/agentStart.ts +++ b/src/pages/agentStart.ts @@ -1,19 +1,20 @@ /** 검색·읽기 사본의 안내 세대를 구분하는 고정 버전 */ -export const AGENT_GUIDE_VERSION = '2026-10-22.1'; -export const AGENT_GUIDE_UPDATED = '2026-10-22'; +export const AGENT_GUIDE_VERSION = '2026-10-26.1'; +export const AGENT_GUIDE_UPDATED = '2026-10-26'; export const AGENT_GUIDE_STAMP = `안내 버전: ${AGENT_GUIDE_VERSION} · 갱신일: ${AGENT_GUIDE_UPDATED}`; export const AGENT_GUIDE_PATH = `/guide/${AGENT_GUIDE_VERSION}`; export const WEB_SEARCH_DESCRIPTION = - 'ChatGPT 웹 읽기로 설치·API 키·MCP 연결 설정 없이 바로 상품을 조회하세요. open/WebFetch로 /browse/daiso/products/검색어를 엽니다. 쿼리 문자열과 format 지정은 필요 없습니다. 다이소·편의점 재고와 영화·생활 정보도 지원합니다.'; + 'ChatGPT 웹 읽기로 설치·API 키·MCP 연결 설정 없이 바로 상품을 조회하세요. 사용 가능한 실제 브라우저 도구가 있으면 /browse/daiso/products/검색어를 열고, 없으면 open/WebFetch를 사용합니다. 쿼리 문자열과 format 지정은 필요 없습니다. 다이소·편의점 재고와 영화·생활 정보도 지원합니다.'; /** 웹 읽기 환경에서 쿼리 없는 HTML 경로를 우선합니다. */ export const WEB_READ_RULE = - '웹 읽기 전용 에이전트는 /browse/ 경로를 사용하세요. 상품 검색·상세·매장·재고·후속 조회가 모두 자동 HTML입니다. format이나 쿼리 문자열은 필요 없습니다. JSON API 주소를 웹 읽기로 열거나 셸로 우회하지 마세요. 접근 오류만으로 서비스 장애·로그인 필요·이 환경에서 실행할 수 없다고 단정하지 마세요.'; + '웹 조회는 /browse/ 경로를 사용하세요. Work 등에서 실제 브라우저 도구가 있으면 그 도구로 열고, 없으면 open/WebFetch를 사용하세요. 웹 읽기의 접근 오류가 나면 같은 주소를 반복하기 전에 사용 가능한 브라우저로 전환하세요. Crawled가 과거인 수집본을 현재 재고로 단정하지 마세요. 상품 검색·상세·매장·재고·후속 조회가 모두 자동 HTML입니다. format이나 쿼리 문자열은 필요 없습니다. JSON API 주소를 웹 읽기로 열거나 셸로 우회하지 마세요. 접근 오류만으로 서비스 장애·로그인 필요·이 환경에서 실행할 수 없다고 단정하지 마세요.'; /** 셸 존재를 네트워크 접근 가능 여부로 오해하지 않는 실행 순서 */ export const ROUTE_SELECTION_TEXT = `1. 연결된 MCP 도구가 있으면 search_products {"service":"daiso","query":"수납박스","pageSize":3}를 호출합니다. -2. 웹 읽기(open/WebFetch)가 가능하면 /browse/ HTML GET 주소를 바로 엽니다. 셸이 있어도 연결된 MCP 도구가 없으면 이 경로가 우선입니다. 웹 검색(search)은 URL 열기(open)를 대신하지 않습니다. -3. 위 두 수단이 없을 때만 사용 가능한 조회 도구가 없음을 알립니다. 터미널·셸 호출로 우회하지 않습니다. 셸 DNS 오류를 웹 GET 실패나 서버 장애로 확대하지 말고 실제로 실행하지 않은 요청의 실패를 만들지 마세요.`; +2. 실제 브라우저 도구가 있으면 /browse/ HTML GET 주소를 열고 페이지의 실제 상품·매장·재고를 읽습니다. Work의 내장 브라우저도 이 경로를 사용합니다. +3. 웹 읽기(open/WebFetch)만 가능하면 같은 HTML GET을 실행합니다. 접근 오류가 나면 문서의 링크를 따라갈 수 있으나 성공을 보장하지 않습니다. Crawled가 과거인 수집본은 현재 재고 확인으로 쓰지 않습니다. +4. 위 세 수단이 없거나 모두 실패하면 실제 조회 제약과 오류를 알립니다. 셸이 있어도 설치 없는 웹 조회가 우선입니다. 터미널·셸 호출로 우회하지 않습니다. 셸 DNS 오류를 웹 GET 실패나 서버 장애로 확대하지 말고 실제로 실행하지 않은 요청의 실패를 만들지 마세요.`; /** 유사 상품의 웹 검색 이미지를 실제 상품 사진으로 오인하지 않도록 제한합니다. */ export const IMAGE_SOURCE_RULE = @@ -21,7 +22,7 @@ export const IMAGE_SOURCE_RULE = /** 실행 기록 없이 성공·실패나 HTTP 상태를 단정하지 않도록 안내합니다. */ export const EXECUTION_EVIDENCE_RULE = - '실제로 실행한 도구·요청 URL·반환 데이터와 확인된 오류만 보고하세요. 실행하지 않은 웹 GET을 실패했다고 말하거나 검색 결과·문서 예제를 실제 조회 결과로 제시하지 마세요. 도구가 HTTP 상태를 제공하지 않으면 HTTP 200이라고 추정하지 마세요. REST GET 성공과 MCP 도구 호출 성공은 구분합니다.'; + '실제로 실행한 도구·요청 URL·반환 데이터와 확인된 오류만 보고하세요. 웹 읽기의 Crawled 시점과 HTML의 응답 생성 시각을 확인하고 오래된 수집본을 현재 재고로 단정하지 마세요. 응답 생성 시각은 원본 재고 갱신 시각이 아닙니다. 실행하지 않은 웹 GET을 실패했다고 말하거나 검색 결과·문서 예제를 실제 조회 결과로 제시하지 마세요. 도구가 HTTP 상태를 제공하지 않으면 HTTP 200이라고 추정하지 마세요. REST GET 성공과 MCP 도구 호출 성공은 구분합니다.'; /** 설치 없이 첫 실제 조회를 실행하는 에이전트 안내 */ export function buildAgentStartText(baseUrl: string): string { diff --git a/src/pages/apiResult.ts b/src/pages/apiResult.ts index 19e90793..2acb0fac 100644 --- a/src/pages/apiResult.ts +++ b/src/pages/apiResult.ts @@ -118,10 +118,12 @@ export const apiResultHtml: MiddlewareHandler<{ Bindings: AppBindings }> = async const inventoryGuide = japan ? '일본 매장 재고 미지원. 온라인 판매 가능 여부를 매장 재고로 해석하지 마세요.' : '매장 재고는 상품 ID와 매장명 또는 사용자 위치를 확인한 뒤 별도로 조회하세요.'; + const generatedAt = new Date().toISOString(); const html = `${title}: DAISO MCP

${title}

${escapeHtml(AGENT_GUIDE_STAMP)} · 현재 버전 안내 · 항상 최신 안내

${escapeHtml(WEB_READ_RULE)}

+

응답 생성 시각: (UTC). 원본 재고 갱신 시각을 뜻하지 않습니다.

요청: ${source} · HTTP ${c.res.status}

실제 API 조회 결과입니다. 오류를 상품 없음이나 재고 0으로 해석하지 마세요. ${inventoryGuide}

${escapeHtml(IMAGE_SOURCE_RULE)}

${escapeHtml(EXECUTION_EVIDENCE_RULE)}

diff --git a/tests/pages/htmlGuidance.test.ts b/tests/pages/htmlGuidance.test.ts index c7d8603a..1b1cbe11 100644 --- a/tests/pages/htmlGuidance.test.ts +++ b/tests/pages/htmlGuidance.test.ts @@ -4,7 +4,7 @@ import { buildAgentStartText } from '../../src/pages/agentStart.js'; import { generatePromptText } from '../../src/pages/prompt.js'; import app from '../../src/index.js'; -const rule = '웹 읽기 전용 에이전트는 /browse/ 경로를 사용하세요'; +const rule = '웹 조회는 /browse/ 경로를 사용하세요'; describe('웹 읽기 전용 에이전트 안내', () => { it('첫 화면과 llms 및 prompt 앞부분에서 HTML 필수 규칙을 발견한다', () => { const assets = buildDiscoveryAssets({ services: [], tools: [] }); diff --git a/tests/pages/latestGuide.test.ts b/tests/pages/latestGuide.test.ts index af8e6afc..fdb93d25 100644 --- a/tests/pages/latestGuide.test.ts +++ b/tests/pages/latestGuide.test.ts @@ -4,7 +4,7 @@ import app from '../../src/index.js'; import { buildDiscoveryAssets } from '../../src/pages/discovery.js'; import { apiResultHtml } from '../../src/pages/apiResult.js'; -const version = '2026-10-22.1'; +const version = '2026-10-26.1'; describe('최신 안내 경로와 원본 이미지', () => { it.each(['/guide/latest', '/guide/2026-10-21.2', '/guide/unknown'])('%s는 캐시 없이 현재 버전으로 연결한다', async path => { const r = await app.request(path); diff --git a/tests/pages/productCards.test.ts b/tests/pages/productCards.test.ts index 1472b80f..ac4550f9 100644 --- a/tests/pages/productCards.test.ts +++ b/tests/pages/productCards.test.ts @@ -51,8 +51,8 @@ describe('실제 상품 이미지 카드', () => { const assets = buildDiscoveryAssets({ services: [], tools: [] }); expect(assets['discovery.html']).toMatch(/name="description" content="ChatGPT.*browse\//); for (const text of [assets['discovery.html'], buildAgentStartText('https://example.com')]) { - expect(text).toContain('안내 버전: 2026-10-22.1'); - expect(text).toContain('갱신일: 2026-10-22'); + expect(text).toContain('안내 버전: 2026-10-26.1'); + expect(text).toContain('갱신일: 2026-10-26'); } }); }); diff --git a/tests/pages/webFirstGuidance.test.ts b/tests/pages/webFirstGuidance.test.ts index c5483aa1..24f03ae7 100644 --- a/tests/pages/webFirstGuidance.test.ts +++ b/tests/pages/webFirstGuidance.test.ts @@ -8,14 +8,22 @@ import { generatePromptText } from '../../src/pages/prompt.js'; const read = (path: string) => readFileSync(new URL(`../../${path}`, import.meta.url), 'utf8'); describe('무설치 경로 선택 회귀', () => { - it('셸이 있어도 연결된 MCP 다음에는 HTML GET을 선택한다', () => { + it('연결된 MCP 다음에는 실제 브라우저, 웹 읽기 순으로 HTML GET을 선택한다', () => { const assets = buildDiscoveryAssets({ services: [], tools: [] }); - for (const text of [buildAgentStartText('https://example.com'), assets['discovery.html'], read('README.md'), read('docs/ai-instruction.md'), read('skills/daiso-cli/SKILL.md')]) { + for (const text of [ + buildAgentStartText('https://example.com'), + assets['discovery.html'], + read('README.md'), + read('docs/ai-instruction.md'), + read('skills/daiso-cli/SKILL.md'), + ]) { const mcp = text.indexOf('1. 연결된 MCP 도구'); - const web = text.indexOf('2. 웹 읽기'); - const shell = text.indexOf('3. 위 두 수단이 없을 때만'); + const browser = text.indexOf('2. 실제 브라우저'); + const web = text.indexOf('3. 웹 읽기'); + const shell = text.indexOf('4. 위 세 수단이 없거나 모두 실패하면'); expect(mcp).toBeGreaterThanOrEqual(0); - expect(web).toBeGreaterThan(mcp); + expect(browser).toBeGreaterThan(mcp); + expect(web).toBeGreaterThan(browser); expect(shell).toBeGreaterThan(web); expect(text).toContain('셸이 있어도'); expect(text).toContain('DNS'); @@ -24,7 +32,12 @@ describe('무설치 경로 선택 회귀', () => { }); it('실행 안내는 deprecated MCP 이름을 새 호출로 권하지 않는다', () => { - for (const text of [buildAgentStartText('https://example.com'), generatePromptText('https://example.com'), read('docs/ai-instruction.md'), read('skills/daiso-cli/SKILL.md')]) { + for (const text of [ + buildAgentStartText('https://example.com'), + generatePromptText('https://example.com'), + read('docs/ai-instruction.md'), + read('skills/daiso-cli/SKILL.md'), + ]) { for (const group of COMPACT_CATALOG) { for (const route of group.routes) { if (route.legacy !== group.name) expect(text).not.toContain(route.legacy); @@ -37,7 +50,15 @@ describe('무설치 경로 선택 회귀', () => { it('모든 진입 안내에서 해당 응답의 이미지와 실제 실행 근거만 사용한다', () => { const assets = buildDiscoveryAssets({ services: [], tools: [] }); - for (const text of [assets['discovery.html'], buildAgentStartText('https://example.com'), generatePromptText('https://example.com'), read('README.md'), read('docs/ai-instruction.md'), read('skills/daiso-cli/SKILL.md'), read('docs/gpts-maintenance.md')]) { + for (const text of [ + assets['discovery.html'], + buildAgentStartText('https://example.com'), + generatePromptText('https://example.com'), + read('README.md'), + read('docs/ai-instruction.md'), + read('skills/daiso-cli/SKILL.md'), + read('docs/gpts-maintenance.md'), + ]) { expect(text).toContain('imageUrl'); expect(text).toContain('웹 검색 이미지'); expect(text).toContain('대체하지'); diff --git a/tests/pages/work-browser-recovery.test.ts b/tests/pages/work-browser-recovery.test.ts new file mode 100644 index 00000000..abf18762 --- /dev/null +++ b/tests/pages/work-browser-recovery.test.ts @@ -0,0 +1,50 @@ +import { readFileSync } from 'node:fs'; +import { Hono } from 'hono'; +import { expect, it, vi } from 'vitest'; +import { apiResultHtml } from '../../src/pages/apiResult.js'; +import { buildDiscoveryAssets } from '../../src/pages/discovery.js'; +import { buildAgentStartText } from '../../src/pages/agentStart.js'; +import { generatePromptText } from '../../src/pages/prompt.js'; + +it('모든 진입 안내는 MCP 다음 실제 브라우저를 선택하고 웹 읽기의 오래된 결과를 구분한다', () => { + const assets = buildDiscoveryAssets({ services: [], tools: [] }); + const docs = [ + 'README.md', + 'docs/ai-instruction.md', + 'docs/client-guide.md', + 'docs/service-reference.md', + 'docs/web-discovery.md', + 'skills/daiso-cli/SKILL.md', + ].map((p) => readFileSync(p, 'utf8')); + for (const text of [ + ...docs, + assets['discovery.html'], + assets['llms.txt'], + buildAgentStartText('https://example.com'), + generatePromptText('https://example.com'), + ]) { + expect(text).toContain('브라우저'); + expect(text).toContain('Crawled'); + expect(text).toContain('현재 재고'); + } +}); + +it('HTML의 응답 생성 시각을 표시하면서 원본 갱신 시각이나 JSON 계약으로 혼동하지 않는다', async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-10-26T09:20:00.000Z')); + try { + const app = new Hono(); + app.use('/api/*', apiResultHtml); + const payload = { success: true, data: { quantity: 2 } }; + app.get('/api/test', (c) => c.json(payload)); + const response = await app.request('/api/test?format=html'); + expect(response.headers.get('Cache-Control')).toBe('no-store'); + const html = await response.text(); + expect(html).toContain('