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
1 change: 1 addition & 0 deletions website/src/content/docs/docs/tutorials/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Tutorials are hands-on lessons. Use this section when you want to learn a workfl
## What is in this section

1. [Observe Bub with tapes and Jaeger](/docs/tutorials/observability/) — inspect Bub's own tape first, then export Logfire/OpenTelemetry traces to Jaeger.
2. [Connect MCP Servers with bub-mcp](/docs/tutorials/mcp/) — install the MCP plugin, wire up a time server, and call it from a Bub turn.

## Next steps

Expand Down
184 changes: 184 additions & 0 deletions website/src/content/docs/docs/tutorials/mcp.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
---
title: Connect MCP Servers with bub-mcp
description: Install the bub-mcp plugin, register a time MCP server, and call its tools from a Bub turn.
sidebar:
order: 2
---

This tutorial wires a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server into Bub through the `bub-mcp` plugin. MCP servers expose external capabilities — APIs, local tools, data sources — that Bub can call as tools during a turn.

By the end you will have the official `mcp-server-time` registered with Bub and verified as connected. From there, swapping in any other stdio, HTTP, or SSE server is a one-line change.

## Before you begin

You need:

- Bub installed and runnable with `uv run bub --help`.
- [`uv`](https://docs.astral.sh/uv/) on `PATH` so `uvx mcp-server-time` can launch the [time MCP server](https://pypi.org/project/mcp-server-time/) on demand.
- A working model provider if you want to call the MCP tool from a real turn (see the final section).

## 1. Install bub-mcp

`bub-mcp` lives in [`bubbuild/bub-contrib`](https://github.com/bubbuild/bub-contrib) and is not on PyPI. Use `bub install` — it resolves bare names against bub-contrib when given an `@<ref>` suffix:

```bash
bub install bub-mcp@main
```

This requires Bub to be running inside a virtualenv (see [`bub install`](/docs/reference/cli/#bub-install)); activate it first if needed.

Verify the plugin loaded:

```bash
uv run bub hooks
```

You should see `mcp` listed alongside `builtin`:

```text
load_state: builtin, mcp
provide_channels: builtin, mcp
register_cli_commands: builtin, mcp
```

If `mcp` is missing, the plugin landed in a different environment than the one `uv run bub` resolves.

## 2. Register the time server

`bub-mcp` reads server definitions from `~/.bub/mcp.json` (or `$BUB_HOME/mcp.json` when `BUB_HOME` is set). Create the file with one entry that launches `mcp-server-time` over stdio:

```bash
mkdir -p ~/.bub
cat > ~/.bub/mcp.json <<'EOF'
{
"mcpServers": {
"time": {
"command": "uvx",
"args": ["mcp-server-time"]
}
}
}
EOF
```

For stdio servers, `command` is required; `args` and `env` are optional. The presence of `command` selects stdio — there is no `transport` field on stdio entries.

## 3. Verify the server is connected

```bash
uv run bub mcp list
```

Expected output:

```text
🔌 MCP Tools
- time
Status: Connected
Tools: mcp.time_get_current_time, mcp.time_convert_time
```

`Status: Connected` means `bub-mcp` started the child process, completed the MCP handshake, and discovered the server's tools. Each remote tool is exposed to Bub under the prefix `mcp.<server>_<tool>`.

If you see `Status: Disconnected`, run the launch command directly to debug it:

```bash
uvx mcp-server-time
```

The process should start without exiting. Press `Ctrl-C` to stop it, fix the underlying issue, then re-run `bub mcp list`.

## 4. Use the MCP tool from a running turn

`bub mcp list` is enough to confirm the integration. Calling the tool from a real turn requires Bub's **channel runtime**, which only `bub gateway` starts:

- `bub run` and `bub chat` do **not** start any `Channel`. The `mcp.lifecycle` channel that owns the MCP servers never boots, so MCP tools are not exposed to the model in those commands.
- `bub gateway` starts every channel returned by the `provide_channels` hook (subject to `--enable-channel` / `BUB_ENABLED_CHANNELS`). With `mcp.lifecycle` enabled, the channel boots in the background, registers each remote tool into the global tool registry as `mcp.<server>_<tool>`, and from then on the model can call them.

Run the gateway with both the input channel and the MCP lifecycle channel enabled:

```bash
uv run bub gateway --enable-channel cli --enable-channel mcp.lifecycle
```

Wait a few seconds after `channel.manager started listening` so the MCP bootstrap can complete, then ask Bub a question that needs the time server (for example, `What time is it right now in UTC?`). The model should call `mcp.time_get_current_time` and include the result in its reply.

If the model answers without calling the MCP tool, the bootstrap had not finished yet when the turn started — wait longer or send a warm-up message first. The bootstrap is asynchronous (`asyncio.create_task`), so it does not block channel startup, but it also does not block the first turn.

For long-running deployments, see [Deploy](/docs/operate/deploy/) — the same gateway invocation is what runs in the container image.

## Add other server types

Edit `~/.bub/mcp.json` to add more entries under `mcpServers`. Each transport has its own shape.

**HTTP** — `url` plus `transport: "http"`, with optional `headers`:

```json
{
"weather": {
"url": "https://weather.example.com/mcp",
"transport": "http"
}
}
```

**SSE** — `url` plus `transport: "sse"`, with optional `headers`:

```json
{
"events": {
"url": "https://events.example.com/mcp",
"transport": "sse",
"headers": { "Authorization": "Bearer token" }
}
}
```

**Another stdio server** — for example, the Node [`@modelcontextprotocol/server-filesystem`](https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem) over `npx`. Add allowed directories as positional `args`, and pass credentials through `env`:

```json
{
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
}
}
```

After saving, run `bub mcp list` to confirm each new server connects.

### CLI alternatives

If you prefer the CLI over editing JSON, `bub mcp add` writes the same entries:

```bash
# stdio
uv run bub mcp add --transport stdio time -- uvx mcp-server-time
uv run bub mcp add --transport stdio --env API_KEY=secret example -- node ./my-server.js

# http / sse
uv run bub mcp add --transport http weather https://weather.example.com/mcp
uv run bub mcp add --transport sse --header "Authorization: Bearer token" \
events https://events.example.com/mcp

# remove
uv run bub mcp remove time
```

`--env` is only allowed with `--transport stdio`; `--header` is only allowed with `--transport http` or `--transport sse`.

## Troubleshooting

| Symptom | Check |
|---|---|
| `mcp` does not appear in `bub hooks` | The plugin was installed in a different environment than `uv run bub` resolves. Re-install into the active Bub venv. |
| `bub mcp list` reports `Status: Disconnected` | Run the configured `command` (or open the `url`) outside Bub and confirm it starts cleanly; the error column shows the underlying cause. |
| `bub mcp add` prints `CancelledError` after `Added MCP server …` | Cosmetic only — the entry is written. Use `bub mcp list` to verify, or edit `mcp.json` by hand. |
| Tool never called during a turn | Confirm `bub mcp list` shows `Status: Connected` and lists the expected tool, then ask a question that clearly maps to that tool. |
| Permission denied on `mcp.json` | Verify `~/.bub/` is writable, or set `BUB_HOME` to a directory you own. |

## Next steps

- [Build plugins](/docs/build/plugins/) — write your own Bub plugins.
- [Configure](/docs/operate/configure/) — Bub's config layer and environment variables.
- [bub-mcp source](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-mcp) — the plugin's full source and advanced options.
1 change: 1 addition & 0 deletions website/src/content/docs/zh-cn/docs/tutorials/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ sidebar:
## 本节内容

1. [使用 tape 与 Jaeger 观察 Bub](/zh-cn/docs/tutorials/observability/) — 先检查 Bub 自身的 tape,再把 Logfire/OpenTelemetry trace 导出到 Jaeger。
2. [使用 bub-mcp 连接 MCP 服务器](/zh-cn/docs/tutorials/mcp/) — 安装 MCP 插件,接入时间服务器,并在 Bub turn 中调用。

## 下一步

Expand Down
184 changes: 184 additions & 0 deletions website/src/content/docs/zh-cn/docs/tutorials/mcp.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
---
title: 使用 bub-mcp 连接 MCP 服务器
description: 安装 bub-mcp 插件,注册一个时间 MCP 服务器,并在 Bub turn 中调用其工具。
sidebar:
order: 2
---

本教程通过 `bub-mcp` 插件,将一个 [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 服务器接入 Bub。MCP 服务器把外部能力——API、本地工具、数据源——暴露成 Bub 在 turn 中可调用的工具。

完成后,你将把官方的 `mcp-server-time` 注册到 Bub 并验证连接通畅。在此基础上,替换为其他 stdio、HTTP 或 SSE 服务器只需改一行配置。

## 前提条件

你需要:

- 已安装 Bub 并可通过 `uv run bub --help` 运行。
- 系统 `PATH` 中存在 [`uv`](https://docs.astral.sh/uv/),以便 `uvx mcp-server-time` 能按需启动 [time MCP 服务器](https://pypi.org/project/mcp-server-time/)。
- 如果想在真实 turn 中调用 MCP 工具,需要一个可用的模型提供商(参见最后一节)。

## 1. 安装 bub-mcp

`bub-mcp` 位于 [`bubbuild/bub-contrib`](https://github.com/bubbuild/bub-contrib) 仓库,**未发布到 PyPI**。请使用 `bub install`——当传入 `<name>@<ref>` 形式的裸名时,它会自动到 bub-contrib 中解析:

```bash
bub install bub-mcp@main
```

这要求 Bub 自身运行在 virtualenv 中(参见 [`bub install`](/zh-cn/docs/reference/cli/#bub-install));如未激活该 venv,请先激活。

验证插件已加载:

```bash
uv run bub hooks
```

应能看到 `mcp` 与 `builtin` 并列:

```text
load_state: builtin, mcp
provide_channels: builtin, mcp
register_cli_commands: builtin, mcp
```

如果没有 `mcp`,说明插件被装到了与 `uv run bub` 解析出的环境不同的地方。

## 2. 注册时间服务器

`bub-mcp` 从 `~/.bub/mcp.json`(设置 `BUB_HOME` 时为 `$BUB_HOME/mcp.json`)读取服务器定义。创建该文件,写入一个通过 stdio 拉起 `mcp-server-time` 的条目:

```bash
mkdir -p ~/.bub
cat > ~/.bub/mcp.json <<'EOF'
{
"mcpServers": {
"time": {
"command": "uvx",
"args": ["mcp-server-time"]
}
}
}
EOF
```

stdio 服务器中,`command` 必填;`args`、`env` 可选。**存在 `command` 即视为 stdio**——stdio 条目无需 `transport` 字段。

## 3. 验证服务器已连接

```bash
uv run bub mcp list
```

预期输出:

```text
🔌 MCP Tools
- time
Status: Connected
Tools: mcp.time_get_current_time, mcp.time_convert_time
```

`Status: Connected` 表示 `bub-mcp` 已启动子进程、完成 MCP 握手并发现了服务器工具。每个远程工具会以 `mcp.<server>_<tool>` 的前缀暴露给 Bub。

如果出现 `Status: Disconnected`,先在 Bub 之外直接运行启动命令排错:

```bash
uvx mcp-server-time
```

进程应能正常启动而不退出,按 `Ctrl-C` 终止;修复底层问题后再次执行 `bub mcp list`。

## 4. 在运行中的 turn 里使用 MCP 工具

`bub mcp list` 已经足以确认接入正确。**真正在 turn 中调用工具,需要 Bub 的 channel 运行时**——而只有 `bub gateway` 才会启动它:

- `bub run` 与 `bub chat` **不会**启动任何 `Channel`。承载 MCP 服务器的 `mcp.lifecycle` channel 不会启动,因此这两个命令下模型看不到 MCP 工具。
- `bub gateway` 会启动 `provide_channels` 钩子返回的所有 channel(受 `--enable-channel` / `BUB_ENABLED_CHANNELS` 控制)。当 `mcp.lifecycle` 启用后,channel 会在后台启动、把每个远程工具按 `mcp.<server>_<tool>` 名称注册到全局工具表中,模型从此即可调用它们。

同时启用输入 channel 与 MCP 生命周期 channel 来运行 gateway:

```bash
uv run bub gateway --enable-channel cli --enable-channel mcp.lifecycle
```

看到 `channel.manager started listening` 后**等几秒**让 MCP 完成 bootstrap,再向 Bub 提一个会用到时间服务器的问题(例如 `What time is it right now in UTC?`)。模型应该会调用 `mcp.time_get_current_time` 并把结果纳入回复。

如果模型在没调用 MCP 工具的情况下就回答了,多半是 turn 开始时 bootstrap 还没完成——多等一会,或先发一条预热消息。bootstrap 通过 `asyncio.create_task` 异步启动,既不阻塞 channel 启动,也不阻塞首个 turn。

长期运行的部署请参见 [Deploy](/zh-cn/docs/operate/deploy/) —— 容器镜像跑的就是同样的 gateway 命令。

## 添加其他类型的服务器

编辑 `~/.bub/mcp.json`,在 `mcpServers` 下追加更多条目。不同传输方式的字段不同。

**HTTP**——`url` 加 `transport: "http"`,可选 `headers`:

```json
{
"weather": {
"url": "https://weather.example.com/mcp",
"transport": "http"
}
}
```

**SSE**——`url` 加 `transport: "sse"`,可选 `headers`:

```json
{
"events": {
"url": "https://events.example.com/mcp",
"transport": "sse",
"headers": { "Authorization": "Bearer token" }
}
}
```

**另一个 stdio 服务器**——例如通过 `npx` 启动的 Node 实现 [`@modelcontextprotocol/server-filesystem`](https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem)。允许访问的目录作为位置参数写入 `args`,凭证通过 `env` 传入:

```json
{
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
}
}
```

保存后,执行 `bub mcp list` 确认每个新服务器都已连接。

### CLI 等价命令

如果你不想手动编辑 JSON,可以使用 `bub mcp add`,效果完全一致:

```bash
# stdio
uv run bub mcp add --transport stdio time -- uvx mcp-server-time
uv run bub mcp add --transport stdio --env API_KEY=secret example -- node ./my-server.js

# http / sse
uv run bub mcp add --transport http weather https://weather.example.com/mcp
uv run bub mcp add --transport sse --header "Authorization: Bearer token" \
events https://events.example.com/mcp

# 移除
uv run bub mcp remove time
```

`--env` 仅在 `--transport stdio` 下可用;`--header` 仅在 `--transport http` 或 `--transport sse` 下可用。

## 故障排除

| 症状 | 检查项 |
|---|---|
| `bub hooks` 中没有 `mcp` | 插件被装到了与 `uv run bub` 解析出的环境不同的地方。重新装入当前 Bub venv。 |
| `bub mcp list` 显示 `Status: Disconnected` | 在 Bub 之外直接运行配置的 `command`(或访问 `url`)确认能正常启动;错误信息会显示根本原因。 |
| `bub mcp add` 在 `Added MCP server …` 之后打印 `CancelledError` | 仅是表面问题——条目已写入。用 `bub mcp list` 复核,或改为手动编辑 `mcp.json`。 |
| turn 中工具未被调用 | 确认 `bub mcp list` 显示 `Status: Connected` 且列出了预期工具,然后提一个明显需要该工具的问题。 |
| `mcp.json` 权限被拒绝 | 确认 `~/.bub/` 对当前用户可写,或将 `BUB_HOME` 设到你拥有的目录后重试。 |

## 下一步

- [构建插件](/zh-cn/docs/build/plugins/) — 编写自己的 Bub 插件。
- [配置](/zh-cn/docs/operate/configure/) — Bub 的配置层与环境变量。
- [bub-mcp 源码](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-mcp) — 插件完整源码与高级选项。
Loading