Skip to content
Open
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
6 changes: 6 additions & 0 deletions src/lingtai/tools/mcp/manual/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ The `mcp` capability is your interface to Model Context Protocol (MCP) servers

This is the router. Detail lives in `reference/`. Load only what you need.

## TUI command boundary

`/addon` is retired; never recommend it. `/mcp` is the only current TUI command for this surface, and it is read-only config/status inspection. It is **not** a guided setup or configuration screen; never describe it as one or redirect a human there for addon setup.

For curated addon setup, load `reference/curated-addons.md` (the curated-addon setup contract) and the relevant provider docs before editing. Make exact configuration changes only within explicit human authorization, using that contract's four-step mechanism; do not redirect the human to a nonexistent or setup-like TUI screen.

## Three states of an MCP

For any MCP server, relative to this agent:
Expand Down
2 changes: 1 addition & 1 deletion src/lingtai/tools/mcp/manual/reference/curated-addons.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ LingTai's first-party email and chat integrations. They now ship inside the `lin

## The four-step setup

1. **Read the curated setup docs before editing config.** The table below gives the registry/module/env/config-file names. If exact provider-specific fields are needed, inspect the shipped module resources or the catalog `homepage` for that addon. Field names like `email_password` (imap), `bot_token` (telegram), `app_id`/`app_secret` (feishu), and gewechat host (wechat) are addon-specific; do not guess them from memory.
1. **Read the curated setup docs before editing config.** The table below gives the registry/module/env/config-file names. If exact provider-specific fields are needed, inspect the shipped module resources or the catalog `homepage` for that addon. Field names like `email_password` (imap), `bot_token` (telegram), `app_id`/`app_secret` (feishu), and gewechat host (wechat) are addon-specific; do not guess them from memory. Make exact configuration changes only within explicit human authorization; this contract is the setup procedure, not a TUI setup screen.

2. **Add the addon to `init.json`.** Append the registry name to the top-level `addons:` list, then add an `mcp.<name>` activation entry with the subprocess spec from this table or the addon docs:

Expand Down
35 changes: 35 additions & 0 deletions tests/test_mcp_capability.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from __future__ import annotations

import json
import re
import sys
from pathlib import Path
from unittest.mock import MagicMock
Expand Down Expand Up @@ -295,6 +296,40 @@ def test_mcp_show_action_returns_health_snapshot(tmp_path):
assert "mcp_manual" in manual and manual["mcp_manual"] # umbrella SKILL.md body


def test_mcp_manual_preserves_tui_command_boundary():
"""The shipped MCP router must not route setup to a retired TUI surface."""
manual_root = Path(__file__).resolve().parents[1] / "src/lingtai/tools/mcp/manual"
router = (manual_root / "SKILL.md").read_text(encoding="utf-8")
curated = (manual_root / "reference/curated-addons.md").read_text(encoding="utf-8")

assert re.search(r'/addon.{0,120}(?:retired|never recommended)', router, re.I | re.S)
assert not re.search(r'(?:use|open|run|launch|recommend)[ \t]+`?/addon', router, re.I)
assert re.search(r'/mcp.{0,140}only current TUI command', router, re.I | re.S)
assert re.search(r'/mcp.{0,180}read[- ]only', router, re.I | re.S)
assert re.search(
r"/mcp.{0,240}(?:not|isn't).{0,90}(?:guided[ \t]+)?(?:setup|configuration)",
router,
re.I | re.S,
)
assert re.search(
r'curated addon setup.{0,220}(?:curated-addons.*contract|provider docs)',
router,
re.I | re.S,
)
assert re.search(r'explicit human authorization', router, re.I)

# Keep the existing four-step mechanism in the owning reference while the
# router adds only the TUI boundary and authorization rule.
assert re.search(r'## The four-step setup', curated)
for step in (
r'1[.].*read.*setup docs',
r'2[.].*init[.]json',
r'3[.].*config file',
r"4[.].*system[(]action=.*refresh.*[)]",
):
assert re.search(step, curated, re.I | re.S)


def test_mcp_show_unknown_action_returns_error(tmp_path):
agent, workdir = _mk_agent(tmp_path, addons=["imap"])
handler = agent._tool_handlers.get("mcp")
Expand Down