{
  "markdown": "# octen-mcp\n\n[![npm version](https://img.shields.io/npm/v/octen-mcp.svg?color=blue)](https://www.npmjs.com/package/octen-mcp)\n[![npm downloads](https://img.shields.io/npm/dm/octen-mcp.svg)](https://www.npmjs.com/package/octen-mcp)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![CI](https://github.com/Octen-Team/octen-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Octen-Team/octen-mcp/actions/workflows/ci.yml)\n\nMCP server for **Octen**. Plug it into Claude, Cursor, VS Code, Windsurf, or any MCP client to give your agent live web search and URL extraction.\n\nCore capabilities:\n\n- **`search` / `news_search`**: search the live web with domain, text, language, and time filters.\n- **`broad_search`**: decompose a query into multiple sub-queries, search them concurrently, and return results grouped per sub-query for broad coverage.\n- **`extract`**: turn one or more URLs into clean, LLM-ready content.\n- **`image_search`** (In Beta — contact us for beta access): search the web for images by text query, by a reference image URL, or by base64 image data — exactly one of the three.\n- **`video_search`** (In Beta — contact us for beta access): search the web for videos by text query.\n\nWhat makes Octen useful for agents is that `extract` returns more than page text. Each successful result also includes:\n\n- **`category`**: what the page is about\n- **`page_structure`**: what kind of page it is\n- **`highlights`**: ranked snippets when you pass a `query`\n\nThat lets an agent skip login walls, nav pages, and off-topic URLs before spending tokens on the full body.\n\n## Why Octen MCP\n\n### Fast\nWeb search averages 62ms. Fast enough for multi-step MCP workflows.\n\n### Accurate\nPowered by SOTA text and VL embedding models. Better sources, fewer hallucinations.\n\n### Fresh\nLive web data with minute-level updates. Useful for news, prices, and fast-moving pages.\n\n### Efficient\nClean highlights, optional `full_content`, and page labels keep model context relevant.\n\n## Quick start\n\nYou need an `OCTEN_API_KEY` from [octen.ai](https://octen.ai).\n\n**Two ways to connect.** Both serve the same six tools.\n\n| | Endpoint | When |\n|---|---|---|\n| **Hosted (recommended)** | `https://mcp.octen.ai/mcp` | Nothing to install or update. Works with any client that speaks remote MCP. |\n| **Local** | `npx -y octen-mcp` | Clients without remote support, air-gapped setups, or when you want the process on your own machine. |\n\n> **Node compatibility (local only):** 0.4.2 and later run on every supported\n> Node (>= 18.17), including Node 26+. Versions 0.4.1 and below fail every call\n> on hosts whose embedded undici is v8+ (Node 26 and later) with\n> `Network error … code=UND_ERR_INVALID_ARG cause=invalid onError method` — if\n> you see that error, upgrade the package (or run on Node <= 24).\n\n## Connect to the hosted endpoint\n\n### Passing your key\n\nThree ways, tried in this order. **Prefer a header** — a key in a URL is\nexposed to proxy logs, browser history and `Referer` headers.\n\n| Form | Use when |\n|---|---|\n| `x-api-key: <key>` | The default. |\n| `Authorization: Bearer <key>` | Your client only offers one header field. |\n| `?octenApiKey=<key>` appended to the URL | Your client accepts nothing but a URL. |\n\nOr use **OAuth** and paste no key at all — see [Signing in instead](#signing-in-instead).\n\n### By client\n\n**Claude Code**\n\n```bash\nclaude mcp add --transport http octen https://mcp.octen.ai/mcp --header \"x-api-key: your-key-here\"\n```\n\n**Cursor** — `~/.cursor/mcp.json`\n\n```json\n{\n  \"mcpServers\": {\n    \"octen\": {\n      \"url\": \"https://mcp.octen.ai/mcp\",\n      \"headers\": { \"x-api-key\": \"your-key-here\" }\n    }\n  }\n}\n```\n\n**VS Code** — `.vscode/mcp.json` (workspace) or the user config. Note `servers`,\nnot `mcpServers`, and the explicit `type`:\n\n```json\n{\n  \"servers\": {\n    \"octen\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.octen.ai/mcp\",\n      \"headers\": { \"x-api-key\": \"your-key-here\" }\n    }\n  }\n}\n```\n\n**Codex**\n\n```bash\ncodex mcp add octen --url https://mcp.octen.ai/mcp --header \"x-api-key: your-key-here\"\n```\n\n**Windsurf** — `~/.codeium/windsurf/mcp_config.json`. The key is `serverUrl`,\nnot `url`:\n\n```json\n{\n  \"mcpServers\": {\n    \"octen\": {\n      \"serverUrl\": \"https://mcp.octen.ai/mcp\",\n      \"headers\": { \"x-api-key\": \"your-key-here\" }\n    }\n  }\n}\n```\n\n**Gemini CLI** — `~/.gemini/settings.json`. The key is `httpUrl`:\n\n```json\n{\n  \"mcpServers\": {\n    \"octen\": {\n      \"httpUrl\": \"https://mcp.octen.ai/mcp\",\n      \"headers\": { \"x-api-key\": \"your-key-here\" }\n    }\n  }\n}\n```\n\n**Claude Desktop** takes the URL directly — no config file, no bridge. In\n**Settings → Connectors**, click **Add custom connector**, name it `Octen`, and\nenter:\n\n```\nhttps://mcp.octen.ai/mcp\n```\n\nLeave the Advanced settings empty: the OAuth Client ID and Secret fields are\nfor servers that cannot register a client on their own, and ours can. Claude\nwill offer to sign you in, and an Octen key is issued to that connection when\nyou approve.\n\nThe connector dialog has no field for request headers, so an **API key** goes\nin the URL instead:\n\n```\nhttps://mcp.octen.ai/mcp?octenApiKey=your-key-here\n```\n\nSigning in is the better of the two — a URL is not a secret-carrying medium,\nand the sign-in flow can be revoked from your account without editing anything\non this side. Note also that Claude connects from Anthropic's servers rather\nthan from your machine, so a self-hosted deployment has to be reachable from\nthe public internet for this to work at all; a private one wants the bridge\nbelow.\n\n**Clients that only speak stdio** (Zed, Warp, Raycast, Cline) reach a remote\nserver through the `mcp-remote` bridge. Note there is **no space after the\ncolon** in `--header` — the value is split on the first one:\n\n```json\n{\n  \"mcpServers\": {\n    \"octen\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\", \"mcp-remote\",\n        \"https://mcp.octen.ai/mcp\",\n        \"--header\", \"x-api-key:your-key-here\"\n      ]\n    }\n  }\n}\n```\n\nIf a client cannot pass a header at all and takes only a URL, put the key in\nthe URL instead: `https://mcp.octen.ai/mcp?octenApiKey=your-key-here`.\n\n### Signing in instead\n\nIf the deployment is configured with an authorization server — the hosted one\nis — a request with no credential is answered `401` +\n`WWW-Authenticate: Bearer resource_metadata=\"…\"`. That is the signal MCP\nclients use to start an OAuth flow, so a client that supports OAuth will offer\nto sign you in rather than ask for a key.\n\nTo move a connection that already has a key onto OAuth, point it at\n`https://mcp.octen.ai/mcp/oauth` (or append `?login`). That path issues the\nchallenge even when a key is present, which is the only way to trigger the\nswitch — with a key attached, the ordinary endpoint has no reason to.\n\n### Loading fewer tools\n\n`?tools=search,extract` limits a connection to the tools you name, for both\n`tools/list` and `tools/call`:\n\n```\nhttps://mcp.octen.ai/mcp?tools=search,extract\n```\n\nClients load every advertised tool's full schema into the model's context, so\nnarrowing the set is a real saving when you only need one or two. An unknown\nname is refused with a `400` listing the valid ones, rather than a connection\nthat quietly comes up short.\n\n## Run it locally instead\n\n```json\n{\n  \"mcpServers\": {\n    \"octen\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"octen-mcp\"],\n      \"env\": { \"OCTEN_API_KEY\": \"your-key-here\" }\n    }\n  }\n}\n```\n\n[![Install in VS Code](https://img.shields.io/badge/Install%20in-VS%20Code-007ACC?logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect/mcp/install?name=octen&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apiKey%22%2C%22description%22%3A%22Octen%20API%20Key%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22octen-mcp%22%5D%2C%22env%22%3A%7B%22OCTEN_API_KEY%22%3A%22%24%7Binput%3AapiKey%7D%22%7D%7D)\n[![Install in VS Code Insiders](https://img.shields.io/badge/Install%20in-VS%20Code%20Insiders-24bfa5?logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=octen&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apiKey%22%2C%22description%22%3A%22Octen%20API%20Key%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22octen-mcp%22%5D%2C%22env%22%3A%7B%22OCTEN_API_KEY%22%3A%22%24%7Binput%3AapiKey%7D%22%7D%7D&quality=insiders)\n\n| Agent | One-line install |\n|--|--|\n| Claude Code | `claude mcp add --scope user octen -e OCTEN_API_KEY=your-key-here -- npx -y octen-mcp` |\n| Codex | `codex mcp add octen --env OCTEN_API_KEY=your-key-here -- npx -y octen-mcp` |\n| Gemini CLI | `gemini mcp add octen -e OCTEN_API_KEY=your-key-here -- npx -y octen-mcp` |\n| VS Code | `code --add-mcp '{\"name\":\"octen\",\"command\":\"npx\",\"args\":[\"-y\",\"octen-mcp\"],\"env\":{\"OCTEN_API_KEY\":\"your-key-here\"}}'` (or click a badge above) |\n| Cursor | [Add to Cursor](https://cursor.com/en/install-mcp?name=octen&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm9jdGVuLW1jcCJdLCJlbnYiOnsiT0NURU5fQVBJX0tFWSI6InlvdXIta2V5LWhlcmUifX0%3D) (then edit the key), or use the JSON above in `~/.cursor/mcp.json` |\n| Claude Desktop | No CLI. For the hosted endpoint use **Settings → Connectors → Add custom connector** (above); for a local install, the config file (below) |\n\n### Config file locations\n\n- **Claude Desktop**: `~/Library/Application\\ Support/Claude/claude_desktop_config.json` — only needed for a local (stdio) install; the hosted endpoint is added as a connector instead\n- **Cursor**: `~/.cursor/mcp.json`\n- **VS Code workspace**: `.vscode/mcp.json` (use `servers` instead of `mcpServers`)\n- **Windsurf**: `~/.codeium/windsurf/mcp_config.json`\n- **Gemini CLI**: `~/.gemini/settings.json`\n- **Cline / other clients**: paste it into that client's MCP settings\n\n## Troubleshooting\n\n**`401` on every call.** No credential reached the server. Check the header\nname — `x-api-key`, or `Authorization: Bearer` — and, if you are using\n`mcp-remote`, that there is no space after the colon in `--header`.\n\n**The tools do not appear.** Most clients read MCP config once at startup;\nrestart the client after editing it. If the config uses the wrong key for your\nclient (`url` vs `serverUrl` vs `httpUrl`), the server is never contacted at\nall — see the per-client sections above.\n\n**`406 Not Acceptable`.** The request's `Accept` header must list *both*\n`application/json` and `text/event-stream`. Clients do this for you; hand-rolled\n`curl` probes usually do not, and the resulting 406 looks like an auth failure.\n\n**`400` mentioning `tools`.** A name in `?tools=` is not one this deployment\nserves. The error lists the ones that are.\n\n**A tool errors with `code=403 … beta access`.** `image_search` and\n`video_search` are in Beta and enabled per account; the message says how to\nrequest it.\n\n**Errors name what happened.** A failed call reports which dependency failed\nand why — a timeout, a connection code, an expired token and by how long — plus\nOcten's own `request_id` where the API returned one. Quote that id in a support\nrequest; it is the one an engineer can look up.\n\n## Tools\n\n| Tool | What it does | Best for |\n|---|---|---|\n| `search` | Search the live web with domain, text, language (ISO 639-1), time, and content controls | a single focused web search |\n| `news_search` | Same engine as `search`, fixed to news | current events and timely reporting |\n| `broad_search` | Decompose a query into up to `max_queries` sub-queries, search concurrently, return grouped results (same per-sub-query options as `search`, including the `language` filter) | research-style, multi-angle coverage |\n| `extract` | Fetch 1-20 URLs and return clean content, labels, and optional highlights | summarization, RAG, fact lookup |\n| `image_search` | _In Beta — contact us for beta access._ Search the web for images by text query (optional reference `image_url`) | finding pictures, photos, visual references |\n| `video_search` | _In Beta — contact us for beta access._ Search the web for videos by text query | finding videos, clips, footage |\n\nThese six are the whole surface, on purpose. Embeddings, chat completions,\nAnswer and Research are Octen APIs that this server deliberately does not\nexpose — reach them through the [Python SDK](https://pypi.org/project/octen/)\nor the [CLI](https://www.npmjs.com/package/@octen.ai/cli). A tool an agent can\nsee is a tool it will try, so the roster stays the set that earns its place in\na tool list.\n\nReference docs:\n\n- Search: [docs.octen.ai/api-reference/search](https://docs.octen.ai/api-reference/search)\n- Extract: [docs.octen.ai/api-reference/extract](https://docs.octen.ai/api-reference/extract)\n\n### Parameters\n\nTranscribed from the schemas each tool publishes — and those are enforced, so an\nout-of-contract value is refused before any call is made, with a message naming\nthe field. The limits track the\n[API reference](https://docs.octen.ai/api-reference/search); a test fails if the\ntwo drift apart.\n\n`image_search` takes **exactly one** of `query`, `image_url` or `image_data`.\n\n`query` must be non-empty: an empty string is refused by the schema, and a\nwhitespace-only one by the handler. Neither reaches the API.\n\n**`timeout` means two different things.** On `search`, `news_search`,\n`broad_search`, `image_search` and `video_search` it is this server's own HTTP\ndeadline for the call — it is removed from the request body and never sent to\nthe API. On `extract` it is the API's documented **per-URL fetch budget**\n(1-60s) and does travel in the body; the local deadline for an extract call is\nderived from it with headroom, so raising `timeout` raises both.\n\nOverlapping `include_domains` and `exclude_domains` are passed through as\ngiven. The API currently answers a domain listed in both with a validation\nerror; the published\n[reference](https://docs.octen.ai/api-reference/search) describes exclusion\ntaking precedence. Until those agree, this server neither silently drops nor\nreorders your filters — you get the API's answer.\n\n#### `search`\n\n| Parameter | Type | Required | Limits | Default |\n|---|---|---|---|---|\n| `query` | string | yes | 1–500 chars |  |\n| `topic` | string |  | `general` / `news` | `\"general\"` |\n| `count` | integer |  | 1–100 | `5` |\n| `include_domains` | array |  | ≤1200 items; each ≤60 |  |\n| `exclude_domains` | array |  | ≤1200 items; each ≤60 |  |\n| `include_text` | array |  | ≤5 items; each ≤30 |  |\n| `exclude_text` | array |  | ≤5 items; each ≤30 |  |\n| `time_basis` | string |  | `auto` / `published` / `crawled` | `\"auto\"` |\n| `time_range` | string |  | `day` / `week` / `month` / `year` / `d` / `w` / `m` / `y` |  |\n| `start_time` | string |  |  |  |\n| `end_time` | string |  |  |  |\n| `format` | string |  | `text` / `markdown` | `\"text\"` |\n| `safesearch` | string |  | `off` / `strict` | `\"strict\"` |\n| `language` | array |  |  | `[]` |\n| `highlight` | object |  | `enable`, `max_tokens` |  |\n| `full_content` | object |  | `enable`, `max_tokens` |  |\n| `include_images` | boolean |  |  | `false` |\n| `timeout` | integer |  | 1–60 |  |\n\n#### `news_search`\n\n| Parameter | Type | Required | Limits | Default |\n|---|---|---|---|---|\n| `query` | string | yes | 1–500 chars |  |\n| `count` | integer |  | 1–100 | `5` |\n| `include_domains` | array |  | ≤1200 items; each ≤60 |  |\n| `exclude_domains` | array |  | ≤1200 items; each ≤60 |  |\n| `include_text` | array |  | ≤5 items; each ≤30 |  |\n| `exclude_text` | array |  | ≤5 items; each ≤30 |  |\n| `time_basis` | string |  | `auto` / `published` / `crawled` | `\"auto\"` |\n| `time_range` | string |  | `day` / `week` / `month` / `year` / `d` / `w` / `m` / `y` |  |\n| `start_time` | string |  |  |  |\n| `end_time` | string |  |  |  |\n| `format` | string |  | `text` / `markdown` | `\"text\"` |\n| `safesearch` | string |  | `off` / `strict` | `\"strict\"` |\n| `language` | array |  |  | `[]` |\n| `highlight` | object |  | `enable`, `max_tokens` |  |\n| `full_content` | object |  | `enable`, `max_tokens` |  |\n| `include_images` | boolean |  |  | `false` |\n| `timeout` | integer |  | 1–60 |  |\n\n#### `broad_search`\n\n| Parameter | Type | Required | Limits | Default |\n|---|---|---|---|---|\n| `query` | string | yes | 1–500 chars |  |\n| `max_queries` | integer |  | 1–30 | `5` |\n| `topic` | string |  | `general` / `news` | `\"general\"` |\n| `count` | integer |  | 1–100 | `5` |\n| `include_domains` | array |  | ≤1200 items; each ≤60 |  |\n| `exclude_domains` | array |  | ≤1200 items; each ≤60 |  |\n| `include_text` | array |  | ≤5 items; each ≤30 |  |\n| `exclude_text` | array |  | ≤5 items; each ≤30 |  |\n| `time_basis` | string |  | `auto` / `published` / `crawled` | `\"auto\"` |\n| `time_range` | string |  | `day` / `week` / `month` / `year` / `d` / `w` / `m` / `y` |  |\n| `start_time` | string |  |  |  |\n| `end_time` | string |  |  |  |\n| `format` | string |  | `text` / `markdown` | `\"text\"` |\n| `safesearch` | string |  | `off` / `strict` | `\"strict\"` |\n| `language` | array |  |  | `[]` |\n| `highlight` | object |  | `enable`, `max_tokens` |  |\n| `full_content` | object |  | `enable`, `max_tokens` |  |\n| `include_images` | boolean |  |  | `false` |\n| `timeout` | integer |  | 1–300 |  |\n\n#### `extract`\n\n| Parameter | Type | Required | Limits | Default |\n|---|---|---|---|---|\n| `urls` | array | yes | ≤20 items; each ≤2048 |  |\n| `query` | string |  | ≤500 chars |  |\n| `max_age_seconds` | integer |  | 300–31536000 | `86400` |\n| `format` | string |  | `markdown` / `text` | `\"markdown\"` |\n| `timeout` | integer |  | 1–60 | `30` |\n| `include_images` | boolean |  |  | `false` |\n| `include_videos` | boolean |  |  | `false` |\n| `include_audio` | boolean |  |  | `false` |\n\n#### `image_search`\n\n| Parameter | Type | Required | Limits | Default |\n|---|---|---|---|---|\n| `query` | string |  | ≤500 chars |  |\n| `image_url` | string |  |  |  |\n| `image_data` | string |  | ≤5242880 chars |  |\n| `topic` | string |  | `general` / `design` | `\"general\"` |\n| `count` | integer |  | 1–10 | `5` |\n| `include_domains` | array |  |  |  |\n| `exclude_domains` | array |  |  |  |\n| `safesearch` | string |  | `off` / `strict` | `\"strict\"` |\n| `html_snippet` | object |  | `enable`, `max_tokens` |  |\n| `timeout` | integer |  | 1–60 |  |\n\n#### `video_search`\n\n| Parameter | Type | Required | Limits | Default |\n|---|---|---|---|---|\n| `query` | string | yes | 1–500 chars |  |\n| `count` | integer |  | 1–10 | `5` |\n| `time_range` | string |  | `day` / `week` / `month` / `year` / `d` / `w` / `m` / `y` |  |\n| `start_time` | string |  |  |  |\n| `end_time` | string |  |  |  |\n| `safesearch` | string |  | `off` / `strict` | `\"strict\"` |\n| `timeout` | integer |  | 1–60 |  |\n\n### Keep the tools always on (optional)\n\nIn clients with **MCP tool search** enabled (the Claude Code default), tools are\n*deferred* — the model runs a `ToolSearch` step to load them on demand. If you'd\nrather have the Octen tools resident from the first turn (no discovery step), set\n`alwaysLoad` on the server in your `.mcp.json` (Claude Code v2.1.121+):\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"octen\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"octen-mcp\"],\n      \"env\": { \"OCTEN_API_KEY\": \"your-key-here\" },\n      \"alwaysLoad\": true\n    }\n  }\n}\n```\n\nEach always-loaded tool uses context on every turn, and `alwaysLoad` blocks startup\nuntil the server connects (capped at the ~5s connect timeout), so reserve it for tools\nyou hit constantly. To keep the cost down, mark just the highest-traffic tools — e.g.\n`search` and `broad_search` — with `\"anthropic/alwaysLoad\": true` in each tool's `_meta`,\nleaving the rest deferred.\n\n## Why agents like this\n\nMost extract tools stop at \"here is the page body.\" Octen helps one step earlier:\n\n- **Skip bad pages early**: `page_structure.primary == \"No Main Content\"` tells the agent it hit a login wall, empty shell, or similar non-content page.\n- **Filter by topic early**: `category` helps a pipeline ignore pages outside the target vertical before embedding or summarizing.\n- **Use less context**: `query` returns `highlights` when the user wants a specific fact instead of the full page.\n\nFor the full decision tree and integration patterns, see [docs/best-practices.md](docs/best-practices.md).\n\n## Example prompts\n\n- `Fetch octen.ai and summarize the main product features.`\n- `Search for recent MCP news from the last week.`\n- `Fetch these URLs and only summarize the ones whose category is Finance.`\n- `Search site:docs.anthropic.com prompt caching and return only the relevant highlights.`\n\n## Self-hosting the HTTP server\n\nThe hosted endpoint above runs this same code; you can run it yourself instead:\n\n```bash\nPORT=8080 npx -y -p octen-mcp octen-mcp-http   # or: octen-mcp-http after a global install\n```\n\nEndpoints: `POST /mcp` (stateless Streamable HTTP) and `GET /healthz`. Stateless\nby design — there is no session to lose, so scaling is a matter of running more\ncopies behind a load balancer. Credentials travel per request and never come\nfrom the server's own environment: a request without one fails rather than\nquietly spending the host's key.\n\n**Authorization is off unless you configure it.** Set both\n`OCTEN_OAUTH_AUTHORIZATION_SERVER` and `OCTEN_MCP_RESOURCE` and the server\nadvertises RFC 9728 protected-resource metadata and answers uncredentialed\nrequests with a `401` challenge. Leave them unset and it advertises nothing —\nan instance should never point clients at an authorization server it does not\nhave — and a missing key surfaces at call time instead.\n\n**Argument validation.** Each tool's declared `inputSchema` is enforced, not\njust advertised: types, ranges, string lengths, array sizes, enums, required\nparameters, and the property list itself. An out-of-contract call is refused\nwith a message naming the field, before anything is sent to the API.\n\n**Sending an image as base64.** `image_search` accepts `image_data` up to 5MB\nencoded, which travels inside the JSON-RPC body. `OCTEN_MCP_MAX_BODY` defaults\nto 6 MiB to make that work unconfigured; `OCTEN_MCP_MAX_INFLIGHT_BODY` (24 MiB)\nseparately bounds what all in-flight bodies hold at once, since a per-request\ncap does nothing about many concurrent ones.\n\n## Environment variables\n\n| Variable | Required | Default | Notes |\n|---|---|---|---|\n| `OCTEN_API_KEY` | yes | — | |\n| `OCTEN_API_URL` | no | `https://api.octen.ai` | |\n| `OCTEN_ENABLE_BETA_TOOLS` | no | on | Set to `false`/`0`/`off`/`no` to hide the Beta `image_search` / `video_search` tools from discovery. |\n| `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` | no | — | Honoured since 0.4.0. Node's built-in `fetch` ignores these by default, so before 0.4.0 the server could not reach the API from behind a proxy even when every other tool on the machine could. **Set them explicitly in your client config** — see below; most MCP clients do not pass your shell environment through. |\n| `OCTEN_KEEP_ALIVE_MS` | no | `60000` | How long an idle connection is kept for reuse. undici's own default of 4s meant nearly every call re-paid a full TLS handshake (~515ms measured). 60s is measured against `api.octen.ai`, which closes idle connections between 60s and 90s — staying under that means we always release first, instead of dispatching onto a socket the origin has already closed. Re-measure if you point `OCTEN_API_URL` elsewhere. |\n| `OCTEN_KEEP_ALIVE_MAX_MS` | no | `600000` | Upper bound on the above when the origin advertises its own `Keep-Alive` hint. `api.octen.ai` does not send one. |\n| `OCTEN_CONNECT_TIMEOUT_MS` | no | `10000` | Ceiling on **establishing the outbound connection to `api.octen.ai`** — unrelated to the MCP client's own startup connect timeout mentioned above. Lower it (e.g. `5000`) on a path where connections fail intermittently, so the automatic retry engages sooner. |\n| `OCTEN_RETRY` | no | on | Set to `false`/`0`/`off`/`no` to disable the single automatic retry on connection-level failures. Retries cost quota when the original request had in fact reached the server. |\n| `OCTEN_HTTP2` | no | off | Opt into HTTP/2. Measured no faster for the usual one-request-at-a-time pattern, and not reliable through every CONNECT proxy — worth trying if you issue many tool calls in parallel. |\n| `OCTEN_MCP_DEBUG` | no | off | Request tracing on **stderr** (stdout carries MCP framing). See below. |\n\n### Remote HTTP form only\n\nThese are read only by `octen-mcp-http`; the stdio entry ignores them.\n\n| Variable | Required | Default | Notes |\n|---|---|---|---|\n| `PORT` | no | `8080` | `0` picks an ephemeral port. |\n| `OCTEN_MCP_LOG` | no | human text | `json` emits one JSON object per event, for a log pipeline. |\n| `OCTEN_MCP_MAX_BODY` | no | `6291456` | Per-request body cap, in bytes, enforced on bytes received. Over it: `413`. Sized so `image_search`'s `image_data` (5MB encoded) works unconfigured; turn it down if this deployment never sends images. |\n| `OCTEN_MCP_MAX_INFLIGHT_BODY` | no | `25165824` | Ceiling on the total bytes all in-flight bodies hold at once. The per-request cap stops one huge request; this stops many ordinary ones. Over it: `503` + `Retry-After`. Measured: resident cost runs about ten times this, because a base64 body exists two or three times over as a UTF-16 string. |\n| `OCTEN_MCP_BODY_DRAIN_MS` | no | `2000` | After a `413`, how long to keep discarding the rest of the body so the `413` itself gets delivered. |\n| `OCTEN_DRAIN_TIMEOUT_MS` | no | `310000` | On `SIGTERM`, how long in-flight calls may finish. Must stay below the pod's `terminationGracePeriodSeconds`, and above the longest tool budget (`broad_search`, 300s). |\n| `OCTEN_APPS_CHALLENGE_TOKEN` | no | — | Plugin-directory domain-verification token. When set, `GET /.well-known/openai-apps-challenge` serves exactly this value as plain text (the body must be the token alone); unset, the path answers `404`. Anonymous by design, like the PRM: the token is generated by the directory for its own verifier to read, not a secret of this deployment. |\n\n**OAuth.** Off unless both of the first two are set, so an instance never\nadvertises an authorization server it does not have.\n\n| Variable | Required | Default | Notes |\n|---|---|---|---|\n| `OCTEN_OAUTH_AUTHORIZATION_SERVER` | for OAuth | — | Authorization server base URL. Compared byte-for-byte against the token's `iss`, and used to build the JWKS URL — **no trailing slash**; the process refuses to start with one, because it would reject every token. |\n| `OCTEN_MCP_RESOURCE` | for OAuth | — | This deployment's public URL, e.g. `https://mcp.example.com/mcp`. Compared byte-for-byte against the token's `aud` (RFC 8707), so a stray trailing slash fails every call. Cannot be inferred behind a proxy, hence explicit. |\n| `OCTEN_OAUTH_RESOLVE_URL` | for OAuth | — | Internal endpoint that exchanges a grant for an API key. Must be reachable privately; a public address will not serve it. |\n| `OCTEN_OAUTH_RESOLVE_TOKEN` | for OAuth | — | Shared secret sent to that endpoint as `X-Octen-Service-Token`. |\n| `OCTEN_OAUTH_RESOLVE_CACHE_TTL_MS` | no | `60000` | How long a resolved grant is reused. This is the revocation-propagation window: a token revoked now keeps working for up to this long. `0` resolves on every call, at the cost of putting that service in front of every tool call. |\n| `OCTEN_JWKS_REFETCH_COOLDOWN_MS` | no | `10000` | Floor between JWKS refetches triggered by an unknown key id. The trigger is an unverified token header, so without a floor each such request becomes one request to the authorization server. Lowering it widens that by the same ratio. |\n\n### Setting these in Claude Desktop (and where the logs go)\n\nMCP servers do not inherit your shell environment. Claude Desktop spawns them\nwith `HOME`, `LOGNAME`, `PATH`, `SHELL` and `USER` — and nothing else except what\nyou put in the server's `env` block. **A proxy configured system-wide will not be\npicked up**; it has to be named explicitly, alongside the API key:\n\n```json\n{\n  \"mcpServers\": {\n    \"octen\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"octen-mcp\"],\n      \"env\": {\n        \"OCTEN_API_KEY\": \"your-key-here\",\n        \"HTTPS_PROXY\": \"http://proxy.example:8080\"\n      }\n    }\n  }\n}\n```\n\nRestart Claude Desktop after editing the config — it reads it at launch.\n\nServer output lands in:\n\n- **macOS**: `~/Library/Logs/Claude/mcp-server-octen.log`\n- **Windows**: `%APPDATA%\\Claude\\logs\\mcp-server-octen.log`\n\nEverything the server writes to stderr, including the tracing below, goes there.\n\n### Diagnosing a slow or failing call\n\nAdd `\"OCTEN_MCP_DEBUG\": \"1\"` to the `env` block above while you are investigating,\nand **take it out afterwards** — the client appends this to a log file that is\nnever rotated, so leaving it on grows that file for the life of the install.\n\nWith it on, every call is traced to stderr:\n\n```\n[octen-mcp 2026-08-14T04:36:36.219Z] call #1 received tool=search\n[octen-mcp 2026-08-14T04:36:36.637Z] connect #1 established to api.octen.ai in 410ms peer=203.0.113.10:443 tls=TLSv1.3 alpn=http/1.1\n[octen-mcp 2026-08-14T04:36:37.155Z] /search attempt=1 status=200 elapsed=935ms socket=new request_id=42cd56a5-…\n[octen-mcp 2026-08-14T04:36:37.157Z] call #1 returning tool=search handler_total=938ms\n```\n\nEach field answers a specific question:\n\n- **`call #N received` timestamp** — when the call reached this process. Subtract it\n  from the time your MCP client issued the tool call: the difference is time spent\n  entirely outside `octen-mcp`, in the host or in whatever relays between them. A\n  client-side stopwatch alone cannot separate that from time we are responsible for.\n- **`connect … established in Xms`** — a handshake happened, and what it cost.\n  `connect FAILED` names the phase and error code instead.\n- **`peer=` / `tls=` / `alpn=`** — which address the connection actually reached\n  (the API hostname is anycast, so the hostname alone cannot tell you which\n  edge), and the negotiated TLS version and protocol — a mismatch there\n  otherwise presents as an unexplained slow or failed handshake.\n- **`socket=new` / `socket=reused`** — whether this call paid for a handshake. This\n  is the difference between \"the service is slow\" and \"the connection was thrown\n  away between calls\".\n- **`elapsed`** vs **`handler_total`** — time in the HTTP request vs time in the tool\n  handler. A large gap means the cost is in request assembly or response formatting,\n  not the network.\n- **`request_id`** — in *this trace only*: the client-generated correlation id,\n  stable across the retry, tying a call's attempts together. It never appears in\n  user-facing error messages, deliberately: Octen support cannot look it up (the\n  gateway does not record the header), and an id labelled `request_id` reads\n  like one they could. Error messages carry only ids verified searchable on\n  Octen's side — today that is exactly one: the server's own `request_id` from\n  an API error envelope.\n\nFailures name the cause rather than `fetch failed`:\n\n```\nNetwork error calling Octen Search: code=ECONNREFUSED cause=connect ECONNREFUSED 203.0.113.9:443\naddress=203.0.113.9:443 — could not establish a connection.\nIf this machine requires an HTTP proxy, set HTTPS_PROXY.\n```\n\n`UND_ERR_CONNECT_TIMEOUT` means the connection was never established, `ECONNRESET`\nmeans it was established and then torn down, and `ENOTFOUND` means DNS — three\ndifferent problems with three different owners.\n\nRequest timeouts: `search` and the media tools default to 30s, `broad_search` to 120s (raisable to 300s),\nand `extract` to its per-URL budget plus headroom. The search tools accept a\n`timeout` parameter to override; `extract`'s `timeout` is the *server-side,\nper-URL* fetch budget, so the client ceiling is derived from it rather than\nequal to it. The automatic retry draws down the same deadline as the first\nattempt, so the stated timeout bounds the whole call, retry included.\n\n## Local development\n\n```bash\ngit clone https://github.com/Octen-Team/octen-mcp.git\ncd octen-mcp\nnpm install\nnpm run build\nOCTEN_API_KEY=<key> npm run inspect\n```\n\n## More docs\n\n- Best practices for agent integration: [docs/best-practices.md](docs/best-practices.md)\n- Search API reference: [docs.octen.ai/api-reference/search](https://docs.octen.ai/api-reference/search)\n- Extract API reference: [docs.octen.ai/api-reference/extract](https://docs.octen.ai/api-reference/extract)\n\n## License\n\n[MIT](LICENSE) © Octen\n",
  "bytes": 32312,
  "sha": "0b697f387af9ec5d7cd295e138cc06263d668bd7eb6483f829d67623f5c69538",
  "repo_slug": "octen-team/octen-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_octen_team_octen_mcp_f81de9a8/readme"
}