Note (2026-04-24): After this document was written,
legal_entitywas renamed totenantand the oldtenantwas renamed toorganization. Read references to these terms with the pre-rename meaning.
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Two MCP tools for querying master data (legal entities + GL accounts / cost centers / business partners), used by agents during the Data Discovery Protocol.
Architecture: Single new file src/com/getorcha/link/mcp/tools/master_data.clj with both tools registered via tools/register-tool!. Legal entity resolution is a shared private function. JSONB array filtering and pagination happens server-side via jsonb_array_elements. Tests in test/com/getorcha/link/mcp_test.clj.
Tech Stack: Clojure, HoneySQL, next-jdbc, cheshire, embedded PostgreSQL for tests
orcha-master-data-legal-entities tool — test + implementationFiles:
src/com/getorcha/link/mcp/tools/master_data.cljsrc/com/getorcha/link/mcp/tools.clj:153-159 (add require to init-tools!)test/com/getorcha/link/mcp_test.cljStep 1: Write the failing test
Add to test/com/getorcha/link/mcp_test.clj:
;; orcha-master-data-legal-entities Tool Tests
;; -----------------------------------------------------------------------------
(deftest test-tool-master-data-legal-entities
(testing "orcha-master-data-legal-entities returns accessible legal entities"
(mcp.tools/init-tools!)
(let [{:keys [identity legal-entity]} (create-test-identity-with-legal-entity! fixtures/*db*)
token (get-test-access-token fixtures/*db* identity)
response (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-master-data-legal-entities"
:arguments {}}
:id 1}
token)]
(is (= 200 (:status response)))
(let [content (get-in response [:body :result :content 0 :text])
result (json/parse-string content true)]
(is (= 1 (count (:legal_entities result))))
(is (= (str (:legal-entity/id legal-entity))
(get-in result [:legal_entities 0 :id])))
(is (= (:legal-entity/name legal-entity)
(get-in result [:legal_entities 0 :name])))))))
(deftest test-tool-master-data-legal-entities-search
(testing "orcha-master-data-legal-entities filters by name"
(mcp.tools/init-tools!)
(let [{:keys [identity legal-entity]} (create-test-identity-with-legal-entity! fixtures/*db*)
le-name (:legal-entity/name legal-entity)
token (get-test-access-token fixtures/*db* identity)
;; Search with a substring that matches
match (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-master-data-legal-entities"
:arguments {"search" (subs le-name 0 10)}}
:id 1}
token)
;; Search with something that doesn't match
no-match (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-master-data-legal-entities"
:arguments {"search" "zzz-nonexistent-zzz"}}
:id 2}
token)]
(let [result1 (json/parse-string (get-in match [:body :result :content 0 :text]) true)
result2 (json/parse-string (get-in no-match [:body :result :content 0 :text]) true)]
(is (= 1 (count (:legal_entities result1))))
(is (= 0 (count (:legal_entities result2))))))))
Step 2: Run test to verify it fails
Run: clj -X:test:silent :nses '[com.getorcha.link.mcp-test]' :vars '[test-tool-master-data-legal-entities test-tool-master-data-legal-entities-search]'
Expected: FAIL — tool not found
Step 3: Write the implementation
Create src/com/getorcha/link/mcp/tools/master_data.clj:
(ns com.getorcha.link.mcp.tools.master-data
"MCP tools for querying master data.
- orcha-master-data-legal-entities: Lists legal entities accessible to the identity
- orcha-data-master-data: Queries GL accounts, cost centers, or business partners"
(:require [cheshire.core :as json]
[com.getorcha.db.sql :as db.sql]
[com.getorcha.link.mcp.tools :as tools]))
(defn ^:private handle-legal-entities
"Handler for orcha-master-data-legal-entities tool."
[args {:keys [db-pool legal-entity-ids] :as _context}]
(let [search (:search args)
where (cond-> [:in :id [:lift (vec legal-entity-ids)]]
search (vector :and [:like [:lower :name] (str "%" (clojure.string/lower-case search) "%")]))
results (db.sql/execute! db-pool {:select [:id :name]
:from [:legal-entity]
:where where
:order-by [[:name :asc]]})]
{:content [{:type "text"
:text (json/generate-string
{:legal_entities (mapv (fn [{:legal-entity/keys [id name]}]
{:id (str id)
:name name})
results)})}]}))
(tools/register-tool!
{:name "orcha-master-data-legal-entities"
:description "List legal entities (companies) accessible to your organization. Use this to discover available legal entity IDs before querying master data."
:inputSchema {:type "object"
:properties {"search" {:type "string"
:description "Filter by name (case-insensitive partial match)"}}
:required []}
:handler handle-legal-entities
:scope "mcp:read"})
Note: Use [:like [:lower :name] ...] instead of ILIKE because HoneySQL doesn't have a built-in :ilike operator. The [:lower ...] + [:like ...] combination achieves the same result.
Add to init-tools! in src/com/getorcha/link/mcp/tools.clj:
(require 'com.getorcha.link.mcp.tools.master-data)
Step 4: Run test to verify it passes
Run: clj -X:test:silent :nses '[com.getorcha.link.mcp-test]' :vars '[test-tool-master-data-legal-entities test-tool-master-data-legal-entities-search]'
Expected: PASS
Step 5: Commit
git add src/com/getorcha/link/mcp/tools/master_data.clj src/com/getorcha/link/mcp/tools.clj test/com/getorcha/link/mcp_test.clj
git commit -m "feat: add orcha-master-data-legal-entities MCP tool"
orcha-data-master-data — legal entity resolution + GL accountsFiles:
src/com/getorcha/link/mcp/tools/master_data.cljtest/com/getorcha/link/mcp_test.cljStep 1: Write the failing tests
Add to test/com/getorcha/link/mcp_test.clj:
;; Helper: insert GL accounts dataset
(defn ^:private create-test-gl-accounts!
"Creates an active GL accounts dataset for a legal entity."
[db legal-entity-id accounts]
(db.sql/execute-one!
db
{:insert-into :gl-accounts-dataset
:values [{:legal-entity-id legal-entity-id
:data [:cast (json/generate-string accounts) :jsonb]
:is-active true}]}))
;; orcha-data-master-data Tool Tests
;; -----------------------------------------------------------------------------
(deftest test-tool-master-data-gl-accounts
(testing "orcha-data-master-data returns GL accounts"
(mcp.tools/init-tools!)
(let [{:keys [identity legal-entity]} (create-test-identity-with-legal-entity! fixtures/*db*)
le-id (:legal-entity/id legal-entity)
accounts [{:number "4200" :name "Erlöse" :balance-position "GuV"}
{:number "6300" :name "Büromaterial" :balance-position "GuV"}
{:number "1200" :name "Bank" :balance-position "Aktiva"}]
_ (create-test-gl-accounts! fixtures/*db* le-id accounts)
token (get-test-access-token fixtures/*db* identity)
response (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-data-master-data"
:arguments {"type" "gl-accounts"}}
:id 1}
token)]
(is (= 200 (:status response)))
(let [content (get-in response [:body :result :content 0 :text])
result (json/parse-string content true)]
(is (= "gl-accounts" (:type result)))
(is (= (str le-id) (:legal_entity_id result)))
(is (= 3 (:total_count result)))
(is (= 3 (count (:items result))))))))
(deftest test-tool-master-data-gl-accounts-search
(testing "orcha-data-master-data filters GL accounts by search term"
(mcp.tools/init-tools!)
(let [{:keys [identity legal-entity]} (create-test-identity-with-legal-entity! fixtures/*db*)
le-id (:legal-entity/id legal-entity)
accounts [{:number "4200" :name "Erlöse" :balance-position "GuV"}
{:number "6300" :name "Büromaterial" :balance-position "GuV"}
{:number "1200" :name "Bank" :balance-position "Aktiva"}]
_ (create-test-gl-accounts! fixtures/*db* le-id accounts)
token (get-test-access-token fixtures/*db* identity)
response (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-data-master-data"
:arguments {"type" "gl-accounts"
"search" "4200"}}
:id 1}
token)]
(is (= 200 (:status response)))
(let [content (get-in response [:body :result :content 0 :text])
result (json/parse-string content true)]
(is (= 1 (:total_count result)))
(is (= "4200" (get-in result [:items 0 :number])))))))
(deftest test-tool-master-data-gl-accounts-pagination
(testing "orcha-data-master-data paginates GL accounts"
(mcp.tools/init-tools!)
(let [{:keys [identity legal-entity]} (create-test-identity-with-legal-entity! fixtures/*db*)
le-id (:legal-entity/id legal-entity)
accounts (mapv (fn [i] {:number (str (+ 1000 i)) :name (str "Account " i) :balance-position "GuV"})
(range 5))
_ (create-test-gl-accounts! fixtures/*db* le-id accounts)
token (get-test-access-token fixtures/*db* identity)
response (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-data-master-data"
:arguments {"type" "gl-accounts"
"per_page" 2
"page" 2}}
:id 1}
token)]
(is (= 200 (:status response)))
(let [content (get-in response [:body :result :content 0 :text])
result (json/parse-string content true)]
(is (= 5 (:total_count result)))
(is (= 2 (:per_page result)))
(is (= 2 (:page result)))
(is (= 2 (count (:items result))))))))
(deftest test-tool-master-data-auto-select-legal-entity
(testing "orcha-data-master-data auto-selects when identity has one LE"
(mcp.tools/init-tools!)
(let [{:keys [identity legal-entity]} (create-test-identity-with-legal-entity! fixtures/*db*)
le-id (:legal-entity/id legal-entity)
_ (create-test-gl-accounts! fixtures/*db* le-id [{:number "1000" :name "Test" :balance-position "GuV"}])
token (get-test-access-token fixtures/*db* identity)
;; Call without legal_entity_id — should auto-select
response (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-data-master-data"
:arguments {"type" "gl-accounts"}}
:id 1}
token)]
(is (= 200 (:status response)))
(let [content (get-in response [:body :result :content 0 :text])
result (json/parse-string content true)]
(is (= (str le-id) (:legal_entity_id result)))
(is (= 1 (:total_count result)))))))
(deftest test-tool-master-data-unauthorized-legal-entity
(testing "orcha-data-master-data rejects unauthorized legal entity ID"
(mcp.tools/init-tools!)
(let [{:keys [identity]} (create-test-identity-with-legal-entity! fixtures/*db*)
token (get-test-access-token fixtures/*db* identity)
response (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-data-master-data"
:arguments {"type" "gl-accounts"
"legal_entity_id" "00000000-0000-0000-0000-000000000000"}}
:id 1}
token)]
(is (= 200 (:status response)))
(let [result (get-in response [:body :result])]
(is (:isError result))
(is (str/includes? (get-in result [:content 0 :text]) "not authorized"))))))
(deftest test-tool-master-data-no-dataset
(testing "orcha-data-master-data returns empty when no active dataset"
(mcp.tools/init-tools!)
(let [{:keys [identity]} (create-test-identity-with-legal-entity! fixtures/*db*)
token (get-test-access-token fixtures/*db* identity)
response (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-data-master-data"
:arguments {"type" "gl-accounts"}}
:id 1}
token)]
(is (= 200 (:status response)))
(let [content (get-in response [:body :result :content 0 :text])
result (json/parse-string content true)]
(is (= 0 (:total_count result)))
(is (empty? (:items result)))))))
Step 2: Run tests to verify they fail
Run: clj -X:test:silent :nses '[com.getorcha.link.mcp-test]' :vars '[test-tool-master-data-gl-accounts test-tool-master-data-gl-accounts-search test-tool-master-data-gl-accounts-pagination test-tool-master-data-auto-select-legal-entity test-tool-master-data-unauthorized-legal-entity test-tool-master-data-no-dataset]'
Expected: FAIL — tool not found
Step 3: Write the implementation
Add to src/com/getorcha/link/mcp/tools/master_data.clj:
(defn ^:private resolve-legal-entity-id
"Resolves the legal entity ID from args and context.
- Provided + authorized → returns the UUID
- Provided + not authorized → returns error map
- Omitted + 1 LE in context → auto-selects
- Omitted + multiple LEs → returns error map"
[args {:keys [legal-entity-ids] :as _context}]
(if-let [le-id-str (:legal_entity_id args)]
(let [le-id (parse-uuid le-id-str)]
(if (contains? (set legal-entity-ids) le-id)
{:ok le-id}
{:error "You are not authorized to access this legal entity."}))
(if (= 1 (count legal-entity-ids))
{:ok (first legal-entity-ids)}
{:error "Multiple legal entities available. Use orcha-master-data-legal-entities to list them, then specify legal_entity_id."})))
(defn ^:private error-response
"Returns an MCP error response."
[message]
{:isError true
:content [{:type "text" :text message}]})
(def ^:private type-config
"Configuration for each master data type.
- :table — the dataset table
- :active-where — condition for active datasets
- :search-fields — JSONB keys to search (nil = search all string values)"
{"gl-accounts" {:table :gl-accounts-dataset
:active-where [:= :is-active true]
:search-fields ["number" "name"]}
"cost-centers" {:table :cost-center-dataset
:active-where [:is-not :position nil]
:search-fields nil}
"business-partners" {:table :business-partner-dataset
:active-where [:= :is-active true]
:search-fields ["account-number" "name" "vat-id" "iban"]}})
(defn ^:private build-search-condition
"Builds a WHERE clause for JSONB search.
When search-fields is nil (cost centers), searches across all values
in the JSONB object using jsonb_each_text."
[search search-fields]
(if search-fields
(into [:or] (map (fn [field]
[:like
[:lower [:raw (str "elem->>'" field "'")]]
(str "%" (clojure.string/lower-case search) "%")])
search-fields))
[:raw (str "EXISTS (SELECT 1 FROM jsonb_each_text(elem) kv "
"WHERE lower(kv.value) LIKE '%" (clojure.string/lower-case search) "%')")]))
(defn ^:private query-master-data
"Queries a master data dataset with JSONB unnesting, optional search, and pagination."
[db-pool legal-entity-id {:keys [table active-where search-fields]} search page per-page]
(let [;; Base: get the data column from the active dataset
;; We use a CTE to unnest the JSONB array, then filter/paginate
search-clause (when search
(build-search-condition search search-fields))
base-where (cond-> [:and
[:= :legal-entity-id legal-entity-id]
active-where]
search-clause identity)
;; Count query: total matching rows
count-sql {:select [[[:raw "count(*)"] :total]]
:from [[[:raw (str (name table) ", jsonb_array_elements(data) elem")]]]
:where (if search-clause
[:and base-where search-clause]
base-where)}
total (or (:total (db.sql/execute-one! db-pool count-sql)) 0)
;; Data query: paginated results
data-sql {:select [[:raw "elem"]]
:from [[[:raw (str (name table) ", jsonb_array_elements(data) elem")]]]
:where (if search-clause
[:and base-where search-clause]
base-where)
:limit per-page
:offset (* (dec page) per-page)}
rows (db.sql/execute! db-pool data-sql)
;; For cost centers, also fetch headers
headers (when (= table :cost-center-dataset)
(:cost-center-dataset/headers
(db.sql/execute-one! db-pool {:select [:headers]
:from [table]
:where [:and
[:= :legal-entity-id legal-entity-id]
active-where]})))]
{:total total
:items (mapv :elem rows)
:headers headers}))
(defn ^:private handle-master-data
"Handler for orcha-data-master-data tool."
[args {:keys [db-pool] :as context}]
(let [resolution (resolve-legal-entity-id args context)]
(if-let [error (:error resolution)]
(error-response error)
(let [le-id (:ok resolution)
type-str (:type args)
config (get type-config type-str)
page (or (:page args) 1)
per-page (min (or (:per_page args) 100) 500)
search (:search args)
result (query-master-data db-pool le-id config search page per-page)]
{:content [{:type "text"
:text (json/generate-string
(cond-> {:type type-str
:legal_entity_id (str le-id)
:total_count (:total result)
:page page
:per_page per-page
:items (:items result)}
(:headers result) (assoc :headers (:headers result))))}]}))))
(tools/register-tool!
{:name "orcha-data-master-data"
:description "Query master data for a legal entity. Returns GL accounts (chart of accounts), cost centers, or business partners (creditors/debtors). Use for cross-referencing financial data against known entities."
:inputSchema {:type "object"
:properties {"legal_entity_id" {:type "string"
:description "Legal entity UUID. Omit to auto-select if you have access to exactly one legal entity."}
"type" {:type "string"
:description "Type of master data to query"
:enum ["gl-accounts" "cost-centers" "business-partners"]}
"search" {:type "string"
:description "Filter by name or number (case-insensitive partial match). For GL accounts: searches number and name. For cost centers: searches all fields. For business partners: searches account number, name, VAT ID, and IBAN."}
"page" {:type "integer"
:description "Page number (1-indexed)"
:minimum 1
:default 1}
"per_page" {:type "integer"
:description "Results per page (max 500)"
:minimum 1
:maximum 500
:default 100}}
:required ["type"]}
:handler handle-master-data
:scope "mcp:read"})
Note on the build-search-condition for cost centers: since cost center data has flexible headers, we use jsonb_each_text to search across all string values in each element. The search value is sanitized by lowercasing, but the SQL injection risk from clojure.string/lower-case being interpolated into the raw SQL string needs to be addressed. Use parameterized queries or escape the search string. Check if db.sql/execute! supports parameterized raw SQL — if not, escape single quotes in the search string.
Step 4: Run tests to verify they pass
Run: clj -X:test:silent :nses '[com.getorcha.link.mcp-test]' :vars '[test-tool-master-data-gl-accounts test-tool-master-data-gl-accounts-search test-tool-master-data-gl-accounts-pagination test-tool-master-data-auto-select-legal-entity test-tool-master-data-unauthorized-legal-entity test-tool-master-data-no-dataset]'
Expected: PASS
Step 5: Commit
git add src/com/getorcha/link/mcp/tools/master_data.clj test/com/getorcha/link/mcp_test.clj
git commit -m "feat: add orcha-data-master-data MCP tool with GL accounts support"
Files:
test/com/getorcha/link/mcp_test.cljThis task adds tests for the remaining two data types. The implementation from Task 2 already handles them via type-config — we just need to verify they work.
Step 1: Write the tests
Add to test/com/getorcha/link/mcp_test.clj:
;; Helpers for cost centers and business partners
(defn ^:private create-test-cost-centers!
"Creates an active cost center dataset for a legal entity."
[db legal-entity-id cost-centers headers]
(db.sql/execute-one!
db
{:insert-into :cost-center-dataset
:values [{:legal-entity-id legal-entity-id
:data [:cast (json/generate-string cost-centers) :jsonb]
:headers [:cast (json/generate-string headers) :jsonb]
:position 0}]}))
(defn ^:private create-test-business-partners!
"Creates an active business partner dataset for a legal entity."
[db legal-entity-id partners]
(db.sql/execute-one!
db
{:insert-into :business-partner-dataset
:values [{:legal-entity-id legal-entity-id
:data [:cast (json/generate-string partners) :jsonb]
:is-active true}]}))
(deftest test-tool-master-data-cost-centers
(testing "orcha-data-master-data returns cost centers with headers"
(mcp.tools/init-tools!)
(let [{:keys [identity legal-entity]} (create-test-identity-with-legal-entity! fixtures/*db*)
le-id (:legal-entity/id legal-entity)
centers [{"Number" "100" "Name" "Sales" "Employee" "John Doe"}
{"Number" "200" "Name" "IT" "Employee" "Jane Smith"}]
headers ["Number" "Name" "Employee"]
_ (create-test-cost-centers! fixtures/*db* le-id centers headers)
token (get-test-access-token fixtures/*db* identity)
response (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-data-master-data"
:arguments {"type" "cost-centers"}}
:id 1}
token)]
(is (= 200 (:status response)))
(let [content (get-in response [:body :result :content 0 :text])
result (json/parse-string content true)]
(is (= "cost-centers" (:type result)))
(is (= 2 (:total_count result)))
(is (= ["Number" "Name" "Employee"] (:headers result)))
(is (= 2 (count (:items result))))))))
(deftest test-tool-master-data-cost-centers-search
(testing "orcha-data-master-data searches across all cost center fields"
(mcp.tools/init-tools!)
(let [{:keys [identity legal-entity]} (create-test-identity-with-legal-entity! fixtures/*db*)
le-id (:legal-entity/id legal-entity)
centers [{"Number" "100" "Name" "Sales" "Employee" "John Doe"}
{"Number" "200" "Name" "IT" "Employee" "Jane Smith"}]
_ (create-test-cost-centers! fixtures/*db* le-id centers ["Number" "Name" "Employee"])
token (get-test-access-token fixtures/*db* identity)
;; Search by employee name
response (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-data-master-data"
:arguments {"type" "cost-centers"
"search" "Jane"}}
:id 1}
token)]
(is (= 200 (:status response)))
(let [content (get-in response [:body :result :content 0 :text])
result (json/parse-string content true)]
(is (= 1 (:total_count result)))
(is (= "200" (get-in result [:items 0 (keyword "Number")])))))))
(deftest test-tool-master-data-business-partners
(testing "orcha-data-master-data returns business partners"
(mcp.tools/init-tools!)
(let [{:keys [identity legal-entity]} (create-test-identity-with-legal-entity! fixtures/*db*)
le-id (:legal-entity/id legal-entity)
partners [{:account-number "70001" :name "Acme GmbH" :vat-id "DE123456789" :iban "DE89370400440532013000"}
{:account-number "70002" :name "Beta AG" :vat-id "DE987654321" :iban "DE27100777770209299700"}]
_ (create-test-business-partners! fixtures/*db* le-id partners)
token (get-test-access-token fixtures/*db* identity)
response (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-data-master-data"
:arguments {"type" "business-partners"}}
:id 1}
token)]
(is (= 200 (:status response)))
(let [content (get-in response [:body :result :content 0 :text])
result (json/parse-string content true)]
(is (= "business-partners" (:type result)))
(is (= 2 (:total_count result)))
(is (nil? (:headers result)))))))
(deftest test-tool-master-data-business-partners-search
(testing "orcha-data-master-data filters business partners by VAT ID"
(mcp.tools/init-tools!)
(let [{:keys [identity legal-entity]} (create-test-identity-with-legal-entity! fixtures/*db*)
le-id (:legal-entity/id legal-entity)
partners [{:account-number "70001" :name "Acme GmbH" :vat-id "DE123456789" :iban "DE89370400440532013000"}
{:account-number "70002" :name "Beta AG" :vat-id "DE987654321" :iban "DE27100777770209299700"}]
_ (create-test-business-partners! fixtures/*db* le-id partners)
token (get-test-access-token fixtures/*db* identity)
response (mcp-request {:jsonrpc "2.0"
:method "tools/call"
:params {:name "orcha-data-master-data"
:arguments {"type" "business-partners"
"search" "DE123"}}
:id 1}
token)]
(is (= 200 (:status response)))
(let [content (get-in response [:body :result :content 0 :text])
result (json/parse-string content true)]
(is (= 1 (:total_count result)))
(is (= "Acme GmbH" (get-in result [:items 0 :name])))))))
Step 2: Run tests to verify they pass
Run: clj -X:test:silent :nses '[com.getorcha.link.mcp-test]' :vars '[test-tool-master-data-cost-centers test-tool-master-data-cost-centers-search test-tool-master-data-business-partners test-tool-master-data-business-partners-search]'
Expected: PASS (implementation already supports these types)
If any fail, debug and fix the implementation.
Step 3: Commit
git add test/com/getorcha/link/mcp_test.clj
git commit -m "test: add cost center and business partner master data tests"
Files:
test/com/getorcha/link/mcp_test.cljStep 1: Update the test-tools-list test
The existing test-tools-list asserts (<= 4 (count tools)) and checks for specific tool names. Update to include the new tools:
In test-tools-list, change:
(is (<= 4 (count tools))) → (is (<= 6 (count tools)))(is (contains? tool-names "orcha-master-data-legal-entities"))(is (contains? tool-names "orcha-data-master-data"))Step 2: Run lint
Run: clj-kondo --lint src/com/getorcha/link/mcp/tools/master_data.clj test/com/getorcha/link/mcp_test.clj
Fix any issues.
Step 3: Run all MCP tests
Run: clj -X:test:silent :nses '[com.getorcha.link.mcp-test]'
Expected: ALL PASS
Step 4: Commit
git add test/com/getorcha/link/mcp_test.clj src/com/getorcha/link/mcp/tools/master_data.clj
git commit -m "chore: update tools list test for master data tools + lint fixes"
SQL injection in build-search-condition: The raw SQL interpolation for cost center search (jsonb_each_text) is a potential injection vector. The implementer must either:
' with '')This applies to all [:raw ...] SQL that includes user input. Check how existing code in accounts_payable.clj:768 handles this — it may already have a pattern.
HoneySQL ILIKE: Verify if HoneySQL supports :ilike directly. If it does, use it instead of the [:lower ...] + [:like ...] workaround. Check the HoneySQL source at /home/volrath/code/oss/honeysql/.
clojure.string require: The implementation uses clojure.string/lower-case — ensure it's in the :require vector of the namespace declaration.