使用说明
MCP Observatory
MCP Observatory now includes a two-phase execution pattern for high-risk MCP tool calls, with a generic proposer/verifier wrapper that can be reused by any tool:
- PROPOSE: plan/simulate, evaluate uncertainty/integrity, no side effects.
- COMMIT: execute side effects only when a signed commit token is valid.
Two-Phase Sequence (Text Diagram)
Client
-> transfer_funds_propose(amount,to)
-> scoring(output_instability, numeric_variance, prompt_drift)
-> decision:
- blocked: deterministic fallback (create_draft), no side effects
- allowed: issue signed commit_token bound to tool args hash
<- {proposal_id, commit_token?}
Client
-> transfer_funds_commit(proposal_id, commit_token, amount, to)
-> verify signature + expiry + proposal existence + args_hash binding + nonce replay
-> if valid: perform side effect (funds transfer)
-> else: block with explicit reason
<- commit outcome
New Modules
mcp_observatory/proposal_commit/hashing.py- canonical JSON hashing for stable
tool_args_hash - normalized
prompt_hash
- canonical JSON hashing for stable
mcp_observatory/proposal_commit/scoring.pyoutput_instability = 1 - jaccard_similaritynumeric_variancefrom extracted numbersprompt_driftfrom prompt hash vs baseline- weighted renormalized
composite_score - demo
model_generate(prompt, temperature)stub
mcp_observatory/proposal_commit/token.py- HMAC-SHA256 token issue/verify
- payload fields:
token_id, proposal_id, tool_name, tool_args_hash, issued_at, expires_at, nonce, composite_score
mcp_observatory/proposal_commit/proposer.py- generic
ToolProposer.propose(...)for any tool name/args - deterministic blocked fallback
- generic
mcp_observatory/proposal_commit/verifier.py- commit verification and nonce replay protection
mcp_observatory/proposal_commit/storage.py- in-memory storage fallback
- optional Postgres storage via
asyncpg
mcp_observatory/core/wrapper_api.py- generic
InvocationWrapperAPIfor wrapping eitheragentormodelinvocations - captures input/output hashes, token/cost metrics, and emits
allow/review/blockdecisions
- generic
mcp_observatory/instrument.py- adds
instrument_wrapper_api(...)helper for fast wrapper setup
- adds
mcp_observatory/demo/server.py- MCP-like tools:
transfer_funds_proposetransfer_funds_commit
- MCP-like tools:
mcp_observatory/demo/run_demo.py- propose -> commit -> replay-attempt demo
sql/schema.sql- Postgres tables:
proposals,commits,nonces,tool_prompt_baselines
- Postgres tables:
Security / Verification Rules
Commit verifies all of the following:
- token signature is valid (
bad_signatureon failure) - token not expired (
expired) - proposal exists and was allowed (
unknown_proposal) - commit args hash equals token payload args hash (
args_hash_mismatch) - nonce has not already been used (
nonce_replay)
Deterministic Fallback on Proposal Block
Blocked proposal response is deterministic and side-effect free:
{
"status": "blocked",
"action": "create_draft",
"reason": "low_integrity",
"draft": {"tool": "transfer_funds", "amount": 100, "to": "acct_123"}
}
Environment Variables
| Variable | Default | Description |
|---|---|---|
MCP_OBSERVATORY_TOKEN_SECRET |
(none — required) | HMAC secret for TokenIssuer/TokenVerifier (the v2 control-plane execution token). |
MCP_OBSERVATORY_COMMIT_SECRET |
(none — required) | HMAC secret for CommitTokenManager (the propose/commit token). |
MCP_OBSERVATORY_ALLOW_DEV_SECRET |
unset | Set to 1 to allow the library's known development secrets when the two variables above are unset. Local development and tests only — the default secrets are public (they are in this repository's source) and a production deployment that relies on them is signing tokens with a key anyone can look up. The bundled demos (mcp_observatory.demo.server, mcp_observatory.demo.real_world_server) set this automatically, with a loud warning, so they keep running with no configuration. |
MCP_OBSERVATORY_MAX_INPUT_BYTES |
10240 |
Ceiling, in UTF-8 bytes, on the canonical JSON of tool_args and on any single text input (answer, retrieved context, tool result summary, prompt, candidate outputs) before it is scored. Scoring cost is linear in input size (docs/gate-properties.md P5), so the gate has no latency bound of its own without this. Over the limit, core/interceptor.py's v2 path routes to the fallback and proposal_commit/proposer.py returns a blocked response, both with reason input_too_large, instead of hashing or scoring the oversized input. |
MCP_OBSERVATORY_PG_DSN (or DATABASE_URL) |
unset | Postgres DSN for create_storage_from_env()/PostgresExporter. Falls back to in-memory storage when unset. |
Neither secret variable has a default: constructing TokenIssuer, TokenVerifier, or CommitTokenManager without an explicit secret and without one of these set raises InsecureDefaultSecretError (mcp_observatory.utils.secrets) rather than silently signing with a hardcoded value.
Running the Demo
Without Postgres (default)
No env vars needed; in-memory store is used.
python -m mcp_observatory.demo.run_demo
With Postgres
- Set DSN:
export MCP_OBSERVATORY_PG_DSN='postgresql://user:pass@localhost:5432/postgres'
- Apply schema:
psql "$MCP_OBSERVATORY_PG_DSN" -f sql/schema.sql
- Run demo:
python -m mcp_observatory.demo.run_demo
Testing
PYTHONPATH=. pytest -q
The suite includes tests for token verification, hash stability, replay protection, and expired-token rejection.
Real-World MCP Scenario Demo (10 End-to-End Flows)
A realistic MCP server example is available at:
mcp_observatory/demo/real_world_server.py- executable shim:
examples/real_world_mcp_server.py - executable client:
examples/real_world_mcp_client.py - prompt-to-invocation MVP:
examples/prompt_to_mcp_invocation_mvp.py - OpenAI GPT utility:
examples/openai_prompt_to_mcp_invocation.py
It includes:
- 10 distinct prompts mapped to 10 different MCP tool handlers
- per-invocation annotations (e.g.
destructiveHint,idempotentHint,openWorldHint) - proposal/commit execution for HIGH-risk tools (no secondary-response gating)
- irreversible actions never pass a secondary LLM response
- simulated LLM responses and grounding summaries for standard-risk tools
- deterministic fallback routing for blocked/review-required scenarios
- prompt-to-invocation MVP now extracts required tool parameters from user prompts before server invocation
- openai-gpt utility for service selection + parameter extraction + MCP invocation
Run server demo:
python examples/real_world_mcp_server.py
Run client demo (client interacting with server):
python examples/real_world_mcp_client.py
Run prompt -> LLM planner -> server invocation MVP:
python examples/prompt_to_mcp_invocation_mvp.py
Run OpenAI GPT utility (manual, requires OPENAI_API_KEY):
python examples/openai_prompt_to_mcp_invocation.py
Optional (recommended): install OpenAI SDK for first-class client support (client auto-detects and uses SDK when available; otherwise it uses HTTPS fallback):
pip install -e .[openai]
Wrapper API (Agent or Model Invocation)
Use the wrapper to route either agent-side orchestration calls or direct model calls through a single observability envelope.
from mcp_observatory.instrument import instrument_wrapper_api
wrapper = instrument_wrapper_api("my-service")
result = await wrapper.invoke(
source="agent",
model="gpt-4o-mini",
prompt="Generate deployment plan",
input_payload={"request_id": "abc123", "task": "deployment_plan"},
call=lambda: {"plan": "blue-green rollout"},
)
print(result.decision.action) # allow/review/block
print(result.span.cost_usd)
Dual-run measurement is supported with dual_invoke=True and shadow parameters (shadow_source, shadow_model, shadow_agent_params, shadow_model_params, and shadow_call) to compare alternate execution paths and capture disagreement metrics.
The wrapper output (WrapperResult) includes:
output: raw callable outputspan: captured telemetry metrics (tokens, cost, hashes, timing)decision: policy decision suitable for downstream execution routingshadow_outputandshadow_span(whendual_invoke=True) with comparison metrics on primary span (shadow_disagreement_score,shadow_numeric_variance)
AWS Integration (mcp_observatory.aws)
Common wiring extracted from services that deploy this library on AWS Lambda. Install the extra to use it:
pip install mcp-observatory[aws]
Importing mcp_observatory.aws never requires boto3 — it is imported lazily on first use, so the rest of the library still imports fine without it.
from mcp_observatory.aws import DynamoDBSpanExporter, build_gate
# Writes every populated TraceContext field to a DynamoDB table (name from
# OBSERVATORY_METRICS_TABLE, or pass table_name=) with a TTL attribute.
exporter = DynamoDBSpanExporter(ttl_seconds=90 * 24 * 60 * 60)
# Wires an InMemoryStorage- (or Postgres-, when MCP_OBSERVATORY_PG_DSN is
# set) backed ToolProposer + CommitVerifier sharing one CommitTokenManager.
# secret_env/block_threshold_env let each service keep its own env var
# names; the secret still fails closed per the Environment Variables table
# above.
proposer, verifier, token_manager = build_gate(
secret_env="OBSERVATORY_SECRET_KEY",
block_threshold_env="OBSERVATORY_BLOCK_THRESHOLD",
)
There is no observe_model_request-style helper here: the services this was extracted from do not agree on its shape (see CHANGELOG.md 0.3.0), so none is provided.