Semantic Layer Gap Closure MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Semantic Layer Gap Closure MCP ServerReview case note 'crossmatch not clear' before booking courier; proceed, escalate, or block?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Semantic layer gap closure
Follow-up to Jagadish-semantic-engineering-layer (commit e290442). That repo shows why an agent needs governed meanings before it acts. This repo closes the gaps it listed or that probing found, then measures the result on a held-out set the engine never saw.
Recruiter summary. The original layer scored 1.0 on 30 cases its author labelled. On 318 new held-out cases it scored 0.346 accuracy, with 102 unsafe proceeds and 12 crashes. The labels came from a facts-only oracle, and the split was frozen by hash before any engine work. This engine adds negation, hedging, typo tolerance, Spanish, French and German, units, timestamps, text-vs-source cross-checks, domain routing, declared ambiguity, governance, a persisted review queue, drift monitoring and a stdio MCP server. On the same split it scores 0.965, with 2 unsafe proceeds and 0 crashes, and it still scores 1.0 on the original 30. All data is synthetic, every company is fictional, and everything runs offline with no API keys.
Purpose and who it is for
Data and AI engineers putting an agent in front of domain text, such as ops chat or case notes, where "positive", "clear" or "30 hours" can trigger an irreversible action.
Reviewers who want to see what it takes to turn "1.0 on my own cases" into a claim that holds up.
The owner (and interviewers), as a worked example of negation, held-out evaluation, ablation and glossary governance without an LLM.
This repo does not repeat the original's motivation, glossary design or baseline comparison. Read the original first.
Related MCP server: DeadBolt
The gaps and how each was closed
The "before" numbers come from the original code, vendored unchanged at reference/original_e290442/. The "after" numbers come from this engine on the frozen held-out test split. The per-gap table in the results block below is regenerated by CI.
Gap | Where it came from | Before (original e290442) | How it was closed | After (this repo) |
Score was author-labelled (1.0 on 30) | original README | no held-out set | Facts-only label oracle (label policy); phrase bank split even/odd into dev/test with disjoint phrasings, frames and orgs; test split frozen by SHA-256 before engine work; tuned on dev, ran test once (protocol) | test: 0.346 → 0.965 accuracy, Wilson 95% CIs reported |
Negation | original limitations | "File is not clear to close" → proceed | Cue lists in four languages, scoped to 3 tokens before, inside the frame and 2 after, stopping at clause breaks. A negated clearance never clears. A negated critical term escalates to a person. | negation: 0.000 → 1.000, 7 → 0 unsafe |
Hedges and conditionals | found by probing | "Book courier once crossmatch negative" → proceed | Hedge cues ("once", "if", "likely", "pending", ...) treated like negation | hedge: 0.059 → 0.941, 9 → 0 unsafe |
Typos | original limitations | "positive crossmtch" → proceed | Restricted edit distance (OSA). Max distance 1 for ≤7 letters, 2 for longer, minimum length 5, and a protected word list. Ties are refused unless every candidate has the same signal. A corrected clearance never clears. | typo: 0.213 → 0.957, 16 → 1 unsafe |
Synonyms, abbreviations, word order | parent brief | "Positive cross-match", "crossmatch result: positive" → miss | Lemmas, compound joins, and slot frames (head + value within a window, no clause break) | synonym_variant: 0.586 → 1.000 |
Multilingual (es, fr, de) | parent brief | Spanish text → refuse to guess, or proceed on a valid reading | Per-language translations, each with its own approval status and reviewer. A draft translation escalates. | multilingual: 0.379 → 0.966, 4 → 0 unsafe |
Unit conversion | parent brief | "hours" and "hr" rejected | Unit aliases, ISO-8601 durations, °F/K to °C, and a temperature-band contract. "m" (ambiguous) and months (unsupported) escalate. | units: 0.372 → 1.000 |
Timestamps | found by probing | mixed time zones or missing | Readings from start/now, refusing a mixed aware/naive pair. Naive pairs are noted in the trace. | timestamps: 8 → 0 crashes, 0.333 → 1.000 |
Malformed input | found by probing | NaN → proceed; None → crash | Strict reading schema: finite numbers only, no bools or strings, no negatives, no extra keys | malformed_input: 8 unsafe + 4 crashes → 0 + 0 |
Numbers only in text | found by probing | "xm neg, cold ischemia 30 hours" → proceed | Quantities in text are tied to the nearest field mention. Expired → block. Text-only → escalate. Text vs source disagreement > 1% of limit → escalate. | text_quantity: 0.103 → 1.000, 35 → 0 unsafe |
Field named, no reading | found by probing | proceed | Escalate when the text names a governed field that has no reading | 8 → 0 unsafe |
Wrong domain routing | found by probing | "clear to close, crossmatch positive" in mortgage → proceed | Approved critical phrases from other domains escalate | cross_domain: 8 → 0 unsafe |
Ambiguous abbreviations | parent brief | n/a | Declared per domain ("OOS", "COC") and always escalate | 4 → 0 unsafe |
Glossary versioning and conflicts | parent brief | version field only |
| |
Drift | parent brief | none | Unrecognised, correction and escalation rates; PSI over term mix; per-field median ratio (catches a silent unit change at the source); candidate-phrase miner that only proposes draft terms | drift demo below |
In-process review queue | original built-vs-planned | Python list, lost on exit | SQLite, append-only, SHA-256 hash chain, idempotent enqueue, named-human resolution, text hash rather than raw text | |
No real MCP transport | original built-vs-planned | in-process call | JSON-RPC 2.0 over stdio (initialize, tools/list, tools/call, ping), standard error codes, line size cap; tested as a subprocess | same file |
Novel paraphrases | original limitations | miss | Still open. A phrase list does not understand meaning. Draft-term mining helps owners catch up. | novel_phrasing: 0.462 → 0.462, 1 unsafe left |
Results
Synthetic data, measured by python scripts/render_readme.py. Tests: 185 collected, 185 passed. Held-out test split: 318 cases labelled by a facts-only oracle, frozen by SHA-256 before engine work and run once.
Original 30 author-labelled cases (regression check): original layer accuracy 1.000, gap closure 1.000, unsafe proceeds 0.
Held-out test split, overall:
System | Accuracy (95% CI) | Critical recall (95% CI) | Unsafe proceeds | Crashes | Proceed recall |
naive_baseline | 0.148 [0.113, 0.191] | 0.101 [0.056, 0.176] | 220 | 0 | 0.698 |
original_layer | 0.346 [0.296, 0.4] | 0.172 [0.11, 0.258] | 102 | 12 | 0.679 |
gap_closure | 0.965 [0.939, 0.981] | 0.919 [0.849, 0.958] | 2 | 0 | 0.981 |
Per gap, original (commit e290442) vs this repo:
Gap (held-out test) | n | Original accuracy | New accuracy | Original unsafe proceeds | New unsafe proceeds | Original crashes |
control | 26 | 1.000 | 1.000 | 0 | 0 | 0 |
synonym_variant | 29 | 0.586 | 1.000 | 1 | 0 | 0 |
negation | 16 | 0.000 | 1.000 | 7 | 0 | 0 |
multilingual_negation | 3 | 1.000 | 1.000 | 0 | 0 | 0 |
hedge | 17 | 0.059 | 0.941 | 9 | 0 | 0 |
typo | 47 | 0.213 | 0.957 | 16 | 1 | 0 |
multilingual | 29 | 0.379 | 0.966 | 4 | 0 | 0 |
units | 43 | 0.372 | 1.000 | 0 | 0 | 0 |
timestamps | 12 | 0.333 | 1.000 | 0 | 0 | 8 |
malformed_input | 20 | 0.400 | 1.000 | 8 | 0 | 4 |
text_quantity | 39 | 0.103 | 1.000 | 35 | 0 | 0 |
direction | 4 | 1.000 | 1.000 | 0 | 0 | 0 |
field_without_reading | 8 | 0.000 | 1.000 | 8 | 0 | 0 |
cross_domain | 8 | 0.000 | 1.000 | 8 | 0 | 0 |
ambiguous_abbrev | 4 | 0.000 | 1.000 | 4 | 0 | 0 |
novel_phrasing | 13 | 0.462 | 0.462 | 2 | 1 | 0 |
Drift demo (synthetic windows): stable window alerts 0; drifted window alerts 2 (PSI 0.2848); top draft candidate 'vxm' (count 33).
The full tables, ablation and every miss are in results/heldout_eval.md.
Ablation: turning off any single feature raises unsafe proceeds on the test split. The biggest drops come from turning off text quantities (24 unsafe), fuzzy matching (16) and inflection (12). tests/test_ablation_and_eval.py enforces this on dev for every flag.
Architecture
flowchart LR
A[agent proposes action] --> T[tool.call_tool<br/>strict schema]
T --> N[normalize<br/>fold, sentences, clause breaks]
N --> F[fuzzy + lemma + compounds]
F --> M[matcher<br/>phrases, frames, polarity,<br/>ambiguity, foreign terms, quantities]
G[(glossary 2026.11.0<br/>owners, approvers,<br/>translations)] --> M
M --> E[engine rules<br/>strictest wins]
C[(field contracts<br/>units, limits, bands)] --> E
E -->|proceed| ACT[agent acts]
E -->|escalate / block| Q[(review queue<br/>SQLite + hash chain)]
E --> D[drift windows<br/>PSI, rates, medians]
D --> P[draft term proposals]
P -.owner + second approver.-> G
S[MCP stdio server] --> THow it works
Normalize. Fold accents and case, split into sentences, and keep commas and dashes as clause breaks so cues cannot leak across clauses.
Repair words. Lemmas and compound joins come first. Fuzzy correction applies only to tokens in the domain vocabulary, never to protected words, and is refused on ties between signals.
Match. Exact phrases are matched longest first, including approved translations. Slot frames ("crossmatch ... positive") come next. Then dangling checks: a polarity word with no term, or a critical topic with no outcome, escalates.
Polarity. Negation and hedge cues are read in the window around each hit.
Routing and ambiguity. Critical phrases from other domains, and declared ambiguous abbreviations, escalate.
Readings. Structured readings are checked against contracts. Quantities in text are cross-checked against them.
Decide. The strictest outcome wins. Proceed also needs a clearance: an exact, factual clearance term or a valid reading.
Record. Every decision carries the glossary version and hash. Anything that is not a proceed goes to the review queue.
More detail: docs/how-it-works.md.
Real code and output
# src/semgap/engine.py (excerpt)
if t.signal == "critical_block":
if h.negated or h.hedged:
kind = "negated" if h.negated else "hedged"
bump("escalate", f"{t.id} is {kind} ('{h.cue}'): a person must confirm, never auto-proceed")
else:
bump("block", f"{t.id}: {t.definition}" + (" (matched after spelling correction)" if h.fuzzy else ""))
elif t.signal == "clear":
if h.negated or h.hedged:
bump("escalate", f"{t.id} is {'negated' if h.negated else 'hedged'} ('{h.cue}'): not a clearance")
elif h.fuzzy:
bump("escalate", f"{t.id} only matched after spelling correction: clearance must be exact")PYTHONPATH=src python examples/demo.py (real output, reasons trimmed):
'File is not clear to close' original=proceed new=escalate MTG.CTC is negated ('not'): not a clearance
'Book courier once crossmatch negative' original=proceed new=escalate MED.XM_NEGATIVE is hedged ('once'): not a clearance
'xm neg, cold ischemia 30 hours' original=proceed new=block cold_ischemia_elapsed in text is outside its limit (30.0h elapsed of 24h)
'crossmatch negative' + NaN reading original=proceed new=escalate cold_ischemia_elapsed: value must be finite
'clear to close, crossmatch positive' original=proceed new=escalate text names MED.XM_POSITIVE from medical_logistics: domain routing may be wrong
'Positive cross-match' original=proceed new=block MED.XM_POSITIVE: Recipient antibodies react against donor cells...
'positive crossmtch' original=proceed new=block MED.XM_POSITIVE ...
'prueba cruzada positiva' original=proceed new=block MED.XM_POSITIVE ...
'crossmatch negative' + mixed-tz reading original=crash (TypeError) new=escalate one timestamp has a timezone and the other does notHow to run (offline, standard library only)
python -m venv .venv && . .venv/bin/activate && pip install -r requirements-dev.txt
python scripts/count_tests.py # prints TOTAL_TESTS=...
PYTHONPATH=src python -m semgap.evaluate # writes results/heldout_eval.{json,md}
PYTHONPATH=src python examples/demo.py
python scripts/render_readme.py --check # README numbers match a fresh run
sha256sum -c heldout/FROZEN.sha256 # test split unchanged
PYTHONPATH=src python -m semgap.mcp_server # JSON-RPC over stdio; queue at var/review_queue.sqlite
python labeling/kappa.py export --n 60 # blind sheet for real expert labellingThere is no cloud part and nothing is billable.
Guardrails
Fail safe: unknown, malformed, negated, hedged, misspelled-clearance, ambiguous, foreign-domain, text-only and draft inputs all escalate, and the strictest outcome wins.
Proceed needs positive evidence (a clearance), not just the absence of a problem.
Draft terms and draft translations can never clear or block on their own.
Two-person rule: the approver must differ from the owner, translations need a named reviewer, and a weakened signal needs a second approver.
The review queue is append-only and hash-chained, can only be resolved by a named human, and stores a text hash rather than raw text.
The tool schema is strict: enum domain, 4000-character text cap, no extra keys. The MCP server caps line size.
The held-out test split is checked against its SHA-256 in CI.
See docs/guardrails.md and docs/threat-model.md.
Limitations (kept as tests in tests/test_known_limits.py)
Novel paraphrases ("deferred interest added to principal") are not understood. With a valid reading this is an unsafe proceed (1 on test).
Negation that splits a non-English multi-word term ("prueba cruzada no positiva") hides the term.
A typo on a non-term word in a clearance sentence still proceeds. The oracle says escalate (1 on test).
Decimal commas ("25,5 h") are misread. A fuzzy tie can pick the wrong language form (fails safe). A trailing modal ("neg am may apply") blocks where the oracle escalates.
Labels are synthetic and come from one author's phrase bank. Independence comes from fact-derived labels and the freeze, not from separate people. A kappa kit is included but has not been run with experts.
Limits are lab values, not clinical, actuarial or lending policy.
Full list: docs/limitations.md.
Built vs planned
Item | Status |
Held-out dev/test with facts-only oracle, frozen hash | Built, run |
Negation, hedging, fuzzy, multilingual, inflection, frames | Built, measured |
Units, ISO durations, timestamps, temperature band contract | Built, measured |
Text-quantity cross-check, field-without-reading, cross-domain, ambiguity | Built, measured |
Ablation per feature, Wilson CIs | Built, run |
Governance validate / diff / changelog / replay | Built, tested |
SQLite hash-chained review queue | Built, tested |
MCP-style stdio JSON-RPC server | Built, tested as a subprocess; not tried with a third-party MCP client |
Drift monitor and draft-term miner | Built, run on synthetic windows |
Expert annotation kit (Cohen's kappa) | Written, not run with humans |
Glossary approval UI | Planned (CLI and JSON only) |
Meaning-level understanding of novel paraphrases | Planned; out of reach for a phrase list |
CI (ruff, pytest, docs check, brand check, gitleaks), CodeQL default setup | Built, runs on GitHub |
Docs
how it works · guardrails · limitations · implementation guide · interview points · threat model · ADRs · training: labs and capstone · label policy · held-out protocol
Related repos
Jagadish-semantic-engineering-layer: the original. Motivation, base glossary, baseline comparison and the semantic engineering checklist live there.
MIT licence. Synthetic data only. Organisation names (Northwind, Contoso, Fabrikam, Kestrel, Larkspur, Brambleton, Tidewell, Quillmark) are fictional.
Available Tools
1 toolsemantic_checkA
Interpret a domain message against the governed glossary and field contracts before an agent acts. Returns proceed, escalate or block with a trace. Anything uncertain escalates.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | ||
| domain | Yes | ||
| fields | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the behavioral burden and does disclose the core output behavior: it returns proceed, escalate or block with a trace. It also explicitly states that uncertainty escalates, which is an important safety-relevant trait. It does not discuss permissions, side effects, or rate limits, but the key decision semantics are clear.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences, front-loaded with purpose and followed by return behavior and escalation policy. There is no wasted text, and the structure moves from what the tool does to what it outputs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The high-level purpose, timing, and decision outcomes are covered, which is enough for an agent to understand the tool's role. However, with a nested optional fields object and no output schema, the description should say more about the fields structure and trace format. It is minimally adequate but leaves meaningful gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description should compensate for undocumented parameters, but it does not. 'Domain message' and 'field contracts' only vaguely gesture at domain, text, and fields, and do not explain the nested fields object or its meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb, 'Interpret', and states the resource: a domain message against the governed glossary and field contracts. It also says this happens before an agent acts, which clarifies the tool's role. It is clear, though 'governed glossary and field contracts' remains somewhat abstract.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states the timing: use it 'before an agent acts'. It also gives a clear behavioral policy, 'Anything uncertain escalates', which helps an agent understand the expected conservative posture. There are no sibling tools, so no alternative routing comparison is needed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.1.0- First observed
semantic_check
TDQS
Scored across 1 tool
There is only one tool, so there is no intra-server ambiguity or risk of misselecting between overlapping tools. Its purpose as a pre-action semantic gate is clearly distinguishable from ordinary data operations.
The single tool uses a readable snake_case name with a clear semantic meaning. Consistency cannot truly be assessed across one tool, but the naming style is predictable and not confusing.
A single tool is thin for a server framed as closing semantic-layer gaps. It can serve as a focused gate, but the scope implied by the server name suggests more lifecycle support would be expected.
The surface only supports checking a message and returning proceed/escalate/block. There are no tools for managing glossary terms, field contracts, gap records, traces, or escalation resolution, which are significant gaps for a semantic-layer gap-closure server.
Maintenance
Related MCP Connectors
Pre-execution governance for AI agents. Deterministic PASS/FAIL/REVIEW verdicts, replayable proof.
Supervised API-write gateway for AI agents with policy, human approval and execution receipts.
Deterministic allow/require_approval/deny verdicts for agent actions, before they happen.
Preflight, approve, and prove consequential agent actions with signed evidence and x402 tools.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceEnables autonomous agents and MCP-compatible clients to route inputs through a deterministic firewall that flags and sanitizes prompt injection, jailbreaks, system prompt exfiltration, and tool hijacking. It supports JSON-RPC stdio integration for tools like Claude Desktop, Cursor, and multi-agent frameworks.7-
- AlicenseAqualityAmaintenanceLocal execution gate and stdio MCP proxy for AI agents, with tool policies, expiring leases, approvals and operator kill. Starts with two read-only setup tools; protection requires a trusted downstream server and private operator configuration. Not an OS sandbox. Apache-2.0.231 PyPI4Apache 2.0
- AlicenseAqualityAmaintenanceEnables AI agents to register proposed outbound or destructive actions, such as sending, deleting, publishing, paying or deploying, and block until a human approves or denies them out-of-band via a CLI. Requests default to fail-closed with a one-hour TTL and every request, decision and expiry is written to an append-only audit log.6262 npmMIT
- AlicenseNot gradedqualityAmaintenanceProvides a stdio MCP server that drives structured multi-agent development workflows: claiming and updating tracked tasks, logging actions and handoffs, gating work on server-owned health checks, and managing use-case, functional, and technical specification documents on the filesystem. It also exposes read-only skills for triage, consultation, review, and product guidance while enforcing approval and repair authorizations.29 npmMIT