From f079a6aea8c31896127218e2ff2da4a22d3e897c Mon Sep 17 00:00:00 2001 From: Piers Macrae Cockram Date: Mon, 27 Apr 2026 02:28:16 +0000 Subject: [PATCH] fix: respect page_size, slim list_clients output, add OAuth scope=all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes discovered during initial deployment against interakt.halopsa.com: - halo-client: include scope=all in the client_credentials token request. Without it Halo issues a token with no read scopes — every /api/* call fails authorization despite a successful exchange. Manifested as the original "permissions issue". - tools: add pageinate=true to every list_* call. Halo ignores page_size unless pagination is explicitly enabled, so requesting page_size=5 was returning the server-side default of 50. - tools: project list_clients to a summary shape (id, name, top-level, status, customer_relationship names). Full Halo client records are ~18 KB each; a 5-row page was 89 KB, exceeding tool-result budgets. Pass full=true for the original payload; get_client still returns full detail. Also: switch list_clients filter from inactive=false to the canonical includeinactive=false, and ship start.sh (the wrapper documented in CLAUDE.md Step 4) so downstream setups don't have to author it. Verified end-to-end against the live instance: GET /Client with pageinate=true&page_size=3 now returns exactly 3 records and record_count=122 (total active clients). Co-Authored-By: Claude Opus 4.7 --- src/halo-client.ts | 1 + src/tools.ts | 44 +++++++++++++++++++++++++++++++++++++++++--- start.sh | 5 +++++ 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100755 start.sh diff --git a/src/halo-client.ts b/src/halo-client.ts index 6015eca..253eaff 100644 --- a/src/halo-client.ts +++ b/src/halo-client.ts @@ -41,6 +41,7 @@ export class HaloClient { grant_type: "client_credentials", client_id: this.config.clientId, client_secret: this.config.clientSecret, + scope: "all", }); if (this.config.tenant) { diff --git a/src/tools.ts b/src/tools.ts index dde23bd..77e6f93 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -1,5 +1,24 @@ import { HaloClient } from "./halo-client"; +// ─── Helpers ───────────────────────────────────────────────────────────────── + +function summarizeClient(c: any) { + return { + id: c.id, + name: c.name, + toplevel_id: c.toplevel_id, + toplevel_name: c.toplevel_name, + inactive: c.inactive, + is_vip: c.is_vip, + is_account: c.is_account, + accountsid: c.accountsid, + customertype: c.customertype, + customer_relationship: Array.isArray(c.customer_relationship) + ? c.customer_relationship.map((r: any) => r.name).filter(Boolean) + : undefined, + }; +} + // ─── Tool Definitions ──────────────────────────────────────────────────────── export const TOOL_DEFINITIONS = [ @@ -40,7 +59,7 @@ export const TOOL_DEFINITIONS = [ // ── Clients ── { name: "list_clients", - description: "List clients/customers with optional search. Returns company name, ID, status, and key info.", + description: "List clients/customers with optional search. Returns a summary per client (id, name, top-level, status, customer relationships). For full detail use get_client.", inputSchema: { type: "object" as const, properties: { @@ -49,6 +68,7 @@ export const TOOL_DEFINITIONS = [ search: { type: "string", description: "Search by client name" }, toplevel_id: { type: "number", description: "Filter by top-level/parent client ID" }, active_only: { type: "boolean", default: true }, + full: { type: "boolean", description: "Return the full Halo client payload instead of a summary", default: false }, }, }, }, @@ -281,6 +301,7 @@ export async function handleTool( // ── Tickets ── case "list_tickets": { const params: Record = { + pageinate: true, page_no: args.page || 1, page_size: args.page_size || 50, order: "id_desc", @@ -305,13 +326,22 @@ export async function handleTool( // ── Clients ── case "list_clients": { const params: Record = { + pageinate: true, page_no: args.page || 1, page_size: args.page_size || 50, }; if (args.search) params.search = args.search; if (args.toplevel_id) params.toplevel_id = args.toplevel_id; - if (args.active_only !== false) params.inactive = false; - return client.get("/Client", params); + if (args.active_only !== false) params.includeinactive = false; + const result = await client.get("/Client", params); + if (args.full) return result; + const clients = Array.isArray(result?.clients) ? result.clients.map(summarizeClient) : []; + return { + page_no: result?.page_no, + page_size: result?.page_size, + record_count: result?.record_count, + clients, + }; } case "get_client": { @@ -330,6 +360,7 @@ export async function handleTool( // ── Contracts ── case "list_contracts": { const params: Record = { + pageinate: true, page_no: args.page || 1, page_size: args.page_size || 50, }; @@ -346,6 +377,7 @@ export async function handleTool( // ── Invoices (non-recurring) ── case "list_invoices": { const params: Record = { + pageinate: true, page_no: args.page || 1, page_size: args.page_size || 50, }; @@ -363,6 +395,7 @@ export async function handleTool( // ── Recurring Invoices ── case "list_recurring_invoices": { const params: Record = { + pageinate: true, page_no: args.page || 1, page_size: args.page_size || 50, }; @@ -378,6 +411,7 @@ export async function handleTool( // ── Projects ── case "list_projects": { const params: Record = { + pageinate: true, page_no: args.page || 1, page_size: args.page_size || 50, }; @@ -394,6 +428,7 @@ export async function handleTool( // ── Actions / Time Entries ── case "list_actions": { const params: Record = { + pageinate: true, page_no: args.page || 1, page_size: args.page_size || 50, }; @@ -409,6 +444,7 @@ export async function handleTool( // ── Assets ── case "list_assets": { const params: Record = { + pageinate: true, page_no: args.page || 1, page_size: args.page_size || 50, }; @@ -457,6 +493,7 @@ export async function handleTool( // ── Agents ── case "list_agents": { const params: Record = { + pageinate: true, page_no: args.page || 1, page_size: args.page_size || 50, }; @@ -468,6 +505,7 @@ export async function handleTool( // ── Reports ── case "list_reports": { const params: Record = { + pageinate: true, page_no: args.page || 1, page_size: args.page_size || 50, }; diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..2451e86 --- /dev/null +++ b/start.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -a +source "$(dirname "$0")/.env" +set +a +exec node "$(dirname "$0")/dist/index.js"