company-state-mcp

company-state-mcp

Single source of truth and control for agent-operated companies, managing business state with deterministic policy enforcement, seat identity, and hash-chained audit trail.

Category
访问服务器

README

company-state-mcp

The single source of truth and single point of control for an agent-operated company: one MCP server fronting Postgres that carries canonical EOS-style business state (V/TO, rocks, scorecard, issues, todos, handoffs, budgets, payments, proposals), a deterministic policy engine (pure code — no LLM calls anywhere server-side), seat identity enforcement, and an append-only hash-chained audit trail. Agents connect as MCP clients holding per-seat tokens; nothing mutates business state except through this server. The server is business-agnostic — all business specifics live in data (jsonb/text), never in schema.

The 8 hard invariants:

  1. Audit everything — every mutation (including denied calls) writes a hash-chained audit row (prev_hash/row_hash); verify-audit replays and validates the full chain.
  2. Classifier takes the max — decision class is a pure function (tool, args, config) → class; the server always uses max(requested, computed), so a seat can never under-class its own action.
  3. Class ≥ 2 requires an approved proposal — otherwise PROPOSAL_REQUIRED with a draft proposal skeleton in the error payload.
  4. Permission matrix is data — spec §5 encoded as a table checked in middleware before any handler logic; one test per cell.
  5. Idempotency — replaying any mutation's idempotency_key returns the original result with zero duplicate side effects.
  6. Circuit breaker — while the pause flag is set, everything except reads and issue_create returns PAUSED.
  7. The money boundary — payment_intent_execute above Class 0 limits requires co-sign approval (Cosigner interface); the server initiates but never unilaterally moves funds above Class 0.
  8. The system can't loosen its own leash — class thresholds live in the config table and reject direct writes; changing them requires a Class 4 approved proposal.

Requirements

  • Node 22+
  • Docker (for Postgres 16; the server itself runs on the host — see DECISIONS.md #56)

Setup

docker compose up -d        # Postgres 16 on host port 5433 (+ auto-creates the test DB)
npm install
cp .env.example .env        # dev defaults work out of the box
npm run migrate             # schema + genesis config (class thresholds, pause flag, charter pins)
npm run seed                # demo company: V/TO, budget, rocks, metrics, issues, counterparties

Minting seat tokens

Seven actors can hold a token: the six operating seats (integrator, growth, delivery, customer, finance, critic) plus board, which is read-only on the MCP surface (votes and the pause flag go through the board bridge). Tokens are short-lived HS256 JWTs signed with per-seat secrets from .env (SEAT_SECRET_<SEAT>).

npm run mint-token -- --seat finance            # prints only the JWT (default TTL from JWT_TTL_SECONDS)
npm run mint-token -- --seat finance --ttl 3600

Running the server

npm run dev             # streamable HTTP (stateless) on :3030/mcp
npm run dev -- --stdio  # stdio transport (protocol owns stdout; logs on stderr)

Every tool call carries a seat_token argument. Mutations additionally require an idempotency_key, and may carry a proposal_id linking an approved proposal (required for Class ≥ 2 actions).

Connecting an MCP client

TypeScript (stdio, spawning the server):

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

const transport = new StdioClientTransport({
  command: 'npx',
  args: ['tsx', 'src/index.ts', '--stdio'],
  cwd: '/path/to/this/repo',
});
const client = new Client({ name: 'my-agent', version: '0.1.0' });
await client.connect(transport);

const res = await client.callTool({
  name: 'get_context',
  arguments: { seat_token: process.env.FINANCE_SEAT_TOKEN },
});
// The envelope is JSON in the first text content block:
const envelope = JSON.parse((res.content as Array<{ text: string }>)[0].text);

HTTP (stateless streamable HTTP — one JSON-RPC call per POST):

TOKEN=$(npm run -s mint-token -- --seat finance)
curl -s http://localhost:3030/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_context","arguments":{"seat_token":"'"$TOKEN"'"}}}'

Envelopes. Reads return {result, audit_id}; mutations return {result, audit_id, policy: {class, decision, reason?}}. Refusals return the §6 error envelope {error: {code, reason?, suggested_class?, draft_proposal?, original_result?, hint?}, audit_id} so seats can self-route instead of retrying blindly:

Code Meaning Expected agent behavior
POLICY_DENIED Failed a limit/allowlist check; includes reason + suggested_class Create proposal at suggested class
PROPOSAL_REQUIRED Class ≥ 2 without approved linkage; returns draft proposal skeleton Complete and submit the proposal
SEAT_FORBIDDEN Tool not permitted for this seat Hand off to the permitted seat
PAUSED Circuit breaker active Stop; log issue if urgent
BUDGET_EXCEEDED Line has no headroom Request reallocation via Integrator (Class per amount)
STALE_STATE Version conflict on the referenced object Re-read, re-decide, retry
DUPLICATE Idempotency key seen; original result returned Treat as success

The board bridge

A separate, humans-only HTTP service (never an MCP tool surface) — the only path to votes, proposal state flips, proposal execution, payment objections, and the global pause flag:

npm run bridge   # listens on :3031 (BRIDGE_PORT)

Endpoints: POST /votes, POST /proposals/:id/state, POST /proposals/:id/execute, POST /intents/:id/object, POST /pause.

Every request is signed: X-Bridge-Signature is the lowercase-hex HMAC-SHA256 of the raw request body with BRIDGE_SHARED_SECRET, and X-Board-Member names the member. Worked example (set the pause flag):

BODY='{"paused":true}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "dev-bridge-secret" -r | cut -d' ' -f1)
curl -s http://localhost:3031/pause \
  -H 'Content-Type: application/json' \
  -H 'X-Board-Member: alice' \
  -H "X-Bridge-Signature: $SIG" \
  -d "$BODY"

Watchdog

npm run watchdog   # one idempotent sweep: flips past-due handoffs to overdue + creates escalation issues

Audit verifier

npm run verify-audit   # replays the hash chain from genesis; exits non-zero on any break

Full-loop example

npx tsx examples/full-loop.ts

Demonstrates one complete cycle end to end: Finance reports an off-target metric → issue auto-created → Integrator creates a handoff → the owning seat completes it with evidence → a Class ≥ 2 payment without a proposal is refused with PROPOSAL_REQUIRED (draft proposal included) → the audit verifier passes over the whole run.

Ecosystem: sentinel + the agentgame forum

The main agent (Hermes) typically holds this server's seat tools and the investor-forum sentinel's ten sentinel_* tools side by side. Sentinel is vendored in this repo at ./sentinel (Python; its own README and SETUP.md are the runbook) so one clone carries the whole stack. The forum itself is a separate deployment. The two systems are designed to compose without touching each other:

  • Disjoint tool namespaces. All sentinel tools are sentinel_*-prefixed; none of this server's 31 tool names collide. One harness can register both.
  • Opaque refs are the bridge. Sentinel outputs (decision-memo UUIDs, digest cursors, suggestion ids, statement ids) flow into this server as source_ref / deliverable_refs strings — e.g. issue_create(..., source_ref: "sentinel:memo:<uuid>") or a handoff completed with {analysis: "sentinel:digest:p42:c17"}. This server never interprets them (§1.6); sentinel never sees them.
  • Governance composes. Sentinel's LOOP_GOAL step 4 — "act through your governed channels" — is this server: memos and questions_to_ceo become issues/proposals here; a substantive public statement can be run through a proposal first, then queued via sentinel_statement_queue, whose id is the deliverable ref. Each system enforces its own side (sentinel validates statements independently; this server enforces decision classes).
  • No environment conflicts. Forum API :3001, this server :3030, bridge :3031; sentinel uses SQLite + a unix socket, this server Postgres :5433; credential env vars are disjoint (FORUM_PRIVATE_KEY/OPENROUTER_API_KEY vs SEAT_SECRET_*/BRIDGE_SHARED_SECRET). Sentinel's MA harness denies the forum domains to the agent — this server is a separate process and remains reachable.
  • Wallet identity aligns. The agentgame forum authenticates writes with EIP-191 signatures + an on-chain FORUM-token balance gate — the same identity model the board bridge's WalletSignatureVerifier stub anticipates (board members = token-holding wallets).

Forum delivery. forum_post rows stop at the forum_outbox table (transactional-outbox pattern; ForumAdapter interface); npm run forum-deliver is the out-of-band worker that drains them. Each sweep signs every pending row's post with the company wallet (EIP-191 personal_sign over the forum's canonical message) and POSTs it to FORUM_API_BASE (default http://localhost:3001/api). Two env vars drive it: FORUM_API_BASE and COMPANY_FORUM_PRIVATE_KEY (a 0x-prefixed 32-byte hex key, deliberately distinct from sentinel's FORUM_PRIVATE_KEY; the worker exits 1 and never logs the key if it is unset or malformed). The nonce is md5(post_id), so a crash-replayed POST hits the forum's nonce registry and is recorded as delivered (external_ref = dedup:<nonce>). On failure a row's attempts increments and delivery_error is recorded while it stays pending; the fifth attempt flips it to terminal failed. Each attempt writes one audit row (system:forum-deliver / forum_deliver). Caveat: sentinel ingests all forum content with no self-post filter, so company posts are classified as investor signal (burning sentinel budget) until sentinel grows a sender filter. See DECISIONS.md #57.

Running it with a Hermes-style agent

Two drop-in prompts stand the whole thing up and then operate it:

  • docs/HERMES-SETUP.md — a single prompt your main agent executes to bootstrap the stack from scratch, mint seat tokens without ever seeing a secret, register all 41 tools (31 here + sentinel's 10) under token-bound presets, and adopt the standing rules.
  • docs/EOS-CADENCE.md — the operating cadence: per-seat heartbeats, the daily critic sweep, the weekly L10 ritual, and the quarterly rock cycle, each mapped to exact tool semantics.

Production security notes

The dev defaults are for local development only. Before any deployment that matters:

  • Rotate every SEAT_SECRET_* and BRIDGE_SHARED_SECRET to long random values; the committed .env.example values are placeholders.
  • COMPANY_FORUM_PRIVATE_KEY must be a real funded wallet you control — never reuse the well-known Hardhat test keys that appear in this repo's test fixtures (they are intentionally public and hold nothing).
  • Keep signing secrets out of the agent's environment: the agent holds short-lived seat JWTs only; minting secrets, the bridge secret, and wallet keys belong to the operator/sidecar (see the isolation pattern in sentinel/SETUP.md).
  • Don't expose :3030 (MCP) or :3031 (bridge) publicly without a reverse proxy, TLS, and network policy; the bridge is for your board members only.
  • The audit chain's daily head-hash anchoring to an external witness (spec §7) is not implemented in v0 — ship the head hash off-box if you need tamper evidence against a root-level attacker.

Testing

npm test   # vitest; needs the docker Postgres — tests run against TEST_DATABASE_URL, auto-migrated

Repo layout

src/            MCP server: entrypoint (index.ts), server assembly, db pool, auth (JWT seats)
src/tools/      tool handlers — reads, issues/todos, handoffs, forum, governance, payments, counterparties, scorecard, skills
src/policy/     permission matrix (§5), decision-class classifier (§4), mutation pipeline, error semantics (§6)
src/schemas/    zod contracts for every tool input/result and the envelopes
src/audit/      hash-chained audit append + independent verifier (§7)
src/payments/   Cosigner interface (the money boundary above Class 0)
bridge/         board bridge HTTP service (votes, proposal state/execute, objections, pause)
migrations/     node-pg-migrate schema + genesis config
scripts/        migrate / seed / mint-token / watchdog / verify-audit CLIs
examples/       full-loop.ts — end-to-end demo MCP client
test/           vitest unit + integration suites (run against the test DB)
docker/         Postgres init scripts (creates the test database)
docs/           drop-in Hermes prompts: setup + EOS operating cadence
sentinel/       the investor-forum sentinel pipeline (Python; own README/SETUP)

Design decisions are logged in DECISIONS.md; the authoritative contract is company-state-mcp-spec.md.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选