baatjie-mcp-server
This server provides a unified MCP interface to the Baatjie Group's tanOS (property OS, sales pipeline, sequencing) and sigscheCore (signal scheduler) systems, offering 16 tools across generic CRUD, task management, operational reporting, signal scheduling, and safety features.
Schema & Data Access:
baatjie_describe_schemalists tables and columns.baatjie_queryreads with filtering, sorting, pagination, and optional PII.baatjie_insert,baatjie_update, andbaatjie_deletehandle writes with audit logging; destructive actions require confirmations or filters.Task Sequencing & Action Planning:
baatjie_create_todo,baatjie_list_todos,baatjie_update_todomanage todos with dependencies, waves, tracks, and effort.baatjie_next_actionsidentifies startable tasks ranked by unblocking power.Operations & Sales:
baatjie_dashboardgives a table count snapshot and BDOP clock phase.baatjie_pipelineshows per-BRM outreach/demos/sales vs. 26/day target.baatjie_log_activityrecords BRM actions.baatjie_arrearslists overdue active leases with notice counts.Signal Scheduling (sigscheCore):
baatjie_signal_queuelists posts by brand/status/date.baatjie_schedule_signalqueues posts, recognizing angel windows (08:17, 11:11, 13:13, 22:22).baatjie_brand_snapshotshows platform readiness and content library depth.Safety & Compliance: PII is redacted by default except where explicitly allowed; all writes are audited; deletions require confirmation; unfiltered updates/deletes are refused; table access is allowlisted (24 tables) and non-destructive tools carry read-only hints.
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., "@baatjie-mcp-server What should I do next on the property plan?"
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.
baatjie-mcp-server
An MCP server exposing the two Baatjie Group systems through one interface:
tanOS — property OS, sales pipeline, and the dependency-aware action plan
sigscheCore — multi-brand signal scheduler across 33 platforms
Sixteen tools: five generic CRUD tools covering every allowlisted table, and eleven workflow tools for the paths used daily.
Install
npm install
npm run build
cp .env.example .env # then fill in both service role keysRelated MCP server: fallpharm-mcp
Run
# stdio (local MCP clients — Claude Desktop, Claude Code)
npm start
# streamable HTTP
TRANSPORT=http PORT=3000 npm startClaude Desktop / Claude Code config
{
"mcpServers": {
"baatjie": {
"command": "node",
"args": ["/absolute/path/to/baatjie-mcp-server/dist/index.js"],
"env": {
"TANOS_URL": "https://tlhzwovsgjptifhnzcvh.supabase.co",
"TANOS_SERVICE_KEY": "...",
"SIGSCHE_URL": "https://bikmrclrpgxenncqodti.supabase.co",
"SIGSCHE_SERVICE_KEY": "..."
}
}
}
}⚠️ Read this before pointing it at production
Service role keys are required, and this is not a preference. RLS is enabled on
every table in both projects with zero policies defined. A non-privileged key
therefore returns empty result sets rather than errors — the server would cheerfully
report 0 agencies signed, 0 tenants, 0 todos as though those were the facts.
This was observed during testing, not theorised.
baatjie_dashboard warns when every table reads zero, since all-zero across nineteen
tables is far more likely to be a credentials problem than a genuinely empty group.
Treat that warning as a hard stop.
The same fact has a second consequence worth stating plainly: because there are no RLS policies, the service role key is the only thing standing between this server and every row in both databases — tenant ID numbers, passport numbers and permit status, landlord banking details, and the policy book. Until RLS policies exist, guard the key accordingly and keep the server local rather than exposed over HTTP.
Design notes
Personal data is withheld by default. Reads of tenants, foreign_nationals,
landlords and agents drop direct identifiers (ID numbers, passport numbers, phone,
email, banking details) unless include_pii: true is passed, and the response reports
which fields were withheld. Routine queries shouldn't pull identity numbers into an
agent's context as a side effect.
Every write is audited. Inserts, updates and deletes write a row to tanOS
audit_log with the actor you supply. Pass a meaningful actor (baatjie,
marius-ai, deon-ai) rather than the default. If the audit write fails, the response
says so rather than pretending it succeeded.
Destructive operations are guarded. baatjie_delete requires confirm: true.
Unfiltered updates and deletes are refused outright rather than rewriting a whole table.
Tools carry MCP annotations (readOnlyHint, destructiveHint, idempotentHint) so
calling agents can reason about consequences.
Table access is allowlisted. The generic CRUD tools reach only the nineteen tanOS
and five sigscheCore tables named in constants.ts. They are not an arbitrary-schema
backdoor.
Tools
Generic — comprehensive coverage
Tool | Purpose |
| List reachable tables per project. Start here when unsure. |
| Read any table: filters, column selection, ordering, pagination |
| Insert rows into any table |
| Update rows matching a filter |
| Delete rows — requires |
Sequencing — the dependency graph
Tool | Purpose |
| What is genuinely startable now, ranked by downstream unblock count |
| Full board with wave, track, effort, blockers |
| Add an item, optionally sequenced |
| Change status/sequencing; reports what the change unblocked |
baatjie_next_actions is the one to reach for on "what should I do next". A flat todo
list hides the fact that most items are blocked; this answers the question the list is
standing in for.
Operations
Tool | Purpose |
| Counts across every table, sales position, BDOP clock and 33/22 sprint block |
| Sales rollup per BRM against the 26/day objective |
| Record a BRM's outreach/demos/sales for a sprint block |
| Active leases with no recent payment, ranked, with notices already sent |
sigscheCore
Tool | Purpose |
| Scheduled, sent and failed posts |
| Queue a post; sets the angel-window label automatically |
| Per-brand readiness across platforms |
Angel windows (08:17, 11:11, 13:13, 22:22) are recognised automatically when scheduling. Times outside them are accepted; the label is simply null.
A note on notices
notices rows and the baatjie_arrears report describe real occupants. baatjie_arrears
is deliberately read-only — it reports the position and does not send anything, because
serving a notice should be a separate deliberate act.
South African arrears and eviction notices carry statutory requirements under the Rental
Housing Act and the PIE Act, and proof-of-service technicalities are exactly where cases
fail. Route wording and timing through the legal-exposure-check skill before anything
reaches an occupant rather than after.
Development
npm run dev # watch mode
npm run build # compile to dist/
node test-harness.mjs # protocol + guardrail checks (needs env vars set)
npm run test:rls # RLS isolation regression (needs TANOS_ANON_KEY too, see below)test-harness.mjs verifies initialisation, tool registration, annotation coverage,
description quality, the delete confirmation guard, unfiltered-write refusal, Zod range
validation, and actionable error text. All 13 checks should pass.
tests/rls-regression.mjs automates the P0.1 isolation check that was previously run by
hand: seeds two agencies and two real Supabase Auth users, confirms an unauthenticated
(anon) read returns empty rather than erroring, confirms agency A can read its own row,
and confirms agency A's cross-agency read/update/delete/insert against agency B's row
are all blocked — then tears every fixture down in a finally block regardless of
outcome. Needs TANOS_ANON_KEY in addition to the usual service key, since the whole
point is to test as a non-privileged client rather than the key that bypasses RLS.
Scoped to the 10 tables that actually carry account_id + policies today (landlords,
properties, rooms, tenants, foreign_nationals, leases, lease_agreements, payments,
maintenance, notices) — a live get_advisors check on 2026-08-11 found ten other
tables (agents, ai_agents, audit_log, brms, daily_activity, employers, locare_accounts,
member_consents, policies, todos) still have RLS enabled with zero policies. That's
default-deny, not a live hole, but there's no per-agency scoping on those tables yet
for a test like this one to exercise.
evaluations.xml contains ten questions for testing whether an LLM can actually use
this server to answer realistic operational questions.
Available Tools
16 toolsbaatjie_arrearsArrears ReportARead-onlyIdempotent
Identify active leases with no recent payment, ranked by days since last payment, alongside how many notices have already been sent on each.
Read-only. It reports the position; it does not send anything. Serving a notice is a separate, deliberate act — in South Africa arrears and eviction notices carry statutory requirements under the Rental Housing Act and PIE Act, so wording and timing should be checked before anything reaches an occupant.
Args:
min_days_overdue (number): Only leases with no payment in at least this many days (default: 1)
limit (number): Max leases (default: 25)
include_pii (boolean): Include tenant identifiers (default: false)
response_format ('markdown' | 'json'): Output format
Returns: { "leases": [{ "lease_id","room_id","rent_due_day","status", "last_payment_date","days_since_payment","total_paid","notices_sent" }], "summary": { "active_leases","in_arrears","never_paid","total_notices_sent" } }
Examples:
"Who's behind on rent?" -> no args
"Anyone more than 30 days down?" -> min_days_overdue=30
Error Handling:
Leases with no payment history report last_payment_date=null and are counted under never_paid rather than being silently dropped
Inputs: limit, include_pii, response_format, min_days_overdue.
baatjie_brand_snapshotBrand Snapshot Across PlatformsARead-onlyIdempotent
Per-brand status across sigscheCore: platform registration progress, queued and sent counts, library depth and how many brand cards exist.
Answers "is this brand ready to broadcast" and "where are we still unregistered".
Args:
brand_id (string, optional): One brand; omit to roll up every brand present
response_format ('markdown' | 'json'): Output format
Returns: { "brands": [{ "brand_id","queued","sent","failed","library_items", "brand_cards","platforms_registered","platforms_pending" }], "totals": { "brands","queued","sent","registered" } }
Examples:
"Is locare ready to post?" -> brand_id='locare'
"Which brands still need registration?" -> no args
Inputs: brand_id, response_format.
baatjie_create_todoCreate a TodoA
Add a todo to the tanOS action plan, optionally with sequencing metadata.
Args:
title (string): Short imperative summary
detail (string, optional): Context, why it matters, what "done" looks like
category (string): Grouping label, e.g. 'security', 'legal', 'pricing' (default: 'general')
product (string): 'locare' | 'tanos' | 'sigschecore' | 'brand' | 'cross-cutting' (default: 'cross-cutting')
priority ('critical' | 'high' | 'medium' | 'low'): Default 'medium'
wave (number, optional): 0=today, 1=root, 2=near, 3=downstream, 4=later
track ('A' | 'B' | 'SPINE' | 'TODAY', optional): Which workstream owns it
blocked_by (string[], optional): Todo ids that must complete first
effort (string, optional): Rough size, e.g. '1 hour', '2-3 days'
actor (string): Who is creating it, for the audit trail
response_format ('markdown' | 'json'): Output format
Returns: { "ok": true, "todo": { "id","title",... }, "audit_logged": boolean }
Examples:
Quick capture -> title='Confirm iKhokha reversal path', priority='high'
Sequenced item -> title='Load the policy book', wave=2, track='SPINE', blocked_by=[''], effort='1 day'
Inputs: title (required), wave, actor, track, +7 more.
baatjie_dashboardBaatjie Group Operations SnapshotARead-onlyIdempotent
One call for the whole operational picture: row counts across every tanOS table, the sales position against the 26/day objective, and where the clock sits in the BDOP day (phase, 33/22 sprint block, next angel window).
This is the cheapest way to orient at the start of a session or a sprint. It replaces roughly a dozen separate count queries.
Args:
response_format ('markdown' | 'json'): Output format (default: 'markdown')
Returns: { "counts": { "": number | null }, "sales": { "accounts_signed": number, "daily_target": 26, "brms": number, "agent_pool": number, "employer_pool": number, "inherited_book": number }, "property": { "landlords","properties","rooms","tenants","leases", "payments","notices","maintenance" }, "clock": { "phase","sprint","block","minutes_left","next_angel_window" } }
Examples:
"Where are we?" / "Status?" -> no args
Start of a Factory sprint -> no args, read the clock block
Error Handling:
A table that cannot be counted returns null for that entry rather than failing the whole call
Inputs: response_format.
baatjie_deleteDelete RowsADestructiveIdempotent
Permanently delete rows matching a filter. Requires confirm=true and at least one filter. Recorded in tanOS audit_log.
Deletes are irreversible and there is no soft-delete on these tables. Prefer a status change (baatjie_update) over deletion wherever the schema has a status column — the ledger is append-only by design, and payment corrections belong as compensating rows rather than deletions.
Args:
project ('tanos' | 'sigsche'): Which system
table (string): Target table
filters (Filter[]): Which rows to delete — must not be empty
confirm (boolean): Must be true; guards against accidental invocation
actor (string): Who is deleting, for the audit trail
response_format ('markdown' | 'json'): Output format
Returns: { "ok": true, "table": string, "action": "delete", "affected": number, "audit_logged": boolean }
Error Handling:
confirm=false returns an explanation without deleting anything
Foreign key violations name the dependent rows blocking the delete
Inputs: table (required), project (required), actor, confirm, +2 more.
baatjie_describe_schemaDescribe Baatjie Group SchemaARead-onlyIdempotent
List every table reachable through this server, with row counts and column names.
Call this first when you are unsure which table or column to use. It is the cheapest way to orient before querying.
Args:
project ('tanos' | 'sigsche' | omit for both): Which system to describe
response_format ('markdown' | 'json'): Output format (default: 'markdown')
Returns: { "projects": { "tanos": { "tables": [{ "name": string, "columns": string[] }] }, "sigsche": { "tables": [...] } } }
tanOS tables: Property: landlords, properties, rooms, tenants, foreign_nationals, leases, lease_agreements, payments, maintenance, notices Commercial: locare_accounts, brms, agents, employers, policies, daily_activity Internal: todos, ai_agents, audit_log
sigscheCore tables: profiles, queue_items, library_items, brand_cards, registration_status
Inputs: project, response_format.
baatjie_insertInsert RowsA
Insert one or more rows into any table in tanOS or sigscheCore. Every insert is recorded in tanOS audit_log with the actor you supply.
Args:
project ('tanos' | 'sigsche'): Which system
table (string): Target table
rows (object[]): Rows to insert, 1-100. Omit columns with database defaults.
actor (string): Who is writing, for the audit trail (default: 'mcp')
response_format ('markdown' | 'json'): Output format
Returns: { "ok": true, "table": string, "action": "insert", "affected": number, "rows": object[], "audit_logged": boolean, "note"?: string }
Examples:
Log a signed agency -> table='locare_accounts', rows=[{"agency_name":"Cape Letting","brm_id":"...","tier":"Growth","status":"active"}]
Record a payment -> table='payments', rows=[{"lease_id":"...","amount":3400,"payment_date":"2026-08-08","method":"payroll"}]
Note: writes to occupant-facing tables (tenants, leases, notices, payments) are flagged in the response, because a row there corresponds to a real person's tenancy or money. A 'notices' row in particular represents a notice served on an occupant — South African arrears and eviction notices carry statutory requirements, so route the wording through your legal-exposure-check skill before sending rather than after.
Error Handling:
Foreign key failures name the missing parent record
CHECK constraint failures point at columns with restricted values
Inputs: rows (required), table (required), project (required), actor, +1 more.
baatjie_list_todosList Todos With SequencingARead-onlyIdempotent
List todos with their wave, track, effort, blocker count and unblock count.
Use this for a full picture of the board. For "what do I do next", baatjie_next_actions is the better tool because it filters to what is actually actionable.
Args:
status ('open' | 'in_progress' | 'done' | 'blocked', optional): Filter by status. Omit to return everything except done.
track ('A' | 'B' | 'SPINE' | 'TODAY', optional): Filter by workstream
wave (number, optional): Filter by wave (0=today, 1=roots, 2..4=later)
priority ('critical' | 'high' | 'medium' | 'low', optional): Filter by priority
limit (number): Max items (default: 25)
offset (number): Pagination offset (default: 0)
response_format ('markdown' | 'json'): Output format
Returns: { "total","count","offset","items":[...],"has_more","next_offset"? } where each item carries id, title, priority, status, wave, track, effort, open_blockers, startable and unblocks.
Examples:
"Show the critical list" -> priority='critical'
"What's in wave 1?" -> wave=1
"Everything on the housing rail" -> track='B'
Inputs: wave, limit, track, offset, +3 more.
baatjie_log_activityLog BRM Sprint ActivityA
Record a BRM's outreach, demos and sales for a 33/22 sprint block. This is how the daily 26/day objective gets measured — unlogged work is invisible to baatjie_pipeline.
Args:
brm_id (string): BRM identifier, must exist in tanOS brms
outreach (number): Contacts made in this block (default: 0)
demos (number): Demos booked or run (default: 0)
sales (number): Agencies signed (default: 0)
sprint_block (string, optional): Which block, e.g. 'sprint-3-build'. Defaults to the current 33/22 position from the clock.
activity_date (string, optional): YYYY-MM-DD, defaults to today
actor (string): Who is logging, for the audit trail
response_format ('markdown' | 'json'): Output format
Returns: { "ok": true, "activity": {...}, "clock": {...}, "audit_logged": boolean }
Examples:
End of a build block -> brm_id='marius-ai', outreach=12, demos=3
Backfill yesterday -> brm_id='...', sales=1, activity_date='2026-08-07'
Inputs: brm_id (required), actor, demos, sales, +4 more.
baatjie_next_actionsWhat Can Actually Be Started NowARead-onlyIdempotent
Return the todos that are genuinely startable right now — every blocker done — ranked by how much each one unblocks downstream.
This is the tool to reach for when asked "what should I do next", "what's the priority", or "what's blocking us". A raw todo list hides the fact that most items cannot be started; this one answers the question the list is standing in for.
Args:
track ('A' | 'B' | 'SPINE' | 'TODAY', optional): Restrict to one workstream. A = agency business, B = payroll housing rail, SPINE = shared infrastructure, TODAY = time-boxed launch-day items
priority ('critical' | 'high' | 'medium' | 'low', optional): Minimum priority
include_blocked (boolean): Also list blocked items with their blocker counts (default: false)
limit (number): Max items (default: 25)
response_format ('markdown' | 'json'): Output format
Returns: { "startable": [{ "id","title","priority","track","wave","effort","unblocks" }], "blocked_count": number, "roots": [...], // startable items that unblock the most "summary": { "total","startable","blocked","by_priority": {...} } }
Examples:
"What should I work on?" -> no args
"What's next on the housing rail?" -> track='B'
"Show me everything including what's stuck" -> include_blocked=true
Error Handling:
Returns an empty startable list with an explanation if every open item is blocked, which itself signals the roots need attention first
Inputs: limit, track, priority, include_blocked, +1 more.
baatjie_pipelineSales Pipeline by BRMARead-onlyIdempotent
Roll up sales performance per Business Relationship Manager: inherited book size, agencies signed, and logged outreach/demo/sale activity.
Answers "who is performing", "where is the pipeline", and "are we hitting 26/day".
Args:
since (string, optional): ISO date (YYYY-MM-DD). Restrict activity to on/after this date.
limit (number): Max BRMs to return (default: 25)
response_format ('markdown' | 'json'): Output format
Returns: { "brms": [{ "brm_id","inherited_book_size","accounts_signed", "outreach","demos","sales" }], "totals": { "accounts_signed","outreach","demos","sales","brm_count" }, "against_target": { "daily_target": 26, "signed_today": number, "gap": number } }
Examples:
"How's the pipeline?" -> no args
"Activity this week" -> since='2026-08-03'
Error Handling:
BRMs with no logged activity appear with zeros rather than being omitted, so silence is visible rather than hidden
Inputs: limit, since, response_format.
baatjie_queryQuery Any Baatjie TableARead-onlyIdempotent
Read rows from any table in tanOS or sigscheCore, with filtering, column selection, ordering and pagination.
This is the general-purpose read. For sequencing work prefer baatjie_next_actions, and for the ops snapshot prefer baatjie_dashboard — both are cheaper and better shaped.
Personal identifiers (ID numbers, passport numbers, phone, email, banking details) are withheld unless include_pii is true, and the response reports which fields were withheld.
Args:
project ('tanos' | 'sigsche'): Which system
table (string): Table name — must be in the allowlist
columns (string[], optional): Specific columns; omit for all
filters (Filter[]): AND-combined filters
order_by (string, optional): Column to sort on
ascending (boolean): Sort direction (default: true)
limit (number): Max rows, 1-200 (default: 25)
offset (number): Rows to skip (default: 0)
include_pii (boolean): Include personal identifiers (default: false)
response_format ('markdown' | 'json'): Output format
Returns: { "total": number, "count": number, "offset": number, "items": object[], "has_more": boolean, "next_offset"?: number, "redacted_fields": string[] }
Examples:
"Which todos are still open?" -> project='tanos', table='todos', filters=[{"column":"status","op":"neq","value":"done"}]
"Show agencies signed this month" -> table='locare_accounts', filters=[{"column":"signed_date","op":"gte","value":"2026-08-01"}]
"Biggest BRM books" -> table='brms', order_by='inherited_book_size', ascending=false
Error Handling:
"Unknown table" lists the valid table names for that project
Column errors suggest calling baatjie_describe_schema
tanOS tables: Property: landlords, properties, rooms, tenants, foreign_nationals, leases, lease_agreements, payments, maintenance, notices Commercial: locare_accounts, brms, agents, employers, policies, daily_activity Internal: todos, ai_agents, audit_log
sigscheCore tables: profiles, queue_items, library_items, brand_cards, registration_status
Inputs: table (required), project (required), limit, offset, +6 more.
baatjie_schedule_signalSchedule a Signal PostA
Queue a post or ad for broadcast across platforms, with angel-window awareness.
If the scheduled time matches one of the reserved windows (08:17, 11:11, 13:13, 22:22) the angel label is set automatically and reported back. Times outside those windows are accepted without complaint — the label is simply null.
Args:
brand_id (string): Brand key, e.g. 'locare', 'dantalan'
brand_name (string): Display name
caption (string): Post body
platforms (string[]): Target platforms, e.g. ['linkedin','x','instagram']
scheduled_date (string): YYYY-MM-DD
scheduled_time (string): HH:MM (24h)
is_ad (boolean): Whether this is a paid ad (default: false)
is_master (boolean): Master/primary signal for the slot (default: false)
media (string, optional): Media URL or reference
item_type (string, optional): Free-form classification
user_id (string, uuid): Owning sigscheCore profile id
actor (string): Who is scheduling, for the audit trail
response_format ('markdown' | 'json'): Output format
Returns: { "ok": true, "item": {...}, "angel_label": string | null, "audit_logged": boolean }
Examples:
Launch post -> brand_id='locare', scheduled_date='2026-08-08', scheduled_time='13:13', platforms=['linkedin','x']
Evening story -> scheduled_time='22:22', platforms=['instagram']
Error Handling:
Invalid time format is rejected before the write
A user_id with no matching profile returns a foreign key explanation
Inputs: caption (required), user_id (required), brand_id (required), platforms (required), +9 more.
baatjie_signal_queueList the Signal QueueARead-onlyIdempotent
List scheduled, sent and failed posts in the sigscheCore broadcast queue, optionally filtered by brand, status or date.
Args:
brand_id (string, optional): Restrict to one brand
status ('scheduled' | 'sent' | 'failed', optional): Filter by state
from_date (string, optional): YYYY-MM-DD, on or after
to_date (string, optional): YYYY-MM-DD, on or before
limit (number): Max items (default: 25)
offset (number): Pagination offset (default: 0)
response_format ('markdown' | 'json'): Output format
Returns: { "total","count","offset","items":[{ "id","brand_name","scheduled_date", "scheduled_time","angel_label","platforms","status","caption","is_ad" }], "has_more","next_offset"? }
Examples:
"What's queued today?" -> from_date='2026-08-08', to_date='2026-08-08'
"Anything failed?" -> status='failed'
"locare's schedule" -> brand_id='locare'
Inputs: limit, offset, status, to_date, +3 more.
baatjie_updateUpdate RowsADestructiveIdempotent
Update rows matching a filter, in any table in either system. At least one filter is required — an unfiltered update is refused rather than rewriting the whole table. Recorded in tanOS audit_log.
Args:
project ('tanos' | 'sigsche'): Which system
table (string): Target table
patch (object): Columns and new values
filters (Filter[]): Which rows to change — must not be empty
actor (string): Who is writing, for the audit trail
response_format ('markdown' | 'json'): Output format
Returns: { "ok": true, "table": string, "action": "update", "affected": number, "rows": object[], "audit_logged": boolean }
Examples:
Mark a todo done -> table='todos', patch={"status":"done"}, filters=[{"column":"id","op":"eq","value":""}]
Close a maintenance ticket -> table='maintenance', patch={"status":"resolved","resolved_at":"2026-08-08T12:00:00Z"}, filters=[{"column":"ticket_id","op":"eq","value":""}]
Error Handling:
Empty filters are rejected with an explanation
Unknown columns in the patch are reported by name
Inputs: patch (required), table (required), project (required), actor, +2 more.
baatjie_update_todoUpdate a TodoADestructiveIdempotent
Change a todo's status or sequencing metadata by id.
Marking an item done automatically unblocks anything that listed it in blocked_by — the dependency graph is recomputed on read, so the next call to baatjie_next_actions will surface newly available work.
Args:
id (string, uuid): Todo id
status ('open' | 'in_progress' | 'done' | 'blocked', optional)
priority ('critical' | 'high' | 'medium' | 'low', optional)
wave (number, optional)
track ('A' | 'B' | 'SPINE' | 'TODAY', optional)
blocked_by (string[], optional): Replaces the existing blocker list
effort (string, optional)
detail (string, optional)
actor (string): Who is writing, for the audit trail
response_format ('markdown' | 'json'): Output format
Returns: { "ok": true, "todo": {...}, "newly_unblocked": [{ "id","title" }], "audit_logged": boolean }
Examples:
Close an item -> id='', status='done'
Re-sequence -> id='', wave=1, track='SPINE'
Inputs: id (required), wave, actor, track, +6 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 16 tools
Tools are mostly distinct: generic CRUD (query/insert/update/delete) is clearly separated from specialized reads like dashboard, pipeline, arrears, and signal_queue. A few overlaps exist (e.g., baatjie_query could be used for many read needs), but descriptions guide to the specialized tools, so confusion is minimal.
All tools share the 'baatjie_' prefix and mostly use snake_case with verb_noun patterns (schedule_signal, list_todos, create_todo, update_todo, log_activity). However, some are noun-only (dashboard, pipeline, arrears, signal_queue, brand_snapshot), which is a minor deviation from the otherwise consistent pattern.
With 16 tools, the server sits just above the ideal 3-15 range but feels justified given the two subsystems (tanOS and sigscheCore) and the combination of generic CRUD plus domain-specific reads/actions. The count is manageable and not bloated.
Generic CRUD tools (query/insert/update/delete) cover virtually all tables, while specialized tools like baatjie_next_actions, baatjie_pipeline, baatjie_arrears, and baatjie_signal_queue provide high-level views. Minor gaps like a specific cancel_signal or update_signal are covered by the generic update/delete, so there are no critical dead ends.
Maintenance
Related MCP Connectors
Control plane for autonomous software labor. Agents claim objectives over MCP with audit trail.
Guarded MCP server for agent-readable business truth, provenance, readiness, and discovery.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA production-grade MCP server designed for multi-tenant, authenticated, and observable AI agent systems, enabling secure tool execution across heterogeneous data sources.67MIT
- AlicenseNot gradedqualityCmaintenanceProvides a sovereign, MIT-licensed MCP server for professional-service workflows, running entirely on your infrastructure with Ed25519 cryptographic signing for every action.MIT
- AlicenseNot gradedqualityCmaintenanceA sovereign, MIT-licensed MCP server for professional-service workflow automation, enabling offline-capable, cryptographically signed operations without vendor lock-in.MIT
- FlicenseNot gradedqualityDmaintenanceA governed MCP server with OAuth 2.1 + PKCE, declarative tool scoping, row-level data filters, per-identity rate limits, and a tamper-evident audit trail.-