Metadata-Version: 2.4
Name: easydeploy-ai-mcp
Version: 0.2.0
Summary: EasyDeploy AI MCP server — REST API tools for Claude and other MCP clients
Author: EasyDeploy AI
License: MIT
Project-URL: Homepage, https://easydeploy.ai
Project-URL: Documentation, https://github.com/easydeploy-ai/easydeploy-ai-mcp#readme
Project-URL: Repository, https://github.com/easydeploy-ai/easydeploy-ai-mcp
Project-URL: Issues, https://github.com/easydeploy-ai/easydeploy-ai-mcp/issues
Keywords: mcp,easydeploy,llm,claude,automl
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
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
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastmcp<4,>=3.1.1
Requires-Dist: httpx<1,>=0.27
Requires-Dist: python-dotenv>=1.1.0
Requires-Dist: uvicorn[standard]<1,>=0.30
Provides-Extra: oauth
Requires-Dist: pyjwt[crypto]<3,>=2.8; extra == "oauth"
Provides-Extra: dev
Requires-Dist: pytest<10,>=8.2; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
Requires-Dist: pyjwt[crypto]<3,>=2.8; extra == "dev"
Requires-Dist: certifi>=2024.0.0; extra == "dev"
Dynamic: license-file

# EasyDeploy AI MCP (`easydeploy-ai-mcp`)

A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that exposes the **[EasyDeploy](https://easydeploy.ai)** public REST API as tools for Claude, Cursor, Claude Code, and other MCP clients.

**PyPI package name:** `easydeploy-ai-mcp` · **Import package:** `easydeploy_ai_mcp`

## Contents

- [Connect with Claude](#connect-with-claude)
- [What you get](#what-you-get)
- [Requirements](#requirements)
- [Install](#install)
- [Environment variables](#environment-variables)
- [Local MCP (stdio)](#local-mcp-stdio)
- [Remote MCP (HTTP)](#remote-mcp-http)
- [Documentation](#documentation)
- [REST API reference](#rest-api-reference)
- [Releases and API compatibility](#releases-and-api-compatibility)
- [Development](#development)
- [Security](#security)
- [License](#license)

## Connect with Claude

### Hosted connector ([claude.ai](https://claude.ai) or Claude Desktop)

We host the MCP endpoint. You add it once inside Claude; after you connect and sign in, new chats can use EasyDeploy like any other enabled connector.

1. Open Claude in the browser or open **Claude Desktop**.
2. Open **Settings**, then **Connectors**, and choose **Add custom connector**.
3. Enter exactly:
   - **Name:** `EasyDeploy AI`
   - **Remote MCP server URL:** `https://mcp.easydeploy.ai/mcp`
4. Save the connector so EasyDeploy appears in your list of connectors.
5. Open the EasyDeploy connector entry and choose **Connect**, then finish sign-in in your browser. That step authorizes Claude to use your EasyDeploy account.

**Connect and sign in (inside Claude):** After the custom connector is set up, Claude shows an EasyDeploy card with the MCP URL and a **Connect** button. Use that flow to sign in. You do not paste an API key into Claude. Access stays tied to the EasyDeploy profile you authenticate in the browser.

**Claude Desktop and file uploads:** File uploads and some tool calls reach EasyDeploy over the network. On Desktop, Claude blocks outbound traffic unless you allow the domains it should call. Open **Settings → Capabilities**, turn on **Allow network egress**, and under the domain allowlist add this entry exactly (including the leading `*.`):

`*.execute-api.us-east-1.amazonaws.com`

> The domain allowlist UI is available on paid Claude plans.

More detail and variants (for example a URL from your own deployment) are in [docs/claude-getting-started.md](docs/claude-getting-started.md).

---

### Local MCP on your computer (stdio)

Run the MCP server on your own machine using your EasyDeploy API key. Nothing is exposed to the internet.

**1. Install**

```bash
pip install easydeploy-ai-mcp
```

**2. Add to Claude Desktop config**

Edit (or create) the Claude Desktop config file:

| OS      | Path |
| ------- | ---- |
| macOS   | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |

Merge the following into the root of that JSON (keep any existing keys):

```json
{
  "mcpServers": {
    "EasyDeploy AI": {
      "command": "easydeploy-ai-mcp-stdio",
      "env": {
        "EDA_API_KEY": "eda_live_YOUR_KEY"
      }
    }
  }
}
```

Replace `eda_live_YOUR_KEY` with your key from **Account → API Keys** in the EasyDeploy dashboard. Use the full path to `easydeploy-ai-mcp-stdio` (run `which easydeploy-ai-mcp-stdio` to find it) if Claude cannot locate it on your `PATH`.

**3. Restart Claude Desktop**

Fully quit and reopen the app. **EasyDeploy AI** will appear in your MCP servers.

---

For self-hosting on Docker or a cloud provider, see [Remote MCP (HTTP)](#remote-mcp-http).

## What you get

- **24 tools** covering projects, datasets (including upload flow), model versions, training jobs, predictions, and account status.
- **stdio** transport for local clients, or **HTTP** with Streamable MCP on `/mcp` and **GET /healthz** for load balancers.
- **Hardening:** HTTPS-only calls to the EasyDeploy API; optional `MCP_SERVICE_TOKEN` for the HTTP MCP surface; response fields trimmed where appropriate for agents.

For **production** and **SOC 2–sensitive** setups, prefer self-hosting so your data stays within your own infrastructure. The hosted connector at `https://mcp.easydeploy.ai/mcp` is fine for most users.

## Requirements

- Python **3.10+**
- An EasyDeploy **API key** from the dashboard (**Account → API Keys**). The client uses the production EasyDeploy API host by default.

## Install

### From PyPI (after first release)

```bash
pip install easydeploy-ai-mcp
```

### From source

```bash
git clone https://github.com/easydeploy-ai/easydeploy-ai-mcp.git
cd easydeploy-ai-mcp
pip install -e ".[dev]"   # includes pytest
# or minimal runtime only:
pip install .
```

## Environment variables


| Variable                           | Required | Description                                                                                     |
| ---------------------------------- | -------- | ----------------------------------------------------------------------------------------------- |
| `EDA_API_KEY`                      | stdio / legacy HTTP | Required for **stdio** and **legacy** HTTP (no OAuth). **Not** used for outbound API calls when `EDA_OAUTH_ENABLED=1` — each MCP request must include `Authorization: Bearer <JWT or eda_live_…>`. |
| `EDA_API_BASE`                     | No       | Overrides the default production API (`https://api.easydeploy.ai`). Set only when targeting a non-production endpoint. Trailing `/v1` is optional. |
| `EDA_UI_BASE_URL`                  | No       | Prefix for `ui_url` fields (default `https://easydeploy.ai`).                                   |
| `MCP_SERVICE_TOKEN`                | No       | Legacy single-tenant gate. If set, HTTP mode requires `Authorization: Bearer <token>` for `/mcp` (not for `GET /healthz`). Mutually exclusive with `EDA_OAUTH_ENABLED`. |
| `EDA_OAUTH_ENABLED`                | No       | Set to `1` to run the HTTP transport as an OAuth 2.0 resource server. Requires `EDA_COGNITO_USER_POOL_ID` and `EDA_COGNITO_CLIENT_ID`. See [Remote MCP (HTTP)](#remote-mcp-http). |
| `EDA_COGNITO_USER_POOL_ID`         | OAuth    | Cognito user pool that issues access tokens for the EasyDeploy API.                             |
| `EDA_COGNITO_CLIENT_ID`            | OAuth    | App client ID expected in the access token's `client_id` claim.                                 |
| `EDA_COGNITO_REGION`               | No       | AWS region for the user pool (default `us-east-1`).                                             |
| `EDA_REPORT_MAX_WAIT_SECONDS`      | No       | `get_model_report` poll budget (default `300`).                                                 |
| `EDA_REPORT_POLL_INTERVAL_SECONDS` | No       | Poll interval in seconds (default `10`).                                                        |
| `HOST` / `PORT`                    | No       | HTTP bind (defaults `0.0.0.0` / `8080`).                                                        |
| `EDA_TRUST_FORWARDED_HEADERS`      | No       | Set to `1` behind ALB/reverse proxy so RFC 9728 `resource` uses `https` (trusts `X-Forwarded-Proto`). |
| `EDA_MCP_OAUTH_ISSUER`             | No       | Public MCP base URL (no path) for `authorization_servers` and proxy `/.well-known/oauth-authorization-server` **`issuer`**. Default: request origin. Use if `Host` / `X-Forwarded-Proto` are wrong behind a proxy. |
| `EDA_CORS_EXTRA_ORIGINS`           | No       | Comma-separated browser origins to allow in addition to the built-in list (Claude, ChatGPT, VS Code Web, Cursor, MCP Inspector). |
| `EDA_MCP_OAUTH_BROKER`             | No       | Set to `1` to broker the authorization redirect. Default off. See [Brokered authorization](#brokered-authorization). |
| `EDA_MCP_EXTRA_REDIRECT_HOSTS`     | No       | Broker mode: comma-separated extra HTTPS callbacks accepted as client `redirect_uri`. `host` (any path), `host/path` (exact), `host/path/` (prefix), `.host` (host and subdomains). |
| `EDA_MCP_EXTRA_REDIRECT_SCHEMES`   | No       | Broker mode: comma-separated extra private-use URI schemes (RFC 8252 §7.1) accepted as client `redirect_uri`. |
| `EDA_MCP_ALLOW_LOOPBACK_REDIRECT`  | No       | Broker mode: set to `0` to refuse `http://127.0.0.1:<port>` callbacks. Default `1` — desktop clients need them. |
| `EDA_MCP_BROKER_SECRET`            | No       | Broker mode: HMAC key that seals the OAuth `state`. Set it in any real deployment; the fallback is derived from the Cognito pool and client ids, which are public, and the server logs a warning without it. |


## Local MCP (stdio)

Use when the client **starts** the server as a subprocess (Claude Desktop, Cursor, etc.).

```bash
export EDA_API_KEY="eda_live_..."
easydeploy-ai-mcp-stdio
```

Or: `python -m easydeploy_ai_mcp`

Example config snippet:

```json
{
  "mcpServers": {
    "easydeploy-ai": {
      "command": "easydeploy-ai-mcp-stdio",
      "env": {
        "EDA_API_KEY": "eda_live_..."
      }
    }
  }
}
```

## Remote MCP (HTTP)

Serves **Streamable HTTP** via FastMCP on **`/mcp`** (confirm with your pinned **FastMCP 3.x** version). Health checks: **GET /healthz**.

```bash
export EDA_API_KEY="eda_live_..."
easydeploy-ai-mcp-http
```

Or: `uvicorn easydeploy_ai_mcp.http_main:app --host 0.0.0.0 --port 8080`

If you embed `mcp.http_app()` in another ASGI app, pass through **`lifespan`** from the FastMCP HTTP app ([FastMCP ASGI](https://gofastmcp.com/deployment/asgi)); `easydeploy_ai_mcp.http_main` already does this for uvicorn.

### Auth modes

Pick exactly one (setting both `EDA_OAUTH_ENABLED` and `MCP_SERVICE_TOKEN` raises at import):

- **OAuth 2.0 resource server** (multi-tenant): set `EDA_OAUTH_ENABLED=1` plus
  `EDA_COGNITO_USER_POOL_ID` and `EDA_COGNITO_CLIENT_ID`. Install the optional
  extra: `pip install easydeploy-ai-mcp[oauth]`. The server validates incoming
  Cognito **access** JWTs locally against the Cognito JWKS (issuer, signature,
  `exp`, `token_use=='access'`, `client_id`) and forwards the token to the
  EasyDeploy API. EasyDeploy API keys (prefix `eda_live_`) are accepted in the
  same `Authorization: Bearer` header and forwarded as-is — the API is the
  source of truth for revocation. RFC 9728 metadata is published at
  `/.well-known/oauth-protected-resource`; RFC 8414 proxy metadata includes
  **`registration_endpoint`**, and **`POST /oauth/register`** returns the static
  Cognito MCP **`EDA_COGNITO_CLIENT_ID`** (RFC 7591-style, public client). 401
  responses include `WWW-Authenticate: Bearer …` so MCP clients can discover the
  auth server.
  Note: Cognito access tokens carry `client_id`, **not** `aud`; do not configure
  an audience.
- **Shared-secret gate** (legacy single-tenant): set `MCP_SERVICE_TOKEN`. All
  outbound API calls use the static `EDA_API_KEY`.
- **No auth**: development only.

### Brokered authorization

Cognito matches `redirect_uri` **literally** against the app client's callback
list — no wildcards, no prefixes, no ports — and it checks at
`/oauth2/authorize`, *before* rendering the Hosted UI. A client whose callback
is not registered therefore never sees a sign-in page: the browser lands on
`redirect_mismatch`, which reads as "the connector could not load the login
page" rather than as a configuration problem.

That works for clients with one fixed callback and cannot work for the rest:

| Client | Callback | Registrable? |
| --- | --- | --- |
| Claude (web, Desktop) | `https://claude.ai/api/mcp/auth_callback` | yes |
| ChatGPT custom connector | `https://chatgpt.com/connector_platform_oauth_redirect`, or `https://chatgpt.com/connector/oauth/<callback_id>` when it cannot identify the issuer | the first only |
| VS Code / Insiders | `https://vscode.dev/redirect` | yes |
| Claude Code, Cursor, MCP Inspector | `http://127.0.0.1:<port chosen at runtime>/callback` | no |

Set `EDA_MCP_OAUTH_BROKER=1` and the server sends Cognito **its own** callback,
`{issuer}/oauth/callback`, which is one fixed URL you register once. It seals
the client's real `redirect_uri` into the OAuth `state`, and when Cognito comes
back it forwards the authorization code to that URI with the client's own
`state` restored. `/token` rewrites `redirect_uri` to match, because Cognito
checks it a second time at the token exchange.

**This moves the redirect check from Cognito to this server, so it stays a real
check.** Without one, anybody could start a flow here with a callback they
control and collect a signed-in user's authorization code. `oauth_broker.py`
is about as tight as the list it replaces: an HTTPS callback must match a known
client's host exactly and, where that client's callback path is known, the path
too (`claude.ai/api/mcp/auth_callback`, `chatgpt.com/connector/oauth/…`,
`vscode.dev/redirect`). Subdomains are never implied — one open redirect or
dangling CNAME under a vendor's domain would otherwise be a place to collect
codes. Loopback is accepted per RFC 8252 §7.3, and private-use schemes from a
short allowlist. Extend it with `EDA_MCP_EXTRA_REDIRECT_HOSTS` /
`EDA_MCP_EXTRA_REDIRECT_SCHEMES` rather than by widening the code.

**PKCE is mandatory in broker mode.** Cognito treats `code_challenge` as
optional, and the broker's `/token` rewrite removes the one check Cognito made
there, so a flow without PKCE would leave the code bound to nothing — anyone
who found it in a browser history or a proxy log could redeem it. `/authorize`
refuses a brokered request without `code_challenge_method=S256`. Every MCP
client already sends one.

**Rollout order matters.** Register `{issuer}/oauth/callback` on the Cognito app
client *first*, then set `EDA_MCP_OAUTH_BROKER=1`. Doing it the other way round
breaks the clients that work today, which is why the default is off.

**Docker — run locally** (same image you deploy to ECS/Fargate; includes `easydeploy-ai-mcp[oauth]`):

```bash
# Convenience: build + run (reads .env in the repo root if present)
./scripts/run_mcp_docker_local.sh

# Explicit env vars (no .env)
./scripts/run_mcp_docker_local.sh -e EDA_API_KEY="eda_live_..."

# Different host port
PORT=9000 ./scripts/run_mcp_docker_local.sh
```

**Host on AWS (Fargate + ALB):** build and push this repo’s **Dockerfile** to a container registry, then deploy behind an HTTPS load balancer. Set the env vars listed above on the task/container.

Manual equivalent:

```bash
docker build -t easydeploy-ai-mcp .
docker run --rm -p 8080:8080 \
  -e EDA_API_KEY="eda_live_..." \
  easydeploy-ai-mcp
```

## Documentation

- **[docs/claude-getting-started.md](docs/claude-getting-started.md)** — EasyDeploy + Claude: Connectors or local Desktop config JSON
- **[docs/claude.md](docs/claude.md)** — Claude Connectors vs Claude Code, transports, headers

## REST API reference

This MCP server is a thin client over the **EasyDeploy public REST API**. Endpoint behavior, request bodies, and response shapes are defined by **EasyDeploy** (dashboard, product help, and official API materials at [easydeploy.ai](https://easydeploy.ai)). This repo does not duplicate the full OpenAPI spec; it maps those operations to MCP tools.

## Releases and API compatibility

**This repository** is the open-source home of the EasyDeploy MCP server. New releases track the **EasyDeploy public REST API** as documented for customers (dashboard and official API materials). If the API adds or changes endpoints, expect corresponding updates here. Contributors should follow [CONTRIBUTING.md](CONTRIBUTING.md) when changing tools or client behavior.

## Development

```bash
pip install -e ".[dev]"
pytest
```

**Optional — real Cognito JWT against the HTTP app** (live JWKS, no mocks): set `EDA_INTEGRATION_COGNITO_ACCESS_TOKEN` plus the same `EDA_COGNITO_*` vars you use for OAuth mode, then run `pytest tests/test_cognito_jwt_integration.py -v`. See the docstring in that file.

See [CONTRIBUTING.md](CONTRIBUTING.md) for pull requests and reporting issues.

## Security

See [SECURITY.md](SECURITY.md) for vulnerability reporting and deployment notes.

## License

MIT — see [LICENSE](LICENSE).
