# Welt hosted MCP server Welt's MCP server lets an agent in a chat app or IDE use Welt's models on the user's data. It runs at Welt; there is nothing to install. - Endpoint: `https://welt.ergodic.dev/mcp` - Transport: Streamable HTTP, stateless, JSON responses. `POST` only; there are no sessions and no server-sent event streams. Protocol versions 2025-11-25 and 2026-07-28 are supported. - Sign-in: OAuth 2.1 (the client registers itself with a client ID metadata document or dynamic client registration), or a Welt API key as a Bearer token. - Tools: see the [tool reference](https://welt.ergodic.dev/agents/tools.md). - Concepts, recipes, credits and error codes: see the [agent guide](https://welt.ergodic.dev/agents/index.md). MCP is enabled for every workspace. A workspace where it has been turned off gets `403 workspace_not_enabled`. ## The Welt plugin The quickest route for any assistant is the Welt plugin: one package of per-client files that all point at the server above, published at [welt.ergodic.dev/plugin/README.md](https://welt.ergodic.dev/plugin/README.md) and listed in [index.json](https://welt.ergodic.dev/plugin/index.json). Signed-in users find the setup for their assistant under **Connect** in the console, and their connected assistants under **Settings → API keys**. | Client | Install | | --- | --- | | Claude Code (2.1.224+) | `claude plugin marketplace add https://welt.ergodic.dev/plugin/marketplace.json`, then `claude plugin install welt@welt` | | OpenAI Codex | `codex mcp add welt --url https://welt.ergodic.dev/mcp`, then `codex mcp login welt` | | Cursor | [mcp.json](https://welt.ergodic.dev/plugin/mcp.json) in `~/.cursor/mcp.json` | | VS Code | [vscode.mcp.json](https://welt.ergodic.dev/plugin/vscode.mcp.json), or **MCP: Add Server** | | Claude, ChatGPT, Gemini apps | paste the server URL into the app's connector settings (steps below) | | Gemini CLI (enterprise) | [gemini-extension.json](https://welt.ergodic.dev/plugin/gemini-extension.json) | | MCP Registry | [server.json](https://welt.ergodic.dev/plugin/server.json) | The Claude Code plugin is a zip archive ([welt.zip](https://welt.ergodic.dev/plugin/welt.zip)) whose SHA-256 digest is pinned in the marketplace file; Claude Code verifies it before installing. The plugin contains only configuration, never a credential. ## Sign in Add the server URL to your client (steps below). On first use the client opens a browser window at `welt.ergodic.dev`. The user signs in to Welt if needed and sees a consent screen that shows: - the app's name, and the host Welt will send access to (for example `claude.ai` or a `localhost` address for desktop apps). Approve only a connection you started; - **Read workspace resources**: always included. List and inspect datasets, models, predictors, jobs and results; - **Write: upload data, run models, cancel jobs**: can be unticked. Write access spends credits within the workspace's spend cap; - **Workspace**: the one workspace this connection can use. Workspaces where MCP is turned off cannot be chosen; - **Access expires after**: 1, 7, 30 (default), 90 or 365 days. The user chooses **Allow** or **Deny**. After approval the app holds a one-hour access token that it refreshes on its own until the connection expires. Each approved connection appears in the console under **Settings → API keys**, marked **Connected app**, with the host it returns to. **Disconnect** there ends it at once. Workspace owners and admins can also see and disconnect other members' connections. A connection is not an API key and has no secret anyone can copy. To use another workspace or change read/write access, disconnect and connect again. ## Client setup Server name in the examples: `welt`. Use any name you like. ### Claude Code ```sh claude mcp add --transport http welt https://welt.ergodic.dev/mcp ``` Then, in Claude Code, run `/mcp` and follow the steps in your browser to sign in. From a shell you can run `claude mcp login welt` instead (`--no-browser` prints the URL for a machine without a browser). `claude mcp logout welt` forgets the credentials. Scopes: `--scope local` (default, only you, this project), `--scope project` (writes `.mcp.json` in the project root to share with the team) or `--scope user` (all your projects). With an API key instead of OAuth (headless use). Claude Code stores the expanded header in `~/.claude.json`, so use this only with the default `--scope local` or `--scope user`. **Never combine `--header` with `--scope project`:** the shell expands the key and it would be written into the committed `.mcp.json`. ```sh claude mcp add --transport http welt https://welt.ergodic.dev/mcp \ --header "Authorization: Bearer $WELT_API_KEY" ``` In a shared `.mcp.json`, reference an environment variable so the key is never committed: ```json { "mcpServers": { "welt": { "type": "http", "url": "https://welt.ergodic.dev/mcp", "headers": { "Authorization": "Bearer ${WELT_API_KEY}" } } } } ``` Leave out `headers` to use OAuth sign-in. ### Claude (claude.ai web and Claude Desktop) Custom connectors are available on Free (one custom connector), Pro, Max, Team and Enterprise plans. Connectors added to your Claude account work in claude.ai and Claude Desktop; the connection to Welt is made from Anthropic's servers. Pro and Max: 1. Open **Customize → Connectors**, click **+ Add**, then **Add custom connector**. 2. Enter a name (`Welt`) and the URL `https://welt.ergodic.dev/mcp`. Click **Continue**. 3. Review the detected authentication settings and click **Continue**. 4. Under **Authentication**, choose **Sign in now** or **Sign in when needed**. Leave the OAuth client choice at its default (**Use Claude's published identity**) or choose **Register automatically**. Do not choose your own OAuth client: Welt has no pre-registered clients. Leave **Request headers** empty. 5. Click **Add**, then complete the Welt consent screen. Team and Enterprise: an owner adds the connector under **Organization settings → Connectors → Add**, hovers **Custom** and selects **Web**, then enters the same URL. Members then open **Customize → Connectors** and click **Connect**; each member signs in to Welt and picks their own workspace. Do not put an API key in an organization connector's request headers: everyone using it would share that one credential and workspace. In a chat, click **+** at the lower left, choose **Connectors** and switch Welt on for the conversation. ### OpenAI Codex ```sh codex mcp add welt --url https://welt.ergodic.dev/mcp codex mcp login welt ``` Or add this to `~/.codex/config.toml` and run `codex mcp login welt`: ```toml [mcp_servers.welt] url = "https://welt.ergodic.dev/mcp" ``` ### Gemini Where your Gemini plan offers **Connected apps** (Settings → Connected apps → add a custom app), enter `https://welt.ergodic.dev/mcp` and sign in when asked. Google restricts this feature by plan, region and language, and Welt has not verified the steps against Google's documentation; if the option is missing it is not available to your account. Google ended Gemini CLI access for consumer plans on 2026-06-18; where the CLI still runs, the extension manifest is [gemini-extension.json](https://welt.ergodic.dev/plugin/gemini-extension.json). ### ChatGPT OpenAI's current developer documentation describes adding a custom MCP server from ChatGPT's plugins page: 1. Go to `https://chatgpt.com/plugins`, select **+**, then **Add custom MCP server**. 2. Enter a name and description. Under **Connection**, enter the server URL `https://welt.ergodic.dev/mcp`. 3. Choose OAuth authentication, review the warning and select **I understand and want to continue**. 4. Select **Create as a plugin**, review the discovered tools and complete the Welt consent screen. ChatGPT cannot send an API key header; use OAuth. ChatGPT asks the user to approve calls to tools that are not marked read-only (every Welt tool that uploads data, runs a model or cancels a job); approvals can be remembered within a conversation. Not verified: which ChatGPT plans offer custom MCP servers, and whether an admin must enable developer mode first. OpenAI's help-center article could not be read when this page was written. If your workspace shows a "developer mode" setting under **Settings → Apps**, the connector may need it. Check OpenAI's help center. ### Cursor Put this in `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (this project): ```json { "mcpServers": { "welt": { "url": "https://welt.ergodic.dev/mcp" } } } ``` Cursor supports OAuth for remote servers and starts the Welt sign-in when it first connects. Cursor's documentation also shows a `headers` field (with `${env:NAME}` interpolation) for static credentials, but as of May 2026 Cursor staff had confirmed a bug where Cursor ignores a configured `Authorization` header and starts OAuth whenever the server advertises OAuth, which Welt does. Use OAuth sign-in in Cursor. If a connection fails, check **Output → MCP Logs**. Not verified: the exact label of the sign-in button in Cursor's MCP settings. ### VS Code (GitHub Copilot agent mode) Put this in `.mcp.json` at the workspace root (VS Code's recommended, portable file): ```json { "mcpServers": { "welt": { "type": "http", "url": "https://welt.ergodic.dev/mcp" } } } ``` The older `.vscode/mcp.json` location still works but is marked deprecated; it uses `"servers"` instead of `"mcpServers"`. VS Code opens the browser for sign-in on first use. It uses client ID metadata documents when the authorization server supports them, as Welt does. Other ways to add it: **MCP: Add Server** in the Command Palette, or **MCP: Open User Configuration** for all workspaces. With an API key instead, let VS Code prompt for it so it is not stored in the file (shown in the `.vscode/mcp.json` format, which supports `inputs`): ```json { "inputs": [ { "type": "promptString", "id": "welt-api-key", "description": "Welt API key", "password": true } ], "servers": { "welt": { "type": "http", "url": "https://welt.ergodic.dev/mcp", "headers": { "Authorization": "Bearer ${input:welt-api-key}" } } } } ``` ### Other clients and headless agents Any MCP client that supports Streamable HTTP can connect. Clients that support OAuth discover Welt's authorization server from the `401` response's `WWW-Authenticate` header (`resource_metadata` points to `https://welt.ergodic.dev/.well-known/oauth-protected-resource/mcp`). A headless agent can send a Welt API key on every request: ``` Authorization: Bearer welt_... ``` Create a key in the console (**Settings → API keys**, choose read or read and write, and an expiry) and keep it in an environment variable or secret store. Do not read keys out of the CLI's private configuration directory. Never paste it into a chat, and never commit it. Requests that carry an `Origin` header are accepted only from the console origin and these vendor origins: `https://chatgpt.com`, `https://claude.ai`, `https://gemini.google.com`, `https://vscode.dev`, `https://insiders.vscode.dev`, `https://cursor.com`, `https://www.cursor.com`; any other `Origin` is refused with 403. Requests without an `Origin` header, which is what desktop apps and vendor back ends normally send, are not affected. This is not CORS: a web page cannot call the server from the browser. ## Getting data in Every path ends in the same validation as other Welt uploads, so the same column rules, type inference and limits apply. Each ends in a dataset (`id`, `name`, `rows`, `columns`, `types`, `target`). These three tools need write access. ### Inline rows: `create_dataset` For small tables you already hold as values, up to about 1,000 rows. The request body and dataset limits apply (bodies over 10 MiB by default fail with `payload_limit`). ```json { "name": "churn sample", "columns": ["tenure", "plan", "monthly_spend", "churned"], "rows": [[12, "basic", 20.5, false], [3, "pro", 54.0, true], [40, "basic", null, false]], "target": "churned" } ``` ### Upload link: `create_upload_link` For a local CSV or Parquet file when you can run a shell command. Call `create_upload_link` with optional `name`, `target` and `format` (`csv` default, or `parquet` where enabled). It returns: ```text {"url": "https://welt.ergodic.dev/v1/upload-links/", "method": "PUT", "expires_at": "", "max_bytes": , "format": "csv", "how_to": "curl -sS -T 'https://welt.ergodic.dev/v1/upload-links/'"} ``` Send the file with one PUT: ```sh curl -sS -T data.csv 'https://welt.ergodic.dev/v1/upload-links/' ``` The response body is the new dataset. The link: - works once and expires after one hour; - needs no other credentials, so anyone with it can upload into the workspace. Do not share it, log it or write it to a file others read; - is used up when the file fails validation; fix the file and create a new link. After a temporary failure (429 `upload_busy` or a 503) the same link can be retried. - answers 409 `upload_link_used`, 410 `upload_link_expired` or 403 `link_revoked` (the creating connection or key was revoked) when it can no longer be used. Check the file size against `max_bytes` before sending. ### URL import: `import_dataset_from_url` For a file at a public `https://` URL, such as a raw GitHub file or a signed cloud-storage link. Welt fetches it on the server. - Only `https` URLs. Private, internal, loopback and link-local addresses are refused, also after redirects. - Size limit 10 MiB by default; at most three redirects; 20 seconds. - The format comes from `format`, else the URL's suffix (`.csv`, `.parquet`, `.pq`), else the response's content type; otherwise 422 `format_required`. - Policy refusals are 422 `url_refused`; temporary upstream failures (timeouts, 5xx) are 502 `url_fetch_failed` and can be retried. ```json {"url": "https://raw.githubusercontent.com/org/repo/main/data/train.csv", "name": "train", "target": "label"} ``` ### Files attached in a chat app A file attached in claude.ai or ChatGPT stays inside that app's sandbox. The MCP server cannot read it, and pasting a large table into a tool call is slow and error-prone. Ask the user to upload it in the Welt console: **Datasets → Upload a dataset**, choose the CSV or Parquet file, set the target column if any, then **Upload dataset**. Then call `list_datasets` and use the dataset by name or ID. ### Reading results in full `get_result` returns at most `max_rows` items per list (default 20, up to 200) and lists what it cut under `truncated`. It then normally also returns `download` (omitted if a link could not be created): a link to the full result as JSON (`GET https://welt.ergodic.dev/v1/result-links/`), valid for one hour and reusable until then. Anyone with the link can read the result; treat it like a password. ### Evaluation reports `get_evaluation` returns an agent-sized summary of an evaluation, never its raw out-of-fold predictions. For the user, `create_evaluation_report_link` returns a link to the HTML report (a `/reports/` URL on Welt's origin), valid for 1 to 7 days (default 7) and shown only once. Anyone with the link can view the report, without signing in, until it expires or is revoked (console, SDK or REST API). It also stops working when the key or connection that created it is revoked or expires, or its creator is no longer a verified workspace member. It shows the leaderboard and charts built from out-of-fold predictions; for regression these plot each row's actual and predicted value. It never opens the raw predictions endpoint. Give it only to the people the user chooses. Raw predictions are read through the REST API or the Python SDK. ### Forecasting models `list_models` with `family: "forecasting"` lists `chronos-2`, then `timestral-209m` and `timestral-28m`, each with `run_with: "forecast"` and its `forecast_envelopes`. Chronos-2 takes panels: several series in one `forecast` call, with an `id_column`. Timestral forecasts one series per request (every corner has `max_series: 1`): call `forecast` once per series; a call with more than one series fails with `unsupported_capability`. Timestral entries carry `attribution`: the weights are under the Modified MIT licence with the attribution notice "2026 - Mistral AI" ([Timestral-209M licence](https://huggingface.co/mistralai/Timestral-209M/blob/9874d252ab0aae16f7f7c999f98f98d6ad7d8365/LICENSE), [Timestral-28M licence](https://huggingface.co/mistralai/Timestral-28M/blob/180698bbf5a50dda3516aa6419798437fb03a430/LICENSE)). Show the notice and the link when you present a Timestral forecast. ## Read and write access | Access | Tools | | --- | --- | | Read | `list_models`, `get_model`, `get_workspace`, `get_pricing`, `list_datasets`, `get_dataset`, `list_jobs`, `get_job`, `get_result`, `list_predictors`, `get_usage`, `search_research_datasets`, `get_research_dataset`, `find_similar_research_datasets`, `list_evaluations`, `get_evaluation` | | Write | `create_dataset`, `create_upload_link`, `import_dataset_from_url`, `fit_model`, `predict`, `discover_causal_graph`, `forecast`, `run_model`, `cancel_job`, `copy_research_dataset`, `evaluate_models`, `create_evaluation_report_link` | A read-only connection gets a tool error starting `permission_denied:` from write tools. Write tools that run models spend credits (`fit_model`, `predict`, `discover_causal_graph`, `evaluate_models` and `forecast`; `run_model` runs of testing models are currently unpriced but may be priced later, except the univariate `chronos-2` run, which is priced like a forecast). `copy_research_dataset` is charged like a research download. `create_evaluation_report_link` is listed as a write tool because it creates a link anyone can open, but read access suffices for it. ## What the server never does - It never deletes datasets, predictors, results, evaluations or anything else, and it never revokes report links. - It never manages accounts, members, invitations, API keys, workspaces or billing. It cannot buy credits or change a spend cap. Those need the user in the console. - It never reaches another workspace than the one the connection was approved for. - It never sends your data to external model APIs; models run on Welt's workers. ## Troubleshooting | Symptom | Meaning | Fix | | --- | --- | --- | | HTTP 401, or `authentication_required` | No credential, or it expired or was disconnected | Reconnect Welt in the client (Claude Code: `/mcp`). With an API key, check it is current. | | HTTP 403 `workspace_not_enabled` | MCP access is turned off for this workspace | Use the CLI or SDK, or connect a workspace where MCP is enabled. | | Tool error `permission_denied:` | The connection is read-only, or the role does not allow it | Disconnect in **Settings → API keys**, then reconnect and keep **Write** ticked. | | HTTP 403 `request_forbidden` | The request carried a foreign `Origin` header | Call the server from an MCP client, not from a web page on another site. | | HTTP 405 on `GET` or `DELETE /mcp` | Expected: the server is stateless and accepts `POST` only | Nothing to fix; MCP clients fall back to `POST`. A client that requires an SSE stream or a session cannot use this server. | | Tool error `insufficient_credits:` or `spend_cap_reached:` | The workspace cannot pay for the work | Tell the user. An owner buys credits or raises the cap in the console. | | `get_job` returns `finished: false` | The job is still running | Call `get_job` again. Do not resubmit. | Every tool error includes a request ID. Give it to the user if they need support. ## Sources Client steps were checked on 2026-10-13 against: - Claude Code: https://code.claude.com/docs/en/mcp - Claude custom connectors: https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp - ChatGPT: https://developers.openai.com/plugins/deploy/connect-chatgpt, https://developers.openai.com/plugins/build/auth and https://developers.openai.com/api/docs/guides/custom-mcp-server - Cursor: https://cursor.com/docs/context/mcp; header bug: https://forum.cursor.com/t/mcp-headers-config-ignored-when-server-has-oauth-discovery/156054 - VS Code: https://code.visualstudio.com/docs/copilot/customization/mcp-servers, https://code.visualstudio.com/docs/agents/reference/mcp-configuration and https://code.visualstudio.com/updates/v1_106 Vendors change these screens often. If a label differs, follow the vendor's current documentation.