Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 74 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,16 @@ To switch to a different version later, remove and re-add the marketplace:
The skills are plain [Agent Skills](https://agentskills.io) (`SKILL.md` + YAML
frontmatter), so they work in any harness that supports the standard.

> **Only Claude Code gets the MCP servers automatically.** They are declared in
> `plugins/flyte/.mcp.json`, which Claude Code reads by convention. Every other harness
> installs the **skills only** — you can still wire the servers up by hand in a few lines,
> see [Adding the MCP servers elsewhere](#adding-the-mcp-servers-elsewhere).
> **Only Claude Code and Codex get the MCP servers automatically.** They are declared in
> `plugins/flyte/.mcp.json` — Claude Code reads that file by convention, Codex is pointed at
> it by `.codex-plugin/plugin.json`. The other harnesses install the **skills only**; you can
> still wire the servers up by hand in a few lines, see
> [Adding the MCP servers elsewhere](#adding-the-mcp-servers-elsewhere).

| Harness | Skills | MCP servers |
|---|---|---|
| Claude Code | all 14 | both, automatically |
| Codex CLI | all 14 | none — add manually |
| Codex CLI | all 14 | both, automatically |
| Hermes | per-skill | none — add manually |
| opencode | all 14 | none — add manually |
| pi | all 14 | none — add manually |
Expand All @@ -61,12 +62,12 @@ codex plugin marketplace add flyteorg/flyte-agent-plugins # or --ref

Then browse and install the plugins via `/plugins` inside Codex.

Codex plugins *can* bundle MCP servers (via an `mcpServers` field pointing at an
`.mcp.json`), but this one deliberately does not: our `.mcp.json` uses
`${CLAUDE_PLUGIN_ROOT}` to locate the local launcher script, and Codex does not expand it
([openai/codex#22842](https://github.com/openai/codex/issues/22842)), so the local server
would fail to start. Add the servers manually instead — the hosted one needs no path and
works fine.
Both MCP servers come with it: `.codex-plugin/plugin.json` carries an `mcpServers` field
pointing at the same `.mcp.json` Claude Code reads. (The key is spelled `mcpServers`, not
the `mcp_servers` the Codex docs show — the manifest struct is `camelCase`,
[openai/codex#22105](https://github.com/openai/codex/issues/22105).) Neither server needs a
path expanded, so `${CLAUDE_PLUGIN_ROOT}` — which Codex does not expand,
[openai/codex#22842](https://github.com/openai/codex/issues/22842) — never comes up.

### Hermes

Expand Down Expand Up @@ -164,35 +165,47 @@ Installing the plugin registers **two MCP servers**, split so nothing is duplica
| Server | Tools | Needs |
|---|---|---|
| **`flyte-docs`** (hosted HTTP) | 3 `search` — Flyte SDK examples, docs examples, `llms.txt` | nothing at all |
| **`flyte-cluster`** (local stdio) | 13 control-plane — run/inspect tasks, manage runs, apps, triggers | `uv`, plus a Flyte login |
| **`flyte-cluster`** (local stdio) | 29 control-plane — tasks, runs, actions, logs, apps, triggers, projects, secrets, conditions, `whoami` | `uv`, plus a Flyte login |

`flyte-docs` is a read-only, unauthenticated server **operated by Union**, so search works
the moment you install — no setup, no corpus, no `uv`. Your search queries do leave your
machine; set `FLYTE_MCP_LOCAL_SEARCH=1` to serve search from a local corpus instead
(~120 MB cached under `~/.flyte/mcp`).
machine.

`flyte-cluster` is tenant-agnostic: it calls `flyte.init_from_config()`, so it acts on the same
control plane your `flyte` CLI is authenticated against. **A cluster is optional** — it
starts either way and offers nothing until one is reachable, so the plugin still works
while you are deploying your first cluster. The tools appear once you are logged in
(run `/reload-plugins`, or restart Claude Code — the choice is made at startup).
`flyte-cluster` is the SDK's own `flyte-mcp` entry point, run straight from PyPI with
`uvx` — nothing is vendored here:

Test it end-to-end — this spawns the server exactly as Claude Code does, handshakes, and
reports which mode it landed in:
```
uvx --from "flyte[mcp]>=2.5.18" flyte-mcp --transport stdio \
--tool-groups task,run,action,logs,app,trigger,project,secret,condition,identity
```

`>=2.5.18` is the first release that caps `mcp<2`; below it the server dies at import. The
`search` groups are left out on purpose — `flyte-docs` already serves them hosted, and
enabling them here shallow-clones ~120 MB into `~/.flyte/mcp` on first launch.

It is tenant-agnostic: config discovery is the SDK's normal one, so it acts on the same
control plane your `flyte` CLI is authenticated against. **A cluster is optional** — the
server starts even with no Flyte config at all, so the plugin still works while you are
deploying your first cluster; the tools are registered either way and simply fail when
called until you are logged in.

Test it end-to-end — this spawns the server exactly as a client does, handshakes, lists the
tools, and makes one real read-only call:

```
python3 scripts/smoke_test_mcp.py
```

Override the automatic tool choice with `FLYTE_MCP_TOOL_GROUPS` / `FLYTE_MCP_TOOLS`, and
scope with `FLYTE_MCP_CONFIG`, `FLYTE_MCP_PROJECT`, `FLYTE_MCP_DOMAIN`,
`FLYTE_MCP_{TASK,APP,TRIGGER}_ALLOWLIST`, or `FLYTE_MCP_LOCAL_SEARCH` — see the plugin
[README](plugins/flyte/README.md).
Change what is served by editing `args` in `plugins/flyte/.mcp.json` (`--tool-groups`,
`--tools`, `--read-only`), and scope it with `FLYTE_MCP_PROJECT` / `FLYTE_MCP_DOMAIN` — see
the plugin [README](plugins/flyte/README.md).

### Adding the MCP servers elsewhere

Codex, Hermes, opencode, and pi all support MCP — this plugin just doesn't configure it
for them. Wiring it up yourself is a few lines.
Hermes, opencode, and pi all support MCP — this plugin just doesn't configure it for them.
(Claude Code and Codex get both servers from the plugin; use these snippets only if you
want them configured globally rather than per-plugin.) Wiring it up yourself is a few
lines.

**`flyte-docs`** is plain remote HTTP with no auth and no local dependency, so it drops
into any harness:
Expand All @@ -219,16 +232,39 @@ mcp_servers:

pi uses the same `mcpServers` shape in `~/.pi/agent/mcp.json`.

**`flyte-cluster`** is a local stdio process, so point your harness at the launcher script
with an absolute path — there is no `${CLAUDE_PLUGIN_ROOT}` outside Claude Code:
**`flyte-cluster`** is a local stdio process, but it is just the SDK's published
`flyte-mcp` entry point run with `uvx` — no checkout, no path, so it is as portable as the
hosted one. It needs [`uv`](https://docs.astral.sh/uv/) on `PATH` and picks up whatever
control plane your `flyte` CLI is logged into:

```toml
# Codex — ~/.codex/config.toml
[mcp_servers.flyte-cluster]
command = "uvx"
args = ["--from", "flyte[mcp]>=2.5.18", "flyte-mcp", "--transport", "stdio",
"--tool-groups", "task,run,action,logs,app,trigger,project,secret,condition,identity"]
```
uv run --quiet --no-project /abs/path/to/plugins/flyte/scripts/flyte_mcp_stdio.py

```json
// opencode — opencode.json
{ "mcp": { "flyte-cluster": { "type": "local", "enabled": true,
"command": ["uvx", "--from", "flyte[mcp]>=2.5.18", "flyte-mcp", "--transport", "stdio",
"--tool-groups",
"task,run,action,logs,app,trigger,project,secret,condition,identity"] } } }
```

```yaml
# Hermes — ~/.hermes/config.yaml
mcp_servers:
flyte-cluster:
command: "uvx"
args: ["--from", "flyte[mcp]>=2.5.18", "flyte-mcp", "--transport", "stdio",
"--tool-groups", "task,run,action,logs,app,trigger,project,secret,condition,identity"]
```

Once [flyte-sdk#1319](https://github.com/flyteorg/flyte-sdk/pull/1319) ships, that becomes
`uvx --from "flyte[mcp]" flyte-mcp --transport stdio` — no path, no script, portable
everywhere.
Drop `--tool-groups` to get everything, including the three `search` tools — but then the
server shallow-clones a ~120 MB corpus into `~/.flyte/mcp` on first launch, which is exactly
what `flyte-docs` exists to avoid.

## Layout

Expand All @@ -238,9 +274,8 @@ All skills live in the single `flyte` plugin:
.claude-plugin/marketplace.json # marketplace catalog
package.json # pi package manifest (pi.skills)
plugins/flyte/.claude-plugin/plugin.json # Claude Code plugin manifest
plugins/flyte/.codex-plugin/plugin.json # Codex plugin manifest
plugins/flyte/.mcp.json # the two bundled MCP servers (Claude Code)
plugins/flyte/scripts/flyte_mcp_stdio.py # stdio adapter that .mcp.json launches
plugins/flyte/.codex-plugin/plugin.json # Codex plugin manifest (points at .mcp.json)
plugins/flyte/.mcp.json # the two bundled MCP servers
plugins/flyte/skills/<skill>/SKILL.md
scripts/smoke_test_mcp.py # end-to-end check of the local MCP server
```
Expand All @@ -249,8 +284,9 @@ Each harness consumes a different part of this. Claude Code and Codex read the p
manifests, so the **plugin name** matters to them. Hermes, opencode, and pi install skills
by **directory path**, so `plugins/flyte/skills/…` is their interface.

The `.mcp.json` server is Claude Code-specific; the skills themselves stay portable across
harnesses.
`.mcp.json` is shared by Claude Code (which finds it by convention) and Codex (which is
pointed at it by `.codex-plugin/plugin.json`); the skills themselves stay portable across
every harness.

## Contributing

Expand Down
3 changes: 2 additions & 1 deletion plugins/flyte/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"name": "flyte",
"version": "0.3.0",
"description": "Skills for working with Flyte 2 — deploy Flyte v2 (flyte-binary) clusters on AWS or kind, and author, run, and operate Flyte workflows, apps, agents, data, and ML workloads with the SDK.",
"skills": "./skills/"
"skills": "./skills/",
"mcpServers": "./.mcp.json"
}
13 changes: 8 additions & 5 deletions plugins/flyte/.mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
"url": "https://flyte-mcp.apps.demo.hosted.unionai.cloud/flyte-mcp/mcp"
},
"flyte-cluster": {
"command": "uv",
"command": "uvx",
"args": [
"run",
"--quiet",
"--no-project",
"${CLAUDE_PLUGIN_ROOT}/scripts/flyte_mcp_stdio.py"
"--from",
"flyte[mcp]>=2.5.18",
"flyte-mcp",
"--transport",
"stdio",
"--tool-groups",
"task,run,action,logs,app,trigger,project,secret,condition,identity"
]
}
}
Expand Down
82 changes: 48 additions & 34 deletions plugins/flyte/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,52 +60,66 @@ Convert existing Flyte 1 (`flytekit`) code to Flyte 2, distilled from the offici

## Bundled MCP servers

**Claude Code only.** The servers live in `.mcp.json`, which Claude Code reads by
convention; Codex, Hermes, opencode, and pi install the skills and nothing else. They all
support MCP, so you can add these by hand — see
[Adding the MCP servers elsewhere](../../README.md#adding-the-mcp-servers-elsewhere).
The servers live in `.mcp.json`. Claude Code reads that file by convention; Codex picks it
up through the `mcpServers` entry in `.codex-plugin/plugin.json`. Hermes, opencode, and pi
install the skills and nothing else — they all support MCP, so you can add these by hand,
see [Adding the MCP servers elsewhere](../../README.md#adding-the-mcp-servers-elsewhere).

The plugin's `.mcp.json` declares **two MCP servers**, split so nothing is duplicated:
`.mcp.json` declares **two MCP servers**, split so nothing is duplicated:

- **`flyte-docs`** — hosted HTTP, 3 `search` tools over Flyte SDK examples, docs examples,
and `llms.txt`. Read-only, unauthenticated, **operated by Union**. Needs nothing at all,
so search works the moment you install. Your queries do leave your machine.
- **`flyte-cluster`** — local stdio (`scripts/flyte_mcp_stdio.py`), 13 control-plane tools:
run and inspect tasks, manage runs, apps, and triggers. Needs
[`uv`](https://docs.astral.sh/uv/) and a Flyte config with `project` and `domain`.

**A cluster is optional.** `flyte-cluster` starts either way and offers nothing until one is
reachable, so the plugin still works while you are deploying your first cluster. It is
tenant-agnostic — `flyte.init_from_config()` targets whatever control plane your `flyte`
CLI is authenticated against. After logging in, run `/reload-plugins` (or restart Claude
Code) so the server respawns and picks the tools up — the choice is made at startup, and
plugin MCP servers can't be restarted from `/mcp`.

To test it, run `python3 scripts/smoke_test_mcp.py` from the repo root — it reports what it
landed in. (`flyte-docs` is hosted, so check it with
- **`flyte-cluster`** — local stdio, 29 control-plane tools: run and inspect tasks, runs,
actions, and logs; manage apps, triggers, projects, secrets, and conditions; `whoami`.
Needs [`uv`](https://docs.astral.sh/uv/) (for `uvx`) and a Flyte login.

`flyte-cluster` is the SDK's own published entry point — no wrapper script and no path to
expand, so the same line works in every harness:

```
uvx --from "flyte[mcp]>=2.5.18" flyte-mcp --transport stdio \
--tool-groups task,run,action,logs,app,trigger,project,secret,condition,identity
```

Two things about that command are deliberate:

- **`>=2.5.18`** is the first release that caps `mcp<2`. Below it, `mcp` 2.0.0 resolves,
`mcp.server.fastmcp` is gone, and the server dies at import claiming "mcp is not installed".
- **The `search` groups are left out.** `flyte-docs` already serves those three tools from a
hosted corpus; enabling them here would shallow-clone ~120 MB into `~/.flyte/mcp` on first
launch for no gain.

**A cluster is optional.** The server starts even with no Flyte config at all, so the plugin
still works while you are deploying your first cluster — the tools are registered either way
and simply fail when called until you are logged in. It is tenant-agnostic: config discovery
is the SDK's normal one, so it targets whatever control plane your `flyte` CLI is
authenticated against, and nothing needs restarting once you log in.

To test it, run `python3 scripts/smoke_test_mcp.py` from the repo root — it spawns the
server exactly as a client does, lists the tools, and makes one real read-only call.
(`flyte-docs` is hosted, so check it with
`curl https://flyte-mcp.apps.demo.hosted.unionai.cloud/health` instead.)

### Configuring `flyte-cluster`

Set these in your shell or in the `env` block of an MCP config. The plugin's own `.mcp.json`
sets none of them, so ambient values apply.
Change what is served by editing `args` in `.mcp.json`: `--tool-groups` (valid groups are
`all`, `core`, `task`, `run`, `action`, `logs`, `app`, `trigger`, `project`, `secret`,
`condition`, `identity`, `search`), `--tools` for an explicit tool list instead, or
`--read-only` to narrow whatever those selected down to the tools annotated
`readOnlyHint=True`. `uvx --from "flyte[mcp]" flyte-mcp --help` lists the rest.

Two environment variables are read at startup. Set them in your shell or in an `env` block;
the plugin's own `.mcp.json` sets neither, so ambient values apply.

| Variable | Effect |
|---|---|
| `FLYTE_MCP_LOCAL_SEARCH` | serve search here, from a local corpus (~120 MB under `~/.flyte/mcp`) instead of the hosted server — offline and private |
| `FLYTE_MCP_TOOL_GROUPS` | override the automatic choice; valid groups are `all`, `core`, `task`, `run`, `app`, `trigger`, `search` |
| `FLYTE_MCP_TOOLS` | an explicit tool list (mutually exclusive with groups) |
| `FLYTE_MCP_CONFIG` | a specific Flyte config file, instead of normal discovery |
| `FLYTE_MCP_PROJECT` / `FLYTE_MCP_DOMAIN` | override the project/domain from the config |
| `FLYTE_MCP_TASK_ALLOWLIST` / `_APP_` / `_TRIGGER_` | restrict which resources the tools may target |

Setting `FLYTE_MCP_TOOL_GROUPS`/`FLYTE_MCP_TOOLS` overrides the automatic choice, including
offering control-plane tools while disconnected — those will fail when called.

Note there is no per-server toggle for plugin MCP servers: Claude Code manages them through
plugin installation, not `/mcp`. So `FLYTE_MCP_LOCAL_SEARCH` moves search into
`flyte-cluster` but does not un-declare `flyte-docs` — suppressing that needs a
`deniedMcpServers` entry or disabling the plugin.
| `FLYTE_MCP_PROJECT` | override the project from the resolved config |
| `FLYTE_MCP_DOMAIN` | override the domain from the resolved config |

Note there is no per-server toggle for plugin MCP servers in Claude Code — it manages them
through plugin installation, not `/mcp`. Suppressing one needs a `deniedMcpServers` entry or
disabling the plugin.

To build an MCP server of your own — with allowlists, auth, and a shared endpoint for a
team — ask the `flyte-docs` search tools for `FlyteMCPAppEnvironment`; they return the
Expand Down
Loading
Loading