{
  "markdown": "# @upapi/mcp\n\nEvery public [upAPI](https://upapi.io) operation as an [MCP](https://modelcontextprotocol.io)\ntool — web search, SERP, social profiles, dev-tool lookups, geo/finance data — so an agent can\ncall them directly.\n\nThere are two ways to connect, and they differ only in how a call is authenticated:\n\n|          | Local (stdio)                   | Hosted (HTTP)                                 |\n| -------- | ------------------------------- | --------------------------------------------- |\n| Endpoint | `npx @upapi/mcp`                | `https://app.upapi.io/api/mcp`                |\n| Auth     | your `upapi_` API key           | sign in with your upAPI account (OAuth)       |\n| Runs     | on your machine                 | on upAPI                                      |\n| Best for | scripts, CI, self-hosted agents | Claude, IDEs, anything that speaks remote MCP |\n\nBoth expose the same tools with the same schemas, and both bill the same quota.\n\n## Hosted — no install\n\nPoint any MCP client that supports remote servers at:\n\n```\nhttps://app.upapi.io/api/mcp\n```\n\nIt will walk you through signing in to upAPI in a browser; there is no key to copy. With Claude\nCode:\n\n```bash\nclaude mcp add --transport http upapi https://app.upapi.io/api/mcp\n```\n\n## Local — API key\n\nCreate a key at [app.upapi.io → API Keys](https://app.upapi.io/dashboard/api-keys), then:\n\n```bash\nclaude mcp add upapi -e UPAPI_API_KEY=upapi_xxx -- npx -y @upapi/mcp\n```\n\nClaude Desktop (`claude_desktop_config.json`), Cursor, and Windsurf take the same thing as JSON:\n\n```json\n{\n  \"mcpServers\": {\n    \"upapi\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@upapi/mcp\"],\n      \"env\": { \"UPAPI_API_KEY\": \"upapi_xxx\" }\n    }\n  }\n}\n```\n\n| Variable          |          |                                                        |\n| ----------------- | -------- | ------------------------------------------------------ |\n| `UPAPI_API_KEY`   | required | an `upapi_` key                                        |\n| `UPAPI_BASE_URL`  | optional | gateway origin, defaults to `https://api.upapi.io`     |\n| `UPAPI_TOOL_MODE` | optional | `full` (default), `directory` or `compact` — see Tools |\n\nThe key is never validated locally — only checked for presence, so a missing one fails\nimmediately with a readable message instead of surfacing later as an unexplained 401 inside a\ntool call. Whether a key is real, expired, or over quota is answered at the gateway, the single\nplace that answers it for every machine caller.\n\n## Tools\n\nThe hosted endpoint serves a **compact** table by default: two meta-tools plus a few always-on\noperations, a few kilobytes in total.\n\n| Tool         | What it does                                                                      |\n| ------------ | --------------------------------------------------------------------------------- |\n| `search_ops` | Find operations by intent — returns slug, description, parameters, and quota cost |\n| `call_op`    | Run one operation by slug: `{ \"slug\": \"github-repo.get\", \"input\": { … } }`        |\n\nA tool table is re-sent as context on every turn, so one tool per operation means tens of\nkilobytes of JSON Schema per turn and a table large enough to measurably degrade tool selection.\n`search_ops` + `call_op` stays flat as the catalog grows. `web-search.post`, `github-repo.get`,\nand `wikipedia-article.get` stay on the table as full tools so the common case needs no discovery\nround-trip.\n\nThere are two other tables. `?tools=full` gives every operation its own tool:\n\n```bash\nclaude mcp add --transport http upapi 'https://app.upapi.io/api/mcp?tools=full'\n```\n\n`?tools=directory` gives a curated set of **named** tools for the flagship operations — the Maps\ntrio, web search, page-to-Markdown, screenshot, HTML-to-PDF, PDF text, OCR, transcription, GitHub\nrepo/user, npm package, IP geolocation, Wikipedia, currency — with read tools and write tools\nlisted separately and no `call_op`. That is the shape AI-directory review criteria ask for (a\ncatch-all dispatcher with a target parameter is a rejection), and it is what the Claude Desktop\nExtension ships with. The local stdio server takes the same three names in `UPAPI_TOOL_MODE`,\ndefaulting to `full`.\n\nAll three modes reach exactly the same operations — the mode changes what is advertised, never\nwhat is allowed. Every tool in every mode carries `readOnlyHint`, `destructiveHint`,\n`idempotentHint` and `openWorldHint`, derived from what the worker does rather than from the\nslug's verb suffix. Operations are named after their slug with `.` and `-` replaced by `_`\n(`web-search.post` → `web_search_post`), and each advertises the operation's real JSON Schema\n(formats, bounds, defaults, nullability), because that schema is generated from the worker's own\nmodel and passed through untouched.\n\nThe local (stdio) server always serves one tool per operation, and the whole catalog: it is\ninstalled deliberately, with your own key, into a client you chose.\n\nDescriptions carry the quota cost, so an agent can budget:\n\n> Search the web… upAPI operation `web-search.post` (Search). Costs 25 units of monthly quota\n> per call.\n\nA failed operation comes back as a normal tool result with `isError: true` and text leading with\nupAPI's public error code — `RATE_LIMITED`, `INVALID_INPUT`, `UPSTREAM_UNREACHABLE`. A rate limit\nalso states the wait in seconds. Nothing about a failing operation breaks the session.\n\nNo `outputSchema` is declared, deliberately: MCP requires a server that declares one to return\nmatching `structuredContent`, and these outputs describe live third-party payloads. One\nunexpected null would turn a successful call into a protocol error.\n\n## Use it from Mastra\n\nThe tools work in a Mastra agent directly, without an MCP transport in between. `@mastra/core` and\n`@mastra/mcp` are **optional peer dependencies** — install them yourself, and import the bindings\nfrom the `/mastra` subpath. Nothing else in this package touches Mastra, which is what keeps a\nplain `npm i @upapi/mcp` (and the desktop-extension bundle built from it) small.\n\n```ts\nimport { Agent } from '@mastra/core/agent';\nimport { createGatewayCaller } from '@upapi/mcp';\nimport { createUpapiTools } from '@upapi/mcp/mastra';\n\nconst agent = new Agent({\n  name: 'researcher',\n  instructions: 'Research topics using upAPI.',\n  model: /* … */,\n  tools: createUpapiTools({\n    caller: createGatewayCaller({ apiKey: process.env.UPAPI_API_KEY! }),\n  }),\n});\n```\n\nNarrow the table with `filter` when an agent should only see part of the catalog:\n\n```ts\ncreateUpapiTools({\n  caller,\n  filter: (op) => op.category === 'Search',\n});\n```\n\n## Build your own server\n\n`caller` is the only thing the tool table does not supply, which is what lets the same tools run\nover different transports:\n\n```ts\nimport { startUpapiStdioServer, type Caller } from '@upapi/mcp';\n\nconst caller: Caller = async (slug, input) => {\n  // resolve with the operation's output, or throw\n  // { code, message, status?, retryAfterSeconds? }\n};\n\nawait startUpapiStdioServer({ caller, mode: 'directory' });\n```\n\nFor a web-standard `Request`/`Response` server (Next.js route, Worker, Hono), import the\nhandler from the `/http` subpath — this is how `app.upapi.io/api/mcp` is built:\n\n```ts\nimport { handleUpapiMcpRequest, type Caller } from '@upapi/mcp/http';\n\nawait handleUpapiMcpRequest(request, {\n  caller,\n  // mode defaults to the request's own `?tools=` parameter (compact unless `full`/`directory`)\n  canExecute: true, // false hides every executable tool and refuses a call to one\n  canSearch: true, // false hides `search_ops`\n});\n```\n\n`canExecute` / `canSearch` are how a host projects its own authorization onto the table —\nupAPI maps them to the access token's `ops:execute` and `ops:read` scopes. Both default to\ntrue, so a host without a scope model is unaffected.\n\nPrefer that subpath over the package root in a bundled or file-traced deployment: it reaches only\nthe MCP SDK, while the root entry also pulls the stdio server in. Neither reaches Mastra — the\nbindings live behind `@upapi/mcp/mastra` precisely so that a build which never runs a Mastra agent\nnever sees `@mastra/core`.\n\n## Related\n\n- [`@upapi/sdk`](https://github.com/DevinoSolutions/upapi-node) — the typed HTTP client, and the operation catalog this package's tool\n  table is generated from\n- [upapi.io/docs](https://upapi.io/docs) — operation reference\n\n## Where development happens\n\nThis repository is the published home of `@upapi/mcp`: it is what npm installs, and\nissues and pull requests are welcome here. The tool table is derived from the\noperation catalog in [`@upapi/sdk`](https://github.com/DevinoSolutions/upapi-node),\nwhich is itself generated from upAPI’s private operation definitions and synced\nautomatically — so the set of tools changes upstream. The server, facade, error\nmapping and tests in these files are hand-written and are the code to change.\n",
  "bytes": 8894,
  "sha": "ce28a5199b1fcfe0f8effb4076d91e90dfccef23a0403809cc8ad1c6adc33104",
  "repo_slug": "devinosolutions/upapi-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_devinosolutions_upapi_mcp_fab1ae88/readme"
}