@integrity-labs/xero-broker 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +37 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20987,10 +20987,41 @@ var StdioServerTransport = class {
|
|
|
20987
20987
|
|
|
20988
20988
|
// src/turn-initiator-marker.ts
|
|
20989
20989
|
import { readFileSync } from "fs";
|
|
20990
|
+
import { dirname, join } from "path";
|
|
20990
20991
|
var TURN_INITIATOR_MAX_AGE_MS = 5 * 60 * 1e3;
|
|
20992
|
+
var TURN_INITIATOR_LEDGER_FILENAME = ".turn-initiator-ledger.json";
|
|
20993
|
+
function readTurnInitiatorFromLedger(file, maxAgeMs) {
|
|
20994
|
+
const ledgerFile = join(dirname(file), TURN_INITIATOR_LEDGER_FILENAME);
|
|
20995
|
+
let entries;
|
|
20996
|
+
try {
|
|
20997
|
+
const parsed = JSON.parse(readFileSync(ledgerFile, "utf8"));
|
|
20998
|
+
if (!parsed || parsed.v !== 1 || !Array.isArray(parsed.entries)) return "no-ledger";
|
|
20999
|
+
entries = parsed.entries;
|
|
21000
|
+
} catch {
|
|
21001
|
+
return "no-ledger";
|
|
21002
|
+
}
|
|
21003
|
+
const now = Date.now();
|
|
21004
|
+
const fresh = entries.filter(
|
|
21005
|
+
(e) => e && typeof e.channel === "string" && e.channel && typeof e.sender_id === "string" && e.sender_id && typeof e.ts === "number" && Number.isFinite(e.ts) && now - e.ts >= 0 && now - e.ts <= maxAgeMs
|
|
21006
|
+
);
|
|
21007
|
+
if (fresh.length === 1) {
|
|
21008
|
+
const e = fresh[0];
|
|
21009
|
+
return { channel: e.channel, sender_id: e.sender_id };
|
|
21010
|
+
}
|
|
21011
|
+
if (fresh.length >= 2) {
|
|
21012
|
+
process.stderr.write(
|
|
21013
|
+
`turn-initiator: ambiguous (${fresh.length} fresh senders), withholding initiator
|
|
21014
|
+
`
|
|
21015
|
+
);
|
|
21016
|
+
return null;
|
|
21017
|
+
}
|
|
21018
|
+
return "no-ledger";
|
|
21019
|
+
}
|
|
20991
21020
|
function readTurnInitiator(maxAgeMs = TURN_INITIATOR_MAX_AGE_MS) {
|
|
20992
21021
|
const file = process.env["AGT_TURN_INITIATOR_FILE"];
|
|
20993
21022
|
if (!file) return null;
|
|
21023
|
+
const fromLedger = readTurnInitiatorFromLedger(file, maxAgeMs);
|
|
21024
|
+
if (fromLedger !== "no-ledger") return fromLedger;
|
|
20994
21025
|
try {
|
|
20995
21026
|
const raw = readFileSync(file, "utf8");
|
|
20996
21027
|
const m = JSON.parse(raw);
|
|
@@ -21159,17 +21190,18 @@ var XERO_WRITE_VERBS = [
|
|
|
21159
21190
|
"xero.bill.approve",
|
|
21160
21191
|
"xero.invoice.void",
|
|
21161
21192
|
"xero.contact.merge",
|
|
21162
|
-
"xero.bank_transaction.delete"
|
|
21193
|
+
"xero.bank_transaction.delete",
|
|
21194
|
+
"xero.manual_journal.create"
|
|
21163
21195
|
];
|
|
21164
21196
|
var verbSchema = external_exports.enum(XERO_WRITE_VERBS).describe(
|
|
21165
|
-
"The Xero write operation to perform. One of: xero.payment.create (pay an invoice/bill \u2014 moves money), xero.bill.create (create a payable), xero.bill.approve (DRAFT/SUBMITTED \u2192 AUTHORISED), xero.invoice.void (irreversible), xero.contact.merge (irreversible), xero.bank_transaction.delete (irreversible)."
|
|
21197
|
+
"The Xero write operation to perform. One of: xero.payment.create (pay an invoice/bill \u2014 moves money), xero.bill.create (create a payable), xero.bill.approve (DRAFT/SUBMITTED \u2192 AUTHORISED), xero.invoice.void (irreversible), xero.contact.merge (irreversible), xero.bank_transaction.delete (irreversible), xero.manual_journal.create (post a multi-line GL entry \u2014 reclasses, accruals, corrections; debits must equal credits)."
|
|
21166
21198
|
);
|
|
21167
21199
|
var payloadSchema = external_exports.object({
|
|
21168
21200
|
tenant_id: external_exports.string().min(1).max(128).describe(
|
|
21169
21201
|
"Xero organisation id (the Xero-Tenant-Id header value) the write lands in. NOT the Augmented team. Required \u2014 a missing tenant risks writing to the wrong set of books."
|
|
21170
21202
|
)
|
|
21171
21203
|
}).passthrough().describe(
|
|
21172
|
-
"Verb-specific fields. xero.payment.create: { invoice_id, account_id, amount, currency?, counterparty? }. xero.bill.create: { contact_id, amount, currency?, counterparty?, line_items_preview }. xero.bill.approve: { invoice_id, amount?, currency?, counterparty? }. xero.invoice.void: { invoice_id, invoice_number?, amount?, counterparty? }. xero.contact.merge: { source_contact_id, target_contact_id, source_name?, target_name? }. xero.bank_transaction.delete: { bank_transaction_id, amount?, counterparty? }. Always include tenant_id."
|
|
21204
|
+
"Verb-specific fields. xero.payment.create: { invoice_id, account_id, amount, currency?, counterparty? }. xero.bill.create: { contact_id, amount, currency?, counterparty?, line_items_preview }. xero.bill.approve: { invoice_id, amount?, currency?, counterparty? }. xero.invoice.void: { invoice_id, invoice_number?, amount?, counterparty? }. xero.contact.merge: { source_contact_id, target_contact_id, source_name?, target_name? }. xero.bank_transaction.delete: { bank_transaction_id, amount?, counterparty? }. xero.manual_journal.create: { narration, journal_lines: [{ account_code, line_amount (positive=debit, negative=credit), description?, tax_type? }], date? (YYYY-MM-DD), status? (DRAFT|POSTED, default DRAFT), amount?, currency? } \u2014 lines must balance to zero. Always include tenant_id."
|
|
21173
21205
|
);
|
|
21174
21206
|
var reasonSchema = external_exports.string().min(1).max(2e3).describe(
|
|
21175
21207
|
"Why the agent needs to perform this write. Surfaced verbatim on the approval card for the human reviewer. Mandatory."
|
|
@@ -21207,7 +21239,7 @@ var awaitDecisionSchema = external_exports.object({
|
|
|
21207
21239
|
// package.json
|
|
21208
21240
|
var package_default = {
|
|
21209
21241
|
name: "@integrity-labs/xero-broker",
|
|
21210
|
-
version: "0.1.
|
|
21242
|
+
version: "0.1.4",
|
|
21211
21243
|
description: "Xero Broker \u2014 MCP server that routes money- and ledger-touching Xero writes through approval-core (HITL). Sibling to cloud-broker / channel-broker; the only write path for an agent once the vendor xero-mcp-server's write tools are stripped. ENG-4922.",
|
|
21212
21244
|
type: "module",
|
|
21213
21245
|
bin: {
|
|
@@ -21287,7 +21319,7 @@ server.tool(
|
|
|
21287
21319
|
);
|
|
21288
21320
|
server.tool(
|
|
21289
21321
|
"xero_request_approval",
|
|
21290
|
-
'Perform a Xero write (pay an invoice/bill, create or approve a bill, void an invoice, merge contacts, delete a bank transaction). This is the ONLY path you have to a Xero write \u2014 the vendor Xero tools are read-only on your host. The broker hard-denies (team denylist), auto-approves (rare, only if team policy auto-approves the verb tier), or routes to a human approver. On approval the broker executes the Xero call itself and returns the result. Returns { request_id, status, expires_at, denial_reason?, notification_status? }. status="auto_approve" or "active" means the write already happened (result_payload describes it). status="pending" means a human was paged via Slack \u2014 DO NOT poll; the broker pushes the resolution to you via direct-chat the moment it lands. status="denied" means the request was rejected (denial_reason explains). Pass { verb, payload, reason } and optionally ttl_seconds + source_context; always include payload.tenant_id. agent_id and run_id auto-fill from MCP env. Surface a brief, jargon-free acknowledgement to the user (e.g. "I need approval to pay that invoice \u2014 pinged an admin, will let you know") before returning control on a pending status. NEVER expose broker mechanics ("broker", "approval card", "request_id") in user-facing messages, and NEVER paste raw amounts into operator logs \u2014 the approval card already shows the amount to the human reviewer.',
|
|
21322
|
+
'Perform a Xero write (pay an invoice/bill, create or approve a bill, void an invoice, merge contacts, delete a bank transaction, post a manual journal). This is the ONLY path you have to a Xero write \u2014 the vendor Xero tools are read-only on your host. The broker hard-denies (team denylist), auto-approves (rare, only if team policy auto-approves the verb tier), or routes to a human approver. On approval the broker executes the Xero call itself and returns the result. Returns { request_id, status, expires_at, denial_reason?, notification_status? }. status="auto_approve" or "active" means the write already happened (result_payload describes it). status="pending" means a human was paged via Slack \u2014 DO NOT poll; the broker pushes the resolution to you via direct-chat the moment it lands. status="denied" means the request was rejected (denial_reason explains). Pass { verb, payload, reason } and optionally ttl_seconds + source_context; always include payload.tenant_id. agent_id and run_id auto-fill from MCP env. Surface a brief, jargon-free acknowledgement to the user (e.g. "I need approval to pay that invoice \u2014 pinged an admin, will let you know") before returning control on a pending status. NEVER expose broker mechanics ("broker", "approval card", "request_id") in user-facing messages, and NEVER paste raw amounts into operator logs \u2014 the approval card already shows the amount to the human reviewer.',
|
|
21291
21323
|
requestApprovalSchema.shape,
|
|
21292
21324
|
async (args) => {
|
|
21293
21325
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@integrity-labs/xero-broker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Xero Broker — MCP server that routes money- and ledger-touching Xero writes through approval-core (HITL). Sibling to cloud-broker / channel-broker; the only write path for an agent once the vendor xero-mcp-server's write tools are stripped. ENG-4922.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|