MCP¶
reactifact говорит на MCP в обе стороны:
вызывает инструменты внешнего MCP-сервера как обычные Tool, или отдаёт
собственные Tool (и работающий Context) как MCP-сервер — для Claude
Desktop, Claude Code или другого агента. Оба направления требуют extra mcp:
Ядро reactifact нигде не импортирует mcp — обычный pip install reactifact
никогда не тянет SDK; reactifact.mcp кидает читаемый ImportError с
подсказкой по установке, если вызвать его без extra.
Клиент: вызов удалённых MCP-инструментов¶
mcp_stdio_tools/mcp_http_tools подключаются к MCP-серверу и отдают его
инструменты как list[Tool] — тот же контракт Tool, что уже принимают
ToolUse/LLMAgent, поэтому удалённый MCP-инструмент и локальная функция с
@tool взаимозаменяемы:
from reactifact import Consume, create_agent
from reactifact.mcp import mcp_stdio_tools
from reactifact.tool_use import ToolUse
async with mcp_stdio_tools(
"npx", ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
) as tools:
fs_agent = create_agent(
"fs",
consumes=[Consume(Question)],
produces=[ToolUse("Отвечай на вопросы о файлах в /tmp.", tools)],
)
# tools (и fs_agent) работают, пока открыт блок `async with`
mcp_http_tools(url, headers=...) подключается так же, но по streamable
HTTP — передайте headers для сервера, требующего статический credential
(например, {"Authorization": "Bearer ..."}). Оба — тонкие обёртки над
mcp.ClientSession; используйте mcp_tools(session) напрямую, если сами
управляете сессией (свой транспорт, …).
OAuth (client_credentials)¶
Для сервера, требующего OAuth вместо статического заголовка,
oauth_client_credentials строит значение auth= для mcp_http_tools по
grant'у client_credentials — machine-to-machine, без браузера и согласия
человека:
from reactifact.mcp import mcp_http_tools, oauth_client_credentials
auth = oauth_client_credentials(
"https://mcp.example.com",
client_id="...",
client_secret="...",
issuer="https://auth.example.com", # сервер авторизации, выдавший их
)
async with mcp_http_tools("https://mcp.example.com", auth=auth) as tools:
...
issuer привязывает обмен токена к метаданным именно этого сервера
авторизации — скомпрометированный или неправильно настроенный MCP-сервер не
сможет перенаправить обмен credential'ов куда-то ещё. Токены кэшируются в
памяти на время жизни объекта auth (InMemoryTokenStorage); передайте
свой storage=, чтобы они переживали перезапуск.
Это покрывает только client_credentials — сценарий, где credential
держит сам агент. Authorization-code flow (человек даёт согласие через
редирект в браузере) требует redirect_handler/callback_handler,
привязанных к конкретному хосту приложения — это host-специфичная
инфраструктура вне области этой библиотеки; используйте
mcp.client.auth.OAuthClientProvider напрямую для этого случая.
Сервер: отдать reactifact как MCP¶
create_mcp_server строит mcp.server.mcpserver.MCPServer из списка Tool —
как рукописных подклассов Tool, так и функций с @tool, потому что у обоих
уже есть JSON-схема (Tool.schema), которая становится реальными именами и
типами аргументов MCP-инструмента, а не одним непрозрачным **kwargs:
from reactifact.mcp import create_mcp_server
from reactifact.tools import tool
@tool
async def search_catalog(query: str, limit: int = 10) -> str:
"""Ищет по каталогу товаров."""
...
server = create_mcp_server([search_catalog], name="my-app")
await server.run_stdio_async()
destructive=True у Tool становится аннотацией destructiveHint у
MCP-инструмента — тот же сигнал, что читает собственный UI подтверждения в
Claude Desktop.
Публикация Context¶
Передайте context=, чтобы дополнительно опубликовать два read-only
ресурса по MCP — внешний клиент сможет заглянуть в состояние работающего
reactifact-приложения, включая provenance, так же, как ваш собственный код
через context.list_artifacts()/context.get():
context://artifacts/{artifact_type}— все артефакты одного типа, сначала новые (например,context://artifacts/Answer).context://artifact/{artifact_id}— данные и версия одного артефакта.
Монтирование по HTTP¶
server.streamable_http_app() возвращает Starlette-приложение — монтируйте
его на то же FastAPI-приложение, что и create_trace_router/
create_chat_router:
Модель безопасности: встроенной авторизации нет¶
У reactifact нет встроенного примитива прав/авторизации — это касается и MCP
конкретно, и фреймворка в целом (§57 явно помечен «planned», не
«implemented»; см. appendix статуса реализации в docs/constitution.md).
Конкретно для create_mcp_server:
- Оба ресурса
context=(context://artifacts/...,context://artifact/...) — read-only, но без разграничения: MCP-клиент может заглянуть в любой артефакт переданногоContext, без редакции по типу или полю. - Инструменты никак не изолируются публикацией через MCP. Любой
Tool, переданный вcreate_mcp_server, так же вызываем — и так же способен мутировать состояние — как внутри вашего собственного циклаToolUse/LLMAgent. Если инструмент может писать в базу или дёргать платный API, MCP-клиент, способный его вызвать, может сделать то же самое — ровно как локальный tool-calling агент; MCP это транспорт, не граница прав.
Контроль доступа — на стороне хост-приложения: публикуйте только тот
Context, который готовы отдать целиком в read-only, и только те Tool,
которые готовы разрешить вызывать любому подключённому MCP-клиенту. Для
деструктивных инструментов используйте тот же гейт, что и для локального
HITLLMAgent (ToolUseHITL, §60), если перед мутирующим вызовом нужно
подтверждение человека.
Ошибки: ToolOutput.error → MCP is_error¶
Tool, вернувший ToolOutput(error=...) (или выбросивший исключение),
доходит до MCP-клиента как is_error=True с сохранённым сообщением —
reactifact внутри кидает собственный ToolError из SDK: единственный тип
исключения, который SDK не маскирует до общего "Error executing tool …" для
вызывающей стороны.