Add mcp-gateway/wave-mcp/src/tools/businesses.ts
This commit is contained in:
parent
9beb92cc51
commit
41bfe60ef2
1 changed files with 191 additions and 0 deletions
191
mcp-gateway/wave-mcp/src/tools/businesses.ts
Normal file
191
mcp-gateway/wave-mcp/src/tools/businesses.ts
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { WaveClient } from "../services/wave-client.js";
|
||||||
|
import { WaveBusiness } from "../types.js";
|
||||||
|
|
||||||
|
const ResponseFormat = { MARKDOWN: "markdown", JSON: "json" } as const;
|
||||||
|
type ResponseFormat = (typeof ResponseFormat)[keyof typeof ResponseFormat];
|
||||||
|
|
||||||
|
export function registerBusinessTools(
|
||||||
|
server: McpServer,
|
||||||
|
client: WaveClient
|
||||||
|
): void {
|
||||||
|
// ── wave_list_businesses ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
server.registerTool(
|
||||||
|
"wave_list_businesses",
|
||||||
|
{
|
||||||
|
title: "List Wave Businesses",
|
||||||
|
description: `List all Wave businesses accessible to the authenticated user.
|
||||||
|
|
||||||
|
Returns all business accounts the API token has access to. Most other Wave operations require a businessId, so call this first to discover available businesses.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- response_format ('markdown' | 'json'): Output format (default: 'markdown')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of businesses with id, name, currency, address, and subscription details.`,
|
||||||
|
inputSchema: z.object({
|
||||||
|
response_format: z
|
||||||
|
.enum(["markdown", "json"])
|
||||||
|
.default("markdown")
|
||||||
|
.describe("Output format: 'markdown' for human-readable or 'json' for machine-readable"),
|
||||||
|
}),
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
destructiveHint: false,
|
||||||
|
idempotentHint: true,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async ({ response_format }) => {
|
||||||
|
try {
|
||||||
|
const data = await client.query<{ businesses: { edges: Array<{ node: WaveBusiness }> } }>(`
|
||||||
|
query {
|
||||||
|
businesses(page: 1, pageSize: 100) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
isPersonal
|
||||||
|
organizationalType
|
||||||
|
isClassicAccounting
|
||||||
|
isClassicInvoicing
|
||||||
|
currency { code symbol name }
|
||||||
|
address {
|
||||||
|
addressLine1
|
||||||
|
addressLine2
|
||||||
|
city
|
||||||
|
province { name code }
|
||||||
|
country { name code }
|
||||||
|
postalCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const businesses = data.businesses.edges.map((e) => e.node);
|
||||||
|
|
||||||
|
if (!businesses.length) {
|
||||||
|
return { content: [{ type: "text", text: "No businesses found for this account." }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
let text: string;
|
||||||
|
if (response_format === "json") {
|
||||||
|
text = JSON.stringify({ count: businesses.length, businesses }, null, 2);
|
||||||
|
} else {
|
||||||
|
const lines = [`# Wave Businesses (${businesses.length})`, ""];
|
||||||
|
for (const b of businesses) {
|
||||||
|
lines.push(`## ${b.name}`);
|
||||||
|
lines.push(`- **ID**: ${b.id}`);
|
||||||
|
lines.push(`- **Currency**: ${b.currency.code} (${b.currency.name})`);
|
||||||
|
if (b.address?.city) {
|
||||||
|
const loc = [b.address.city, b.address.province?.code, b.address.country?.code]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(", ");
|
||||||
|
lines.push(`- **Location**: ${loc}`);
|
||||||
|
}
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
text = lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: WaveClient.truncate(text) }],
|
||||||
|
structuredContent: { count: businesses.length, businesses },
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── wave_get_business ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
server.registerTool(
|
||||||
|
"wave_get_business",
|
||||||
|
{
|
||||||
|
title: "Get Wave Business",
|
||||||
|
description: `Get details for a specific Wave business by ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- businessId (string): The Wave business ID (obtain from wave_list_businesses)
|
||||||
|
- response_format ('markdown' | 'json'): Output format (default: 'markdown')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Full business details including name, currency, address, and account settings.`,
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessId: z.string().describe("Wave business ID"),
|
||||||
|
response_format: z
|
||||||
|
.enum(["markdown", "json"])
|
||||||
|
.default("markdown")
|
||||||
|
.describe("Output format"),
|
||||||
|
}),
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
destructiveHint: false,
|
||||||
|
idempotentHint: true,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async ({ businessId, response_format }) => {
|
||||||
|
try {
|
||||||
|
const data = await client.query<{ business: WaveBusiness }>(`
|
||||||
|
query($businessId: ID!) {
|
||||||
|
business(id: $businessId) {
|
||||||
|
id name isPersonal organizationalType
|
||||||
|
isClassicAccounting isClassicInvoicing
|
||||||
|
currency { code symbol name }
|
||||||
|
address {
|
||||||
|
addressLine1 addressLine2 city postalCode
|
||||||
|
province { name code }
|
||||||
|
country { name code }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, { businessId });
|
||||||
|
|
||||||
|
const b = data.business;
|
||||||
|
if (!b) {
|
||||||
|
return { content: [{ type: "text", text: `No business found with ID: ${businessId}` }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
let text: string;
|
||||||
|
if (response_format === "json") {
|
||||||
|
text = JSON.stringify(b, null, 2);
|
||||||
|
} else {
|
||||||
|
const lines = [
|
||||||
|
`# Business: ${b.name}`,
|
||||||
|
"",
|
||||||
|
`- **ID**: ${b.id}`,
|
||||||
|
`- **Currency**: ${b.currency.code} (${b.currency.name})`,
|
||||||
|
`- **Type**: ${b.isPersonal ? "Personal" : (b.organizationalType ?? "Business")}`,
|
||||||
|
];
|
||||||
|
if (b.address) {
|
||||||
|
const addr = b.address;
|
||||||
|
if (addr.addressLine1) lines.push(`- **Address**: ${addr.addressLine1}`);
|
||||||
|
if (addr.addressLine2) lines.push(` ${addr.addressLine2}`);
|
||||||
|
const cityLine = [addr.city, addr.province?.code, addr.postalCode, addr.country?.code]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(", ");
|
||||||
|
if (cityLine) lines.push(` ${cityLine}`);
|
||||||
|
}
|
||||||
|
text = lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text }],
|
||||||
|
structuredContent: b,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue