apick-mcp 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # 변경 기록
2
2
 
3
+ ## 3.6.0 — 2026-09-30
4
+
5
+ - 간편인증 데이터 조회 2종(현금영수증 소득공제 내역, 국세 신고내역 조회)의 접수·결과 Tool 4개와 유튜브 영상 정보·썸네일·자막 목록·자막 다운로드 Tool 4개를 추가했습니다. 전체 114개, Business 29개, Web 17개, 상태 변경 Tool 28개입니다. 원격 서버에는 이미 배포돼 있습니다.
6
+ - Add four request/result tools for two simple-auth data products (cash receipt deductions, tax return history) and four YouTube tools (metadata, thumbnail, subtitle list, subtitle download). 114 total, 29 Business, 17 Web, 28 non-read-only. Already live on the remote server.
7
+ - 새 간편인증 상품은 인증 발송 성공 시 20P, 최초 결과 60P × (1 + 0.5 × (연수 - 1))로 과금되며 대기·유효기간 내 재조회는 무료입니다.
8
+ - The new simple-auth products charge 20P on successful authentication dispatch and 60P × (1 + 0.5 × (years - 1)) for the first result; waiting polls and repeat reads are free.
9
+
3
10
  ## 3.5.0 — 2026-09-28
4
11
 
5
12
  - 간편인증 데이터 조회 5종의 접수·결과 Tool 10개 계약을 추가했습니다. 전체 106개, Business 25개, 상태 변경 Tool 24개입니다. 원격 서버의 대응 패치 배포가 필요합니다.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 APICK
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 APICK
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  <img src="https://raw.githubusercontent.com/lead788/apick-mcp/main/assets/logo-400.png" alt="APICK" width="88" height="88">
4
4
 
5
- # APICK MCP — 106 Korean Data, AI, Image & Video Tools
5
+ # APICK MCP — 114 Korean Data, AI, Image & Video Tools
6
6
 
7
- > 3.5.0 카탈로그: 106개 Tool(Business 25개). 신규 10개 Tool을 사용하려면 대응하는 원격 서버 버전이 필요합니다. 브릿지 설치만으로 활성화되지 않으며 실제 사용 가능 목록은 연결한 서버의 `tools/list`로 확인하세요.
8
- > Catalog for 3.5.0: 106 tools (25 Business). The 10 new tools require the matching remote-server deployment; installing this bridge alone does not enable them. Check the connected server’s `tools/list` for availability.
7
+ > 3.6.0 카탈로그: 114개 Tool(Business 29개, Web 17개). 새 Tool은 원격 서버에 이미 배포돼 있으며, 실제 사용 가능 목록은 연결한 서버의 `tools/list`로 확인하세요.
8
+ > Catalog for 3.6.0: 114 tools (29 Business, 17 Web). The new tools are already live on the remote server; check the connected server’s `tools/list` for availability.
9
9
 
10
10
  **Korean business registry, ID verification, OCR, parcel tracking, file conversion, web intelligence and LLM — as MCP tools for any AI agent.**
11
11
 
@@ -25,9 +25,9 @@
25
25
 
26
26
  ## What is this? / 이게 뭔가요?
27
27
 
28
- **EN** — APICK is a Korean data and AI API platform. This MCP server exposes **106 tools** for Korean business data, identity verification, OCR, parcel tracking, image and video generation, file conversion, web intelligence, and LLM calls.
28
+ **EN** — APICK is a Korean data and AI API platform. This MCP server exposes **114 tools** for Korean business data, identity verification, OCR, parcel tracking, image and video generation, file conversion, web intelligence, and LLM calls.
29
29
 
30
- **KO** — 에이픽(APICK)은 대한민국 데이터·AI API 플랫폼입니다. 이 MCP 서버는 **Tool 106개**로 사업자 조회, 신분증 진위확인, 택배 배송조회, OCR, 이미지·영상 생성, 파일 변환, 웹 검색과 LLM 호출을 **인증키 하나로** 제공합니다.
30
+ **KO** — 에이픽(APICK)은 대한민국 데이터·AI API 플랫폼입니다. 이 MCP 서버는 **Tool 114개**로 사업자 조회, 신분증 진위확인, 택배 배송조회, OCR, 이미지·영상 생성, 파일 변환, 웹 검색과 LLM 호출을 **인증키 하나로** 제공합니다.
31
31
 
32
32
  **The server is hosted by APICK. Nothing to install, build, or keep running.**
33
33
  **서버는 에이픽이 운영합니다. 설치할 것도, 띄워둘 것도 없습니다.**
@@ -45,8 +45,8 @@ https://apick.app/mcp/all
45
45
  Sign up at **[apick.app](https://apick.app)** and copy your license key from the dashboard. New accounts get **1,000 free points**.
46
46
  **[apick.app](https://apick.app)** 에서 가입하고 대시보드에서 인증키를 복사하세요. 신규 가입 시 **1,000포인트 무료**.
47
47
 
48
- > `tools/list` works **without** a key — a client can connect and discover all 106 tools before you sign up. Only `tools/call` validates the key and allowed IP.
49
- > `tools/list`는 **인증 없이** 동작합니다. 가입 전에도 클라이언트가 연결해 106개 Tool을 확인할 수 있고, 키와 허용 IP는 `tools/call`부터 검증합니다.
48
+ > `tools/list` works **without** a key — a client can connect and discover all 114 tools before you sign up. Only `tools/call` validates the key and allowed IP.
49
+ > `tools/list`는 **인증 없이** 동작합니다. 가입 전에도 클라이언트가 연결해 114개 Tool을 확인할 수 있고, 키와 허용 IP는 `tools/call`부터 검증합니다.
50
50
 
51
51
  Leave the allowed-IP list blank for unrestricted access. To restrict access, register the public IPv4 address seen by APICK as an exact address or CIDR such as `/32`. Changes apply immediately with no separate synchronization.
52
52
  마이페이지의 허용 IP가 공란이면 제한 없이 사용할 수 있습니다. 제한하려면 APICK에 도착하는 공인 IPv4를 단일 주소 또는 CIDR(`/32` 등)로 등록하세요. 저장 즉시 반영되며 별도 동기화는 필요하지 않습니다.
@@ -152,11 +152,11 @@ Connect to `all` for everything, or to one server to keep the tool list short an
152
152
 
153
153
  | Server 서버 | Endpoint | Tools | Coverage 범위 |
154
154
  | --- | --- | --- | --- |
155
- | **All 통합** | `https://apick.app/mcp/all` | **106** | 아래 전부 |
156
- | [Business 사업자·커머스](TOOLS.md#business) | `https://apick.app/mcp/business` | 25 | 사업자·법인 조회, 택배 배송조회, 부동산 실거래가, 차량 이력, 유효성 검사 |
155
+ | **All 통합** | `https://apick.app/mcp/all` | **114** | 아래 전부 |
156
+ | [Business 사업자·커머스](TOOLS.md#business) | `https://apick.app/mcp/business` | 29 | 사업자·법인 조회, 택배 배송조회, 부동산 실거래가, 차량 이력, 유효성 검사 |
157
157
  | [Identity 신분증](TOOLS.md#identity) | `https://apick.app/mcp/identity` | 16 | 주민등록증·운전면허증·여권·외국인등록증 진위확인, 실명확인, 개인정보 마스킹 |
158
158
  | [Convert 파일변환](TOOLS.md#convert) | `https://apick.app/mcp/convert` | 22 | PDF·DOCX·엑셀 변환, STT, 비동기 TTS, 워터마크 |
159
- | [Web 웹·검색](TOOLS.md#web) | `https://apick.app/mcp/web` | 13 | 도메인·IP·WHOIS, 웹페이지 수집, 구글 검색, 유튜브 |
159
+ | [Web 웹·검색](TOOLS.md#web) | `https://apick.app/mcp/web` | 17 | 도메인·IP·WHOIS, 웹페이지 수집, 구글 검색, 유튜브 |
160
160
  | [Vision 이미지·영상](TOOLS.md#vision) | `https://apick.app/mcp/vision` | 6 | 얼굴 검출, 이미지 유사도, 유해이미지 판별, 영상 추출 |
161
161
  | [OCR 문자인식](TOOLS.md#ocr) | `https://apick.app/mcp/ocr` | 6 | 이미지 텍스트 추출, 신분증 항목 추출 |
162
162
  | [AI · LLM](TOOLS.md#ai) | `https://apick.app/mcp/ai` | 15 | LLM 챗, 텍스트 요약·교정, 이미지 생성·편집·대량 작업, 비동기 영상 생성 |
@@ -164,13 +164,13 @@ Connect to `all` for everything, or to one server to keep the tool list short an
164
164
 
165
165
  ### Every tool / 전체 Tool
166
166
 
167
- **[→ TOOLS.md](TOOLS.md)** — all 106 tools with parameters, types, and copy-paste JSON-RPC examples.
168
- **[→ TOOLS.md](TOOLS.md)** — 106개 전체를 파라미터·타입·호출 예시까지 정리했습니다.
167
+ **[→ TOOLS.md](TOOLS.md)** — all 114 tools with parameters, types, and copy-paste JSON-RPC examples.
168
+ **[→ TOOLS.md](TOOLS.md)** — 114개 전체를 파라미터·타입·호출 예시까지 정리했습니다.
169
169
 
170
170
  <details>
171
171
  <summary><b>Tool names at a glance / Tool 이름 한눈에 보기</b></summary>
172
172
 
173
- **Business** `biz_detail` `venture_biz_info` `land_rt_price` `req_pccc` `get_pccc` `req_employment` `get_employment` `req_personal_income` `get_personal_income` `req_nps_join_history` `get_nps_join_history` `req_driving_license` `get_driving_license` `req_health_checkup` `get_health_checkup` `get_car_flooding` `get_car_scrap` `parcel_tracking` `parcel_tracking_auto` `check_email_valid` `check_phone_valid` `check_spam_number` `holiday_info` `search_juso` `info`
173
+ **Business** `biz_detail` `venture_biz_info` `land_rt_price` `req_pccc` `get_pccc` `req_employment` `get_employment` `req_personal_income` `get_personal_income` `req_nps_join_history` `get_nps_join_history` `req_driving_license` `get_driving_license` `req_health_checkup` `get_health_checkup` `req_cash_receipt_deduction` `get_cash_receipt_deduction` `req_tax_return_history` `get_tax_return_history` `get_car_flooding` `get_car_scrap` `parcel_tracking` `parcel_tracking_auto` `check_email_valid` `check_phone_valid` `check_spam_number` `holiday_info` `search_juso` `info`
174
174
 
175
175
  **Identity** `identi_card1`–`identi_card5` `identi_card_image1`–`identi_card_image5` `name_rrn_auth` `hide_rrn` `identity_document_id_card` `identity_document_driver_license` `identity_document_passport` `identity_document_residence_card`
176
176
 
@@ -178,7 +178,7 @@ Connect to `all` for everything, or to one server to keep the tool list short an
178
178
 
179
179
  **Finance** `transfer_1won` `account_realname` `bank_code`
180
180
 
181
- **Web** `nslookup` `reverse_ip` `location` `ip_history` `whois` `url_html` `url_screenshot` `url_similarity` `google_search` `google_image_search` `google_lens_search` `crawl_youtube` `download_youtube_video`
181
+ **Web** `nslookup` `reverse_ip` `location` `ip_history` `whois` `url_html` `url_screenshot` `url_similarity` `google_search` `google_image_search` `google_lens_search` `crawl_youtube` `download_youtube_video` `youtube_metadata` `youtube_thumbnail` `youtube_subtitle_list` `youtube_subtitle`
182
182
 
183
183
  **Convert** `stt` `tts_jobs_create` `tts_jobs_status` `tts_jobs_cancel` `tts_jobs_result` `tts_jobs_subtitles` `tts_jobs_quality` `tts_jobs_retry` `tts_jobs_candidate_audio` `voice_change` `face_blur` `pdf_to_docx` `pdf_to_image` `pdf_merge` `html_to_pdf` `docx_to_pdf` `json_to_excel` `base64_to_image` `set_watermark` `get_watermark` `draw_watermark_pdf` `draw_watermark_image`
184
184
 
@@ -283,7 +283,7 @@ Seedance 참조 소재 모드는 지원 버전에서 참조 이미지·영상·
283
283
  | **Transport** | Streamable HTTP — one endpoint per server, JSON-RPC 2.0 over HTTPS POST, stateless | 서버당 단일 엔드포인트, HTTPS POST로 JSON-RPC 2.0, 세션 없이 요청 단위 |
284
284
  | **Protocol** | MCP `2026-07-28`, auto-compatible with earlier client versions | MCP `2026-07-28` 기본, 이전 규격 클라이언트 자동 호환 |
285
285
  | **Discovery** | `tools/list` returns every tool with JSON Schema, description and live price — no key needed | `tools/list`가 스키마·설명·실시간 단가를 반환, 인증 불필요 |
286
- | **Annotations** | Every tool declares `title`, `readOnlyHint`, `openWorldHint`. 24 of 106 are not read-only | 전 Tool이 `title`·`readOnlyHint`·`openWorldHint` 선언. 106개 중 상태 변경 Tool은 24개 |
286
+ | **Annotations** | Every tool declares `title`, `readOnlyHint`, `openWorldHint`. 28 of 114 are not read-only | 전 Tool이 `title`·`readOnlyHint`·`openWorldHint` 선언. 114개 중 상태 변경 Tool은 28개 |
287
287
  | **Results** | Text (JSON) + `structuredContent`. Images as image content; files up to 8MB as base64 | 텍스트(JSON)와 `structuredContent` 동시 반환. 이미지는 이미지 콘텐츠, 8MB 이하 파일은 base64 |
288
288
  | **File input** | File-taking tools accept a public `https` URL (`image_url`, `pdf_url`, …) — APICK downloads and processes it | 파일 Tool은 공개 `https` URL을 받습니다. 에이픽 서버가 내려받아 처리합니다 |
289
289
  | **Errors** | Delivered via `isError`; identity masking also preserves `structuredContent.error_code` | `isError`로 전달되며 신분증 마스킹은 `structuredContent.error_code`도 보존합니다 |
@@ -302,12 +302,14 @@ Call the request tool on Business or All, ask the user to approve on their phone
302
302
  | 국민연금 가입내역 | `requestNpsJoinHistory` / `getNpsJoinHistory` | `req_nps_join_history` / `get_nps_join_history` |
303
303
  | 운전면허 조회 | `requestDrivingLicense` / `getDrivingLicense` | `req_driving_license` / `get_driving_license` |
304
304
  | 국가 건강검진 결과 | `requestHealthCheckup` / `getHealthCheckup` | `req_health_checkup` / `get_health_checkup` |
305
+ | 현금영수증 소득공제 내역 | `requestCashReceiptDeduction` / `getCashReceiptDeduction` | `req_cash_receipt_deduction` / `get_cash_receipt_deduction` |
306
+ | 국세 신고내역 조회 | `requestTaxReturnHistory` / `getTaxReturnHistory` | `req_tax_return_history` / `get_tax_return_history` |
305
307
 
306
308
  공통 입력은 SDK와 같은 `name`, `birthDate`(YYYYMMDD), `phone`, `authProvider`입니다. 상품별 기간 옵션과 응답 상태·오류는 [Tool 계약](TOOLS.md#simple-auth-data)을 확인하세요. 최초 결과 반환 시 조회 범위에 따라 과금하며, 대기 중 조회와 유효기간 내 재조회는 무료입니다.
307
309
 
308
310
  Common inputs match the SDK: `name`, `birthDate`, `phone`, and `authProvider`. The first result delivery is billed by query scope; waiting polls and repeat reads within the result lifetime are free. See the [tool contract](TOOLS.md#simple-auth-data) for options and response fields.
309
311
 
310
- **PCCC 비교:** 승인 흐름은 같지만 `req_pccc`는 `birthday`·`provider`, `get_pccc`는 `tx_id`를 사용하며 결과 재조회도 과금됩니다. 신규 5개 상품의 입력 이름이나 무료 재조회 정책을 PCCC에 적용하지 마세요.
312
+ **PCCC 비교:** 승인 흐름은 같지만 `req_pccc`는 `birthday`·`provider`, `get_pccc`는 `tx_id`를 사용하며 결과 재조회도 과금됩니다. 간편인증 데이터 조회 7개 상품의 입력 이름이나 무료 재조회 정책을 PCCC에 적용하지 마세요.
311
313
 
312
314
  **PCCC comparison:** The approval flow is the same, but PCCC retains `birthday`/`provider` and `tx_id`, and charges for repeated result reads. Its contract is unchanged.
313
315
 
@@ -315,8 +317,8 @@ Common inputs match the SDK: `name`, `birthDate`, `phone`, and `authProvider`. T
315
317
 
316
318
  ### Tools with side effects / 부작용이 있는 Tool
317
319
 
318
- 82 of 106 tools are read-only. The other 24 change state, charge points, cancel work, or consume a result and carry `readOnlyHint: false` so your client can require approval:
319
- 106개 중 82개는 조회입니다. 나머지 24개는 과금·취소·결과 생성 등 상태를 바꾸므로 `readOnlyHint: false`가 붙습니다.
320
+ 86 of 114 tools are read-only. The other 28 change state, charge points, cancel work, or consume a result and carry `readOnlyHint: false` so your client can require approval:
321
+ 114개 중 86개는 조회입니다. 나머지 28개는 과금·취소·결과 생성 등 상태를 바꾸므로 `readOnlyHint: false`가 붙습니다.
320
322
 
321
323
  | Tool | What it does / 하는 일 |
322
324
  | --- | --- |
@@ -330,6 +332,10 @@ Common inputs match the SDK: `name`, `birthDate`, `phone`, and `authProvider`. T
330
332
  | `get_driving_license` | Collects and bills the first result; repeat reads are free · 결과 수집·최초 반환 과금, 재조회 무료 |
331
333
  | `req_health_checkup` | Requests phone approval and charges acceptance · 국가 건강검진 결과 인증 요청·접수 과금 |
332
334
  | `get_health_checkup` | Collects and bills the first result; repeat reads are free · 결과 수집·최초 반환 과금, 재조회 무료 |
335
+ | `req_cash_receipt_deduction` | Requests phone approval and charges acceptance · 현금영수증 소득공제 내역 인증 요청·접수 과금 |
336
+ | `get_cash_receipt_deduction` | Collects and bills the first result; repeat reads are free · 결과 수집·최초 반환 과금, 재조회 무료 |
337
+ | `req_tax_return_history` | Requests phone approval and charges acceptance · 국세 신고내역 조회 인증 요청·접수 과금 |
338
+ | `get_tax_return_history` | Collects and bills the first result; repeat reads are free · 결과 수집·최초 반환 과금, 재조회 무료 |
333
339
  | `image_generate` / `image_edit` / `image_batch_create` | Creates images and charges points · 이미지 생성·편집·작업 접수 과금 |
334
340
  | `tts_jobs_retry` | Resumes a TTS job · TTS 작업 상태 변경 |
335
341
  | `transfer_1won` | Deposits 1 KRW into a bank account · 실제로 1원을 입금합니다 |
package/TOOLS.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # APICK MCP — Full Tool Catalog / 전체 Tool 목록
2
2
 
3
- **106 tools** across **8 domain servers**, plus the combined `all` server.
4
- **Tool 106개**, 분야별 서버 8개와 통합 서버 `all`.
3
+ **114 tools** across **8 domain servers**, plus the combined `all` server.
4
+ **Tool 114개**, 분야별 서버 8개와 통합 서버 `all`.
5
5
 
6
- > 3.5.0 카탈로그: 106개 Tool(Business 25개). 신규 10개 Tool을 사용하려면 대응하는 원격 서버 버전이 필요합니다. 브릿지 설치만으로 활성화되지 않으며 실제 사용 가능 목록은 연결한 서버의 `tools/list`로 확인하세요.
7
- > Catalog for 3.5.0: 106 tools (25 Business). The 10 new tools require the matching remote-server deployment; installing this bridge alone does not enable them. Check the connected server’s `tools/list` for availability.
6
+ > 3.6.0 카탈로그: 114개 Tool(Business 29개, Web 17개). 새 Tool은 원격 서버에 이미 배포돼 있으며, 실제 사용 가능 목록은 연결한 서버의 `tools/list`로 확인하세요.
7
+ > Catalog for 3.6.0: 114 tools (29 Business, 17 Web). The new tools are already live on the remote server; check the connected server’s `tools/list` for availability.
8
8
 
9
9
 
10
10
  Official site 공식 사이트: **<https://apick.app>** · Docs 연동 가이드: **<https://apick.app/dev_guide/mcp>**
@@ -16,19 +16,19 @@ Endpoint pattern: `https://apick.app/mcp/{server}` — connect to `all` for ever
16
16
 
17
17
  | Server 서버 | Endpoint | Tools | Coverage 범위 |
18
18
  | --- | --- | --- | --- |
19
- | [Business & Commerce · 사업자 · 커머스](#business) | `/mcp/business` | 25 | 사업자·법인 조회, 택배 배송조회, 부동산 실거래가, 차량 이력, 유효성 검사. |
19
+ | [Business & Commerce · 사업자 · 커머스](#business) | `/mcp/business` | 29 | 사업자·법인 조회, 택배 배송조회, 부동산 실거래가, 차량 이력, 유효성 검사. |
20
20
  | [Identity Verification · 신분증 진위확인 · 마스킹](#identity) | `/mcp/identity` | 16 | 주민등록증·운전면허증·여권·외국인등록증 진위확인, 실명확인, 개인정보 마스킹. |
21
21
  | [OCR · OCR 문자인식](#ocr) | `/mcp/ocr` | 6 | 이미지 텍스트 추출과 신분증 항목 추출. |
22
22
  | [Finance · 금융 · 계좌확인](#finance) | `/mcp/finance` | 3 | 계좌 예금주 실명조회와 1원 인증. |
23
- | [Web & Search · 웹 · 검색](#web) | `/mcp/web` | 13 | 도메인·IP 조회, WHOIS, 웹페이지 수집, 구글 검색, 유튜브. |
23
+ | [Web & Search · 웹 · 검색](#web) | `/mcp/web` | 17 | 도메인·IP 조회, WHOIS, 웹페이지 수집, 구글 검색, 유튜브. |
24
24
  | [File Conversion · 파일 변환 · 워터마크](#convert) | `/mcp/convert` | 22 | PDF·DOCX·엑셀 변환, 음성인식(STT), 비동기 TTS, 워터마크. |
25
25
  | [Vision · 이미지 · 영상 분석](#vision) | `/mcp/vision` | 6 | 얼굴 검출, 이미지 유사도, 유해이미지 판별, 영상 추출. |
26
26
  | [AI & LLM · AI · LLM](#ai) | `/mcp/ai` | 15 | LLM 챗, 텍스트 도구, 이미지 생성·편집·대량 작업, 비동기 영상 생성. |
27
- | **All 통합** | `/mcp/all` | **106** | 아래 전부 |
27
+ | **All 통합** | `/mcp/all` | **114** | 아래 전부 |
28
28
 
29
- <details><summary><b>All 106 tool names / 전체 Tool 이름</b></summary>
29
+ <details><summary><b>All 114 tool names / 전체 Tool 이름</b></summary>
30
30
 
31
- `biz_detail` · `venture_biz_info` · `land_rt_price` · `req_pccc` · `get_pccc` · `req_employment` · `get_employment` · `req_personal_income` · `get_personal_income` · `req_nps_join_history` · `get_nps_join_history` · `req_driving_license` · `get_driving_license` · `req_health_checkup` · `get_health_checkup` · `get_car_flooding` · `get_car_scrap` · `parcel_tracking` · `parcel_tracking_auto` · `check_email_valid` · `check_phone_valid` · `check_spam_number` · `holiday_info` · `search_juso` · `info`
31
+ `biz_detail` · `venture_biz_info` · `land_rt_price` · `req_pccc` · `get_pccc` · `req_employment` · `get_employment` · `req_personal_income` · `get_personal_income` · `req_nps_join_history` · `get_nps_join_history` · `req_driving_license` · `get_driving_license` · `req_health_checkup` · `get_health_checkup` · `req_cash_receipt_deduction` · `get_cash_receipt_deduction` · `req_tax_return_history` · `get_tax_return_history` · `get_car_flooding` · `get_car_scrap` · `parcel_tracking` · `parcel_tracking_auto` · `check_email_valid` · `check_phone_valid` · `check_spam_number` · `holiday_info` · `search_juso` · `info`
32
32
 
33
33
  `identi_card1` · `identi_card2` · `identi_card3` · `identi_card4` · `identi_card5` · `identi_card_image1` · `identi_card_image2` · `identi_card_image3` · `identi_card_image4` · `identi_card_image5` · `name_rrn_auth` · `hide_rrn` · `identity_document_residence_card` · `identity_document_passport` · `identity_document_id_card` · `identity_document_driver_license`
34
34
 
@@ -36,7 +36,7 @@ Endpoint pattern: `https://apick.app/mcp/{server}` — connect to `all` for ever
36
36
 
37
37
  `transfer_1won` · `account_realname` · `bank_code`
38
38
 
39
- `nslookup` · `reverse_ip` · `location` · `ip_history` · `whois` · `url_html` · `url_screenshot` · `url_similarity` · `google_search` · `google_image_search` · `google_lens_search` · `crawl_youtube` · `download_youtube_video`
39
+ `nslookup` · `reverse_ip` · `location` · `ip_history` · `whois` · `url_html` · `url_screenshot` · `url_similarity` · `google_search` · `google_image_search` · `google_lens_search` · `crawl_youtube` · `download_youtube_video` · `youtube_metadata` · `youtube_thumbnail` · `youtube_subtitle_list` · `youtube_subtitle`
40
40
 
41
41
  `stt` · `tts_jobs_create` · `tts_jobs_status` · `tts_jobs_cancel` · `tts_jobs_result` · `tts_jobs_subtitles` · `tts_jobs_quality` · `tts_jobs_retry` · `tts_jobs_candidate_audio` · `voice_change` · `face_blur` · `pdf_to_docx` · `pdf_to_image` · `pdf_merge` · `html_to_pdf` · `docx_to_pdf` · `json_to_excel` · `base64_to_image` · `set_watermark` · `get_watermark` · `draw_watermark_pdf` · `draw_watermark_image`
42
42
 
@@ -52,7 +52,7 @@ Endpoint pattern: `https://apick.app/mcp/{server}` — connect to `all` for ever
52
52
 
53
53
  ## Business & Commerce · 사업자 · 커머스
54
54
 
55
- `https://apick.app/mcp/business` — 25 tools
55
+ `https://apick.app/mcp/business` — 29 tools
56
56
 
57
57
  Korean business registry, corporate credit, parcel tracking, real-estate prices, vehicle history, and input validation.
58
58
 
@@ -75,6 +75,10 @@ Korean business registry, corporate credit, parcel tracking, real-estate prices,
75
75
  | [`get_driving_license`](#get-driving-license) | 운전면허 조회 상태·결과 | `transactionId` |
76
76
  | [`req_health_checkup`](#req-health-checkup) | 국가 건강검진 결과 인증 요청 | `name`, `birthDate`, `phone`, `authProvider` |
77
77
  | [`get_health_checkup`](#get-health-checkup) | 국가 건강검진 결과 상태·결과 | `transactionId` |
78
+ | [`req_cash_receipt_deduction`](#req-cash-receipt-deduction) | 현금영수증 소득공제 내역 인증 요청 | `name`, `birthDate`, `phone`, `authProvider` |
79
+ | [`get_cash_receipt_deduction`](#get-cash-receipt-deduction) | 현금영수증 소득공제 내역 상태·결과 | `transactionId` |
80
+ | [`req_tax_return_history`](#req-tax-return-history) | 국세 신고내역 조회 인증 요청 | `name`, `birthDate`, `phone`, `authProvider` |
81
+ | [`get_tax_return_history`](#get-tax-return-history) | 국세 신고내역 조회 상태·결과 | `transactionId` |
78
82
  | [`get_car_flooding`](#get-car-flooding) | 차량 침수차 여부 조회 | `type`, `value` |
79
83
  | [`get_car_scrap`](#get-car-scrap) | 차량 폐차사고처리 여부 조회 | `type`, `value` |
80
84
  | [`parcel_tracking`](#parcel-tracking) | 택배 배송조회 | `carrier`, `trackingNumber` |
@@ -188,7 +192,7 @@ req_pccc Tool 호출로 받은 tx_id 를 입력해 처리 상태를 확인합니
188
192
 
189
193
  ### 간편인증 데이터 조회 공통 계약 / Shared data-lookup contract
190
194
 
191
- 아래 5개 상품은 **접수 → 휴대폰 승인 → 결과 조회** 순서로 호출합니다. 접수 전에 알림 발송·과금과 조회 항목을 사용자에게 확인하고, 승인은 사용자가 휴대폰에서 직접 수행합니다. 승인 대기 중 접수를 반복하지 않습니다.
195
+ 아래 7개 상품은 **접수 → 휴대폰 승인 → 결과 조회** 순서로 호출합니다. 접수 전에 알림 발송·과금과 조회 항목을 사용자에게 확인하고, 승인은 사용자가 휴대폰에서 직접 수행합니다. 승인 대기 중 접수를 반복하지 않습니다.
192
196
 
193
197
  Use request → user approval on the phone → result lookup. Confirm the requested data and acceptance charge before submitting. Never approve on the user’s behalf or automatically repeat the authentication request.
194
198
 
@@ -210,6 +214,8 @@ Result tools require the `transactionId` from the same product. Both stages are
210
214
  | 국민연금 가입내역 | from, to: YYYY-MM (각각 선택) | `result.npsJoinHistory` |
211
215
  | 운전면허 조회 | 없음 | `result.drivingLicense` |
212
216
  | 국가 건강검진 결과 | 없음 | `result.healthCheckup` |
217
+ | 현금영수증 소득공제 내역 | incomeYears: 정수 1~3 (선택, 기본 1) | `result.cashReceiptDeduction` |
218
+ | 국세 신고내역 조회 | years: 정수 1~10 (선택, 기본 1) | `result.taxReturnHistory` |
213
219
 
214
220
  **응답:** MCP의 `structuredContent`와 JSON 텍스트에는 REST 응답의 `data`가 그대로 담깁니다. `schemaVersion`, `transactionId`, `product`, `status`, `resultAvailable`, `charged`, `sources`, `message`, `success`를 확인합니다. 접수에는 `expiresAt`와 선택적 `approvals`, 수집 중에는 `progress`, 결과에는 `checkedAt`, `resultExpiresAt`, `result`가 포함될 수 있습니다. 실제 차감 포인트는 `_meta["app.apick/cost"]`입니다.
215
221
 
@@ -224,7 +230,7 @@ Result tools require the `transactionId` from the same product. Both stages are
224
230
 
225
231
  `errorCode`는 `RESULT_EXPIRED`, `AUTH_EXPIRED`, `AUTH_REJECTED`, `COLLECT_FAILED`를 포함합니다. 만료된 결과는 다시 조회할 수 없으며 새로운 인증 접수가 필요합니다. 업무 상태 오류는 `isError: false`인 정상 MCP 응답에도 담길 수 있으므로 `status`와 `errorCode`를 함께 검사하세요.
226
232
 
227
- 접수 시 정액 과금, 결과 최초 반환 시 조회 범위별 과금입니다. 승인 대기·수집 중 조회 및 `resultExpiresAt` 전 재조회는 무료입니다. **PCCC는 별도 계약**으로 `birthday`·`provider`·`tx_id`를 사용하고 결과 재조회도 과금됩니다.
233
+ 접수 시 정액 과금, 결과 최초 반환 시 조회 범위별 과금입니다. 현금영수증 소득공제 내역과 국세 신고내역은 인증 발송 성공 시 20P, 최초 결과 60P × (1 + 0.5 × (연수 - 1))입니다. 승인 대기·수집 중 조회 및 `resultExpiresAt` 전 재조회는 무료입니다. **PCCC는 별도 계약**으로 `birthday`·`provider`·`tx_id`를 사용하고 결과 재조회도 과금됩니다.
228
234
 
229
235
  Acceptance is billed separately; first result delivery is billed by scope. Waiting/collecting polls and repeat reads before `resultExpiresAt` are free. PCCC keeps its existing `birthday`/`provider`/`tx_id` contract and charges repeated result reads.
230
236
 
@@ -382,6 +388,54 @@ REST: `POST /rest/get_health_checkup` · SDK: `getHealthCheckup()`
382
388
 
383
389
  ---
384
390
 
391
+ <a id="req-cash-receipt-deduction"></a>
392
+
393
+ ### `req_cash_receipt_deduction` — 현금영수증 소득공제 내역 인증 요청
394
+
395
+ REST: `POST /rest/req_cash_receipt_deduction` · SDK: `requestCashReceiptDeduction()`
396
+
397
+ 필수: `name`, `birthDate`, `phone`, `authProvider`. 선택: incomeYears: 정수 1~3 (선택, 기본 1). 인증 발송 성공 시 20P이며 조회 범위와 무관합니다.
398
+
399
+ [공통 입력·응답·과금 계약](#simple-auth-data)을 따릅니다.
400
+
401
+ ---
402
+
403
+ <a id="get-cash-receipt-deduction"></a>
404
+
405
+ ### `get_cash_receipt_deduction` — 현금영수증 소득공제 내역 결과 조회
406
+
407
+ REST: `POST /rest/get_cash_receipt_deduction` · SDK: `getCashReceiptDeduction()`
408
+
409
+ 필수: `transactionId`. 성공 결과: `result.cashReceiptDeduction` (`조회연도`, `전체합계`, `연도별[].사용내역`). 최초 결과 60P × (1 + 0.5 × (incomeYears - 1)), 기본 60P.
410
+
411
+ [공통 입력·응답·과금 계약](#simple-auth-data)을 따릅니다.
412
+
413
+ ---
414
+
415
+ <a id="req-tax-return-history"></a>
416
+
417
+ ### `req_tax_return_history` — 국세 신고내역 조회 인증 요청
418
+
419
+ REST: `POST /rest/req_tax_return_history` · SDK: `requestTaxReturnHistory()`
420
+
421
+ 필수: `name`, `birthDate`, `phone`, `authProvider`. 선택: years: 정수 1~10 (선택, 기본 1). 인증 발송 성공 시 20P이며 조회 범위와 무관합니다.
422
+
423
+ [공통 입력·응답·과금 계약](#simple-auth-data)을 따릅니다.
424
+
425
+ ---
426
+
427
+ <a id="get-tax-return-history"></a>
428
+
429
+ ### `get_tax_return_history` — 국세 신고내역 조회 결과 조회
430
+
431
+ REST: `POST /rest/get_tax_return_history` · SDK: `getTaxReturnHistory()`
432
+
433
+ 필수: `transactionId`. 성공 결과: `result.taxReturnHistory` (`조회기간`, `합계`, `신고내역`). 최초 결과 60P × (1 + 0.5 × (years - 1)), 기본 60P.
434
+
435
+ [공통 입력·응답·과금 계약](#simple-auth-data)을 따릅니다.
436
+
437
+ ---
438
+
385
439
  <a id="get-car-flooding"></a>
386
440
 
387
441
  ### `get_car_flooding` — 차량 침수차 여부 조회
@@ -1129,7 +1183,7 @@ _No parameters. 파라미터 없음._
1129
1183
 
1130
1184
  ## Web & Search · 웹 · 검색
1131
1185
 
1132
- `https://apick.app/mcp/web` — 13 tools
1186
+ `https://apick.app/mcp/web` — 17 tools
1133
1187
 
1134
1188
  Domain and IP intelligence, WHOIS, page capture, Google search, and YouTube.
1135
1189
 
@@ -1150,6 +1204,10 @@ Domain and IP intelligence, WHOIS, page capture, Google search, and YouTube.
1150
1204
  | [`google_lens_search`](#google-lens-search) | 구글 렌즈 검색(이미지로 검색) | `image_url` |
1151
1205
  | [`crawl_youtube`](#crawl-youtube) | 유튜브 계정 정보 수집 | `user_id` |
1152
1206
  | [`download_youtube_video`](#download-youtube-video) | 유튜브 동영상 다운로드 | `url` |
1207
+ | [`youtube_metadata`](#youtube-metadata) | 유튜브 영상 정보 조회 | `url` |
1208
+ | [`youtube_thumbnail`](#youtube-thumbnail) | 유튜브 썸네일 다운로드 | `url` |
1209
+ | [`youtube_subtitle_list`](#youtube-subtitle-list) | 유튜브 자막 목록 조회 | `url` |
1210
+ | [`youtube_subtitle`](#youtube-subtitle) | 유튜브 자막 다운로드 | `url`, `lang` |
1153
1211
 
1154
1212
  <a id="nslookup"></a>
1155
1213
 
@@ -1390,6 +1448,89 @@ Download a publicly available YouTube video and return it as an MP4 file.
1390
1448
 
1391
1449
  ---
1392
1450
 
1451
+ <a id="youtube-metadata"></a>
1452
+
1453
+ ### `youtube_metadata` — 유튜브 영상 정보 조회
1454
+
1455
+ Look up metadata of a public YouTube video: title, channel, duration, views, likes, upload date, description, tags, chapters and thumbnails. 20 points per call.
1456
+
1457
+ 유튜브 공개 영상의 제목·채널·길이·조회수·좋아요·업로드일·설명·태그·챕터·썸네일 목록을 조회합니다. 호출당 20포인트.
1458
+
1459
+ > 읽기 전용 / read-only · 외부 데이터 조회 / external lookup · server `web`
1460
+
1461
+ | Parameter | Type | Required | Description 설명 |
1462
+ | --- | --- | --- | --- |
1463
+ | `url` | `string` | **필수 / required** | 유튜브 영상 URL 또는 11자리 영상 ID (watch·youtu.be·shorts 주소 지원) |
1464
+
1465
+ ```json
1466
+ {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"youtube_metadata","arguments":{"url":"<url>"}}}
1467
+ ```
1468
+
1469
+ ---
1470
+
1471
+ <a id="youtube-thumbnail"></a>
1472
+
1473
+ ### `youtube_thumbnail` — 유튜브 썸네일 다운로드
1474
+
1475
+ Download the largest thumbnail of a public YouTube video as a JPG image. 20 points per call.
1476
+
1477
+ 유튜브 공개 영상의 가장 큰 썸네일을 JPG 이미지로 내려받습니다. 호출당 20포인트.
1478
+
1479
+ > 읽기 전용 / read-only · 외부 데이터 조회 / external lookup · server `web`
1480
+
1481
+ | Parameter | Type | Required | Description 설명 |
1482
+ | --- | --- | --- | --- |
1483
+ | `url` | `string` | **필수 / required** | 유튜브 영상 URL 또는 11자리 영상 ID (watch·youtu.be·shorts 주소 지원) |
1484
+
1485
+ ```json
1486
+ {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"youtube_thumbnail","arguments":{"url":"<url>"}}}
1487
+ ```
1488
+
1489
+ ---
1490
+
1491
+ <a id="youtube-subtitle-list"></a>
1492
+
1493
+ ### `youtube_subtitle_list` — 유튜브 자막 목록 조회
1494
+
1495
+ List the manual and auto-generated subtitle languages available for a public YouTube video. 20 points per call.
1496
+
1497
+ 유튜브 공개 영상의 수동 자막과 자동 생성 자막 언어 목록을 조회합니다. 자동 번역 자막은 `translated: true`로 표시됩니다. 호출당 20포인트.
1498
+
1499
+ > 읽기 전용 / read-only · 외부 데이터 조회 / external lookup · server `web`
1500
+
1501
+ | Parameter | Type | Required | Description 설명 |
1502
+ | --- | --- | --- | --- |
1503
+ | `url` | `string` | **필수 / required** | 유튜브 영상 URL 또는 11자리 영상 ID (watch·youtu.be·shorts 주소 지원) |
1504
+
1505
+ ```json
1506
+ {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"youtube_subtitle_list","arguments":{"url":"<url>"}}}
1507
+ ```
1508
+
1509
+ ---
1510
+
1511
+ <a id="youtube-subtitle"></a>
1512
+
1513
+ ### `youtube_subtitle` — 유튜브 자막 다운로드
1514
+
1515
+ Download the subtitles of a public YouTube video in one language as VTT, SRT or plain text. Check available languages with `youtube_subtitle_list` first. 30 points per call.
1516
+
1517
+ 유튜브 공개 영상의 자막을 언어별로 VTT·SRT·텍스트 파일로 내려받습니다. 제공 언어는 `youtube_subtitle_list`로 먼저 확인하고, 영상 원어 자막 사용을 권장합니다. 호출당 30포인트.
1518
+
1519
+ > 읽기 전용 / read-only · 외부 데이터 조회 / external lookup · server `web`
1520
+
1521
+ | Parameter | Type | Required | Description 설명 |
1522
+ | --- | --- | --- | --- |
1523
+ | `url` | `string` | **필수 / required** | 유튜브 영상 URL 또는 11자리 영상 ID (watch·youtu.be·shorts 주소 지원) |
1524
+ | `lang` | `string` | **필수 / required** | 자막 언어 코드 (예: ko, en, en-US, en-orig). 자막 목록 조회 결과의 lang 값 |
1525
+ | `format` | `string` | 선택 / optional | `vtt`(기본)·`srt`·`txt`. `txt`는 시간 정보를 뺀 본문만 반환 |
1526
+ | `type` | `string` | 선택 / optional | `any`(기본: 수동 자막 우선)·`manual`·`auto` |
1527
+
1528
+ ```json
1529
+ {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"youtube_subtitle","arguments":{"url":"<url>","lang":"en","format":"srt"}}}
1530
+ ```
1531
+
1532
+ ---
1533
+
1393
1534
  <a id="convert"></a>
1394
1535
 
1395
1536
  ## File Conversion · 파일 변환 · 워터마크
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "apick-mcp",
3
- "version": "3.5.0",
4
- "description": "APICK MCP — 106 Korean data, AI, image & video tools for AI agents. 사업자조회·신분증·OCR·배송조회·이미지 생성·편집·영상 생성·파일변환·검색·LLM.",
3
+ "version": "3.6.0",
4
+ "description": "APICK MCP — 114 Korean data, AI, image & video tools for AI agents. 사업자조회·신분증·OCR·배송조회·이미지 생성·편집·영상 생성·파일변환·검색·LLM.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "APICK",
package/src/bridge.js CHANGED
@@ -1,175 +1,175 @@
1
- // stdio <-> Streamable HTTP bridge for the APICK MCP server.
2
- //
3
- // APICK serves MCP over Streamable HTTP at https://apick.app/mcp/{server}. Clients that
4
- // speak remote HTTP should connect there directly. This bridge exists for clients that
5
- // only launch local stdio processes.
6
- //
7
- // It is a pump, not a protocol implementation: every JSON-RPC message read from stdin is
8
- // forwarded verbatim, and every message the server returns is written verbatim to stdout.
9
- // Capability and version negotiation, tool schemas and errors are all handled end to end
10
- // by the client and the server.
11
-
12
- export const DEFAULT_ENDPOINT = 'https://apick.app/mcp';
13
-
14
- export const SERVERS = [
15
- 'all',
16
- 'business',
17
- 'identity',
18
- 'ocr',
19
- 'finance',
20
- 'web',
21
- 'convert',
22
- 'vision',
23
- 'ai'
24
- ];
25
-
26
- // A key must never reach stdout, stderr or an error message.
27
- export function redact(text, apiKey) {
28
- if (!text) return text;
29
- let out = String(text);
30
- if (apiKey) out = out.split(apiKey).join('***');
31
- return out.replace(/(Bearer\s+)[\w.\-]+/gi, '$1***');
32
- }
33
-
34
- // Streamable HTTP replies with either a single JSON body or an SSE stream carrying one or
35
- // more JSON-RPC messages. Both shapes reduce to "a list of messages".
36
- function parseEventStream(text) {
37
- const messages = [];
38
- for (const block of text.split(/\r?\n\r?\n/)) {
39
- const data = block
40
- .split(/\r?\n/)
41
- .filter((line) => line.startsWith('data:'))
42
- .map((line) => line.slice(5).trim())
43
- .join('\n');
44
- if (!data) continue;
45
- try {
46
- messages.push(JSON.parse(data));
47
- } catch {
48
- // Keep-alive or comment frame - nothing to forward.
49
- }
50
- }
51
- return messages;
52
- }
53
-
54
- export function createBridge(options = {}) {
55
- const server = options.server || 'all';
56
- const apiKey = options.apiKey || '';
57
- const endpoint = (options.endpoint || DEFAULT_ENDPOINT).replace(/\/+$/, '') + '/' + server;
58
- const fetchImpl = options.fetch || globalThis.fetch;
59
- const write = options.write || ((line) => process.stdout.write(line + '\n'));
60
- const warn = options.warn || ((line) => process.stderr.write(line + '\n'));
61
-
62
- // The gateway is stateless, but honour a session id and the negotiated protocol
63
- // version if the server ever starts sending them.
64
- let sessionId = null;
65
- let protocolVersion = null;
66
-
67
- function headers() {
68
- const h = {
69
- 'content-type': 'application/json',
70
- accept: 'application/json, text/event-stream',
71
- 'user-agent': 'apick-mcp-bridge'
72
- };
73
- if (apiKey) h.authorization = 'Bearer ' + apiKey;
74
- if (sessionId) h['mcp-session-id'] = sessionId;
75
- if (protocolVersion) h['mcp-protocol-version'] = protocolVersion;
76
- return h;
77
- }
78
-
79
- function remember(message) {
80
- const negotiated = message && message.result && message.result.protocolVersion;
81
- if (negotiated) protocolVersion = negotiated;
82
- }
83
-
84
- async function send(message) {
85
- let res;
86
- try {
87
- res = await fetchImpl(endpoint, {
88
- method: 'POST',
89
- headers: headers(),
90
- body: JSON.stringify(message)
91
- });
92
- } catch (err) {
93
- return fail(message, -32001, 'Cannot reach ' + endpoint + ': ' + redact(err.message, apiKey));
94
- }
95
-
96
- const incomingSession = res.headers.get && res.headers.get('mcp-session-id');
97
- if (incomingSession) sessionId = incomingSession;
98
-
99
- const body = await res.text();
100
- if (!body) {
101
- // 202 for notifications and responses - nothing to forward.
102
- if (res.ok) return;
103
- return fail(message, -32002, 'HTTP ' + res.status + ' from ' + endpoint);
104
- }
105
-
106
- const contentType = (res.headers.get && res.headers.get('content-type')) || '';
107
- let messages;
108
- if (contentType.includes('text/event-stream')) {
109
- messages = parseEventStream(body);
110
- } else {
111
- try {
112
- const parsed = JSON.parse(body);
113
- messages = Array.isArray(parsed) ? parsed : [parsed];
114
- } catch {
115
- return fail(message, -32002, 'HTTP ' + res.status + ' from ' + endpoint + ': unexpected response body');
116
- }
117
- }
118
-
119
- for (const out of messages) {
120
- remember(out);
121
- write(JSON.stringify(out));
122
- }
123
- }
124
-
125
- // Only a request (one carrying an id) can be answered with an error; a failed
126
- // notification is reported on stderr so the client is not left waiting on a reply.
127
- function fail(message, code, text) {
128
- const detail = redact(text, apiKey);
129
- if (message && message.id !== undefined && message.id !== null) {
130
- write(JSON.stringify({ jsonrpc: '2.0', id: message.id, error: { code, message: detail } }));
131
- } else {
132
- warn('apick-mcp: ' + detail);
133
- }
134
- }
135
-
136
- // Newline-delimited JSON in, newline-delimited JSON out.
137
- function handleLine(line) {
138
- const trimmed = line.trim();
139
- if (!trimmed) return Promise.resolve();
140
- let message;
141
- try {
142
- message = JSON.parse(trimmed);
143
- } catch {
144
- warn('apick-mcp: ignoring unparseable input line');
145
- return Promise.resolve();
146
- }
147
- return send(message);
148
- }
149
-
150
- function attach(input) {
151
- let buffer = '';
152
- const pending = new Set();
153
- input.setEncoding('utf8');
154
- input.on('data', (chunk) => {
155
- buffer += chunk;
156
- let index;
157
- while ((index = buffer.indexOf('\n')) !== -1) {
158
- const line = buffer.slice(0, index);
159
- buffer = buffer.slice(index + 1);
160
- const task = handleLine(line).catch((err) => warn('apick-mcp: ' + redact(err.message, apiKey)));
161
- pending.add(task);
162
- task.finally(() => pending.delete(task));
163
- }
164
- });
165
- return new Promise((resolve) => {
166
- input.on('end', async () => {
167
- if (buffer.trim()) await handleLine(buffer).catch(() => {});
168
- await Promise.allSettled([...pending]);
169
- resolve();
170
- });
171
- });
172
- }
173
-
174
- return { endpoint, attach, handleLine };
175
- }
1
+ // stdio <-> Streamable HTTP bridge for the APICK MCP server.
2
+ //
3
+ // APICK serves MCP over Streamable HTTP at https://apick.app/mcp/{server}. Clients that
4
+ // speak remote HTTP should connect there directly. This bridge exists for clients that
5
+ // only launch local stdio processes.
6
+ //
7
+ // It is a pump, not a protocol implementation: every JSON-RPC message read from stdin is
8
+ // forwarded verbatim, and every message the server returns is written verbatim to stdout.
9
+ // Capability and version negotiation, tool schemas and errors are all handled end to end
10
+ // by the client and the server.
11
+
12
+ export const DEFAULT_ENDPOINT = 'https://apick.app/mcp';
13
+
14
+ export const SERVERS = [
15
+ 'all',
16
+ 'business',
17
+ 'identity',
18
+ 'ocr',
19
+ 'finance',
20
+ 'web',
21
+ 'convert',
22
+ 'vision',
23
+ 'ai'
24
+ ];
25
+
26
+ // A key must never reach stdout, stderr or an error message.
27
+ export function redact(text, apiKey) {
28
+ if (!text) return text;
29
+ let out = String(text);
30
+ if (apiKey) out = out.split(apiKey).join('***');
31
+ return out.replace(/(Bearer\s+)[\w.\-]+/gi, '$1***');
32
+ }
33
+
34
+ // Streamable HTTP replies with either a single JSON body or an SSE stream carrying one or
35
+ // more JSON-RPC messages. Both shapes reduce to "a list of messages".
36
+ function parseEventStream(text) {
37
+ const messages = [];
38
+ for (const block of text.split(/\r?\n\r?\n/)) {
39
+ const data = block
40
+ .split(/\r?\n/)
41
+ .filter((line) => line.startsWith('data:'))
42
+ .map((line) => line.slice(5).trim())
43
+ .join('\n');
44
+ if (!data) continue;
45
+ try {
46
+ messages.push(JSON.parse(data));
47
+ } catch {
48
+ // Keep-alive or comment frame - nothing to forward.
49
+ }
50
+ }
51
+ return messages;
52
+ }
53
+
54
+ export function createBridge(options = {}) {
55
+ const server = options.server || 'all';
56
+ const apiKey = options.apiKey || '';
57
+ const endpoint = (options.endpoint || DEFAULT_ENDPOINT).replace(/\/+$/, '') + '/' + server;
58
+ const fetchImpl = options.fetch || globalThis.fetch;
59
+ const write = options.write || ((line) => process.stdout.write(line + '\n'));
60
+ const warn = options.warn || ((line) => process.stderr.write(line + '\n'));
61
+
62
+ // The gateway is stateless, but honour a session id and the negotiated protocol
63
+ // version if the server ever starts sending them.
64
+ let sessionId = null;
65
+ let protocolVersion = null;
66
+
67
+ function headers() {
68
+ const h = {
69
+ 'content-type': 'application/json',
70
+ accept: 'application/json, text/event-stream',
71
+ 'user-agent': 'apick-mcp-bridge'
72
+ };
73
+ if (apiKey) h.authorization = 'Bearer ' + apiKey;
74
+ if (sessionId) h['mcp-session-id'] = sessionId;
75
+ if (protocolVersion) h['mcp-protocol-version'] = protocolVersion;
76
+ return h;
77
+ }
78
+
79
+ function remember(message) {
80
+ const negotiated = message && message.result && message.result.protocolVersion;
81
+ if (negotiated) protocolVersion = negotiated;
82
+ }
83
+
84
+ async function send(message) {
85
+ let res;
86
+ try {
87
+ res = await fetchImpl(endpoint, {
88
+ method: 'POST',
89
+ headers: headers(),
90
+ body: JSON.stringify(message)
91
+ });
92
+ } catch (err) {
93
+ return fail(message, -32001, 'Cannot reach ' + endpoint + ': ' + redact(err.message, apiKey));
94
+ }
95
+
96
+ const incomingSession = res.headers.get && res.headers.get('mcp-session-id');
97
+ if (incomingSession) sessionId = incomingSession;
98
+
99
+ const body = await res.text();
100
+ if (!body) {
101
+ // 202 for notifications and responses - nothing to forward.
102
+ if (res.ok) return;
103
+ return fail(message, -32002, 'HTTP ' + res.status + ' from ' + endpoint);
104
+ }
105
+
106
+ const contentType = (res.headers.get && res.headers.get('content-type')) || '';
107
+ let messages;
108
+ if (contentType.includes('text/event-stream')) {
109
+ messages = parseEventStream(body);
110
+ } else {
111
+ try {
112
+ const parsed = JSON.parse(body);
113
+ messages = Array.isArray(parsed) ? parsed : [parsed];
114
+ } catch {
115
+ return fail(message, -32002, 'HTTP ' + res.status + ' from ' + endpoint + ': unexpected response body');
116
+ }
117
+ }
118
+
119
+ for (const out of messages) {
120
+ remember(out);
121
+ write(JSON.stringify(out));
122
+ }
123
+ }
124
+
125
+ // Only a request (one carrying an id) can be answered with an error; a failed
126
+ // notification is reported on stderr so the client is not left waiting on a reply.
127
+ function fail(message, code, text) {
128
+ const detail = redact(text, apiKey);
129
+ if (message && message.id !== undefined && message.id !== null) {
130
+ write(JSON.stringify({ jsonrpc: '2.0', id: message.id, error: { code, message: detail } }));
131
+ } else {
132
+ warn('apick-mcp: ' + detail);
133
+ }
134
+ }
135
+
136
+ // Newline-delimited JSON in, newline-delimited JSON out.
137
+ function handleLine(line) {
138
+ const trimmed = line.trim();
139
+ if (!trimmed) return Promise.resolve();
140
+ let message;
141
+ try {
142
+ message = JSON.parse(trimmed);
143
+ } catch {
144
+ warn('apick-mcp: ignoring unparseable input line');
145
+ return Promise.resolve();
146
+ }
147
+ return send(message);
148
+ }
149
+
150
+ function attach(input) {
151
+ let buffer = '';
152
+ const pending = new Set();
153
+ input.setEncoding('utf8');
154
+ input.on('data', (chunk) => {
155
+ buffer += chunk;
156
+ let index;
157
+ while ((index = buffer.indexOf('\n')) !== -1) {
158
+ const line = buffer.slice(0, index);
159
+ buffer = buffer.slice(index + 1);
160
+ const task = handleLine(line).catch((err) => warn('apick-mcp: ' + redact(err.message, apiKey)));
161
+ pending.add(task);
162
+ task.finally(() => pending.delete(task));
163
+ }
164
+ });
165
+ return new Promise((resolve) => {
166
+ input.on('end', async () => {
167
+ if (buffer.trim()) await handleLine(buffer).catch(() => {});
168
+ await Promise.allSettled([...pending]);
169
+ resolve();
170
+ });
171
+ });
172
+ }
173
+
174
+ return { endpoint, attach, handleLine };
175
+ }
package/src/index.js CHANGED
@@ -1,92 +1,92 @@
1
- #!/usr/bin/env node
2
- // apick-mcp - run the APICK MCP server over stdio.
3
- //
4
- // Usage:
5
- // apick-mcp [--server <name>]
6
- //
7
- // Environment:
8
- // APICK_API_KEY APICK license key. Optional: tools/list works without one,
9
- // tools/call needs it.
10
- // APICK_MCP_SERVER Which server to expose (default: all).
11
- // APICK_MCP_URL Base URL override (default: https://apick.app/mcp).
12
-
13
- import { readFileSync } from 'node:fs';
14
- import { fileURLToPath } from 'node:url';
15
- import { createBridge, DEFAULT_ENDPOINT, SERVERS } from './bridge.js';
16
-
17
- const HELP = `apick-mcp - APICK MCP server over stdio
18
-
19
- apick-mcp [--server <name>]
20
-
21
- Options
22
- --server, -s <name> ${SERVERS.join(', ')} (default: all)
23
- --version, -v print version
24
- --help, -h print this message
25
-
26
- Environment
27
- APICK_API_KEY APICK license key (get one at https://apick.app)
28
- APICK_MCP_SERVER same as --server
29
- APICK_MCP_URL base URL override (default: ${DEFAULT_ENDPOINT})
30
-
31
- Clients that support remote MCP can skip this bridge and connect straight to
32
- ${DEFAULT_ENDPOINT}/all with an Authorization: Bearer header.
33
- Docs: https://apick.app/dev_guide/mcp
34
- `;
35
-
36
- function parseArgs(argv) {
37
- const args = { server: process.env.APICK_MCP_SERVER || 'all' };
38
- for (let i = 0; i < argv.length; i++) {
39
- const arg = argv[i];
40
- if (arg === '--help' || arg === '-h') args.help = true;
41
- else if (arg === '--version' || arg === '-v') args.version = true;
42
- else if (arg === '--server' || arg === '-s') args.server = argv[++i];
43
- else if (arg.startsWith('--server=')) args.server = arg.slice('--server='.length);
44
- else args.unknown = arg;
45
- }
46
- return args;
47
- }
48
-
49
- async function main() {
50
- const args = parseArgs(process.argv.slice(2));
51
-
52
- if (args.help) {
53
- process.stdout.write(HELP);
54
- return;
55
- }
56
- if (args.version) {
57
- const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url));
58
- process.stdout.write(JSON.parse(readFileSync(pkgPath, 'utf8')).version + '\n');
59
- return;
60
- }
61
- if (args.unknown) {
62
- process.stderr.write('apick-mcp: unknown argument ' + args.unknown + '\n\n' + HELP);
63
- process.exitCode = 2;
64
- return;
65
- }
66
- if (!SERVERS.includes(args.server)) {
67
- process.stderr.write(
68
- 'apick-mcp: unknown server "' + args.server + '". Available: ' + SERVERS.join(', ') + '\n'
69
- );
70
- process.exitCode = 2;
71
- return;
72
- }
73
-
74
- const bridge = createBridge({
75
- server: args.server,
76
- apiKey: process.env.APICK_API_KEY,
77
- endpoint: process.env.APICK_MCP_URL
78
- });
79
-
80
- if (!process.env.APICK_API_KEY) {
81
- process.stderr.write(
82
- 'apick-mcp: no APICK_API_KEY set - tool discovery works, tool calls will ask you to sign in at https://apick.app\n'
83
- );
84
- }
85
-
86
- await bridge.attach(process.stdin);
87
- }
88
-
89
- main().catch((err) => {
90
- process.stderr.write('apick-mcp: ' + err.message + '\n');
91
- process.exitCode = 1;
92
- });
1
+ #!/usr/bin/env node
2
+ // apick-mcp - run the APICK MCP server over stdio.
3
+ //
4
+ // Usage:
5
+ // apick-mcp [--server <name>]
6
+ //
7
+ // Environment:
8
+ // APICK_API_KEY APICK license key. Optional: tools/list works without one,
9
+ // tools/call needs it.
10
+ // APICK_MCP_SERVER Which server to expose (default: all).
11
+ // APICK_MCP_URL Base URL override (default: https://apick.app/mcp).
12
+
13
+ import { readFileSync } from 'node:fs';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { createBridge, DEFAULT_ENDPOINT, SERVERS } from './bridge.js';
16
+
17
+ const HELP = `apick-mcp - APICK MCP server over stdio
18
+
19
+ apick-mcp [--server <name>]
20
+
21
+ Options
22
+ --server, -s <name> ${SERVERS.join(', ')} (default: all)
23
+ --version, -v print version
24
+ --help, -h print this message
25
+
26
+ Environment
27
+ APICK_API_KEY APICK license key (get one at https://apick.app)
28
+ APICK_MCP_SERVER same as --server
29
+ APICK_MCP_URL base URL override (default: ${DEFAULT_ENDPOINT})
30
+
31
+ Clients that support remote MCP can skip this bridge and connect straight to
32
+ ${DEFAULT_ENDPOINT}/all with an Authorization: Bearer header.
33
+ Docs: https://apick.app/dev_guide/mcp
34
+ `;
35
+
36
+ function parseArgs(argv) {
37
+ const args = { server: process.env.APICK_MCP_SERVER || 'all' };
38
+ for (let i = 0; i < argv.length; i++) {
39
+ const arg = argv[i];
40
+ if (arg === '--help' || arg === '-h') args.help = true;
41
+ else if (arg === '--version' || arg === '-v') args.version = true;
42
+ else if (arg === '--server' || arg === '-s') args.server = argv[++i];
43
+ else if (arg.startsWith('--server=')) args.server = arg.slice('--server='.length);
44
+ else args.unknown = arg;
45
+ }
46
+ return args;
47
+ }
48
+
49
+ async function main() {
50
+ const args = parseArgs(process.argv.slice(2));
51
+
52
+ if (args.help) {
53
+ process.stdout.write(HELP);
54
+ return;
55
+ }
56
+ if (args.version) {
57
+ const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url));
58
+ process.stdout.write(JSON.parse(readFileSync(pkgPath, 'utf8')).version + '\n');
59
+ return;
60
+ }
61
+ if (args.unknown) {
62
+ process.stderr.write('apick-mcp: unknown argument ' + args.unknown + '\n\n' + HELP);
63
+ process.exitCode = 2;
64
+ return;
65
+ }
66
+ if (!SERVERS.includes(args.server)) {
67
+ process.stderr.write(
68
+ 'apick-mcp: unknown server "' + args.server + '". Available: ' + SERVERS.join(', ') + '\n'
69
+ );
70
+ process.exitCode = 2;
71
+ return;
72
+ }
73
+
74
+ const bridge = createBridge({
75
+ server: args.server,
76
+ apiKey: process.env.APICK_API_KEY,
77
+ endpoint: process.env.APICK_MCP_URL
78
+ });
79
+
80
+ if (!process.env.APICK_API_KEY) {
81
+ process.stderr.write(
82
+ 'apick-mcp: no APICK_API_KEY set - tool discovery works, tool calls will ask you to sign in at https://apick.app\n'
83
+ );
84
+ }
85
+
86
+ await bridge.attach(process.stdin);
87
+ }
88
+
89
+ main().catch((err) => {
90
+ process.stderr.write('apick-mcp: ' + err.message + '\n');
91
+ process.exitCode = 1;
92
+ });