mcp-sentinel

by GCS-ZHN

3 模型与账号接入github未核验到 manifest收录于 08-16

AI代理与MCP服务器之间的哨兵插件,轮询长时间运行的任务,防止昂贵的状态循环进入LLM推理路径

Harness agent plugin that acts as a sentinel between the AI agent and MCP servers — polling long-running tasks so token-costly status loops never enter the LLM inference path

安装

dsh plugin --profile web add github:GCS-ZHN/mcp-sentinel

GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试

安装与环境配置指引、插件开发教程见 DSH 中文社区文档 ↗

安装即在你的机器上以你的权限运行第三方代码——它可读写文件、使用凭据、访问网络,DSH 的工具审批不会为插件代码加沙箱。「检测到 manifest」仅代表发现 dsh.bundle / dsh.plugin 清单,不构成兼容性或安全审查;安装前请审阅源码,不熟悉的插件先在不含密钥的环境试用。

README

目录

A sentinel between an AI agent and MCP servers — polling long-running tasks on the agent's behalf so that token-costly status loops never enter the LLM inference path.

This is a monorepo: a harness-agnostic core (@gcszhn/mcp-sentinel-core) plus one thin plugin package per agent host.

Design principle

Zero MCP re-configuration. The sentinel never asks the user to configure MCP servers of its own. Installing the plugin is the whole setup — it discovers and reuses the MCP servers the harness already has, in whichever way that harness exposes them:

  • From the host's MCP config — OpenCode. The plugin reads client.config.get().mcp and hands the resolved servers to the core, which owns the connection lifecycle. No extra MCP setup.
  • Through the harness SDK — DeepSeek Harness. The plugin calls the mcp__<server>__<tool> tools already registered by @deepseek-ai/dsh-mcp-client via ctx.tools.execute. No extra MCP setup.
  • As a harness-agnostic MCP CLI — any harness. `mcp-sentinel mcp --harness

<codex|opencode|custom> is a plain stdio MCP server that discovers the MCP servers the harness already exposes (codex mcp list --json, opencode debug config, or a --mcp-config` file) and skips its own entry. No message notification channel — agents collect results with attach/status/read.

The agent immediately sees the MCP servers it already configured for that harness; there is no sentinel-specific MCP config, mock server, or demo wiring to maintain.

Supported harnesses

Install instructions and per-harness details live in each plugin's own README.

Harness Plugin package Docs
OpenCode @gcszhn/mcp-sentinel-opencode-plugin README
DeepSeek Harness @gcszhn/mcp-sentinel-deepseek-harness-plugin README
Any harness (CLI) @gcszhn/mcp-sentinel-cli README

The shared core ships separately as @gcszhn/mcp-sentinel-core — see its README.

Motivation

When an agent submits a long-running job through an MCP tool, it must repeatedly call the server to check progress — each round-trip burns context window tokens.

sequenceDiagram
    participant A as Agent (LLM)
    participant M as MCP Server

    Note over A: Without sentinel
    A->>M: check status
    M-->>A: running...
    Note over A: token cost 💸
    A->>M: check status
    M-->>A: running...
    Note over A: token cost 💸
    A->>M: check status
    M-->>A: completed ✓
    Note over A: token cost 💸

mcp-sentinel moves the polling loop out of the agent and into the plugin runtime — 2 inference calls regardless of task duration.

sequenceDiagram
    participant A as Agent (LLM)
    participant S as Sentinel Plugin
    participant M as MCP Server

    A->>S: poll_mcp(server, tool, until)
    Note over A: token cost 💸 (once)

    loop silent polling (zero tokens)
        S->>M: call tool
        M-->>S: running...
        S->>S: evaluate condition
    end

    S->>M: call tool
    M-->>S: completed ✓
    S->>A: promptAsync(result)
    Note over A: token cost 💸 (once)

Configuration

Environment variables for controlling memory usage:

Variable Default Description
SENTINEL_MAX_POLL_LOG unlimited Max poll log entries per task (FIFO trim)
SENTINEL_TASK_TTL_MS unlimited Auto-cleanup completed tasks after N milliseconds

Both accept positive integers only. Zero, negative, or non-numeric values are treated as unlimited/disabled.

Tools

mcp_sentinel_poll

Submit a long-running MCP tool call and poll it at regular intervals until a condition is met. The sentinel polls silently (zero token cost) and notifies you when done.

Parameter Type Default Description
server string required MCP server name (resolved from the host's MCP config)
tool string required Tool name to call on the server
args object {} JSON object of arguments for the tool
interval number 5000 Poll interval in milliseconds
timeout number optional Max poll duration in ms (unset = no limit)
until object required JSON condition object

Returns a sentinel ID immediately. The agent is notified when done (the delivery mechanism is host-specific).

args and until are native JSON values in the tool arguments — not JSON strings. interval is clamped to a minimum of 1000 ms; a positive timeout is clamped to a minimum of 5000 ms (values below the floor are raised).

mcp_sentinel_status

Check the status of sentinel tasks, list active tasks, or cancel a running task.

Parameter Type Description
action "status" | "list" | "cancel" Action to perform
id string Sentinel ID (required for status and cancel)

mcp_sentinel_attach

Block the agent, waiting for a sentinel task to complete. Sleeps and checks status internally with zero token cost. If interrupted by harness, the background async notification still fires normally.

Parameter Type Default Description
id string required Sentinel ID to wait for
timeout number optional Max wait time in ms (unset = wait indefinitely)

mcp_sentinel_read

Read raw poll outputs from a sentinel task. Useful for debugging when a condition isn't matching — inspect actual MCP responses. Supports range-based pagination via offset.

Parameter Type Default Description
id string required Sentinel ID to read outputs from
offset number end-N 0-based start index (default: from end)
limit number 5 Max number of outputs to return

Condition Model

Conditions are pure declarative data — no executable code, no injection surface.

// Simple comparison
{ "path": "status", "is": "eq", "value": "completed" }

// Array index access
{ "path": "[0].data.path", "is": "eq", "value": "found" }

// Regex match
{ "path": "log", "is": "match", "value": "^error" }

// Logical composition
{
  "and": [
    { "path": "status", "is": "eq", "value": "completed" },
    { "path": "tasks[0].exit_code", "is": "eq", "value": 0 }
  ]
}

Operators

Operator Description
eq Strict equality
ne Not equal
gt Greater than (numeric)
gte Greater than or equal
lt Less than
lte Less than or equal
contains String contains
match Regex match (new RegExp(value).test(data))

Logical combinators

Combinator Description
{ "not": <condition> } Negation
{ "and": [...] } All must match
{ "or": [...] } Any must match

Path syntax

Uses property-access notation with array index support:

status               → obj.status
tasks[0].exit_code   → obj.tasks[0].exit_code
[0].data.path        → obj[0].data.path
items[2].name        → obj.items[2].name

Architecture

The project is a monorepo where each layer ships as its own npm package. The core knows nothing about any host; every harness is a thin, self-contained package layered on top of it.

Layers

Package Purpose Published as
packages/core sentinel engine, tool handlers, condition evaluator, connection pool, env, logger, types @gcszhn/mcp-sentinel-core
packages/opencode OpenCode adapter: tool() definitions + client.config.get() + session.promptAsync @gcszhn/mcp-sentinel-opencode-plugin
packages/deepseek-harness DeepSeek Harness adapter: external-invoker mode via ctx.tools.execute + Agent.followup @gcszhn/mcp-sentinel-deepseek-harness-plugin
packages/cli harness-agnostic MCP stdio CLI (mcp-sentinel mcp --harness …) @gcszhn/mcp-sentinel-cli
packages/<harness> (future) one entry per host, e.g. claude-code @gcszhn/mcp-sentinel-<harness>-plugin

Core / harness contract

The core exposes one uniform seam — ToolInvoker, a (server, tool, args) => Promise<unknown> function the engine calls once per poll — so each harness can plug in its own MCP access strategy without the core knowing which host it is running under.

// core — the uniform interface (harness-agnostic)
type ToolInvoker = (server: string, tool: string, args: Record<string, unknown>) => Promise<unknown>;

// the core engine accepts an invoker instead of reading host config itself
startSentinel(request, invoke: ToolInvoker): Promise<string>;

There are two ways to build the invoker:

  1. Connection-pool mode — the harness parses the host's MCP config into a core McpConfig, builds a ServerResolver with makeServerResolver, and wraps it in makeConnectionInvoker, letting the core own the connection lifecycle.
  2. External-invoker mode — the host already owns MCP (e.g. its own bridge registered tools on a tool registry); the harness passes its own (server, tool, args) => result function and the core never opens a connection.

MCP config discovery is the harness's job — different hosts fetch it differently (OpenCode via client.config.get().data mcp.* flat keys, Codex via codex mcp list --json, a --mcp-config file, …), and external-invoker hosts skip config discovery entirely.

The core's second seam is the notifier: a harness installs a completion callback with setNotifier(task, event), delivered through the host's message channel (OpenCode promptAsync, DeepSeek Harness Agent.followup). The core has no opinion on how a notification is rendered or pushed.

Adding a new harness

  1. Develop it in its own git worktree — a new harness plugin is isolated from the core and from other harnesses; see AGENTS.md.
  2. Create packages/<harness>/package.json named @gcszhn/mcp-sentinel-<harness>-plugin with a dependency on @gcszhn/mcp-sentinel-core.
  3. Build a ToolInvoker: either parse the host's MCP config into a McpConfig and wrap it with makeConnectionInvoker(makeServerResolver(...)) (connection-pool mode), or pass a host-owned (server, tool, args) => result function (external-invoker mode).
  4. Register the four tools, delegating to the core's handlePoll / handleStatus / handleAttach / handleRead handlers.
  5. Install the notifier with setNotifier, pushing completions through the host's message channel (e.g. OpenCode promptAsync, DeepSeek Harness Agent.followup).

Data flow (core)

sequenceDiagram
    participant A as Agent (any host)
    participant H as Harness adapter
    participant C as Core engine
    participant M as MCP Server

    A->>H: poll(server, tool, until)
    H->>H: resolveServer()
    H->>C: startSentinel(...)
    C-->>H: sentinel ID
    H-->>A: acknowledgment

    loop every interval ms (zero tokens)
        C->>M: call tool(args)
        M-->>C: response
        C->>C: evaluateCondition(until, response)
    end

    C->>H: notify(completed)
    H->>A: host-specific completion push

Layout

packages/
  core/                         # @gcszhn/mcp-sentinel-core (zero host deps)
    src/
      engine.ts                 # startSentinel / cancel / getTask / getActive / cleanup
      tools.ts                  # handlePoll / handleStatus / handleAttach / handleRead
      condition.ts              # condition evaluator
      connection-pool.ts        # MCP client pool (@modelcontextprotocol/sdk)
      env.ts                    # SENTINEL_* env
      logger.ts                 # pluggable sink
      resolver.ts               # makeServerResolver (McpConfig → ServerResolver)
      types.ts                  # McpServerConfig / ServerResolver / Sentinel*
      index.ts                  # public barrel
    tests/
  opencode/                     # @gcszhn/mcp-sentinel-opencode-plugin
    src/
      plugin.ts                 # PluginModule entry
      index.ts                  # tool() definitions + promptAsync notifier
      config.ts                 # parseOpencodeMcpConfig (opencode `mcp` block) → McpConfig
    tests/
  deepseek-harness/             # @gcszhn/mcp-sentinel-deepseek-harness-plugin
    src/
      index.ts                  # external-invoker mode: ctx.tools.execute + Agent.followup
    tests/
  cli/                          # @gcszhn/mcp-sentinel-cli (harness-agnostic stdio MCP server)
    src/
      cli.ts                    # CLI entry: mcp-sentinel mcp --harness <codex|opencode|custom|none>
      mcp-server.ts             # registers the 4 tools; connection-pool mode; no notifier
      config.ts                 # codex mcp list / opencode debug config / --mcp-config discovery
    schema/                     # mcp-config.schema.json (ships in the npm tarball)
    tests/
  # future harnesses, one concrete package each:
  #   claude-code/   ...

Build order: each package builds independently, but the adapters type-check and run against @gcszhn/mcp-sentinel-core's published dist/. Run bun run build (core first, then opencode, deepseek-harness, cli) before bun test.

License

MIT

原始 README: https://github.com/GCS-ZHN/mcp-sentinel/blob/main/README.md ↗

同类插件

查看全部 →
模型与账号接入Anionex

agent-vision-toolkit

为纯文本模型"看图“设计更好的视觉工具箱和技能,支持多图理解,图片问答,前端UI还原、GUI 自动化等,并可选无缝接入多个主流agent,直接识别粘贴图片| A vision toolkit and skill designed for text-only llms — image Q&A, long-screenshot OCR, frontend UI restoration, and GUI automation, with optional seamless integration for Codex, Claude Code, Pi, Oh My Pi, and OpenCode

查看详情
928github+08-16
模型与账号接入toby-bridges

api-relay-audit

从 DeepSeek Harness 对 AI API 中转站和 LLM 代理运行本地安全审计,生成 Markdown 报告,覆盖提示词注入、模型替换信号、工具调用改写、错误泄漏、流完整性和按 profile 启用的 Web3 风险。

查看详情
791github+08-21
模型与账号接入ZJU-LLMs

OpenStory

✨ OpenStory 现已支持 DeepSeek Harness 插件! 现在可以通过 dsh-openstory 将 OpenStory 多智能体推演接入 DeepSeek Harness,让 agent 直接启动模拟、查看角色、下达指令并逐回合推进故事。查看 DSH 插件配置与使用指南。

查看详情
377github+08-17
模型与账号接入pulseaiclub

phi

pi的编码代理 ∞ 提供者、子代理、hashline编辑和权限门

查看详情
89github+08-16
模型与账号接入anysearch-team

anysearch-dsh

DeepSeek Harness(DSH)的 AnySearch 网络搜索提供方与高级搜索工具。

查看详情
79github+08-17
模型与账号接入kuangre123

codex-switch

Codex Switch 是一个 macOS 工具,一键配置 Codex 的自定义 API,同时保留官方 OpenAI 登录。保存后 Codex 的模型选择器里只会出现你选的那个 provider 的模型。也支持 Claude Code 的官方 / 自定义 API 切换。Codex Switch is a lightweight helper for configuring multiple coding-agent API routes. For Codex, it keeps Official OpenAI and a custom API provider configured in parallel, registers the custom model in Codex's mod

查看详情
67github+08-16