@procurementexpress.com/mcp 1.0.0
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/README.md +376 -0
- package/dist/api-client.d.ts +31 -0
- package/dist/api-client.js +108 -0
- package/dist/auth.d.ts +29 -0
- package/dist/auth.js +59 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +119 -0
- package/dist/tool-helpers.d.ts +34 -0
- package/dist/tool-helpers.js +26 -0
- package/dist/tools/approval-flows.d.ts +3 -0
- package/dist/tools/approval-flows.js +121 -0
- package/dist/tools/budgets.d.ts +3 -0
- package/dist/tools/budgets.js +73 -0
- package/dist/tools/comments.d.ts +3 -0
- package/dist/tools/comments.js +24 -0
- package/dist/tools/companies.d.ts +3 -0
- package/dist/tools/companies.js +67 -0
- package/dist/tools/departments.d.ts +3 -0
- package/dist/tools/departments.js +69 -0
- package/dist/tools/invoices.d.ts +3 -0
- package/dist/tools/invoices.js +132 -0
- package/dist/tools/payments.d.ts +3 -0
- package/dist/tools/payments.js +65 -0
- package/dist/tools/products.d.ts +3 -0
- package/dist/tools/products.js +57 -0
- package/dist/tools/purchase-orders.d.ts +3 -0
- package/dist/tools/purchase-orders.js +152 -0
- package/dist/tools/supplementary.d.ts +3 -0
- package/dist/tools/supplementary.js +70 -0
- package/dist/tools/suppliers.d.ts +3 -0
- package/dist/tools/suppliers.js +80 -0
- package/dist/tools/tax-rates.d.ts +3 -0
- package/dist/tools/tax-rates.js +44 -0
- package/dist/tools/users.d.ts +3 -0
- package/dist/tools/users.js +37 -0
- package/dist/tools/webhooks.d.ts +3 -0
- package/dist/tools/webhooks.js +61 -0
- package/dist/types.d.ts +732 -0
- package/dist/types.js +3 -0
- package/package.json +50 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { ApiClient } from "./api-client.js";
|
|
6
|
+
import { AuthManager } from "./auth.js";
|
|
7
|
+
import { jsonResponse, textResponse, withErrorHandling } from "./tool-helpers.js";
|
|
8
|
+
import { registerApprovalFlowTools } from "./tools/approval-flows.js";
|
|
9
|
+
import { registerBudgetTools } from "./tools/budgets.js";
|
|
10
|
+
import { registerCommentTools } from "./tools/comments.js";
|
|
11
|
+
import { registerCompanyTools } from "./tools/companies.js";
|
|
12
|
+
import { registerDepartmentTools } from "./tools/departments.js";
|
|
13
|
+
import { registerInvoiceTools } from "./tools/invoices.js";
|
|
14
|
+
import { registerPaymentTools } from "./tools/payments.js";
|
|
15
|
+
import { registerProductTools } from "./tools/products.js";
|
|
16
|
+
import { registerPurchaseOrderTools } from "./tools/purchase-orders.js";
|
|
17
|
+
import { registerSupplementaryTools } from "./tools/supplementary.js";
|
|
18
|
+
import { registerSupplierTools } from "./tools/suppliers.js";
|
|
19
|
+
import { registerTaxRateTools } from "./tools/tax-rates.js";
|
|
20
|
+
import { registerUserTools } from "./tools/users.js";
|
|
21
|
+
import { registerWebhookTools } from "./tools/webhooks.js";
|
|
22
|
+
// Create API client and auth manager
|
|
23
|
+
const apiClient = new ApiClient();
|
|
24
|
+
const authManager = new AuthManager(apiClient);
|
|
25
|
+
const isV1 = authManager.isV1();
|
|
26
|
+
// Create MCP server
|
|
27
|
+
const server = new McpServer({
|
|
28
|
+
name: "procurementexpress",
|
|
29
|
+
version: "1.0.0",
|
|
30
|
+
description: `MCP server for ProcurementExpress API (${apiClient.getApiVersion()}) - manage purchase orders, invoices, budgets, suppliers, and procurement workflows`,
|
|
31
|
+
});
|
|
32
|
+
// Register version-appropriate authentication tools
|
|
33
|
+
if (isV1) {
|
|
34
|
+
// V1: Token-based authentication
|
|
35
|
+
server.registerTool("authenticate", {
|
|
36
|
+
description: "Authenticate with ProcurementExpress V1 API using a static authentication token and company ID. " +
|
|
37
|
+
"Reads from PROCUREMENTEXPRESS_AUTH_TOKEN and PROCUREMENTEXPRESS_COMPANY_ID environment variables if not provided.",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
authentication_token: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Authentication token (reads from PROCUREMENTEXPRESS_AUTH_TOKEN env var if omitted)"),
|
|
43
|
+
company_id: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Company ID (reads from PROCUREMENTEXPRESS_COMPANY_ID env var if omitted)"),
|
|
47
|
+
},
|
|
48
|
+
}, withErrorHandling(async (args) => {
|
|
49
|
+
const token = args.authentication_token || process.env.PROCUREMENTEXPRESS_AUTH_TOKEN || "";
|
|
50
|
+
const companyId = args.company_id || process.env.PROCUREMENTEXPRESS_COMPANY_ID || "";
|
|
51
|
+
if (!token) {
|
|
52
|
+
return textResponse("Error: No authentication token provided. Pass it as a parameter or set PROCUREMENTEXPRESS_AUTH_TOKEN environment variable.");
|
|
53
|
+
}
|
|
54
|
+
if (!companyId) {
|
|
55
|
+
return textResponse("Error: No company ID provided. Pass it as a parameter or set PROCUREMENTEXPRESS_COMPANY_ID environment variable.");
|
|
56
|
+
}
|
|
57
|
+
authManager.authenticateV1(token, companyId);
|
|
58
|
+
return textResponse(`Authenticated with V1 API. Company ID set to ${companyId}. You can now make API calls.`);
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// V3: OAuth2 authentication
|
|
63
|
+
server.registerTool("authenticate", {
|
|
64
|
+
description: "Sign in to ProcurementExpress via OAuth2. Returns a bearer token for subsequent API calls. " +
|
|
65
|
+
"Requires PROCUREMENTEXPRESS_CLIENT_ID and PROCUREMENTEXPRESS_CLIENT_SECRET environment variables.",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
email: z.string().email().describe("User email address"),
|
|
68
|
+
password: z.string().describe("User password"),
|
|
69
|
+
},
|
|
70
|
+
}, withErrorHandling(async (args) => {
|
|
71
|
+
const response = await authManager.authenticateV3(args.email, args.password);
|
|
72
|
+
return textResponse(`Authenticated successfully via OAuth2. Token expires in ${response.expires_in} seconds. Use set_active_company to select a company before making API calls.`);
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
server.registerTool("validate_token", {
|
|
76
|
+
description: isV1
|
|
77
|
+
? "Validate the current V1 authentication token by fetching the current user"
|
|
78
|
+
: "Check if the current OAuth2 token is valid and get token metadata",
|
|
79
|
+
inputSchema: {},
|
|
80
|
+
}, withErrorHandling(async () => {
|
|
81
|
+
const info = await authManager.validateToken();
|
|
82
|
+
return jsonResponse(info);
|
|
83
|
+
}));
|
|
84
|
+
server.registerTool("revoke_token", {
|
|
85
|
+
description: isV1
|
|
86
|
+
? "Clear the current authentication token from the session"
|
|
87
|
+
: "Sign out and revoke the current OAuth2 token",
|
|
88
|
+
inputSchema: {},
|
|
89
|
+
}, withErrorHandling(async () => {
|
|
90
|
+
await authManager.revokeToken();
|
|
91
|
+
return textResponse(isV1
|
|
92
|
+
? "Token cleared. You are now signed out."
|
|
93
|
+
: "Token revoked successfully. You are now signed out.");
|
|
94
|
+
}));
|
|
95
|
+
// Register all tool groups
|
|
96
|
+
registerUserTools(server, apiClient);
|
|
97
|
+
registerBudgetTools(server, apiClient);
|
|
98
|
+
registerCompanyTools(server, apiClient);
|
|
99
|
+
registerDepartmentTools(server, apiClient);
|
|
100
|
+
registerSupplierTools(server, apiClient);
|
|
101
|
+
registerProductTools(server, apiClient);
|
|
102
|
+
registerPurchaseOrderTools(server, apiClient);
|
|
103
|
+
registerInvoiceTools(server, apiClient);
|
|
104
|
+
registerApprovalFlowTools(server, apiClient);
|
|
105
|
+
registerCommentTools(server, apiClient);
|
|
106
|
+
registerPaymentTools(server, apiClient);
|
|
107
|
+
registerTaxRateTools(server, apiClient);
|
|
108
|
+
registerWebhookTools(server, apiClient);
|
|
109
|
+
registerSupplementaryTools(server, apiClient);
|
|
110
|
+
// Start the server
|
|
111
|
+
async function main() {
|
|
112
|
+
const transport = new StdioServerTransport();
|
|
113
|
+
await server.connect(transport);
|
|
114
|
+
console.error(`ProcurementExpress MCP Server running on stdio (API version: ${apiClient.getApiVersion()})`);
|
|
115
|
+
}
|
|
116
|
+
main().catch((error) => {
|
|
117
|
+
console.error("Fatal error in main():", error);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
/**
|
|
3
|
+
* Returns a text content response for MCP tools.
|
|
4
|
+
*/
|
|
5
|
+
export declare function textResponse(text: string): {
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Returns a JSON-formatted text content response for MCP tools.
|
|
13
|
+
*/
|
|
14
|
+
export declare function jsonResponse(data: unknown): {
|
|
15
|
+
content: {
|
|
16
|
+
type: "text";
|
|
17
|
+
text: string;
|
|
18
|
+
}[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Wraps a tool handler with standard error handling.
|
|
22
|
+
*/
|
|
23
|
+
export declare function withErrorHandling<T>(handler: (args: T) => Promise<{
|
|
24
|
+
content: {
|
|
25
|
+
type: "text";
|
|
26
|
+
text: string;
|
|
27
|
+
}[];
|
|
28
|
+
}>): (args: T) => Promise<{
|
|
29
|
+
content: {
|
|
30
|
+
type: "text";
|
|
31
|
+
text: string;
|
|
32
|
+
}[];
|
|
33
|
+
}>;
|
|
34
|
+
export type Server = McpServer;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a text content response for MCP tools.
|
|
3
|
+
*/
|
|
4
|
+
export function textResponse(text) {
|
|
5
|
+
return { content: [{ type: "text", text }] };
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Returns a JSON-formatted text content response for MCP tools.
|
|
9
|
+
*/
|
|
10
|
+
export function jsonResponse(data) {
|
|
11
|
+
return textResponse(JSON.stringify(data, null, 2));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Wraps a tool handler with standard error handling.
|
|
15
|
+
*/
|
|
16
|
+
export function withErrorHandling(handler) {
|
|
17
|
+
return async (args) => {
|
|
18
|
+
try {
|
|
19
|
+
return await handler(args);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
23
|
+
return textResponse(`Error: ${message}`);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonResponse, withErrorHandling } from "../tool-helpers.js";
|
|
3
|
+
const approvalConditionSchema = z.object({
|
|
4
|
+
property: z.string().describe("Condition property (budget, department, supplier, requester, gross_amount, net_amount, custom_field_xxx)"),
|
|
5
|
+
operator: z.string().describe("Operator: equals(0), not_equals(1), greater_than(2), less_than(3), contains(4), not_contains(5)"),
|
|
6
|
+
value: z.string().describe("Condition value (single ID or comma-separated IDs)"),
|
|
7
|
+
custom_field_id: z.number().int().optional().describe("Custom field ID (for custom_field properties)"),
|
|
8
|
+
});
|
|
9
|
+
const approvalStepSchema = z.object({
|
|
10
|
+
step_no: z.number().int().describe("Step number"),
|
|
11
|
+
all_should_approve: z.boolean().describe("Whether all approvers in step must approve"),
|
|
12
|
+
approver_user_ids: z.array(z.number().int()).describe("Approver user IDs"),
|
|
13
|
+
conditions: z.array(approvalConditionSchema).optional().describe("Step-level conditions"),
|
|
14
|
+
});
|
|
15
|
+
export function registerApprovalFlowTools(server, apiClient) {
|
|
16
|
+
server.registerTool("list_approval_flows", {
|
|
17
|
+
description: "List approval flows with search and pagination",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
search: z.string().optional().describe("Search term"),
|
|
20
|
+
page: z.number().int().positive().optional().describe("Page number"),
|
|
21
|
+
per_page: z.number().int().positive().optional().describe("Results per page"),
|
|
22
|
+
sort: z.string().optional().describe("Sort field"),
|
|
23
|
+
direction: z.enum(["asc", "desc"]).optional().describe("Sort direction"),
|
|
24
|
+
},
|
|
25
|
+
}, withErrorHandling(async (args) => {
|
|
26
|
+
const params = new URLSearchParams();
|
|
27
|
+
if (args.search)
|
|
28
|
+
params.set("search", args.search);
|
|
29
|
+
if (args.page)
|
|
30
|
+
params.set("page", String(args.page));
|
|
31
|
+
if (args.per_page)
|
|
32
|
+
params.set("per_page", String(args.per_page));
|
|
33
|
+
if (args.sort)
|
|
34
|
+
params.set("sort", args.sort);
|
|
35
|
+
if (args.direction)
|
|
36
|
+
params.set("direction", args.direction);
|
|
37
|
+
const query = params.toString();
|
|
38
|
+
const path = `${apiClient.buildPath("/approval_flows")}${query ? `?${query}` : ""}`;
|
|
39
|
+
const result = await apiClient.get(path);
|
|
40
|
+
return jsonResponse(result);
|
|
41
|
+
}));
|
|
42
|
+
server.registerTool("get_approval_flow", {
|
|
43
|
+
description: "Get approval flow details including steps, approvers, and conditions",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
id: z.number().int().positive().describe("Approval Flow ID"),
|
|
46
|
+
},
|
|
47
|
+
}, withErrorHandling(async (args) => {
|
|
48
|
+
const flow = await apiClient.get(apiClient.buildPath(`/approval_flows/${args.id}`));
|
|
49
|
+
return jsonResponse(flow);
|
|
50
|
+
}));
|
|
51
|
+
server.registerTool("create_approval_flow", {
|
|
52
|
+
description: "Create an approval flow with steps, approvers, and conditions. document_type: 0=purchase_order, 1=invoice",
|
|
53
|
+
inputSchema: {
|
|
54
|
+
name: z.string().describe("Flow name"),
|
|
55
|
+
document_type: z.number().int().min(0).max(1).describe("0=purchase_order, 1=invoice"),
|
|
56
|
+
self_approval_allowed: z.boolean().optional().describe("Allow self-approval"),
|
|
57
|
+
steps: z.array(approvalStepSchema).describe("Approval steps with approvers"),
|
|
58
|
+
conditions: z.array(approvalConditionSchema).optional().describe("Flow-level conditions"),
|
|
59
|
+
},
|
|
60
|
+
}, withErrorHandling(async (args) => {
|
|
61
|
+
const body = {
|
|
62
|
+
approval_flow: {
|
|
63
|
+
name: args.name,
|
|
64
|
+
document_type: args.document_type,
|
|
65
|
+
self_approval_allowed: args.self_approval_allowed,
|
|
66
|
+
approval_steps_attributes: args.steps.map((step) => ({
|
|
67
|
+
step_no: step.step_no,
|
|
68
|
+
all_should_approve: step.all_should_approve,
|
|
69
|
+
approval_step_approvers_attributes: step.approver_user_ids.map((uid) => ({
|
|
70
|
+
user_id: uid,
|
|
71
|
+
})),
|
|
72
|
+
...(step.conditions
|
|
73
|
+
? { approval_conditions_attributes: step.conditions }
|
|
74
|
+
: {}),
|
|
75
|
+
})),
|
|
76
|
+
...(args.conditions
|
|
77
|
+
? { approval_conditions_attributes: args.conditions }
|
|
78
|
+
: {}),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
const flow = await apiClient.post(apiClient.buildPath("/approval_flows"), body);
|
|
82
|
+
return jsonResponse(flow);
|
|
83
|
+
}));
|
|
84
|
+
server.registerTool("delete_approval_flow", {
|
|
85
|
+
description: "Delete an approval flow",
|
|
86
|
+
inputSchema: { id: z.number().int().positive().describe("Approval Flow ID") },
|
|
87
|
+
}, withErrorHandling(async (args) => {
|
|
88
|
+
const result = await apiClient.delete(apiClient.buildPath(`/approval_flows/${args.id}`));
|
|
89
|
+
return jsonResponse(result);
|
|
90
|
+
}));
|
|
91
|
+
server.registerTool("archive_approval_flow", {
|
|
92
|
+
description: "Archive an approval flow",
|
|
93
|
+
inputSchema: { id: z.number().int().positive().describe("Approval Flow ID") },
|
|
94
|
+
}, withErrorHandling(async (args) => {
|
|
95
|
+
const result = await apiClient.put(apiClient.buildPath(`/approval_flows/${args.id}/archive`));
|
|
96
|
+
return jsonResponse(result);
|
|
97
|
+
}));
|
|
98
|
+
server.registerTool("list_approval_flow_runs", {
|
|
99
|
+
description: "List runs for an approval flow with status and date filters",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
id: z.number().int().positive().describe("Approval Flow ID"),
|
|
102
|
+
status: z.enum(["in_progress", "completed", "rejected"]).optional().describe("Filter by status"),
|
|
103
|
+
keyword: z.string().optional().describe("Search keyword"),
|
|
104
|
+
date_range: z.enum(["24h", "7d", "30d", "60d", "custom"]).optional().describe("Date range filter"),
|
|
105
|
+
date_from: z.string().optional().describe("Start date (MM/DD/YYYY) for custom range"),
|
|
106
|
+
date_to: z.string().optional().describe("End date (MM/DD/YYYY) for custom range"),
|
|
107
|
+
page: z.number().int().positive().optional().describe("Page number"),
|
|
108
|
+
},
|
|
109
|
+
}, withErrorHandling(async (args) => {
|
|
110
|
+
const { id, ...filters } = args;
|
|
111
|
+
const params = new URLSearchParams();
|
|
112
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
113
|
+
if (value !== undefined)
|
|
114
|
+
params.set(key, String(value));
|
|
115
|
+
}
|
|
116
|
+
const query = params.toString();
|
|
117
|
+
const path = `${apiClient.buildPath(`/approval_flows/${id}/runs`)}${query ? `?${query}` : ""}`;
|
|
118
|
+
const result = await apiClient.get(path);
|
|
119
|
+
return jsonResponse(result);
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonResponse, withErrorHandling } from "../tool-helpers.js";
|
|
3
|
+
export function registerBudgetTools(server, apiClient) {
|
|
4
|
+
server.registerTool("list_budgets", {
|
|
5
|
+
description: "List budgets with pagination. Filter by department, archived status, or active only",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
page: z.number().int().positive().optional().describe("Page number (default: 1)"),
|
|
8
|
+
archived: z.boolean().optional().describe("Filter by archived status"),
|
|
9
|
+
only_active: z.boolean().optional().describe("Show only active budgets"),
|
|
10
|
+
department_id: z.number().int().optional().describe("Filter by department ID"),
|
|
11
|
+
},
|
|
12
|
+
}, withErrorHandling(async (args) => {
|
|
13
|
+
const params = new URLSearchParams();
|
|
14
|
+
if (args.page)
|
|
15
|
+
params.set("page", String(args.page));
|
|
16
|
+
if (args.archived !== undefined)
|
|
17
|
+
params.set("archived", String(args.archived));
|
|
18
|
+
if (args.only_active)
|
|
19
|
+
params.set("only_active", "true");
|
|
20
|
+
if (args.department_id)
|
|
21
|
+
params.set("department_id", String(args.department_id));
|
|
22
|
+
const query = params.toString();
|
|
23
|
+
const path = `${apiClient.buildPath("/budgets")}${query ? `?${query}` : ""}`;
|
|
24
|
+
const result = await apiClient.get(path);
|
|
25
|
+
return jsonResponse(result);
|
|
26
|
+
}));
|
|
27
|
+
server.registerTool("get_budget", {
|
|
28
|
+
description: "Get a specific budget by ID, including remaining amount",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
id: z.number().int().positive().describe("Budget ID"),
|
|
31
|
+
},
|
|
32
|
+
}, withErrorHandling(async (args) => {
|
|
33
|
+
const budget = await apiClient.get(apiClient.buildPath(`/budgets/${args.id}`));
|
|
34
|
+
return jsonResponse(budget);
|
|
35
|
+
}));
|
|
36
|
+
server.registerTool("create_budget", {
|
|
37
|
+
description: "Create a new budget",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
name: z.string().describe("Budget name"),
|
|
40
|
+
amount: z.number().describe("Budget amount"),
|
|
41
|
+
currency_id: z.number().int().describe("Currency ID"),
|
|
42
|
+
creator_id: z.number().int().describe("Creator user ID"),
|
|
43
|
+
cost_code: z.string().optional().describe("Cost code"),
|
|
44
|
+
cost_type: z.string().optional().describe("Cost type"),
|
|
45
|
+
start_date: z.string().optional().describe("Start date (must match company date format)"),
|
|
46
|
+
end_date: z.string().optional().describe("End date"),
|
|
47
|
+
allow_anyone_to_approve_a_po: z.boolean().optional().describe("Allow anyone to approve"),
|
|
48
|
+
approver_ids: z.array(z.number().int()).optional().describe("Approver user IDs"),
|
|
49
|
+
department_ids: z.array(z.number().int()).optional().describe("Department IDs"),
|
|
50
|
+
},
|
|
51
|
+
}, withErrorHandling(async (args) => {
|
|
52
|
+
const budget = await apiClient.post(apiClient.buildPath("/budgets"), { budget: args });
|
|
53
|
+
return jsonResponse(budget);
|
|
54
|
+
}));
|
|
55
|
+
server.registerTool("update_budget", {
|
|
56
|
+
description: "Update an existing budget",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
id: z.number().int().positive().describe("Budget ID"),
|
|
59
|
+
name: z.string().optional().describe("Budget name"),
|
|
60
|
+
amount: z.number().optional().describe("Budget amount"),
|
|
61
|
+
cost_code: z.string().optional().describe("Cost code"),
|
|
62
|
+
cost_type: z.string().optional().describe("Cost type"),
|
|
63
|
+
start_date: z.string().optional().describe("Start date"),
|
|
64
|
+
end_date: z.string().optional().describe("End date"),
|
|
65
|
+
approver_ids: z.array(z.number().int()).optional().describe("Approver user IDs"),
|
|
66
|
+
department_ids: z.array(z.number().int()).optional().describe("Department IDs"),
|
|
67
|
+
},
|
|
68
|
+
}, withErrorHandling(async (args) => {
|
|
69
|
+
const { id, ...data } = args;
|
|
70
|
+
const budget = await apiClient.put(apiClient.buildPath(`/budgets/${id}`), { budget: data });
|
|
71
|
+
return jsonResponse(budget);
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonResponse, withErrorHandling } from "../tool-helpers.js";
|
|
3
|
+
export function registerCommentTools(server, apiClient) {
|
|
4
|
+
server.registerTool("add_purchase_order_comment", {
|
|
5
|
+
description: "Add a comment to a purchase order",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
purchase_order_id: z.number().int().positive().describe("Purchase Order ID"),
|
|
8
|
+
comment: z.string().describe("Comment text"),
|
|
9
|
+
},
|
|
10
|
+
}, withErrorHandling(async (args) => {
|
|
11
|
+
const result = await apiClient.post(apiClient.buildPath(`/purchase_order/${args.purchase_order_id}/comments`), { comment: args.comment });
|
|
12
|
+
return jsonResponse(result);
|
|
13
|
+
}));
|
|
14
|
+
server.registerTool("add_invoice_comment", {
|
|
15
|
+
description: "Add a comment to an invoice",
|
|
16
|
+
inputSchema: {
|
|
17
|
+
invoice_id: z.number().int().positive().describe("Invoice ID"),
|
|
18
|
+
comment: z.string().describe("Comment text"),
|
|
19
|
+
},
|
|
20
|
+
}, withErrorHandling(async (args) => {
|
|
21
|
+
const result = await apiClient.post(apiClient.buildPath(`/invoices/${args.invoice_id}/create_comment`), { comment: args.comment });
|
|
22
|
+
return jsonResponse(result);
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonResponse, textResponse, withErrorHandling } from "../tool-helpers.js";
|
|
3
|
+
export function registerCompanyTools(server, apiClient) {
|
|
4
|
+
server.registerTool("list_companies", {
|
|
5
|
+
description: "List all companies the current user belongs to",
|
|
6
|
+
inputSchema: {},
|
|
7
|
+
}, withErrorHandling(async () => {
|
|
8
|
+
const companies = await apiClient.get(apiClient.buildPath("/companies"));
|
|
9
|
+
return jsonResponse(companies);
|
|
10
|
+
}));
|
|
11
|
+
server.registerTool("get_company", {
|
|
12
|
+
description: "Get company details including settings, custom fields, and supported currencies",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
id: z.number().int().positive().describe("Company ID"),
|
|
15
|
+
},
|
|
16
|
+
}, withErrorHandling(async (args) => {
|
|
17
|
+
const company = await apiClient.get(apiClient.buildPath(`/companies/${args.id}`));
|
|
18
|
+
return jsonResponse(company);
|
|
19
|
+
}));
|
|
20
|
+
server.registerTool("set_active_company", {
|
|
21
|
+
description: "Set the active company ID for subsequent API calls. Required before most operations.",
|
|
22
|
+
inputSchema: {
|
|
23
|
+
company_id: z.string().describe("Company ID to use for subsequent requests"),
|
|
24
|
+
},
|
|
25
|
+
}, withErrorHandling(async (args) => {
|
|
26
|
+
apiClient.setCompanyId(args.company_id);
|
|
27
|
+
return textResponse(`Active company set to ${args.company_id}`);
|
|
28
|
+
}));
|
|
29
|
+
server.registerTool("list_approvers", {
|
|
30
|
+
description: "List approvers filtered by department (respects auto-approval settings)",
|
|
31
|
+
inputSchema: {
|
|
32
|
+
department_id: z.number().int().positive().describe("Department ID to filter approvers"),
|
|
33
|
+
},
|
|
34
|
+
}, withErrorHandling(async (args) => {
|
|
35
|
+
const approvers = await apiClient.get(`${apiClient.buildPath("/companies/approvers")}?department_id=${args.department_id}`);
|
|
36
|
+
return jsonResponse(approvers);
|
|
37
|
+
}));
|
|
38
|
+
server.registerTool("list_all_approvers", {
|
|
39
|
+
description: "List all approvers regardless of auto-approval routing",
|
|
40
|
+
inputSchema: {},
|
|
41
|
+
}, withErrorHandling(async () => {
|
|
42
|
+
const approvers = await apiClient.get(apiClient.buildPath("/companies/all_approvers"));
|
|
43
|
+
return jsonResponse(approvers);
|
|
44
|
+
}));
|
|
45
|
+
server.registerTool("list_employees", {
|
|
46
|
+
description: "List all employees of the current company with their roles",
|
|
47
|
+
inputSchema: {},
|
|
48
|
+
}, withErrorHandling(async () => {
|
|
49
|
+
const employees = await apiClient.get(apiClient.buildPath("/companies/employees"));
|
|
50
|
+
return jsonResponse(employees);
|
|
51
|
+
}));
|
|
52
|
+
server.registerTool("invite_user", {
|
|
53
|
+
description: "Invite a user to the company. Roles: companyadmin, approver, finance, teammember",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
email: z.string().email().describe("Email address to invite"),
|
|
56
|
+
name: z.string().describe("Name of the user"),
|
|
57
|
+
roles: z
|
|
58
|
+
.array(z.enum(["companyadmin", "approver", "finance", "teammember"]))
|
|
59
|
+
.describe("Roles to assign"),
|
|
60
|
+
},
|
|
61
|
+
}, withErrorHandling(async (args) => {
|
|
62
|
+
const result = await apiClient.post(apiClient.buildPath("/companies/send_user_invite"), {
|
|
63
|
+
invite_user: args,
|
|
64
|
+
});
|
|
65
|
+
return jsonResponse(result);
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonResponse, withErrorHandling } from "../tool-helpers.js";
|
|
3
|
+
export function registerDepartmentTools(server, apiClient) {
|
|
4
|
+
server.registerTool("list_departments", {
|
|
5
|
+
description: "List departments with optional filters for archived status and company-specific",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
archived: z.boolean().optional().describe("Filter by archived status"),
|
|
8
|
+
company_specific: z.boolean().optional().describe("Show only company-specific departments"),
|
|
9
|
+
},
|
|
10
|
+
}, withErrorHandling(async (args) => {
|
|
11
|
+
const params = new URLSearchParams();
|
|
12
|
+
if (args.archived !== undefined)
|
|
13
|
+
params.set("archived", String(args.archived));
|
|
14
|
+
if (args.company_specific !== undefined)
|
|
15
|
+
params.set("company_specific", String(args.company_specific));
|
|
16
|
+
const query = params.toString();
|
|
17
|
+
const path = `${apiClient.buildPath("/departments")}${query ? `?${query}` : ""}`;
|
|
18
|
+
const departments = await apiClient.get(path);
|
|
19
|
+
return jsonResponse(departments);
|
|
20
|
+
}));
|
|
21
|
+
server.registerTool("get_department", {
|
|
22
|
+
description: "Get a specific department by ID",
|
|
23
|
+
inputSchema: {
|
|
24
|
+
id: z.number().int().positive().describe("Department ID"),
|
|
25
|
+
},
|
|
26
|
+
}, withErrorHandling(async (args) => {
|
|
27
|
+
const department = await apiClient.get(apiClient.buildPath(`/departments/${args.id}`));
|
|
28
|
+
return jsonResponse(department);
|
|
29
|
+
}));
|
|
30
|
+
server.registerTool("create_department", {
|
|
31
|
+
description: "Create a new department",
|
|
32
|
+
inputSchema: {
|
|
33
|
+
name: z.string().describe("Department name"),
|
|
34
|
+
contact_person: z.string().optional().describe("Contact person"),
|
|
35
|
+
phone_number: z.string().optional().describe("Phone number"),
|
|
36
|
+
email: z.string().email().optional().describe("Email"),
|
|
37
|
+
address: z.string().optional().describe("Address"),
|
|
38
|
+
tax_number: z.string().optional().describe("Tax number"),
|
|
39
|
+
budget_ids: z.array(z.number().int()).optional().describe("Budget IDs to associate"),
|
|
40
|
+
user_ids: z.array(z.number().int()).optional().describe("User IDs to associate"),
|
|
41
|
+
},
|
|
42
|
+
}, withErrorHandling(async (args) => {
|
|
43
|
+
const department = await apiClient.post(apiClient.buildPath("/departments"), {
|
|
44
|
+
department: args,
|
|
45
|
+
});
|
|
46
|
+
return jsonResponse(department);
|
|
47
|
+
}));
|
|
48
|
+
server.registerTool("update_department", {
|
|
49
|
+
description: "Update an existing department",
|
|
50
|
+
inputSchema: {
|
|
51
|
+
id: z.number().int().positive().describe("Department ID"),
|
|
52
|
+
name: z.string().optional().describe("Department name"),
|
|
53
|
+
archived: z.boolean().optional().describe("Archive status"),
|
|
54
|
+
contact_person: z.string().optional().describe("Contact person"),
|
|
55
|
+
phone_number: z.string().optional().describe("Phone number"),
|
|
56
|
+
email: z.string().email().optional().describe("Email"),
|
|
57
|
+
address: z.string().optional().describe("Address"),
|
|
58
|
+
tax_number: z.string().optional().describe("Tax number"),
|
|
59
|
+
budget_ids: z.array(z.number().int()).optional().describe("Budget IDs"),
|
|
60
|
+
user_ids: z.array(z.number().int()).optional().describe("User IDs"),
|
|
61
|
+
},
|
|
62
|
+
}, withErrorHandling(async (args) => {
|
|
63
|
+
const { id, ...data } = args;
|
|
64
|
+
const department = await apiClient.put(apiClient.buildPath(`/departments/${id}`), {
|
|
65
|
+
department: data,
|
|
66
|
+
});
|
|
67
|
+
return jsonResponse(department);
|
|
68
|
+
}));
|
|
69
|
+
}
|