boardroom-agents
Click on "Install 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., "@boardroom-agentsshow me the agenda for the most recent board meeting"
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.
boardroom-agents
A small Python multi-agent system that answers questions about a board's history, where a dedicated governance agent evaluates every drafted answer against an externalized policy before the answer is returned — allow, revise, or block.
It reads the SQLite database from boardwalk (a board-management web app)
read-only. The interesting property is inherited from that schema: individual anonymous
votes cannot be revealed, because they were never recorded — anonymity is enforced by the
data model, not by a prompt. The governance agent is a second, independent line of defense on
top of that structural guarantee.
I took a governance app whose guardrails are enforced by the schema, not the prompt, and refactored its AI into a multi-agent system where a dedicated governance agent judges every answer against an externalized policy before release — allow, revise, or block. That is conceptually what agent-governance products do: evaluate an agent's action against policy before it executes, with an audit trail.
Runs fully offline with no API key (PROVIDER=mock is the default).
Architecture
question
│
┌─────────▼──────────┐
│ Orchestrator │ bounds the run (global step budget),
│ orchestrator.py │ owns retry / escalation / final answer
└─────────┬──────────┘
│ (1) delegate
┌─────────▼──────────┐ ┌──────────────────────────────┐
│ Research agent │◄──────►│ Audited tool layer │
│ research.py │ bounded│ tools.py (5 tools) │
│ │ loop │ list_meetings │
│ drafts an answer │ MAX_ │ get_meeting │
│ WITH citations │ TOOL_ │ list_motions │
└─────────┬──────────┘ TURNS │ get_motion_results ◄── the│
│ │ search_documents anonymity
│ └───────────┬──────────┘ branch │
│ │ read-only │
│ ┌───────────▼──────────┐ │
│ │ data/board.db │ │
│ │ sqlite mode=ro │ │
│ └──────────────────────┘ │
│ (2) submit draft + the evidence actually used
┌─────────▼──────────┐ ┌──────────────────────────────┐
│ Governance agent │◄───────│ policies/answer_policy.md │
│ governance.py │ reads │ (externalized, hot-editable) │
│ │ at run-└──────────────────────────────┘
│ LLM-as-judge + │ time
│ deterministic │
│ checks (the floor)│
└─────────┬──────────┘
│
verdict ────┼──────────────────────────────────
│ │ │
allow revise block
│ │ │
return answer feed judge feedback return the policy's
back to Research safe refusal — the
(MAX_REVISIONS) draft is never shownEvery step is appended to a Trace, which the CLI prints before the answer. That visible
reasoning — routing, tool calls, draft, verdict — is the demo.
Why three agents
Agent | Authority | Why it exists separately |
Orchestrator | Owns the budget and the final answer. No tools, no DB. | Someone must bound the system and decide what the user sees. Keeping that out of the agent that drafts answers means a leaky draft can't release itself. |
Research | Owns the five tools. Cannot release an answer. | Tool access and answer release are different privileges. |
Governance | Reads the policy and the draft. No tools, no DB, no ability to write an answer. | A judge that could also fetch data or rewrite the answer would be judging its own work. |
Two entry points over one governed core
The loop above is reached two ways. cli.py drives an agent whose code is in this repo. The MCP
server serves agents that aren't — and that difference is the whole point of having it.
human at a terminal any MCP client
(agent loop you wrote) (Claude Desktop / Code / internal)
│ │
cli.py│ │ stdio · JSON-RPC
│ ┌────────▼─────────┐
│ │ mcp_server.py │ ← audit log
│ │ THE BOUNDARY │ ← egress screen
│ └───┬──────────┬───┘
│ ask_board │ │ 5 audited tools
│ ┌────────────────────────┘ │ (raw records)
│ │ │
┌────────▼─────────────▼───────────────────────────┐ │
│ Orchestrator → Research → Governance │ │
│ the governed core — one implementation, shared │ │
└────────────────────────┬─────────────────────────┘ │
│ │
└────────► tools.py ◄─────────────┘
│ read-only
┌────────▼─────────┐
│ data/board.db │
└──────────────────┘Note the asymmetry: ask_board routes through the full governance loop, while a raw tool call
gets the egress screen and the audit entry but no judge — because there is no answer to judge,
only records whose safety the schema already settled.
Related MCP server: ToolBridge
Quickstart
uv sync
uv run python cli.py "What was the result of motion #1?"No API key needed — PROVIDER=mock uses a deterministic offline client that exercises the
real tool layer, the real agent loop, and the real governance checks.
To use a live model instead, copy .env.example to .env and set PROVIDER=openai plus
OPENAI_API_KEY (uv sync already installed the SDK). The deterministic governance checks
still run underneath the LLM judge; a model can only tighten a verdict, never loosen it.
The judge is told each motion's ballot mode as an established fact rather than being left to infer it from the evidence JSON. Without that it reads "a person is named next to a vote" as an anonymity breach and blocks correct answers about recorded votes — where attribution is the entire point. Facts the database has already settled should be stated to a reviewer, not deduced by one.
The four demo questions
All four work offline.
# | Question | What happens |
1 |
|
|
2 |
| recorded ballot → tally with each director's name and choice → allow |
3 |
|
|
4 |
| anonymous election → research can only obtain totals → governance blocks → safe refusal |
Question 2 and question 4 are the pair worth showing together: the same tool, the same agent, and the difference in what comes back is decided by the ballot mode recorded in the schema — not by how the question was phrased or how well the prompt was written.
How governance decides
policies/answer_policy.md holds five rules with stable ids. The abstention phrase and the
safe refusal are parsed out of that markdown, not duplicated in code — edit the file and the
next run behaves differently, with no code change. The rules map to checks like this:
Rule | Check | Verdict |
G1 grounding | A factual claim carries no meeting/motion/document id | revise |
G2 anonymity | The draft names someone as the subject of a vote, without proof the ballot was recorded | block |
G2 anonymity | The question asks who voted a given way on an anonymous ballot | block |
G3 abstention | Tools returned nothing → must be the exact abstention phrase | revise |
Two design choices are worth calling out, because they're the ones an interviewer should push on:
Attribution is default-deny. The check does not ask "does the evidence prove this ballot was anonymous?" — it asks "does the evidence prove it was recorded?" The inverted version looks equivalent and isn't: a model that attributes a vote while citing a motion it never fetched would pass it. Absence of proof is not permission. Blocking on ambiguity costs a revision round; the other error is unrecoverable.
The deterministic checks are the floor, not the fallback. They run under every provider,
including the offline mock, with no network. When a live model is configured it judges too —
but it can only tighten a verdict, never loosen one. A control that works only while the
model cooperates isn't a control; tests/test_governance.py pins this by handing the judge a
leaky draft and having it vote allow, then asserting the answer is still blocked.
Governing an agent you didn't write: the MCP server
uv run python mcp_server.py # stdio MCP servercli.py governs an agent whose loop is in this repo. The MCP server governs one that isn't.
Point Claude Desktop at it and Claude becomes the connected agent — ask it "who voted against
David Lee?" and the refusal comes from the boundary, not from Claude's own judgment. The
guardrail travels with the data instead of living in someone else's prompt.
{
"mcpServers": {
"boardroom": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/boardroom-agents", "run", "python", "mcp_server.py"]
}
}
}What happens at the boundary
MCP client mcp_server.py
────────── ─────────────
initialize ──────────────────► server info, capabilities
tools/list ──────────────────► 5 audited tools + ask_board
(descriptions single-sourced from TOOL_SPECS,
so this surface and the OpenAI function-calling
path can never describe a tool differently)
resources/list ──────────────► policy://answer-policy
resources/read ─────────────► the full policy text
(inspect the rules you are being held to)
tools/call ──────────────────► ┌─────────────────────────────────────┐
get_motion_results(3) │ 1 tools.dispatch() → raw result │
│ 2 screen_egress(result) │
│ 3 audit.jsonl ← decision + shape │
└──────┬───────────────────────┬──────┘
│ clean │ violation
▼ ▼
totals + turnout BLOCKED_RESPONSE
(no voters) {error, violations}
— no records at all
tools/call ──────────────────► ask_board("who voted against David Lee?")
the governed path │
▼
Orchestrator → Research → Governance
│
▼
{ answer, verdict, violations, reasons, trace }
block → the policy's safe refusal, never the draftIt exposes the five audited tools plus ask_board (which routes through the full research +
governance loop and returns the verdict alongside the answer), and the policy itself as a
readable resource — a client should be able to inspect the rules it's being held to.
Two things happen here that don't happen inside the agent loop:
An append-only audit log (
logs/audit.jsonl). The in-processTraceis per-run and vanishes; this is the durable record of what a connected agent actually did. It stores what was asked and what was decided, plus the shape of each response — never the response. An audit trail that copies the records it protects becomes a second place to leak them.An egress screen on every result. The schema already makes an anonymous vote unattributable, so this filter should never fire — which is precisely why it's worth having. It's the assertion that catches a future query, tool, or migration that weakens the guarantee, at the last point before data leaves the process.
Be precise about the claim: exposing these tools over MCP adds no new data safety, because the schema already provides it. What's new is the enforcement point, the audit trail, and request-level governance for a client whose prompt you don't control.
Tests and evals
uv run pytest -q # unit tests: tools, anonymity, governance, MCP, protocol
uv run python evals/run_evals.py # golden set — the gate, pinned offline
uv run python evals/run_evals.py --live # same questions against the configured provider
uv run python evals/redteam.py # adversarial probe; non-zero exit if anything leakedThe gate is pinned offline. tests/conftest.py pins every test to the mock provider, and
run_evals.py does the same by default. A regression gate whose verdict changes because a model
reworded a sentence isn't a gate; it's a coin flip that occasionally fails your build. It also
means the suite makes no billable API calls — which it silently did until I noticed the runtime.
--live runs the same questions against the real provider but checks only what a model cannot
legitimately vary: the verdict, the rules that fired, whether the answer is the refusal or the
abstention, and — always — that no director's name leaked. Wording and citation assertions are
skipped there, because "the model phrased it differently" is not a governance failure. What must
never differ is the decision.
The red-team probe and the skill that drives it
evals/redteam.py runs 20 attacks on the anonymity invariant across five tactics — direct,
indirect inference, authority and pretext, framing and roleplay, and aggregation — and exits
non-zero if any answer paired a director's name with a vote on an anonymous ballot.
The skill exists because the parts a script can't do are the parts that matter: inventing attacks that aren't already in the file, and deciding whether a reported leak is real.
.claude/skills/redteam-anonymity/SKILL.md
the judgement: when to run, what to write, how to triage, where to fix
│
│ 1. run the corpus 2. write NEW attacks 3. triage
▼ (a static list goes stale
evals/redteam.py the moment it is read)
20 attacks × 5 tactics ──┐ uv run … --file attacks.txt
│
▼
Orchestrator → Research → Governance
│
▼
attributed(answer) + was an anonymous ballot in play?
│
┌───────────────────┼────────────────────────┐
▼ ▼ ▼
REAL LEAK recorded ballot detector noise
│ → permitted, not a → fix redteam.py,
│ finding at all not the system
│
│ 4. fix at the deepest layer that can hold it
│ tools.py > governance checks > policy > prompt
▼
--promote ────► evals/golden.jsonl ────► run_evals.py + pytest
a finding cannot be made twiceThat last arrow is the point of the whole thing: a red-team finding that isn't written down is a finding you get to make again.
Triage is the part that matters. An early version of the probe flagged three leaks that were all
its own false positives — one of them reading "nominations for the office of Board Chair;
David Lee" as an attribution, because bare for was in its vote-token list. A probe that cries
wolf is worse than no probe, because it teaches you to skim past it. The detector now requires a
strict vote token and only counts an attribution as a leak when an anonymous ballot was actually
in play — attribution on the recorded motion is correct behaviour and is reported as permitted.
Current state: 20/20 held on the mock, 20/20 on gpt-4o-mini — 7 versus 17 blocked by
governance respectively. Report the provider with the score; "20/20 held" means nothing without
it, and the two numbers differ because a live model explores more tools and trips more checks.
What running the live gate actually found
Worth telling this story, because it's the most useful thing the evals did. Running the golden set against the real model for the first time gave 4/10, and the failures split three ways:
Symptom | Diagnosis |
Missing | Brittle assertions — fixed by splitting decision-shaped checks from wording ones |
Two totals-only questions blocked | Real over-blocking. Policy G2 permits tallies; the judge was reading "individual votes were never recorded" as "don't discuss it" |
| The judge invented its own rule ids, so the two layers described one breach two ways |
Fixing the over-blocking then exposed the opposite failure: the money-demo question came back
allow. The G2-request check had required an anonymous motion in the evidence, and a live
agent doesn't always fetch one. It's now default-deny like the leak check — asking who voted is
only safe once the ballot is known to be recorded.
And fixing that over-blocked "How did the budget vote go?", because read case-insensitively
how did <two words> vote swallows it. Asking how a vote went is a totals question. The named
form is now case-sensitive on the subject, so it needs something that looks like a person.
Three rounds, each fix revealing the next error in the opposite direction. Over-blocking is the
quieter failure — nothing leaks, so nothing alarms — and it's the one that makes the system
useless by denying the board its own records. Both directions are now pinned in
tests/test_governance.py.
tests/test_anonymity.py asserts the structural guarantee: no tool can return a voter paired
with an anonymous choice. tests/test_governance.py asserts the judge blocks a deliberately
leaky draft, asks for a revision when a factual claim has no citation, and allows a well-cited
answer. evals/ is the regression gate — it runs whole questions end-to-end through the
orchestrator and checks properties of the final answer.
Why the anonymity invariant is structural, not prompted
In the source schema, recorded and anonymous ballots live in different tables:
recorded_votes(motion_id, voter_id, choice)— attribution is the point.ballot_participation(motion_id, voter_id)— records that a director voted.anonymous_ballots(id, motion_id, choice, candidate_id)— the choice, with no voter column at all, a random text primary key, and no timestamp, so there is no insertion-order or time signal to correlate a ballot back to a participation row.
So "who voted against this?" on an anonymous motion is not a question the system declines to
answer — it is a question the database cannot answer. get_motion_results' anonymous branch
reads only those two no-voter tables. A prompt-injection attempt, a jailbreak, or a bug in the
agent loop cannot surface data that was never stored.
The governance agent exists because that structural guarantee, while strong, only covers the data layer. A model can still infer, speculate, or launder an implication ("with 4 of 6 votes and David Lee absent from the abstention…"). The judge is the control that catches reasoning about the data, on top of the schema that controls the data itself.
Layout
cli.py entry point: prints the trace, then the answer
mcp_server.py MCP boundary: audit log, egress screen, ask_board
policies/answer_policy.md the governance policy, read at runtime
data/board.db the board database (read-only fixture)
src/boardroom/
config.py env, paths, and every loop budget
db.py read-only SQLite connection
tools.py the five audited tools + JSON specs + dispatcher
llm.py client protocol, deterministic mock, OpenAI client
trace.py structured step log
agents/
orchestrator.py routes, bounds, decides what is returned
research.py tool-calling loop, drafts with citations
governance.py LLM-as-judge + deterministic checks
tests/ pytest, incl. real-stdio MCP protocol tests
evals/
golden.jsonl known-good questions + expected properties
run_evals.py the regression gate
redteam.py adversarial probe on the anonymity invariant
.claude/skills/
redteam-anonymity/ when to probe, how to triage, where to fix
logs/audit.jsonl written at runtime by the MCP boundary (gitignored)Three surfaces, one governed core: cli.py for a human, mcp_server.py for an agent you don't
control, and evals/ for the machine that tries to break both.
Deliberately not here: no web UI, no auth, no write paths, no vector search, no agent framework. The orchestration loop is written out by hand so it can be read top to bottom.
See PLAN.md for the full specification and CLAUDE.md for the invariants.
Available Tools
6 toolsask_boardARead-only
Ask a natural-language question about the board's history. The answer is drafted by a research agent, then reviewed against the board's answer policy before it is returned. Includes the governance verdict and any policy rules that fired.
| Name | Required | Description | Default |
|---|---|---|---|
| question | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond annotations (readOnlyHint=true), the description adds significant behavioral context: the answer is drafted by a research agent, reviewed against an answer policy, and includes governance verdict and policy rules. This fully informs the agent about the tool's internal process.
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?
Two sentences, no redundancy. The first sentence states the core purpose, and the second adds valuable process details. Efficiently front-loaded.
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?
Given the tool's complexity (natural-language Q&A), the description covers purpose, process, and output. However, it lacks guidance on question formulation or potential limitations (e.g., response time due to agent review), which would enhance completeness.
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?
The input schema has 0% description coverage, and the tool description does not elaborate on the 'question' parameter (e.g., format, scope, examples). The description fails to compensate for the missing schema documentation.
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 clearly states the tool's purpose: asking natural-language questions about the board's history. It distinguishes from sibling tools (e.g., list_meetings, search_documents) by focusing on open-ended Q&A rather than structured retrieval.
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 implies when to use (for natural-language queries about board history) but does not explicitly state when not to use or provide alternatives. The sibling context helps, but explicit guidance would improve this dimension.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_meetingARead-only
Get one meeting's agenda, minutes, attendees, and attached documents.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations (readOnlyHint=true, destructiveHint=false) already inform the agent that this is a safe read operation. The description adds value by specifying the returned data types (agenda, minutes, attendees, documents), but does not disclose any additional behavioral traits such as error handling, required permissions, or rate limits. The description builds on annotations but does not exceed expectations.
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 a single sentence of only 9 words, front-loaded with the verb and object. It contains no redundant information and every word contributes to the meaning. It is appropriately sized for a simple tool.
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?
Given the low complexity (one parameter, simple return), the presence of an output schema (not shown but noted), and annotations covering safety, the description is mostly complete. It states what data is returned. However, it omits any mention of error handling (e.g., what if the meeting ID does not exist) or authorization, leaving minor 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?
The input schema has 0% description coverage for its sole parameter 'id'. The description does not explain that this is a meeting identifier or provide any context about the parameter's format, constraints, or how to obtain it. With such low schema coverage, the description should compensate but fails to do so.
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 clearly states the tool's purpose: 'Get one meeting's agenda, minutes, attendees, and attached documents.' It specifies the verb 'Get', the resource 'one meeting', and enumerates the returned data. This distinguishes it from siblings like list_meetings (which returns a list, not details) and other tools focusing on motions or documents.
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 implies usage when a user wants detailed information about a specific meeting, but provides no explicit guidance on when to use this tool versus alternatives (e.g., list_meetings to find the ID, search_documents for documents across meetings). No 'when-not-to-use' is mentioned.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_motion_resultsARead-only
Get results for a motion. Recorded motions return per-voter names and choices. Anonymous motions and elections return ONLY totals and turnout -- never individual votes.
| Name | Required | Description | Default |
|---|---|---|---|
| motionId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already mark the tool as read-only and non-destructive. The description adds critical behavioral details: recorded motions return per-voter data, while anonymous reactions return only totals. This goes beyond the annotations and provides key transparency.
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 concise with two sentences (first line + second line) that front-load the main purpose. Every part is useful, and there is no redundancy or fluff.
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?
Given that an output schema exists (which handles return value specifics), the description is sufficiently complete. It explains the two possible result types, which is the key behavioral nuance. The annotations already cover safety aspects. No gaps remain.
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?
The input schema has 1 parameter (motionId) with 0% schema description coverage. The description does not explicitly describe motionId, though the tool name and context imply it is the motion ID. Since the description does not add significant meaning beyond what is obvious, it only marginally compensates for the low schema coverage.
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 clearly states the tool's purpose: 'Get results for a motion.' It distinguishes between recorded motions (which return per-voter names/choices) and anonymous motions/elections (only totals/turnout), which differentiates it from siblings like list_motions or get_meeting.
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 provides clear context for when to use the tool, explaining how results differ based on motion type. It implicitly advises against expecting individual votes for anonymous motions, but does not explicitly state when not to use it or name alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_meetingsARead-only
List all board meetings (id, date, title).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, so the description does not need to reiterate safety. It adds value by specifying the returned fields (id, date, title). However, there is no mention of ordering, pagination, or limits, though the tool has no parameters, making this adequate.
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 a single sentence that is front-loaded with the action and resource. Every word is necessary, and no superfluous information is included.
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?
Given no parameters and an existing output schema (indicated by context), the description sufficiently explains what the tool does and what it returns. It is complete for a simple list-all tool with good annotations.
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?
The tool has zero parameters, and the schema coverage is 100%. Per guidelines, baseline is 4. The description does not need to add parameter info, and it correctly lists what is returned.
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?
Description clearly states the verb 'list' and resource 'board meetings', and specifies the returned fields (id, date, title). This distinctly separates it from sibling tools like get_meeting (which likely returns full details) and list_motions (different resource type).
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 implies a simple listing tool but does not explicitly state when to use it versus alternatives. For example, it does not indicate that for a specific meeting's full details, one should use get_meeting. Some guidance on use cases and exclusions would improve clarity.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_motionsARead-only
List motions (id, title, kind, ballot_mode, status). Optionally filter by meeting.
| Name | Required | Description | Default |
|---|---|---|---|
| meetingId | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate readOnlyHint=true, and the description adds that it lists specific fields and optionally filters. However, it omits behavioral details such as pagination, ordering, or whether it returns all motions or only those the user can see. The description does not contradict annotations.
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 a single sentence that front-loads the return fields and filtering capability. Every word is necessary, and there is no redundancy.
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 tool is simple with one optional parameter and an existing output schema, so the description could be minimal. However, it doesn't clarify the scope (e.g., 'all motions in the workspace', 'motions for a meeting only'). This vagueness reduces completeness given the sibling tools context.
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?
With 0% schema description coverage, the description must compensate. It explains that meetingId filters motions by meeting, which is functional but minimal. It doesn't specify what the integer represents (e.g., meeting ID from list_meetings) or that it's optional. The added value is marginal.
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 clearly states the verb 'list' and resource 'motions', and enumerates the returned fields (id, title, kind, ballot_mode, status). It distinguishes from sibling tools like list_meetings (different resource) and get_meeting (single meeting), making the purpose unambiguous.
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 hints at when to use the optional meetingId filter ('Optionally filter by meeting') but provides no explicit guidance on when to use this tool versus alternatives like get_motion_results or search_documents. It lacks exclusions or prerequisite context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_documentsARead-only
Full-text search over indexed meeting documents. Returns matching snippets with document and meeting references. (Ballot-bearing documents are excluded from this index.)
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare the tool is read-only and non-destructive. The description adds that ballot-bearing documents are excluded, which is important behavioral info. However, it does not mention any limits, pagination, or performance caveats.
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?
Two sentences, 40 words, no fluff. Essential information is front-loaded.
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?
Given the output schema exists (context signal), the description sufficiently covers return values (snippets with references). One parameter is straightforward. The exclusion of ballot-bearing documents is noted, making the description mostly complete.
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?
With 0% schema description coverage, the description must compensate for the single 'query' parameter. It only says 'full-text search,' lacking details on query syntax (e.g., boolean operators, phrase matching, wildcards).
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 clearly states the tool performs full-text search over indexed meeting documents and returns matching snippets. The exclusion of ballot-bearing documents adds specificity. However, it does not explicitly differentiate from sibling tools like list_meetings or get_meeting.
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 implies use for searching documents but provides no guidance on when not to use or alternatives. No mention of prerequisites or scope (e.g., date range, folders).
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. Dates show when Glama detected each change.
6 tool updates
v0.1.0- First observed
ask_board - First observed
get_meeting - First observed
get_motion_results - First observed
list_meetings - First observed
list_motions - First observed
search_documents
TDQS
Scored across 6 tools
Each tool has a distinct purpose: listing vs getting details for meetings, listing motions vs getting their results, full-text search, and natural-language query. No overlap.
All tools follow a consistent verb_noun pattern in snake_case, e.g., list_meetings, get_meeting, search_documents. Minor singular/plural variations are acceptable.
With 6 tools, the server is well-scoped for a board information retrieval system. Not too few to be thin, not too many to be overwhelming.
The tool surface covers all major query needs: listing meetings, getting meeting details, listing motions, retrieving motion results, document search, and a Q&A system. It is complete for its apparent read-only purpose.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Governed data discovery, exact queries, decisions, simulations, and runtime utilities over MCP.
Hybrid human + AI expertise for faster, trusted answers and decisions via MCP Server.
MCP server for querying and analyzing data from ad platforms, analytics tools, and spreadsheets
AI governance MCP server for EU AI Act compliance and jurisdiction verification
Related MCP Servers
- AlicenseBqualityDmaintenanceAn MCP server that bridges AI assistants with data warehouses through Cube.js to enable governed, natural language semantic analytics queries. It provides tools for metadata discovery and secure query execution while enforcing governance policies like PII blocking and access limits.3611MIT
- FlicenseNot gradedqualityDmaintenanceA governed MCP server for integrating AI agents with customer data, featuring role-based access control, field redaction, and human-in-the-loop approval for secure support operations.1-
- AlicenseNot gradedqualityDmaintenanceMCP server for querying the DodaOne governance framework, enabling users to retrieve governance information and evaluate proposed actions.2MIT

RecoSearchofficial
AlicenseNot gradedqualityCmaintenanceA deterministic MCP server that governs read-only queries across multiple data sources, returning answers with full provenance (every row cited) or a typed refusal, ensuring LLM answers are traceable and contract-enforced.1Apache 2.0
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/edgemoorlf/boardroom-agents'
If you have feedback or need assistance with the MCP directory API, please join our Discord server