Skip to main content
Glama
neelbarm
by neelbarm
README.md
<div align="center">

# mcp-trustlayer

**An MCP server that lets an agent ask whether it is allowed to act on its own output.**

![Python](https://img.shields.io/badge/python-3.10%2B-3776AB?logo=python&logoColor=white)
![MCP](https://img.shields.io/badge/MCP-2024--11--05-D97757)
![Tests](https://img.shields.io/badge/tests-10%20passing-2F7D4F)
![Dependencies](https://img.shields.io/badge/dependencies-none-6E7078)
![License](https://img.shields.io/badge/license-MIT-6E7078)

</div>

---

An agent that has just extracted values from a document is the worst possible judge of whether those values are correct. It has no distance from its own output. This server moves that judgment outside the model: the agent proposes, a policy you control disposes.

```
agent  ──  "I extracted these 12 fields, may I write them?"  ──▶  mcp-trustlayer
                                                                       │
       ◀──  "9 post. 3 to a human. Here is why for each."  ────────────┘
```

## Why this exists

Most extraction pipelines gate on one signal, a confidence score, and inherit a failure they never see. Confidence catches the model being **unsure**. It cannot catch the model being **sure and wrong**, because nothing in a confidently wrong answer looks hesitant.

The worked example below carries a fee field at `0.94` confidence whose value is the plan-review line item rather than the total. No threshold you could reasonably set catches it. An independent verification pass does.

## Tools

### `evaluate_fields`

Returns a disposition per field, one of **`post`**, **`review`**, or **`escaped`**.

```json
{
  "fields": [
    { "name": "permit_number", "value": "BLD-2024-041877", "confidence": 0.99, "verification": "verified" },
    { "name": "permit_fee",    "value": "$1,240.00",       "confidence": 0.94, "verification": "mismatch" },
    { "name": "contractor",    "value": "Meridian",        "confidence": 0.38, "verification": "not_found" }
  ],
  "high_stakes": ["permit_number"],
  "verified": true
}
```

```
1 posted, 2 held for review, 0 escaped

[POST]    permit_number = 'BLD-2024-041877' (0.99/0.95) verified
[HOLD]    permit_fee    = '$1,240.00'       (0.94/0.85) verification mismatch
[HOLD]    contractor    = 'Meridian'        (0.38/0.85) confidence 0.38 below bar 0.85
```

Pass `"verified": false` to model what happens with no verification pass. The fee field flips to `escaped`, meaning it was written to the record while unsupported by its source. That number is what the second pass is worth.

### `calibrate_threshold`

Sweeps the bar and reports review load against escaped errors, with and without verification, so the threshold gets chosen deliberately rather than inherited.

```
bar    verify-on: hold/escaped   verify-off: hold/escaped
0.85      3 / 0                     2 / 1
0.90      5 / 0                     4 / 1
0.95      7 / 0                     7 / 0
```

## Install

No dependencies. Python 3.10+.

```bash
git clone https://github.com/neelbarm/mcp-trustlayer
cd mcp-trustlayer
python -m mcp_trustlayer.server
```

### Claude Desktop

```json
{
  "mcpServers": {
    "trustlayer": {
      "command": "python",
      "args": ["-m", "mcp_trustlayer.server"],
      "cwd": "/path/to/mcp-trustlayer"
    }
  }
}
```

## Speaking to it directly

MCP is JSON-RPC 2.0 over stdio, so you can drive it with a pipe.

```bash
printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
  | python -m mcp_trustlayer.server
```

## Design notes

**The protocol is implemented directly, not via an SDK.** `protocol.py` is about 90 lines and handles `initialize`, `tools/list`, and `tools/call` over newline-delimited JSON-RPC. Keeping it dependency-free makes the wire format legible, which matters more in a reference implementation than convenience does.

**Notifications get no response.** `notifications/initialized` carries no `id` and must not be replied to. This is the most common place a hand-rolled MCP server breaks, so there is a test for it.

**Tool errors return content, not crashes.** A raised exception comes back as `isError: true` with the message in the content block, so the model can see what went wrong and adjust rather than losing the connection.

**Descriptions are written for the model, not for a human reading docs.** Each tool description says when to call it, because that text is the only instruction the model gets.

## Tests

```bash
python -m pytest tests -q     # 10 passed
```

The suite includes an end-to-end case that spawns the server as a subprocess and speaks real JSON-RPC over stdio, rather than only calling the handler in-process.

## Related

- **[trustlayer](https://github.com/neelbarm/trustlayer)** — the same decision logic as a Python library
- **[plancheck](https://github.com/neelbarm/plancheck)** — the idea applied end to end to permit intake, with a [live demo](https://plancheck-neel.netlify.app/)

MIT. Built by [Neel Barmecha](https://neelbarmecha.netlify.app/).

TDQS

A3.8/5.0

Scored across 2 tools

Disambiguation5/5

The two tools have clearly distinct purposes: evaluate_fields determines field dispositions, while calibrate_threshold analyzes trade-offs across threshold values. There is no overlap in their responsibilities.

Naming Consistency5/5

Both tools follow the same verb_noun pattern ('evaluate_fields', 'calibrate_threshold'), using consistent lowercase with underscores. The pattern is predictable and clear.

Tool Count4/5

With only 2 tools, the set is slightly thin, but this matches the narrow, focused scope of the trust-layer functionality. Each tool earns its place.

Completeness3/5

The workflow is missing a way to actually apply the calibrated threshold to the evaluate_fields tool; calibration is informational but there is no explicit 'set_threshold' or parameter. This creates a notable gap for end-to-end usage.

Maintenance

ActivitySlowing
ResponsivenessNo issues