Skip to main content
Glama
README.md
# Bunjang MCP

An MCP server for reading public Bunjang listings through the native search
page, running as a Cloudflare Worker behind the Common Auth gateway.

## Tools

### bunjang_search

bunjang_search returns exactly one native Bunjang search page, up to 60 raw
entries. External shopping ads are excluded, while promoted Bunjang products
are retained and counted in the source page. The result reports
scanned_count, returned_count, disjoint exclusion counts, native pagination,
normalized status, source status, and per-listing enrichment states.

Input fields:

~~~json
{
  "search_word": "아이폰 17",
  "min_price_krw": 150000,
  "max_price_krw": 200000,
  "statuses": ["sold"],
  "sort": "price_low",
  "include_details": true,
  "include_seller_evidence": false
}
~~~

search_word is optional for compatibility. A nonblank search_word is sent to
Bunjang exactly as supplied; only the blank check trims it. When it is absent
or blank, query is normalized using the existing English-to-Korean
normalization rules. At least one of those fields must contain a usable query.

statuses accepts on_sale, reserved, and sold. Bunjang's native SOLD_OUT and
RESERVED filters include other statuses, so MCP applies the requested status
filter after the native request. A request combining sold and reserved is
rejected as unsupported_filter. Unknown source statuses are retained when no
status filter is requested and excluded with a separate count when one is
requested.

With no status filter, MCP omits the native status parameter. The current
Bunjang native response is selling-only in that mode; this is reported as the
current upstream default, not as a historical or snapshot guarantee. The
default sort is recent.

Prices and sorting are sent to Bunjang's native endpoint. cursor is an
opaque, UTF-8-safe cursor tied to the search word, filters, sort, and native
page size. Pass the returned cursor to get the next native page. A page with
no native cursor is terminal. A valid page can still contain zero returned
listings after status or ad filtering and retain a continuation cursor.

Details are enabled by default. Detail failures keep the listing and set
detail_status to failed. Seller evidence is disabled by default. When
enabled, shop stats.salesCount is preserved as source evidence; it is not
converted into a protected-payment count or a safety verdict. Both detail and
seller stages run independently, with at most eight concurrent requests each.

The response is outcome: ok, partial, or error. Partial results keep their
listings when detail or seller lookups fail. Error results include an error
code, retryability, the requested cursor, and
pagination: { has_more: null, next_cursor: null }; they do not pretend that
an error was an empty successful page.

The old offset and max_listings arguments were removed. The Worker never
walks multiple native pages or silently truncates a native page. It also does
not derive sold_at, model classification, storage classification, safety
verdicts, or exact historical price statistics.

### bunjang_get_seller_evidence

Fetch seller evidence in a batch:

~~~json
{
  "seller_ids": ["201368", "80519150"]
}
~~~

The batch accepts 1–100 IDs, deduplicates upstream requests, and returns one
result for each supplied ID. A lookup failure makes the batch partial while
preserving the individual error code and retryability. salesCount is reported
as source_metrics.salesCount; safe_trade_count remains unavailable because
equivalence has not been verified. Review evidence is not_requested in P0.

## Usage

~~~json
{
  "mcpServers": {
    "bunjang": {
      "type": "remote",
      "url": "https://bunjang.lost.plus/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN"
      }
    }
  }
}
~~~

Send a Common Auth token as Authorization: Bearer <token> or X-API-Key:
<token> with scope bunjang. Machine tokens and OAuth access tokens are
accepted. Clients speaking MCP 2026-07-28, 2025-06-18, and 2025-03-26 are
served. The modern protocol receives a five-minute private tools/list cache
hint.

## How it is reached

~~~
client -> bunjang.lost.plus/mcp -> auth-gateway Worker -> [BUNJANG service binding] -> this Worker
~~~

This Worker remains route-less (wrangler.toml has no routes and workers_dev =
false). The gateway owns bunjang.lost.plus/mcp, /mcp/*, /healthz, and the
OAuth metadata path. It validates the credential, strips it, and forwards the
caller identity through x-lost-plus-* headers. This Worker reads those
headers with @lpfchan/gateway-identity and refuses direct requests without a
complete gateway identity.

Deployment, routing, gateway ownership, and rollback are unchanged:

~~~sh
npm install
npm run typecheck
npm test
npm run deploy
~~~

npm run deploy needs CLOUDFLARE_API_TOKEN exported; on the deploy host it comes
from ~/.zshenv with the value of passage infra/CF_MASTER_TOKEN.
Deploying this Worker does not change gateway routes. Adding a route here
would create an unauthenticated entrance.

## Configuration

| Variable | Default | Purpose |
| --- | --- | --- |
| BUNJANG_BASE_URL | https://m.bunjang.co.kr | Browser origin for request headers |
| BUNJANG_API_BASE_URL | https://api.bunjang.co.kr | Public Bunjang JSON API base |
| BUNJANG_TIMEOUT_SECONDS | 20 | Per-request timeout, including response-body reads |
| BUNJANG_USER_AGENT | Safari-compatible value | Upstream HTTP user agent |

The Worker has no D1, KV, R2, or in-memory result cache. Every tool call
fetches fresh upstream data.