From 6df4528f5b55810f654766dbdead1d2789630c6d Mon Sep 17 00:00:00 2001 From: Viktor Yershov Date: Tue, 26 May 2026 09:53:38 -0700 Subject: [PATCH 1/2] Add mcp gateway example --- .../features/large-language-models/mcp.md | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/website/docs/features/large-language-models/mcp.md b/website/docs/features/large-language-models/mcp.md index 56ce6a871..1cf70cee3 100644 --- a/website/docs/features/large-language-models/mcp.md +++ b/website/docs/features/large-language-models/mcp.md @@ -118,3 +118,71 @@ tools: ``` For more details, see the [MCP Tools Reference](../../components/tools/mcp). + +## Example: Spice as an MCP gateway for GitHub and Jira + +Spice can act as a unified MCP gateway, proxying multiple external MCP servers alongside its own built-in tools. Clients connect to a single `/v1/mcp` endpoint and see one combined tool catalog. + +```yaml +version: v1 +kind: Spicepod +name: mcp-gateway + +# GitHub issues and PRs accelerated in-memory for fast SQL queries +datasets: + - from: github:github.com/your-org/your-repo/issues + name: github_issues + description: GitHub issues — filterable by state, label, assignee, or milestone + params: + github_token: ${secrets:GITHUB_PERSONAL_ACCESS_TOKEN} + github_query_mode: search + time_column: updated_at + acceleration: + enabled: true + refresh_check_interval: 5m + + - from: github:github.com/your-org/your-repo/pulls + name: github_pulls + description: GitHub pull requests — open, merged, or closed + params: + github_token: ${secrets:GITHUB_PERSONAL_ACCESS_TOKEN} + github_query_mode: search + time_column: updated_at + acceleration: + enabled: true + refresh_check_interval: 5m + +# MCP servers proxied through Spice's /v1/mcp endpoint +tools: + - name: github + from: mcp:npx + description: GitHub tools — create issues, review PRs, search code + params: + mcp_args: -y @modelcontextprotocol/server-github + env: + GITHUB_PERSONAL_ACCESS_TOKEN: ${secrets:GITHUB_PERSONAL_ACCESS_TOKEN} + + - name: jira + from: mcp:uvx + description: Jira and Confluence tools — query tickets, update status, search projects + params: + mcp_args: mcp-atlassian + env: + JIRA_URL: ${secrets:JIRA_URL} + JIRA_USERNAME: ${secrets:JIRA_USERNAME} + JIRA_API_TOKEN: ${secrets:JIRA_API_TOKEN} +``` + +With this configuration, clients connecting to `/v1/mcp` see a single catalog that includes: + +- Spice built-in tools: `sql`, `list_datasets`, `table_schema`, `search`, and more +- GitHub MCP tools: `github/create_issue`, `github/list_pull_requests`, `github/search_code`, and more +- Jira MCP tools: `jira/get_issue`, `jira/search_issues`, `jira/create_issue`, and more + +Connect Claude Code to this gateway with a single command: + +```bash +claude mcp add --transport http spice http://localhost:8090/v1/mcp --header "X-API-KEY: " +``` + +See the [mcp-server cookbook](https://github.com/spiceai/cookbook/tree/trunk/mcp-server) for a complete runnable example. From 11581af7e3d8d8d70e87c4ae7b64624051f40fbf Mon Sep 17 00:00:00 2001 From: Viktor Yershov Date: Sun, 2 Aug 2026 09:20:05 -1000 Subject: [PATCH 2/2] Fixes --- .../features/large-language-models/mcp.md | 109 +++++++++++++++++- 1 file changed, 106 insertions(+), 3 deletions(-) diff --git a/website/docs/features/large-language-models/mcp.md b/website/docs/features/large-language-models/mcp.md index 1cf70cee3..bc194cfe5 100644 --- a/website/docs/features/large-language-models/mcp.md +++ b/website/docs/features/large-language-models/mcp.md @@ -62,12 +62,31 @@ models: Spice can also act as an MCP server, exposing its tools over Streamable HTTP. This enables other Spice instances or external systems to connect and use the tools. +### Authentication + +The `/v1/mcp` endpoint requires [`runtime.auth`](../../reference/spicepod/runtime#runtimeauth) to be configured. Without it, every request is rejected with `401 Unauthorized`, including requests from local clients: + +```yaml +runtime: + auth: + api_key: + enabled: true + keys: + - ${secrets:SPICE_API_KEY} +``` + +Clients then authenticate with an `X-API-KEY` header. + ### Example: Connecting to another Spice instance via MCP +Because the remote endpoint requires authentication, pass credentials with `mcp_headers` — or `mcp_auth_token` to send `Authorization: Bearer`. See [Connecting to an Auth-Enabled MCP Server](../../components/tools/mcp#example-connecting-to-an-auth-enabled-mcp-server-streamable-http). + ```yaml tools: - name: spice_instance from: mcp:http://localhost:8090/v1/mcp + params: + mcp_headers: 'X-API-KEY: ${secrets:SPICE_API_KEY}' ``` ### Allowed Hosts @@ -128,6 +147,14 @@ version: v1 kind: Spicepod name: mcp-gateway +# Required — /v1/mcp returns 401 to every request without runtime.auth +runtime: + auth: + api_key: + enabled: true + keys: + - ${secrets:SPICE_API_KEY} + # GitHub issues and PRs accelerated in-memory for fast SQL queries datasets: - from: github:github.com/your-org/your-repo/issues @@ -176,13 +203,89 @@ tools: With this configuration, clients connecting to `/v1/mcp` see a single catalog that includes: - Spice built-in tools: `sql`, `list_datasets`, `table_schema`, `search`, and more -- GitHub MCP tools: `github/create_issue`, `github/list_pull_requests`, `github/search_code`, and more -- Jira MCP tools: `jira/get_issue`, `jira/search_issues`, `jira/create_issue`, and more +- GitHub MCP tools: `github__create_issue`, `github__list_pull_requests`, `github__search_code`, and more +- Jira MCP tools: `jira__jira_get_issue`, `jira__jira_search`, `jira__jira_create_issue`, and more + +### Proxied tool names + +A proxied tool is exposed as `__`, joined by a **double underscore**. The `tool-name` is the `name` given in the `tools` section, so renaming the tool renames every tool it proxies. + +The separator is not a `/`, because MCP clients such as Claude and Cursor reject any tool name that does not match `^[a-zA-Z0-9_-]{1,64}$`. + +Where an upstream server already namespaces its own tools, the prefix appears doubled — `mcp-atlassian` exposes `jira_get_issue`, so a tool named `jira` yields `jira__jira_get_issue`. This is expected. + +### Jira credentials + +The `jira` tool contributes **no tools at all** unless all three `JIRA_*` values resolve. `mcp-atlassian` starts successfully without them, registers nothing, and Spice logs no tool-loading error — the runtime reports healthy with Jira silently missing from the catalog. Check the startup log for `runtime_secrets` errors if `jira__*` tools do not appear. + +`JIRA_URL` depends on which kind of API token is used, and a mismatch is the most common cause of an empty Jira catalog: + +| Token type | `JIRA_URL` | +| --- | --- | +| Classic (unscoped) API token | `https://your-org.atlassian.net` | +| Scoped API token | `https://api.atlassian.com/ex/jira/` | + +Scoped tokens authenticate only against the `api.atlassian.com` gateway and return `401` against the site URL. Retrieve the cloud ID for a site from `https://your-org.atlassian.net/_edge/tenant_info`, which requires no authentication. Both token types use `JIRA_USERNAME` (the account email address) with HTTP basic auth. + +### Connecting a client -Connect Claude Code to this gateway with a single command: +Connect Claude Code to this gateway with a single command, using a key from `runtime.auth`: ```bash claude mcp add --transport http spice http://localhost:8090/v1/mcp --header "X-API-KEY: " ``` +:::warning Large tool catalogs + +The two servers above expose close to 100 tools between them, which degrades tool selection in most MCP clients. Restrict each server to the tools actually needed — for `mcp-atlassian`, set `TOOLSETS` in its `env` block. + +::: + See the [mcp-server cookbook](https://github.com/spiceai/cookbook/tree/trunk/mcp-server) for a complete runnable example. + +## Troubleshooting + +### A tool is missing from the catalog + +A failing MCP server does not stop the runtime. Spice logs a warning, retries the connection in the background, and reports healthy — the tool is simply absent from `tools/list`. Verify the runtime log at startup: + +```bash +grep "Unable to load tool" +``` + +A server that starts but authenticates with empty or invalid credentials is harder to spot: it registers zero tools without producing that warning. Check for `runtime_secrets` errors, which indicate a `${secrets:...}` reference that did not resolve. + +### Every request returns `401` + +`/v1/mcp` requires [`runtime.auth`](#authentication). The response body names the missing configuration: + +```json +{ "message": "MCP endpoint (/v1/mcp) requires `runtime.auth` to be configured. ..." } +``` + +### Every request returns `403` + +The `Host` header is not in [`runtime.mcp.allowed_hosts`](#allowed-hosts). This is the DNS-rebinding guard, and it applies to any host other than `localhost`, `127.0.0.1`, or `::1`. + +### Listing the catalog directly + +To confirm what a client actually sees, call `tools/list` over the Streamable HTTP transport. The `Accept` header must offer both content types, and the session ID returned by `initialize` is required on subsequent requests: + +```bash +curl -sS -D headers.txt -X POST http://localhost:8090/v1/mcp \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json, text/event-stream' \ + -H 'X-API-KEY: ' \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' + +SID=$(grep -i mcp-session-id headers.txt | tr -d '\r' | awk '{print $2}') + +curl -sS -X POST http://localhost:8090/v1/mcp \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json, text/event-stream' \ + -H 'X-API-KEY: ' \ + -H "Mcp-Session-Id: $SID" \ + -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' +``` + +Responses are returned as SSE frames prefixed with `data:`.