Metadata-Version: 2.4
Name: uc-mcp-proxy
Version: 0.5.1
Summary: MCP stdio-to-Streamable-HTTP proxy with Databricks OAuth
Project-URL: Homepage, https://github.com/IceRhymers/uc-mcp-proxy
Project-URL: Repository, https://github.com/IceRhymers/uc-mcp-proxy
Project-URL: Issues, https://github.com/IceRhymers/uc-mcp-proxy/issues
Author: Tanner Wendland
License-Expression: MIT
License-File: LICENSE
Keywords: databricks,mcp,oauth,proxy,unity-catalog
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Requires-Dist: anyio
Requires-Dist: databricks-sdk>=0.30.0
Requires-Dist: httpx
Requires-Dist: mcp<2,>=1.8
Description-Content-Type: text/markdown

# uc-mcp-proxy

MCP stdio-to-Streamable-HTTP proxy with Databricks OAuth.

Lets any MCP client that speaks **stdio** (e.g. Claude Desktop, Claude Code) connect to any **Databricks MCP server** — Managed, External, or Apps — handling authentication automatically.

## Installation

```bash
# Run directly (no install needed)
uvx uc-mcp-proxy --url <MCP_SERVER_URL>

# Or install globally
uv tool install uc-mcp-proxy
```

Requires Python 3.10+.

## First-run authentication

uc-mcp-proxy expects a configured Databricks CLI profile. Set one up first:

    databricks configure --host https://<workspace>

Then pass it to the proxy:

    uvx uc-mcp-proxy --url <MCP_SERVER_URL> --profile <name>

If the profile uses OAuth U2M (`auth_type = databricks-cli`) and the cached
token is expired, uc-mcp-proxy runs `databricks auth login --profile <name>`
automatically the first time it launches, opening a browser tab. Subsequent
runs use the refreshed token.

**uc-mcp-proxy will only auto-login for OAuth (`databricks-cli`) profiles.**
For PAT, M2M, Azure, or other auth types, the proxy diagnoses the failure
and points you at the right remediation — it never runs `databricks auth
login` against a non-OAuth profile because that would overwrite your
existing credentials in `~/.databrickscfg`.

To skip the auto-login (CI / headless), pass `--no-auto-login` and ensure
`DATABRICKS_TOKEN` or another credential is set in the environment.

## Databricks MCP Server Types

| Server Type | URL Pattern |
|-------------|-------------|
| **Managed MCP** (UC Functions, Vector Search, Genie, SQL) | `https://<workspace>/api/2.0/mcp/functions/{catalog}/{schema}` |
| **External MCP** (GitHub, Google Drive, and others) | `https://<workspace>/api/2.0/mcp/external/{connection_name}` |
| **Apps** (custom MCP servers) | `https://<app-name>-<workspace-id>.<region>.databricksapps.com/<path>` |

An App is served from its own hostname on `databricksapps.com` — **not** from a
path under your workspace host. Databricks assigns the URL when the app is
created and it cannot be changed afterwards, so copy it from the **Apps** page
in the workspace UI (or `databricks apps list`) rather than constructing it by
hand — on the workspace we tested, the segment Databricks documents as
`<region>` is the cloud name (`aws`), not a region like `us-east-1`. The
`<path>` is whatever route the app serves MCP on; `/mcp` is the common
convention.

> **Apps reject raw personal access tokens.** The App front door requires an
> OAuth token, so use `--auth-type databricks-cli` (browser-based OAuth U2M)
> when connecting to a Databricks App. Managed and External MCP servers also
> work with PAT and other auth types.

## Usage

### Claude Desktop / Claude Code (`.mcp.json`)

Add to your MCP client configuration:

```json
{
  "mcpServers": {
    "unity-catalog": {
      "type": "stdio",
      "command": "uvx",
      "args": [
        "uc-mcp-proxy",
        "--url", "<MCP_SERVER_URL>"
      ]
    }
  }
}
```

### CLI

```bash
uc-mcp-proxy --url <MCP_SERVER_URL> [--profile <DATABRICKS_PROFILE>] [--auth-type <AUTH_TYPE>]
```

| Flag | Description |
|------|-------------|
| `--url` | **(required)** Remote MCP server URL |
| `--profile` | Databricks CLI profile name (uses default if omitted) |
| `--auth-type` | Databricks auth type, e.g. `databricks-cli` |
| `--meta KEY=VALUE` | Meta parameter injected into `tools/call` `_meta` (repeatable) |
| `--no-verify-ssl` | Disable SSL certificate verification (use with caution — see below) |

## Meta Parameters (Managed MCP)

Databricks Managed MCP servers accept configuration — for example, selecting a SQL warehouse — via the MCP [`_meta`](https://modelcontextprotocol.io/specification/2025-11-25/basic#_meta) field in the JSON-RPC request body, **not** as HTTP headers. See the Databricks [meta-param docs](https://docs.databricks.com/aws/en/generative-ai/mcp/managed-mcp-meta-param) for the supported keys per server type.

Use `--meta KEY=VALUE` (repeatable) — the proxy merges these into `params._meta` on every outgoing `tools/call` request:

```bash
uvx uc-mcp-proxy \
  --url https://workspace.databricks.com/api/2.0/mcp/sql \
  --meta warehouse_id=abc123
```

Or in `.mcp.json`:

```json
{
  "mcpServers": {
    "sql-mcp": {
      "type": "stdio",
      "command": "uvx",
      "args": [
        "uc-mcp-proxy",
        "--url", "https://workspace.databricks.com/api/2.0/mcp/sql",
        "--meta", "warehouse_id=abc123"
      ]
    }
  }
}
```

If the MCP client already sets a `_meta` key that the proxy is also configured to inject, the proxy value wins and a warning is written to stderr.

## SSL Certificate Verification

Some Azure Databricks instances use self-signed or internally-signed certificates that are not trusted by the system's default CA bundle. This causes errors like:

```
SSL_CERTIFICATE_VERIFY_FAILED: certificate verify failed: unable to get local issuer certificate
```

Use `--no-verify-ssl` to disable certificate verification:

```bash
uvx uc-mcp-proxy \
  --url https://workspace.azuredatabricks.net/api/2.0/mcp/functions/main/default \
  --no-verify-ssl
```

Or in `.mcp.json`:

```json
{
  "mcpServers": {
    "unity-catalog": {
      "type": "stdio",
      "command": "uvx",
      "args": [
        "uc-mcp-proxy",
        "--url", "https://workspace.azuredatabricks.net/api/2.0/mcp/functions/main/default",
        "--no-verify-ssl"
      ]
    }
  }
}
```

> **Security warning:** `--no-verify-ssl` disables all certificate validation, which exposes connections to man-in-the-middle (MITM) attacks. Only use this flag in trusted network environments (e.g. a private corporate VPN) where you control the network path to the Databricks workspace.

## How It Works

1. Starts an MCP **stdio** server (stdin/stdout)
2. Connects to the remote MCP server via **Streamable HTTP**
3. Injects a fresh Databricks OAuth token on every HTTP request
4. Bridges messages bidirectionally between the two transports

## Authentication

Authentication is handled by the [Databricks SDK](https://docs.databricks.com/dev-tools/sdk-python.html). The SDK auto-detects the method, or you can force one with `--auth-type`.

| Auth type | Managed / External MCP | Apps MCP |
|-----------|------------------------|----------|
| `databricks-cli` — token from `~/.databrickscfg` | ✅ | ✅ recommended |
| `pat` — personal access token | ✅ | ❌ rejected [^pat] |
| `oauth-m2m` — service principal | ✅ | ✅ [^m2m] |
| OAuth U2M — browser-based login | ✅ | ✅ |

[^pat]: An App rejects a PAT sent as-is, because it requires an OAuth token.
    That is a statement about the *token*, not about your profile: a PAT can be
    exchanged for an OAuth token (RFC 8693), which uc-mcp-proxy does not do
    today. Until it does, PAT users should authenticate with
    `--auth-type databricks-cli` rather than treating Apps as unreachable.

[^m2m]: Verified against a live App-hosted MCP server: `initialize`,
    `tools/list`, and `tools/call` all succeed through the proxy with a service
    principal's `oauth-m2m` credentials. The principal must be granted
    `CAN_USE` on the app first — without that grant the App answers **401**,
    not 403, so a missing permission is easy to misread as the auth type being
    unsupported.

## Troubleshooting

When the remote MCP server refuses a request, the proxy prints a diagnosis to
stderr naming the status, the URL, the profile, and the auth type in use.

| Message | Meaning | Fix |
|---------|---------|-----|
| `rejected your credentials (HTTP 401)` | The token was minted locally but the server rejected it — it may have expired, or this profile's identity is not recognized by the target. | Refresh the profile's credentials. For `databricks-cli`, run `databricks auth login --profile <name>`. Against a Databricks App this also appears when the identity simply lacks `CAN_USE` on the app — check the app's permissions before assuming the credential is bad. |
| `refused this request (HTTP 403)` | Authenticated successfully, but not authorized for this target. | Check your grants on the target. Pointing a `pat` profile at a Databricks App produces this — Apps reject a raw PAT and need an OAuth token, so use `--auth-type databricks-cli`. |
| `no MCP endpoint at this URL (HTTP 404)` | The URL is wrong. Not an auth failure. | Check `--url`. |
| `the MCP session expired server-side (HTTP 404)` | The server no longer recognizes this session. | Restart the MCP client to establish a new session. |
| `the remote MCP server failed (HTTP 5xx)` | Server-side error, **not** an authentication problem. | Retry; check Databricks service status. |

A failure on the background server→client stream is reported but does not stop
the proxy: the SDK retries a bounded number of times and then stops, so
server-initiated messages may be lost while tool calls keep working.

## Development

```bash
uv sync                        # install dependencies
uv run pytest -m unit -v       # run unit tests
uv run pytest -m integration -v # run integration tests
uv build                       # build package
```

## License

MIT
