๐Ÿฉบ Klinik Dr Fang Front-Desk Assistant

The Governed Tool Layer & No-Free-SQL Guarantee

Python MCP Toolbox Pattern SQLite (read-only) tools.yaml

๐Ÿ” About This Code Showcase

These snippets show the heart of the build: a declarative toolset that is the agent's entire universe, and the governed gate that can execute nothing but those fixed, parameterized queries.

Synthetic-data generation and the conversational layer are omitted here for clarity. The full project is on GitHub.

๐Ÿงพ The Toolset Is the Agent's Universe โ€” tools.yaml

Each tool's SQL is fixed; the agent supplies only bound parameter values. There is deliberately no execute_sql, no list_tables, and no natural-language-to-SQL tool.

๐Ÿ“„ toolbox/tools.yaml โ€” a tool + the deliberate exclusions
sources: klinik-db: kind: sqlite database: ../data/klinik.db # opened read-only by the runner tools: list_inactive_patients: kind: sqlite-sql source: klinik-db description: Lapsing / recall list. Returns name + last visit only. parameters: - name: since_date type: string description: Cut-off ISO date. statement: | SELECT p.name, MAX(cn.visit_date) AS last_visit FROM patients p JOIN case_notes cn ON cn.patient_id = p.id GROUP BY p.id HAVING MAX(cn.visit_date) < :since_date ORDER BY last_visit; toolsets: front_desk: # scoping = access control - condition_trend - list_inactive_patients - cluster_recent_symptoms # ...10 tools total # DELIBERATELY EXCLUDED (available in MCP Toolbox, switched OFF on purpose): # execute_sql โ€” would let the agent author arbitrary SQL. NOT defined. # list_tables โ€” schema reconnaissance. NOT defined. # any NL2SQL โ€” natural-language-to-SQL generation. NOT defined.

๐Ÿ”’ The Governed Gate โ€” tool_runner.py

One component touches the database. It refuses any unknown tool, any unexpected parameter, and opens the connection read-only. There is no code path that runs caller-supplied SQL.

๐Ÿ“„ agent/tool_runner.py โ€” refusal logic + read-only connection
class GovernedToolset: def __init__(self, toolset_name="front_desk"): cfg = yaml.safe_load(open(TOOLS_YAML)) names = cfg["toolsets"][toolset_name] self.loaded = {n: cfg["tools"][n] for n in names} # scoping # READ-ONLY connection (layer 4). Any write is impossible. self.con = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True) def run(self, name, **params): if name not in self.loaded: raise ValueError( f"Tool '{name}' is not in this agent's toolset. " f"No such capability exists โ€” refused.") spec = self.loaded[name] self._check_params(name, spec, params) # reject unknown params if spec["kind"] == "sqlite-sql": # fixed statement, BOUND named params โ€” never concatenated return [dict(r) for r in self.con.execute(spec["statement"], params)] ... def _check_params(self, name, spec, params): declared = {p["name"] for p in spec.get("parameters", [])} unknown = set(params) - declared if unknown: raise ValueError(f"{name}: unknown parameter(s) {sorted(unknown)} โ€” refused.")

๐Ÿ›ก๏ธ Proving It โ€” guardrails_demo.py

Every test below is supposed to fail. Running the script confirms the four governance layers are real, not rhetorical.

๐Ÿ“„ demo/guardrails_demo.py โ€” everything here should be refused
# Layer 1: no execute_sql tool exists at all expect_refused("Call a generic execute_sql tool", lambda: ts.run("execute_sql", sql="SELECT * FROM patients")) # Layer 2: the agent cannot smuggle in extra parameters expect_refused("Inject an unexpected parameter", lambda: ts.run("list_inactive_patients", since_date="2025-06-01", extra="'; DROP TABLE patients;--")) # Layer 4: the connection itself is read-only def attempt_write(): ts.con.execute("DELETE FROM appointments WHERE status='no-show'") expect_refused("Direct write on the read-only connection", attempt_write) # RESULT: all guardrails held โ€” no free SQL, no writes, no data dumps.

โš™๏ธ Why This Design

Key Decisions

Why SQLite