fix: respect page_size, slim list_clients output, add OAuth scope=all
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 <noreply@anthropic.com>
This commit is contained in:
parent
57c191ee1f
commit
f079a6aea8
@ -41,6 +41,7 @@ export class HaloClient {
|
|||||||
grant_type: "client_credentials",
|
grant_type: "client_credentials",
|
||||||
client_id: this.config.clientId,
|
client_id: this.config.clientId,
|
||||||
client_secret: this.config.clientSecret,
|
client_secret: this.config.clientSecret,
|
||||||
|
scope: "all",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.config.tenant) {
|
if (this.config.tenant) {
|
||||||
|
|||||||
44
src/tools.ts
44
src/tools.ts
@ -1,5 +1,24 @@
|
|||||||
import { HaloClient } from "./halo-client";
|
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 ────────────────────────────────────────────────────────
|
// ─── Tool Definitions ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export const TOOL_DEFINITIONS = [
|
export const TOOL_DEFINITIONS = [
|
||||||
@ -40,7 +59,7 @@ export const TOOL_DEFINITIONS = [
|
|||||||
// ── Clients ──
|
// ── Clients ──
|
||||||
{
|
{
|
||||||
name: "list_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: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
type: "object" as const,
|
||||||
properties: {
|
properties: {
|
||||||
@ -49,6 +68,7 @@ export const TOOL_DEFINITIONS = [
|
|||||||
search: { type: "string", description: "Search by client name" },
|
search: { type: "string", description: "Search by client name" },
|
||||||
toplevel_id: { type: "number", description: "Filter by top-level/parent client ID" },
|
toplevel_id: { type: "number", description: "Filter by top-level/parent client ID" },
|
||||||
active_only: { type: "boolean", default: true },
|
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 ──
|
// ── Tickets ──
|
||||||
case "list_tickets": {
|
case "list_tickets": {
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
|
pageinate: true,
|
||||||
page_no: args.page || 1,
|
page_no: args.page || 1,
|
||||||
page_size: args.page_size || 50,
|
page_size: args.page_size || 50,
|
||||||
order: "id_desc",
|
order: "id_desc",
|
||||||
@ -305,13 +326,22 @@ export async function handleTool(
|
|||||||
// ── Clients ──
|
// ── Clients ──
|
||||||
case "list_clients": {
|
case "list_clients": {
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
|
pageinate: true,
|
||||||
page_no: args.page || 1,
|
page_no: args.page || 1,
|
||||||
page_size: args.page_size || 50,
|
page_size: args.page_size || 50,
|
||||||
};
|
};
|
||||||
if (args.search) params.search = args.search;
|
if (args.search) params.search = args.search;
|
||||||
if (args.toplevel_id) params.toplevel_id = args.toplevel_id;
|
if (args.toplevel_id) params.toplevel_id = args.toplevel_id;
|
||||||
if (args.active_only !== false) params.inactive = false;
|
if (args.active_only !== false) params.includeinactive = false;
|
||||||
return client.get("/Client", params);
|
const result = await client.get<any>("/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": {
|
case "get_client": {
|
||||||
@ -330,6 +360,7 @@ export async function handleTool(
|
|||||||
// ── Contracts ──
|
// ── Contracts ──
|
||||||
case "list_contracts": {
|
case "list_contracts": {
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
|
pageinate: true,
|
||||||
page_no: args.page || 1,
|
page_no: args.page || 1,
|
||||||
page_size: args.page_size || 50,
|
page_size: args.page_size || 50,
|
||||||
};
|
};
|
||||||
@ -346,6 +377,7 @@ export async function handleTool(
|
|||||||
// ── Invoices (non-recurring) ──
|
// ── Invoices (non-recurring) ──
|
||||||
case "list_invoices": {
|
case "list_invoices": {
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
|
pageinate: true,
|
||||||
page_no: args.page || 1,
|
page_no: args.page || 1,
|
||||||
page_size: args.page_size || 50,
|
page_size: args.page_size || 50,
|
||||||
};
|
};
|
||||||
@ -363,6 +395,7 @@ export async function handleTool(
|
|||||||
// ── Recurring Invoices ──
|
// ── Recurring Invoices ──
|
||||||
case "list_recurring_invoices": {
|
case "list_recurring_invoices": {
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
|
pageinate: true,
|
||||||
page_no: args.page || 1,
|
page_no: args.page || 1,
|
||||||
page_size: args.page_size || 50,
|
page_size: args.page_size || 50,
|
||||||
};
|
};
|
||||||
@ -378,6 +411,7 @@ export async function handleTool(
|
|||||||
// ── Projects ──
|
// ── Projects ──
|
||||||
case "list_projects": {
|
case "list_projects": {
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
|
pageinate: true,
|
||||||
page_no: args.page || 1,
|
page_no: args.page || 1,
|
||||||
page_size: args.page_size || 50,
|
page_size: args.page_size || 50,
|
||||||
};
|
};
|
||||||
@ -394,6 +428,7 @@ export async function handleTool(
|
|||||||
// ── Actions / Time Entries ──
|
// ── Actions / Time Entries ──
|
||||||
case "list_actions": {
|
case "list_actions": {
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
|
pageinate: true,
|
||||||
page_no: args.page || 1,
|
page_no: args.page || 1,
|
||||||
page_size: args.page_size || 50,
|
page_size: args.page_size || 50,
|
||||||
};
|
};
|
||||||
@ -409,6 +444,7 @@ export async function handleTool(
|
|||||||
// ── Assets ──
|
// ── Assets ──
|
||||||
case "list_assets": {
|
case "list_assets": {
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
|
pageinate: true,
|
||||||
page_no: args.page || 1,
|
page_no: args.page || 1,
|
||||||
page_size: args.page_size || 50,
|
page_size: args.page_size || 50,
|
||||||
};
|
};
|
||||||
@ -457,6 +493,7 @@ export async function handleTool(
|
|||||||
// ── Agents ──
|
// ── Agents ──
|
||||||
case "list_agents": {
|
case "list_agents": {
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
|
pageinate: true,
|
||||||
page_no: args.page || 1,
|
page_no: args.page || 1,
|
||||||
page_size: args.page_size || 50,
|
page_size: args.page_size || 50,
|
||||||
};
|
};
|
||||||
@ -468,6 +505,7 @@ export async function handleTool(
|
|||||||
// ── Reports ──
|
// ── Reports ──
|
||||||
case "list_reports": {
|
case "list_reports": {
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
|
pageinate: true,
|
||||||
page_no: args.page || 1,
|
page_no: args.page || 1,
|
||||||
page_size: args.page_size || 50,
|
page_size: args.page_size || 50,
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user