metheus-governance-mcp-cli
v0.2.304
Published
Metheus Governance MCP CLI (setup + stdio proxy)
Readme
metheus-governance-mcp-cli
Metheus Governance MCP helper CLI.
Compatibility note: legacy command alias metheus-governance-mcp is still supported.
metheus-governance-mcp-cli(no args): bootstrap mode- checks auth token
- if token is missing/expired, starts
auth login - checks Codex/Claude/Gemini/Antigravity/Cursor MCP registration
- registers only missing clients
setup: registermetheus-governance-mcpinto Codex/Claude/Gemini/Antigravity/Cursor (if installed)bot: local chat bot env setup/list/show/add/edit/remove/global/verifydoctor: run end-to-end health checks (auth/registration/gateway/project/ctxpack/tools)proxy: stdio MCP bridge to Metheus HTTPS gatewayauth: save/check/clear local Metheus token used by proxy
Install
npm install -g metheus-governance-mcp-cli@latestInstall creates local provider settings templates here:
~/.metheus/telegram-bots/~/.metheus/slack.env~/.metheus/kakaotalk.env~/.metheus/bot-runner.json~/.metheus/project-workspaces.json
These files are for local provider bot secrets, local transport options, and optional per-bot local AI binding.
- Store locally:
- Telegram-wide settings in
~/.metheus/telegram-bots/global.env TELEGRAM_BOT_TOKENas a legacy Telegram fallback inglobal.env- one Telegram bot file per entry in
~/.metheus/telegram-bots/<ServerBotName>.env SLACK_BOT_TOKENKAKAOTALK_BOT_TOKEN
- Telegram-wide settings in
- Server-side Metheus stores project chat destination metadata separately:
- provider
chat_id/ channel / room identifier- label / active state
- Do not put project chat destination identifiers in local env files.
- Telegram-wide settings now live in
telegram-bots/global.env; Telegram per-bot secrets and server metadata live intelegram-bots/*.env. - Grouped server bots also persist
TELEGRAM_BOT_SERVER_ROLE_IDS, so the local file keeps the exact role-to-server-bot-id mapping instead of pretending that one grouped bot has only one server UUID.
Example templates:
# ~/.metheus/telegram-bots/global.env
TELEGRAM_API_BASE_URL=
TELEGRAM_AUTO_CLEAR_WEBHOOK=true
TELEGRAM_ALLOWED_UPDATES=message,edited_message
TELEGRAM_DEFAULT_BOT_KEY=ryoai_bot
# Legacy fallback
TELEGRAM_BOT_TOKEN=# ~/.metheus/telegram-bots/<ServerBotName>.env
TELEGRAM_BOT_KEY=<local_selector>
TELEGRAM_BOT_SERVER_BOT_ID=<server_bot_uuid>
TELEGRAM_BOT_SERVER_NAME=<ServerBotName>
TELEGRAM_BOT_SERVER_ROLES=monitor,review,worker,approval
TELEGRAM_BOT_SERVER_ROLE_IDS=monitor:<uuid>,review:<uuid>,worker:<uuid>,approval:<uuid>
TELEGRAM_BOT_USERNAME=ryoai_bot
TELEGRAM_BOT_TOKEN=
TELEGRAM_BOT_ROLE_PROFILE=
TELEGRAM_BOT_AI_CLIENT=
TELEGRAM_BOT_AI_MODEL=
TELEGRAM_BOT_AI_PERMISSION_MODE=
TELEGRAM_BOT_AI_REASONING_EFFORT=# ~/.metheus/slack.env
SLACK_BOT_TOKEN=
# ~/.metheus/kakaotalk.env
KAKAOTALK_BOT_TOKEN=Runner template:
{
"version": 2,
"role_profiles": {
"monitor": {
"client": "gpt",
"model": "",
"permission_mode": "read_only",
"reasoning_effort": "low"
}
},
"bot_bindings": {
"primary_monitor_bot": {
"server_bot_id": "<server_bot_uuid>",
"server_bot_name": "<server_bot_name>",
"role_profile": "monitor",
"client": "gpt",
"model": "",
"permission_mode": "read_only",
"reasoning_effort": "low"
}
},
"routes": [
{
"name": "telegram-monitor",
"enabled": false,
"project_id": "<project_uuid>",
"provider": "telegram",
"role": "monitor",
"server_bot_id": "<server_bot_uuid>",
"trigger_policy": {
"mentions_only": true,
"direct_messages": true,
"reply_to_bot_messages": true,
"ignore_edited_messages": true
},
"archive_policy": {
"mirror_replies": true,
"dedupe_inbound": true,
"dedupe_outbound": true,
"skip_bot_messages": true
}
}
]
}Project workspace registry:
{
"version": 1,
"project_workspaces": {
"<project_uuid>": {
"workspace_dir": "C:\\path\\to\\your\\project",
"source": "ctxpack_pull",
"updated_at": "2026-03-13T00:00:00Z"
}
}
}One command bootstrap (recommended)
Run in your project folder:
metheus-governance-mcp-cli --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --base-url https://metheus.gesiaplatform.comIf auth is not ready, login starts automatically, then MCP registration is completed.
Setup
metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --base-url https://metheus.gesiaplatform.comTechnical fallback: project-id can be auto-detected if your current folder (or parent) has .metheus_ctxpack_sync.json.
setup defaults to --workspace-dir auto (dynamic workspace detection).
Use an explicit path only when you intentionally want a fixed workspace.
Ops policy (recommended):
- Always pass explicit
--project-id <project_uuid>during setup. - Omit
--project-idonly in advanced cases where.metheus_ctxpack_sync.jsonis already present and verified.
Recommended for Codex/Claude/Gemini/Antigravity/Cursor multi-workspace sessions:
metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --base-url https://metheus.gesiaplatform.com --workspace-dir autosetup also ensures local provider templates exist:
~/.metheus/telegram-bots/~/.metheus/slack.env~/.metheus/kakaotalk.env~/.metheus/bot-runner.json~/.metheus/project-workspaces.json
Bootstrap or setup does not create one file per server bot automatically. Per-bot Telegram files are created later when you run bot add, bot verify, or bot edit against a real server bot identity.
When Telegram local config already exists, bootstrap/setup keeps your secrets but auto-normalizes the layout to the latest split structure:
- Telegram-wide settings stay in
~/.metheus/telegram-bots/global.env - per-bot secrets move to
~/.metheus/telegram-bots/<ServerBotName>.env - stale inline keys such as
TELEGRAM_BOT_<NAME>_BOT_*are rewritten into generic per-bot keys
Fill provider bot secrets and provider-local transport options locally. Project chat destination identifiers should be managed on the Metheus server as project chat destinations, not as local env values and not inside legacy Chat Hooks/webhooks.
~/.metheus/bot-runner.json is the local automation profile for:
- which project to watch
- which provider/role bot profile to use
- which role profile maps to which local CLI/model/permission/reasoning policy
- which server bot maps to which local LLM execution profile via
telegram-bots/global.env,telegram-bots/*.env, or fallbackbot_bindings
~/.metheus/project-workspaces.json is the local project workspace registry for:
- which
project_id -> workspace_dirmapping to apply locally - which workspace source updated that mapping last
Runner Reply-Chain Context
Runner follow-up handling is context-first.
- Follow-up human replies and bot replies are resolved from the reply chain first.
- The authoritative source is the normalized runner authority context:
conversation_id- parent/root message linkage
- persisted conversation contract
- next expected responders / summary bot
- The runner also persists authoritative prior-turn content for follow-up requests:
source_message_bodyreply_chain_contextroot_messageparent_messagelatest_prior_bot_replyrecent_chain_messages
Design rule:
reply chainis not a policy branch tree.reply chainis an authority input used once to build normalized context.- AI should be controlled by
agent_context,conversation,project_context_items, and authoritativereply_chain_context. - Code should validate, record, and deliver. It should not reinterpret follow-up meaning from trigger heuristics after context is built.
Operational note:
context_comments/ context window are supplementary recent context only.- They are not the authoritative source for parent/root follow-up content.
- If a user replies to a bot reply, the runner should prefer the persisted authority reply-chain context over generic recent-message scoring.
Telegram Group Privacy
Multi-bot Telegram group rooms depend on Telegram-side bot privacy settings, not only local runner code.
can_read_all_group_messagesis a Telegram bot account capability returned bygetMe- it is not a local
.envsetting - mismatched privacy settings across bots can produce inconsistent local inbound visibility and downstream delivery failures
See the separate guide:
Runner Input Pipeline
The runner input path currently has an architecture note for separating context records from execution ownership.
Use that note when working on receipt evidence, local visibility, responder selection, and accept ordering. The short version is:
archive = context onlyreceipt evidence = execution ownership only
Current fixed module order:
- input reception
- raw trigger evaluation
- continuation link recovery
- final execution decision and execution preparation
- request recording, state, lifecycle, and operator output
- delivery orchestration
- runner-delivery.mjs
- owns delivery context, provider dispatch orchestration, outbound archive mirroring, and final delivery result shaping
- provider transport
- provider-local-transport.mjs
- owns raw provider message transport and raw chat-action transport only
Execution order depends on message type, but module numbering does not:
- normal human message:
1 -> 2 -> 4 -> 5 -> 6 -> 7 - continuation bot reply:
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7
Maintenance guardrail:
- do not renumber these modules during cleanup
- do not solve a local edge case by moving behavior into the wrong module
- interpret old selftest names and old progress notes using the current module order only
Current validation guardrail:
npm run checkmust stay green- runner selftest baseline is
282 pass / 0 fail
Built-in helper command for legacy fallback/testing:
metheus-governance-mcp-cli local-bot-bridge --client gpt --no-updateSupported --client values:
gptclaudegeminisample
Compatibility note:
codexis still accepted as an alias forgptbecause the GPT adapter runs through the Codex CLI binary.
Use sample first for smoke tests before switching to a real AI client.
Gemini CLI note:
gemini mcpcommands require Gemini auth to be configured first (GEMINI_API_KEYor~/.gemini/settings.jsonauth).setupregisters Gemini in bothuserandprojectscopes to improve auto-discovery across folders.
Workspace signal guardrail:
- default recommendation is
--workspace-dir autowith no fixed fallback path. - if a client session does not provide trusted workspace signals (
workspaceFolders/rootUri/cwd), ctxpack local write is blocked (sync_status=guarded,local_file_count=0). - use
--workspace-fallback-dironly when you intentionally want a shared fallback root.
Guardrail note:
- By default, CLI blocks reading/writing ctxpack sync metadata when workspace root resolves to the home directory.
- Override only when intentional:
METHEUS_ALLOW_HOME_WORKSPACE=1.
Bot
Interactive entry:
metheus-governance-mcp-cli bot setupDirect commands:
metheus-governance-mcp-cli bot list
metheus-governance-mcp-cli bot show --provider telegram --bot-name <server_bot_name>
metheus-governance-mcp-cli bot add --provider telegram
metheus-governance-mcp-cli bot edit
metheus-governance-mcp-cli bot edit --provider telegram
metheus-governance-mcp-cli bot remove --provider telegram
metheus-governance-mcp-cli bot set-default --provider telegram --bot-name <server_bot_name>
metheus-governance-mcp-cli bot migrate --provider telegram --bot-name <server_bot_name>
metheus-governance-mcp-cli bot global --provider telegram
metheus-governance-mcp-cli bot verify --provider telegram --bot-name <server_bot_name>
metheus-governance-mcp-cli bot room-audit --provider telegram --project-id <project_uuid> --destination-label <room_label>
metheus-governance-mcp-cli bot room-audit --provider telegram --project-id <project_uuid> --destination-label <room_label> --apply trueBehavior:
bot setupasks forTelegram / Slack / KakaoTalkfirst, then prompts with numbered actions.me.create-botandme.update-botnow support the same local Telegram sync fields when you call them throughmetheus-governance-mcp-cli, so one tool call can update the server personal bot profile and this machine's local runner bot config together.bot addwithout flags now uses the shortest practical guided flow: provider -> token -> verify -> optional save-anyway only when verify fails -> optional username fallback only when verify cannot discover it -> optional AI model selection when the resolved defaults leave it blank -> optional default bot.- In the normal Telegram path,
bot adddoes not ask for a local selector, a server bot UUID, or an approval / worker / review / monitor choice. - For Telegram, the local bot file stem is auto-generated from the matched server bot name or verified username, so you do not have to invent a separate local nickname first.
- For Telegram, the CLI tries to match the verified bot identity against the server
me/botslist first. If the server exposes one logical bot name with multiple roles such asapproval,worker,review, andmonitor, the CLI does not ask you to choose one UUID. It binds by server bot name and keeps the local role/AI fields empty so runtime can use the server bot role for each route. - When the Telegram username matches exactly one server bot role, the CLI still auto-fills the local
role_profileand blank AI defaults from your localbot-runner.jsonrole_profilesmapping. bot editwithout flags now uses the same sequential flow every time: provider -> bot entry -> username/token review -> grouped server-role review when needed -> AI field choices -> default choice -> save.- when the CLI asks for
AI model, it now shows client-specific model choices first and still allows manual entry when you need a custom model name. - display labels are converted to tested execution model IDs at runtime:
gpt-5.4->gpt-5.4gpt-5-mini->gpt-5-minigpt-5.3-codex->gpt-5.3-codexgpt-5.3-codex-spark->gpt-5.3-codex-spark- older uppercase GPT labels are still accepted as compatibility aliases
Sonnet 4.6r->sonnetHaiku 4.5->haikuOpus 4.6->opusgemini-3.1-pro->auto-gemini-3
- if one server bot name maps to multiple server roles,
bot editkeeps the Telegram env entry bound to the server identity and lets you review the localrole_profilesfor each detected role instead of forcing one role/profile UUID choice up front. - In the normal Telegram edit path, the CLI keeps or re-resolves the server bot binding automatically. It no longer asks you to pick
approval / worker / review / monitoror a server bot UUID first. bot set-defaultwithout flags starts a guided numbered flow: provider -> bot entry -> confirm default change.bot verifywithout flags starts a guided numbered flow: provider -> bot entry -> output format.- When Telegram token verification resolves a bot username and the saved bot file is missing or stale,
bot verifynow writes that verified username back into the existing~/.metheus/telegram-bots/<ServerBotName>.enventry file. bot room-auditcompares one Telegram project destination across three views: room-visible bot administrators from Telegram, my server bot registrations from/api/v1/me/bots, and my local bot files under~/.metheus/telegram-bots/*.env.bot room-audituses TelegramgetChatAdministrators, so the room side is limited to bot administrators visible to the auditing local bot. Non-admin room bots may not appear.bot room-auditnow suggests executable runner routes for the managed server+local intersection. Room visibility is still reported, but a bot no longer has to appear in the admin list before route automation can prepare it.bot room-auditdefaults tomonitoronly unless you pass--roleor--roles.bot room-audit --apply truewrites missing suggested routes into~/.metheus/bot-runner.jsonand disables overlapping enabled routes in the same project/provider/destination/bot scope that are outside the selected role set.runner project upis the direct CLI preparation path for Telegram project operations: it runs the same room audit and applies the selected role routes. Userunner start-detachedfor persistent polling, or let the TUI callrunner.project_upto bootstrap detached polling in one step.runner project tuiis the direct CLI project-scoped dashboard: it audits one project destination, ensures one detached runner is reused or started for the selected route set, and then shows status, route selection, and recent log tail in one interactive screen.runner project upcan be narrowed with--bot-name,--bot-id,--role, or--roles <csv>when you do not want every suggested role route for that room.bot removewithout flags starts a guided numbered flow: provider -> bot entry -> confirm removal.- Telegram stores one bot file per entry under
~/.metheus/telegram-bots/<ServerBotName>.envwith generic fields:TELEGRAM_BOT_KEYTELEGRAM_BOT_SERVER_BOT_IDTELEGRAM_BOT_SERVER_NAMETELEGRAM_BOT_SERVER_ROLESTELEGRAM_BOT_SERVER_ROLE_IDSTELEGRAM_BOT_USERNAMETELEGRAM_BOT_TOKENTELEGRAM_BOT_ROLE_PROFILETELEGRAM_BOT_AI_CLIENTTELEGRAM_BOT_AI_MODELTELEGRAM_BOT_AI_PERMISSION_MODETELEGRAM_BOT_AI_REASONING_EFFORT
- Slack and KakaoTalk currently use a single local token entry per provider in this command flow.
bot verifychecks the configured local token, cross-checks the server bot binding, prints the effective runtime role profile summary that the runner will use, and shows which local runner routes currently point at this bot entry.- when linked runner routes exist,
bot verifyalso prints directrunner show --route-name ...andrunner once --dry-run-delivery truefollow-up commands so the next operational step is obvious. bot showprints one local bot entry in detail, including grouped role summaries when one server bot name expands to multiple roles and any linked local runner routes.- In
bot showandbot verify,saved_local_filemeans the values already stored on disk under~/.metheus/telegram-bots/*.env, whilelive_server_responsemeans the fresh/api/v1/me/botsresponse from the server right now. - Telegram per-bot env files now keep both
TELEGRAM_BOT_KEYandTELEGRAM_BOT_USERNAMEso the local selector and the verified Telegram account identity stay explicit in the same bot file. bot globaledits Telegram-wide local settings such as API base URL, allowed updates, and the default Telegram bot selector.bot set-defaultchanges the default Telegram bot selection.bot migratemoves legacyTELEGRAM_BOT_TOKENinto a named Telegram bot entry.
Non-interactive examples:
metheus-governance-mcp-cli bot global --provider telegram --non-interactive true --api-base-url http://127.0.0.1:8999/telegram --auto-clear-webhook false --allowed-updates message,edited_message,channel_post
metheus-governance-mcp-cli bot add --provider telegram --non-interactive true --server-bot-id <server_bot_uuid> --token <telegram_bot_token> --default true
metheus-governance-mcp-cli bot add --provider telegram --non-interactive true --server-bot-id <server_bot_uuid> --token <telegram_bot_token> --ai-client gpt --ai-model "gpt-5.4" --ai-permission-mode read_only --ai-reasoning-effort low
metheus-governance-mcp-cli bot edit --provider telegram --bot-name <server_bot_name> --non-interactive true --ai-client claude --ai-model "Sonnet 4.6r" --ai-permission-mode danger_full_access --ai-reasoning-effort high
# Direct MCP tool calls can also sync local bot config on this machine:
# me.create-bot {..., "bot_username":"<telegram_username>", "token":"<telegram_bot_token>", "role_profile":"monitor", "ai_client":"gpt", "ai_model":"gpt-5.4", "ai_permission_mode":"read_only", "ai_reasoning_effort":"low", "default":true}
# me.update-bot {..., "bot_id":"<server_bot_uuid>", "token":"<new_token>", "ai_client":"claude", "ai_model":"Sonnet 4.6r"}
metheus-governance-mcp-cli bot set-default --provider telegram --bot-name <server_bot_name> --non-interactive true
metheus-governance-mcp-cli bot migrate --provider telegram --bot-name <server_bot_name> --server-bot-id <server_bot_uuid> --role-profile monitor --ai-client gpt --ai-permission-mode read_only --ai-reasoning-effort low --non-interactive true
metheus-governance-mcp-cli bot remove --provider telegram --bot-name <server_bot_name> --non-interactive true
metheus-governance-mcp-cli bot verify --provider telegram --bot-name <server_bot_name> --json true
metheus-governance-mcp-cli bot room-audit --provider telegram --project-id <project_uuid> --destination-label <room_label> --json true
metheus-governance-mcp-cli bot room-audit --provider telegram --project-id <project_uuid> --destination-label <room_label> --apply true --json true
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label> --start false
metheus-governance-mcp-cli runner start-detached --project-id <project_uuid> --provider telegram --destination-label <room_label>
metheus-governance-mcp-cli runner project tui --project-id <project_uuid> --provider telegram --destination-label <room_label>
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label> --bot-name <server_bot_name> --roles monitor,review --start falseFor direct Telegram adds, the CLI derives the local file name from the matched server bot name. You normally do not need --bot-key or --username. Treat --bot-key as a legacy compatibility selector only when you intentionally need to target an older local selector directly.
For direct Telegram edits, prefer --bot-name or --bot-id because server bot identity is the source of truth. Use --bot-key only when you intentionally need the legacy local selector. If one server bot name expands to multiple roles such as approval / worker / review / monitor, prefer the guided bot edit flow so you can keep the current grouped settings, edit one role only, or walk every role in sequence instead of forcing one entry-level AI override.
For runner commands, routes are the executable unit. Use --route-name as the primary selector. --bot-name and --bot-id are convenience route aliases only when they identify one enabled route uniquely. Use runner list first if you are not sure which route belongs to which server bot, and use runner show to inspect the resolved route, server identity, workspace mapping, and execution profile together.
Work-step planning uses a separate planner model policy from the route reply model. Current default behavior:
- planner:
gpt-5-miniwhen the planner client resolves to GPT - planner audit/repair: fall back to the current route model unless overridden
Planner env overrides:
METHEUS_ROLE_PLANNER_CLIENTMETHEUS_ROLE_PLANNER_MODELMETHEUS_ROLE_PLANNER_REASONING_EFFORT- optional audit overrides:
METHEUS_ROLE_PLANNER_AUDITOR_CLIENTMETHEUS_ROLE_PLANNER_AUDITOR_MODELMETHEUS_ROLE_PLANNER_AUDITOR_REASONING_EFFORT
- optional repair overrides:
METHEUS_ROLE_PLANNER_REPAIR_CLIENTMETHEUS_ROLE_PLANNER_REPAIR_MODELMETHEUS_ROLE_PLANNER_REPAIR_REASONING_EFFORT
Current support status:
- Telegram: full local bot entry management, token verification, bot-to-AI binding, inbound runner support
- Slack: single local token entry, verification supported, inbound runner not supported
- KakaoTalk: single local token entry, local config only, remote verification not implemented
Current operating decision:
- keep Telegram as the only full route + inbound runner path
- keep Slack on local token verification and direct local send only
- keep KakaoTalk on local config only until Telegram operations are stable end-to-end
Project ID behavior (verified)
setup --project-id <A>sets default proxy scope to projectA.- Proxy forwards
project_idto gateway query, and gateway setsMCP_PROJECT_IDfor MCP runtime. - If a tool call includes
project_idin its arguments, that value overrides setup-time default scope. - If tool arguments omit
project_id, server falls back toMCP_PROJECT_ID. - If both are missing, the call fails with
project_id is required. - For single-project sessions, keep one pinned
--project-idand avoid sending a differentproject_idunless intentional.
Doctor
metheus-governance-mcp-cli doctor --project-id <project_uuid> --base-url https://metheus.gesiaplatform.com
metheus-governance-mcp-cli doctor --project-id <project_uuid> --base-url https://metheus.gesiaplatform.com --strict trueChecks:
- auth token status (+ auto refresh attempt)
- local provider env template presence
- local bot runner v2 config validity
- project workspace mapping presence
- role profile -> local CLI availability
- route dry-run / trigger-policy safety warnings
- local provider token presence for active project destinations
- enabled runner route -> local bot env binding resolution
- enabled runner route -> server bot UUID cross-check
- legacy
TELEGRAM_BOT_TOKENfallback still in use - codex/claude/gemini/antigravity/cursor registration state
- gateway
tools/listreachability project.summaryaccess- ctxpack auto sync status
- smoke calls:
workitem.list,evidence.list,decision.list - Telegram room audit for active project destinations, including external room bots and visible server/local mismatches
- route-state workspace artifact boundary violations recorded by the runner
- external project artifact findings discovered from known out-of-workspace roots such as
~/.claude/plans(Claude-managed area, not a Metheus authoring target)
--strict true upgrades local runner route safety warnings into failures.
Use it for production validation before enabling long-running bot routes.
Direct bot posting:
me.send-bot-messageuses local provider tokens from~/.metheus/<provider>.env- it does not use a server-stored bot token
- the destination identifier is resolved from the current project's saved Chat Destinations on the Metheus server
- if multiple active destinations exist for the same provider, pass
destination_idordestination_label - pass
dry_run_delivery=trueto preview the resolved bot/destination locally without sending the provider message - direct local delivery is implemented today for:
- Telegram
- Slack
- KakaoTalk profiles and destinations can be stored now, but direct local delivery is not implemented yet
Provider support matrix in this CLI:
telegram: local token verification, direct local delivery, typing, reply-to-message, and automatic inbound runner are implementedslack: local token verification and direct local delivery are implemented, but automatic inbound runner is notkakaotalk: local config can be stored, but token verification, direct local delivery, and automatic inbound runner are not implemented
Local bot runner
The local runner closes the loop:
- provider bot receives a room message
- Metheus archives the message into Governance discussion comments
- local runner reads new archived comments
- local AI command generates a reply
- CLI sends the reply back through
me.send-bot-message - the sent reply is mirrored back into the archive
Execution model:
- server stores bot identity, provider, role, and project chat destination
- local runner stores workspace mapping and role profiles
- runner resolves
project_id -> workspace_dir - runner resolves server bot role to a local
role_profile - runner executes the mapped client adapter (
gpt/claude/gemini) - legacy
commandremains readable for migration, but execution is disabled by default unlessMETHEUS_ALLOW_LEGACY_RUNNER_COMMAND=1
Commands:
metheus-governance-mcp-cli runner list
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label> --start false
metheus-governance-mcp-cli runner start-detached --project-id <project_uuid> --provider telegram --destination-label <room_label>
metheus-governance-mcp-cli runner route add
metheus-governance-mcp-cli runner route edit --route-name telegram-monitor
metheus-governance-mcp-cli runner route remove --route-name telegram-monitor
metheus-governance-mcp-cli runner show --route-name telegram-monitor
metheus-governance-mcp-cli runner artifact scan
metheus-governance-mcp-cli runner once --route-name telegram-monitor
metheus-governance-mcp-cli runner start --route-name telegram-monitor --concurrency 2Route management:
runner route addcreates one executable route by selecting the project, provider, role, server bot, and project chat destination in order.runner route addnow auto-uses the suggested route name,5000ms poll interval, andenabled=trueunless you pass explicit flags or edit the route later.runner project upis the shortest practical direct CLI preparation path for Telegram: it audits one project destination and writes any missing suggested routes. Start persistent polling separately withrunner start-detached, or use the TUI/local tool path that bootstraps detached polling for you.- if room visibility probe fails but one or more enabled routes already exist for that project destination,
runner project upnow treats the probe failure as a warning and can still start those existing routes. runner project up --dry-run-delivery truelets the started runner validate route execution without sending a real provider message.- In public Telegram bot conversations, the stored route role is treated as a hint only. Live room context, the current human request, and recent bot replies take priority over the stored route role hint when the local AI client decides how to answer.
runner route editupdates one stored route. Use it when the destination, role, or server bot for an existing route changes.runner route removedeletes one stored route from~/.metheus/bot-runner.json.runner listandrunner route listare equivalent. They show which routes are executable, thelogical_signaturethat identifies the real processing target, and how--bot-namewould resolve.runner artifact scaninspects runner state and known external output roots so you can see recent workspace boundary violations or legacy project artifacts that landed outside configured workspaces.- Do not keep two enabled routes with the same
project_id + provider + role + server bot + destinationtarget. The CLI now blocks that duplicate route shape.
Recommended operational path:
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label> --start false
metheus-governance-mcp-cli runner start-detached --project-id <project_uuid> --provider telegram --destination-label <room_label>
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label> --bot-name <server_bot_name> --roles monitor,review --start false
metheus-governance-mcp-cli runner start-detached --project-id <project_uuid> --provider telegram --destination-label <room_label> --bot-name <server_bot_name> --role monitorIf you want the older step-by-step path instead:
metheus-governance-mcp-cli bot room-audit --provider telegram --project-id <project_uuid> --destination-label <room_label> --apply true
metheus-governance-mcp-cli runner once --route-name telegram-monitor --dry-run-delivery true
metheus-governance-mcp-cli runner startScale / concurrency guidance:
- For many bots, prefer one
runner startprocess and many enabled routes. - Use
--route-nameas the primary production selector. Treat--bot-nameand--bot-idas convenience aliases only. - Do not run two runner processes against the same logical route target at the same time.
- The logical-route lock is local to one machine. Do not run the same logical route from two different PCs or servers at the same time.
runner start --concurrency <n>lets one process poll multiple due routes in parallel. The default is conservative and the CLI uses a per-logical-route lock to avoid duplicate processing.
What runner list means:
server_bot_namemay come fromroute.server_bot_nameor be resolved from the Telegram bot env file byserver_bot_idserver_bot_name_source=route_configmeans the route file already stores the name directlyserver_bot_name_source=telegram_env_lookupmeans the CLI resolved the name from the local Telegram bot fileunique_route_alias_by_server_bot_nameis a convenience selector, not a separate execution targetrun_once_by_server_bot_nameinrunner showor docs means "find the one enabled route that matches this server bot name, then run that route"runner showalso separates the resolved destination block so you can see the exact destination label/id the route is pointing at without parsing raw route JSON by handrunner onceandrunner startnow printarchive_sourceandarchive_work_item_idso you can tell whether the route used an existing archive thread, reused an existing archive work item, or had to bootstrap a fresh archive threadlogical_signatureis the actual lock/duplicate-detection scope. If two routes share it, they are the same processing target and should not both be enabled.runner shownow includes alast_runblock with the last artifact validation result, artifact paths, artifact errors, boundary violations, and workspace_dir recorded for that route.- Project artifacts must stay inside the selected
workspace_dir. If a bot claims it created or updated files outside that workspace, runner marks the turn aspolicy_violationinstead of success.
Debug/selection overrides:
metheus-governance-mcp-cli runner show --bot-name <server_bot_name>
metheus-governance-mcp-cli runner once --project-id <project_uuid> --provider telegram --role monitor
metheus-governance-mcp-cli runner start --project-id <project_uuid> --provider telegram --role monitor --poll-interval-ms 5000
metheus-governance-mcp-cli runner once --project-id <project_uuid> --provider telegram --role monitor --role-profile reviewRecommended production path:
- keep provider bot token in
~/.metheus/<provider>.env - keep project room identifier in server-side Chat Destinations
- keep automation route config in
~/.metheus/bot-runner.json - keep project workspace mappings in
~/.metheus/project-workspaces.json - treat
project-workspaces.jsonas the local workspace authority for this machine - do not treat ctxpack sync/cache metadata as workspace authority
- do not assume
project.summary/project.describe/project.getwill rewrite local workspace mappings - keep per-role execution policy under
role_profiles - keep Telegram-wide binding defaults in
~/.metheus/telegram-bots/global.env - use
bot_bindingsinbot-runner.jsononly as local fallback/override - runner resolution order is: explicit
route.role_profile-> provider env bot binding ->bot_bindings-> server bot role ->route.role
Why workspace_dir matters:
- the server cannot know each project member's local folder path
- the local runner must pass the correct folder to Codex, Claude Code, or Gemini on that member's machine
- without a valid project mapping, bots may answer with a generic local root instead of the real project workspace
Workspace/source-of-truth model:
project-workspaces.jsonanswers only one question:project_id -> local workspace_dir on this machine- use local tool
project.workspaceto read that binding - use local tool
project.workspace.bindto explicitly update that binding - use local tool
runner.project_upto audit a project destination and create/select the executable runner routes - use local tool
runner.showto inspect one resolved route before launch - use local tool
runner.start_detachedto launch an existing runner route in a separate local terminal - use local tool
runner.statusto inspect detached runner processes launched by this CLI - use local tool
runner.stopto stop detached runner processes launched by this CLI - detached runner lifecycle belongs to the local Governance MCP tools, not to the current Codex/Claude/Gemini chat turn
- client AI should resolve workspace + route and then request
runner.start_detached; it should not try to keep the polling runner attached to its own session - the operational goal is: AI session may exit, but detached runner must continue polling independently on the local machine
runner.start_detachedopens a separate terminal window on Windows, Terminal.app on macOS, and a supported Linux terminal emulator (x-terminal-emulator,gnome-terminal,konsole,mate-terminal,tilix,alacritty, orxterm)- on Linux, install one of those terminal emulators first; detached runner launch will fail fast if none are available
bot-runner.jsonstores executable route config and role profiles; it is not the project workspace registryctxpackis project guidance/specification content stored inside the workspace and on the server; it is not the local workspace authority.metheus/ctxpack-cache/is local synced cache for ctxpack content; do not treat it as the final authoring target for project documents.metheus/runner-runtime/local-ai-scratch/is local runner scratch space for AI CLI runtime support; do not treat it as a project artifact location or a user-facing document location.metheus_ctxpack_sync.jsonis sync metadata only; do not treat it as an official ctxpack source document- Official ctxpack authoring may live under normal project files or
.metheus/...authoring paths, but exclude cache/runtime/metadata paths such as.metheus/ctxpack-cache/,.metheus/runner-runtime/, and.metheus_ctxpack_sync.json
Role profile fields:
client:gpt,claude,gemini, orsamplecodexis still accepted as a compatibility alias forgptmodel: optional CLI-specific model namepermission_mode:read_only,workspace_write,danger_full_accessreasoning_effort:low,medium,high
Recommended role mapping:
monitor:read_only+lowreview:read_only+mediumworker:danger_full_access+mediumapproval:danger_full_access+high
Safety note:
- Keep
monitorandreviewonread_onlyunless you have a very specific reason to let those roles write locally. doctor --strict truenow warns when a route resolvesmonitororreviewto a broader permission or heavier reasoning level than the recommended baseline.
Role profile note:
- Claude maps
reasoning_effortto--effort. - Codex maps
reasoning_effortto-c model_reasoning_effort="...". - Gemini applies
reasoning_effortby injecting a temporary~/.gemini/settings.jsonoverride for the invocation. - For Gemini 3 models,
low -> thinkingLevel LOW,medium -> THINKING_LEVEL_UNSPECIFIED(Gemini default), andhigh -> thinkingLevel HIGH.
Trigger policy fields:
mentions_only: in groups, react only when the bot is mentioned or when a message replies to the botdirect_messages: allow or block private chat messagesreply_to_bot_messages: treat replies to the bot as actionable even without an explicit mentionignore_edited_messages: skip archived edited-message events
Human intent gate:
- The runner first tries a local AI intent parser that returns a structured conversation contract. If parsing fails, it falls back conservatively to explicit mention structure only; it does not rely on language-specific keyword tables.
- Human messages create the conversation contract. The runner derives the initial responder set, optional summary bot, and whether bot-to-bot relay is allowed from the human request before any bot reply can expand the conversation.
- A single-bot human request does not authorize other bots to join just because the first bot publicly mentions them later.
- Multi-bot collaboration is only relayed when the human request itself authorized that collaboration and the relayed bot is inside the stored
allowed_responderscontract. - Bot replies can continue an existing authorized public conversation, but they cannot add new participants outside the original human contract.
- When a lead bot delegates work in public, followers now wake only from an actionable execution contract such as
delegationorsummary_request; a plain mention or "I will tell you later" style reply is not enough. runner onceandrunner starttext logs now print both the stored conversation contract and the current execution contract so operators can see the active lead bot, summary bot, allowed responders, and next expected responders without switching to JSON.
Recommended role baselines:
monitor:mentions_only=true,direct_messages=true,reply_to_bot_messages=true,ignore_edited_messages=truereview:mentions_only=true,direct_messages=true,reply_to_bot_messages=true,ignore_edited_messages=trueworker:mentions_only=true,direct_messages=false,reply_to_bot_messages=true,ignore_edited_messages=trueapproval:mentions_only=true,direct_messages=false,reply_to_bot_messages=true,ignore_edited_messages=true
These are the default runner v2 fallbacks when a route omits explicit trigger settings.
doctor warns when a route opens broader reply conditions than the recommended role baseline.
Archive policy fields:
mirror_replies: mirror bot replies back into Governance archivededupe_inbound: avoid duplicate inbound archive comments for the same provider messagededupe_outbound: avoid duplicate mirrored bot-reply archive comments for the same delivered provider messageskip_bot_messages: ignore provider bot messages during inbound import to avoid self-loop behavior
Mapping behavior:
ctxpack pull --workspace-dir <path>storesproject_id -> workspace_dir- broader project roots are preferred over nested tool folders when updating the mapping intentionally
project.summary/project.describe/project.getsync ctxpack cache, but they do not own local workspace authority- if you need to change a project's local folder on this machine, update the project workspace registry intentionally instead of relying on ctxpack cache side effects
- use
project.workspaceto read the current local binding fromproject-workspaces.json - use
project.workspace.bindto intentionally write the binding intoproject-workspaces.json project.workspace.bindupdates the workspace registry directly; it does not needbot-runner.jsonside effects to succeed- use
runner.project_upafter workspace binding to prepare executable routes for one project destination - use
runner.showto inspect the selected route before launching it - use
runner.start_detachedto hand runner lifecycle over to the local machine after route preparation; do not rely on the current client AI process to keep polling alive - use
project.context.activewhen answering shared project role/rule/fact questions from stored project context instead of model memory alone - use
project.context.suggestwhen a user is explicitly defining a durable shared project role/rule/fact that should be proposed into common project context
Recommended local runner bootstrap:
1. project.workspace
2. project.workspace.bind # only when the registry binding is missing or wrong
3. runner.project_up
4. runner.show
5. runner.start_detached
6. runner.status / runner.stopProject context tools:
project.context.listlists project context items, optionally filtered by statusproject.context.activereturns only active shared context itemsproject.context.getreturns one context item bycontext_idproject.context.createcreates an active context item directlyproject.context.suggestcreates asuggestedcontext item for later approvalproject.context.updateedits one context itemproject.context.activate,project.context.reject, andproject.context.archivechange context statusproject.context.deletedeletes one context item
Recommended project context behavior:
- use active project context for durable shared bot-role/rule/fact answers
- use suggested project context for new shared rules/facts proposed from chat
- do not store ephemeral chat, one-off commands, or personal reminders as project context
Operational principle:
- the local Governance MCP tools own runner startup and lifecycle
- the client AI only decides when to start/inspect/stop the runner
- the detached runner must keep running even if the current AI tool session exits
Runner command contract:
- stdin: JSON payload containing project, destination, trigger message, and recent context comments
- stdout:
- plain text reply, or
- JSON object like
{"reply":"...","reply_to_message_id":123}, or - JSON skip result like
{"skip":true,"reason":"not actionable"}
Notes:
runner onceprocesses the most recent pending archived inbound messagerunner startkeeps polling and stores per-route cursor state in~/.metheus/bot-runner-state.json- request execution is request-ledger first: one human root message becomes one
request_key, and request status is the real execution gate conversation_sessionis advisory metadata only; participants, last speaker, and next expected responders stay there, but sessionopenalone must not revive old work- bot replies and public bot-to-bot delegation replies do not create a fresh request on their own; they continue only when an active request already exists for that conversation
- consumed archived comments are recorded in the runner request comment ledger so the same bot reply does not get re-queued later as new work
- startup cleanup closes stale open sessions without an active request and excludes their archived bot replies before pending selection runs
- first start primes the cursor to the latest inbound message and does not reply to old backlog
- when inline filters match a configured route in
~/.metheus/bot-runner.json, the runner reuses that route's canonical name/destination and state cursor instead of creating a new anonymous route key - stale anonymous route keys in
~/.metheus/bot-runner-state.jsonare auto-migrated to the matching configured route when possible;doctorwarns if ambiguous legacy keys still remain --dry-run-delivery trueresolves the real bot and destination but skips provider send and archive mirror writes- when
trigger_policy.mentions_only=true, unmentioned group messages are archived but skipped for reply generation - mirrored bot replies are deduped by
chat_id + message_id - provider bot messages are ignored during inbound import by default
- text-mode runner output now also shows
logical_signature,trigger,execution,conversation, andarchivefields so an operator can see why one route replied or skipped without switching to JSON output local-bot-bridgereads stdin JSON from the runner and can call Codex/Claude/Gemini for youroute.commandfallback is disabled by default; enable it only temporarily withMETHEUS_ALLOW_LEGACY_RUNNER_COMMAND=1- today this automation path is implemented for Telegram end-to-end
- prefer
TELEGRAM_API_BASE_URL=inside~/.metheus/telegram-bots/global.envfor local Telegram API overrides;METHEUS_TELEGRAM_API_BASE_URLremains a process-level fallback mainly for mock/regression testing - Slack can use direct local send, but automatic inbound runner flow is not completed yet
- KakaoTalk config can be stored now, but direct send/runner flow is not implemented yet
doctornow reports provider support for both enabled runner routes and active project chat destinations
Use in MCP
setup auto-registers this server for codex, claude, gemini, antigravity, and cursor when those CLIs are available.
Antigravity note:
- this CLI manages MCP registration via Antigravity local config because Antigravity does not provide full
mcp list/get/removesubcommands. - primary config path:
~/.gemini/antigravity/mcp_config.json - legacy mirror path:
%APPDATA%/Antigravity/User/mcp.json(Windows) for compatibility - for compatibility across Antigravity variants, setup writes both
mcpServersandserverskeys. - proxy stdio now supports both
Content-Lengthframed MCP and JSONL input for VS Code-family clients.
Cursor note:
- this CLI manages MCP registration via Cursor global MCP config (
~/.cursor/mcp.json).
Tool naming compatibility:
- Claude/Codex/Gemini keep canonical MCP tool names (
project.summary,ctxpack.merge.brief, ...). - Cursor/Antigravity sessions use safe aliases only (
project_summary,ctxpack_merge_brief, ...). - Proxy maps safe alias calls back to canonical names automatically.
Local bootstrap tools exposed by proxy:
project.summaryproject.describe(alias)project.get(alias)ctxpack.merge.briefctxpack.merge.execute
These tools accept project_id and return:
- access state (
granted,unauthorized,denied,not_found) - project metadata (name, org, template, owners, visibility)
- agenda preview from ctxpack (when available)
project.summary automatically syncs ctxpack to local cache:
- missing local cache -> download
- same version -> keep current
- newer server version -> update local cache
- workspace path -> auto-detected from client metadata/env by default
- if workspace signal is missing in auto mode, sync is guarded (no local write)
- use
--workspace-fallback-dir <path>only when you intentionally want fallback writes - this local cache sync does not make ctxpack the source of truth for
project-workspaces.json
Project-ID first-call rule:
- when user gives only
Project ID, callproject.summaryfirst. - call
ctxpack.ensureafterproject.summaryonly when extra ctxpack refresh/export context is needed. - for support/ops workflows, do not run ctxpack sync/download before a successful
project.summary.
Ctxpack merge safety flow:
- call
ctxpack.merge.brieffirst - review who changed what (
created_by,changed_paths, metadata summary) - get explicit owner decision in chat
- then call
ctxpack.merge.executewithowner_confirmation
Manual ctxpack pull/update:
metheus-governance-mcp-cli ctxpack pull --project-id <project_uuid> --base-url https://metheus.gesiaplatform.comCanonical ctxpack write tool name:
- use
ctxpack.update - do not use legacy names like
ctxpack.pushorctxpack.save - chat/runner guidance:
- use
ctxpack.updatefor official project guidance/instruction/policy/spec requests - do not force
ctxpack.updatefor normal README or generic implementation-document requests unless the request explicitly asks for official project guidance
- use
When workitem.list returns empty, proxy appends a hint to call project.summary first.
For ctxpack.update conflicts (ctxpack_conflict / stale baseline), proxy behavior:
- standard conflict message with pull/merge/retry guidance
- auto ctxpack pull-to-local on conflict by default (
--auto-pull-on-conflict=true)
Auth flow (recommended)
- Auto login and save token once (default flow:
device -> callback -> manual hint):
metheus-governance-mcp-cli auth login --base-url https://metheus.gesiaplatform.comOptional flags:
metheus-governance-mcp-cli auth login --callback-port 43819 --open-browser true
metheus-governance-mcp-cli auth login --flow device
metheus-governance-mcp-cli auth login --flow callback --callback-port 43819
metheus-governance-mcp-cli auth login --client-id metheus-cli --realm master --keycloak-url https://oauth3.gesia.ioKeycloak client requirement for auto login:
- Device flow:
- Enable OAuth 2.0 Device Authorization Grant for the client.
- Callback flow:
- Add localhost callback URI to redirect list (example):
http://127.0.0.1/*
- Add localhost callback URI to redirect list (example):
- If callback URI is not configured, CLI can still work via
--flow device.
Manual fallback:
metheus-governance-mcp-cli auth login --manual true --base-url https://metheus.gesiaplatform.com- Check token status:
metheus-governance-mcp-cli auth status- If token is rotated, set directly:
metheus-governance-mcp-cli auth set --token "<access_token>" --refresh-token "<refresh_token>"- Remove local token:
metheus-governance-mcp-cli auth clearproxy priority:
METHEUS_TOKEN
MCP_AUTH_TOKEN
- local file
~/.metheus/governance-mcp-auth.json
- local file
You can still use env token directly:
export METHEUS_TOKEN="<access_token>"PowerShell:
$env:METHEUS_TOKEN="<access_token>"npm publish token (where to store)
Use this local file in this folder:
tools/governance-mcp-cli/.env.npm.local(create from.env.npm.local.example)
Template:
NPM_TOKEN=__PASTE_NPM_TOKEN_HERE__Load token before publish:
PowerShell:
$env:NPM_TOKEN = (Get-Content .\.env.npm.local | Where-Object { $_ -match '^NPM_TOKEN=' } | ForEach-Object { $_.Split('=',2)[1] } | Select-Object -First 1).Trim()
npm publish --access publicbash/zsh:
export NPM_TOKEN="$(grep '^NPM_TOKEN=' ./.env.npm.local | head -n1 | cut -d'=' -f2-)"
npm publish --access publicRelease commands
npm run check
npm run test:compat
npm run publish:dryActual publish:
npm run publish:publicIf npm returns You cannot publish over the previously published versions, bump package.json version and retry.
If npm account uses 2FA, pass OTP:
node release.mjs --otp <6-digit-code>Regression and smoke checks
Local compatibility selftest (no network):
npm run test:compatThis selftest now includes a local mock Telegram runner path:
- mock Telegram
getUpdatesimport into archive - trigger-policy evaluation
- role-based trigger default resolution
- local
sampleAI execution sendChatAction/sendMessage- mirrored bot-reply archive creation and state update
Proxy smoke test (initialize -> tools/list -> project summary -> ctxpack local sync):
npm run smoke:proxy -- --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --workspace-dir <workspace_path>Optional:
--client cursor-vscode(default) or--client antigravity--base-url https://metheus.gesiaplatform.com/governance/mcp
Workspace fallback guardrail:
- If
METHEUS_WORKSPACE_DIRis accidentally set to a non-existing boolean suffix path likeC:\code_test\true, proxy now recovers to the parent workspace (C:\code_test) instead of pinning to the bad path.
