@alfe.ai/myob-mcp 0.3.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/dist/server.d.ts +49 -0
- package/dist/server.js +748 -0
- package/package.json +32 -0
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/myob-client.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* MYOB Business API client — wraps fetch with required headers,
|
|
4
|
+
* 401 retry logic, and token refresh.
|
|
5
|
+
*
|
|
6
|
+
* All MYOB API calls require:
|
|
7
|
+
* - Authorization: Bearer {accessToken}
|
|
8
|
+
* - x-myobapi-key: {clientId}
|
|
9
|
+
* - x-myobapi-version: v2
|
|
10
|
+
*/
|
|
11
|
+
interface MyobClientConfig {
|
|
12
|
+
accessToken: string;
|
|
13
|
+
clientId: string;
|
|
14
|
+
businessId: string;
|
|
15
|
+
onRefresh: () => Promise<{
|
|
16
|
+
accessToken: string;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
declare class MyobClient {
|
|
20
|
+
private accessToken;
|
|
21
|
+
private readonly clientId;
|
|
22
|
+
private readonly businessId;
|
|
23
|
+
private readonly onRefresh;
|
|
24
|
+
constructor(config: MyobClientConfig);
|
|
25
|
+
updateToken(accessToken: string): void;
|
|
26
|
+
private get baseUrl();
|
|
27
|
+
private headers;
|
|
28
|
+
get(path: string, params?: Record<string, string>): Promise<unknown>;
|
|
29
|
+
post(path: string, body: unknown): Promise<unknown>;
|
|
30
|
+
put(path: string, body: unknown): Promise<unknown>;
|
|
31
|
+
delete(path: string): Promise<unknown>;
|
|
32
|
+
private request;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/server.d.ts
|
|
36
|
+
|
|
37
|
+
interface MyobAccountSummary {
|
|
38
|
+
myobBusinessId: string;
|
|
39
|
+
displayName: string | null;
|
|
40
|
+
connectedAt: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a `myobBusinessId` selector to its `MyobClient`. Throws if
|
|
44
|
+
* unknown — the LLM should call `myob_list_businesses` to discover
|
|
45
|
+
* the valid set. This is the per-call dispatch contract for Pattern A.
|
|
46
|
+
*/
|
|
47
|
+
declare function resolveMyobClient(myobBusinessId: string): MyobClient;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { MyobAccountSummary, resolveMyobClient };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,748 @@
|
|
|
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 { resolveConfig } from "@alfe.ai/config";
|
|
5
|
+
import { AgentApiClient } from "@alfe.ai/agent-api-client";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
//#region src/myob-client.ts
|
|
8
|
+
/**
|
|
9
|
+
* MYOB Business API client — wraps fetch with required headers,
|
|
10
|
+
* 401 retry logic, and token refresh.
|
|
11
|
+
*
|
|
12
|
+
* All MYOB API calls require:
|
|
13
|
+
* - Authorization: Bearer {accessToken}
|
|
14
|
+
* - x-myobapi-key: {clientId}
|
|
15
|
+
* - x-myobapi-version: v2
|
|
16
|
+
*/
|
|
17
|
+
const MYOB_API_BASE = "https://api.myob.com/accountright";
|
|
18
|
+
var MyobClient = class {
|
|
19
|
+
accessToken;
|
|
20
|
+
clientId;
|
|
21
|
+
businessId;
|
|
22
|
+
onRefresh;
|
|
23
|
+
constructor(config) {
|
|
24
|
+
this.accessToken = config.accessToken;
|
|
25
|
+
this.clientId = config.clientId;
|
|
26
|
+
this.businessId = config.businessId;
|
|
27
|
+
this.onRefresh = config.onRefresh;
|
|
28
|
+
}
|
|
29
|
+
updateToken(accessToken) {
|
|
30
|
+
this.accessToken = accessToken;
|
|
31
|
+
}
|
|
32
|
+
get baseUrl() {
|
|
33
|
+
return `${MYOB_API_BASE}/${this.businessId}`;
|
|
34
|
+
}
|
|
35
|
+
headers() {
|
|
36
|
+
return {
|
|
37
|
+
"Authorization": `Bearer ${this.accessToken}`,
|
|
38
|
+
"x-myobapi-key": this.clientId,
|
|
39
|
+
"x-myobapi-version": "v2",
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
"Accept": "application/json"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async get(path, params) {
|
|
45
|
+
let url = `${this.baseUrl}${path}`;
|
|
46
|
+
if (params) {
|
|
47
|
+
const qs = new URLSearchParams(params).toString();
|
|
48
|
+
url += `?${qs}`;
|
|
49
|
+
}
|
|
50
|
+
return this.request("GET", url);
|
|
51
|
+
}
|
|
52
|
+
async post(path, body) {
|
|
53
|
+
return this.request("POST", `${this.baseUrl}${path}`, body);
|
|
54
|
+
}
|
|
55
|
+
async put(path, body) {
|
|
56
|
+
return this.request("PUT", `${this.baseUrl}${path}`, body);
|
|
57
|
+
}
|
|
58
|
+
async delete(path) {
|
|
59
|
+
return this.request("DELETE", `${this.baseUrl}${path}`);
|
|
60
|
+
}
|
|
61
|
+
async request(method, url, body) {
|
|
62
|
+
const doFetch = async () => {
|
|
63
|
+
const init = {
|
|
64
|
+
method,
|
|
65
|
+
headers: this.headers()
|
|
66
|
+
};
|
|
67
|
+
if (body !== void 0) init.body = JSON.stringify(body);
|
|
68
|
+
return fetch(url, init);
|
|
69
|
+
};
|
|
70
|
+
let response = await doFetch();
|
|
71
|
+
if (response.status === 401) {
|
|
72
|
+
this.accessToken = (await this.onRefresh()).accessToken;
|
|
73
|
+
response = await doFetch();
|
|
74
|
+
}
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const errorBody = await response.text();
|
|
77
|
+
throw new Error(`MYOB API ${method} ${url} failed (${String(response.status)}): ${errorBody}`);
|
|
78
|
+
}
|
|
79
|
+
const text = await response.text();
|
|
80
|
+
if (!text) return {};
|
|
81
|
+
return JSON.parse(text);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/tools/shared.ts
|
|
86
|
+
/**
|
|
87
|
+
* Shared `myobBusinessId` selector field. Every credential-touching tool
|
|
88
|
+
* requires it (Pattern A per-call dispatch). `McpServer.registerTool`
|
|
89
|
+
* expects a raw Zod shape (`{ field: z.string() }`), NOT a JSON-Schema
|
|
90
|
+
* object — passing JSON-Schema throws synchronously at registration time.
|
|
91
|
+
*/
|
|
92
|
+
const myobBusinessIdField = z.string().describe("MYOB Business ID — use the value from myob_list_businesses to pick which connected business this call should target.");
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/tools/contacts.ts
|
|
95
|
+
function registerContactTools(server, resolveClient) {
|
|
96
|
+
const register = server.registerTool.bind(server);
|
|
97
|
+
register("myob_list_customers", {
|
|
98
|
+
description: "List customer contacts from a connected MYOB Business.",
|
|
99
|
+
inputSchema: {
|
|
100
|
+
myobBusinessId: myobBusinessIdField,
|
|
101
|
+
filter: z.string().optional().describe("OData $filter expression (e.g. \"IsActive eq true\")"),
|
|
102
|
+
top: z.string().optional().describe("Max records to return (default 100)"),
|
|
103
|
+
skip: z.string().optional().describe("Number of records to skip for pagination")
|
|
104
|
+
}
|
|
105
|
+
}, async (args) => {
|
|
106
|
+
const client = resolveClient(args.myobBusinessId);
|
|
107
|
+
const params = {};
|
|
108
|
+
if (args.filter) params.$filter = args.filter;
|
|
109
|
+
if (args.top) params.$top = args.top;
|
|
110
|
+
if (args.skip) params.$skip = args.skip;
|
|
111
|
+
const result = await client.get("/Contact/Customer", Object.keys(params).length > 0 ? params : void 0);
|
|
112
|
+
return { content: [{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: JSON.stringify(result, null, 2)
|
|
115
|
+
}] };
|
|
116
|
+
});
|
|
117
|
+
register("myob_get_customer", {
|
|
118
|
+
description: "Get a specific customer contact by UID from a connected MYOB Business.",
|
|
119
|
+
inputSchema: {
|
|
120
|
+
myobBusinessId: myobBusinessIdField,
|
|
121
|
+
uid: z.string().describe("The customer UID (GUID)")
|
|
122
|
+
}
|
|
123
|
+
}, async (args) => {
|
|
124
|
+
const result = await resolveClient(args.myobBusinessId).get(`/Contact/Customer/${args.uid}`);
|
|
125
|
+
return { content: [{
|
|
126
|
+
type: "text",
|
|
127
|
+
text: JSON.stringify(result, null, 2)
|
|
128
|
+
}] };
|
|
129
|
+
});
|
|
130
|
+
register("myob_create_customer", {
|
|
131
|
+
description: "Create a new customer contact in a connected MYOB Business.",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
myobBusinessId: myobBusinessIdField,
|
|
134
|
+
CompanyName: z.string().optional().describe("Company name"),
|
|
135
|
+
FirstName: z.string().optional().describe("First name"),
|
|
136
|
+
LastName: z.string().optional().describe("Last name"),
|
|
137
|
+
IsIndividual: z.boolean().optional().describe("true for individual, false for company"),
|
|
138
|
+
DisplayID: z.string().optional().describe("Display ID (account number)"),
|
|
139
|
+
data: z.record(z.string(), z.unknown()).optional().describe("Full customer data object — overrides individual fields if provided")
|
|
140
|
+
}
|
|
141
|
+
}, async (args) => {
|
|
142
|
+
const client = resolveClient(args.myobBusinessId);
|
|
143
|
+
const body = args.data ?? {};
|
|
144
|
+
if (args.CompanyName && !body.CompanyName) body.CompanyName = args.CompanyName;
|
|
145
|
+
if (args.FirstName && !body.FirstName) body.FirstName = args.FirstName;
|
|
146
|
+
if (args.LastName && !body.LastName) body.LastName = args.LastName;
|
|
147
|
+
if (args.IsIndividual !== void 0 && body.IsIndividual === void 0) body.IsIndividual = args.IsIndividual;
|
|
148
|
+
if (args.DisplayID && !body.DisplayID) body.DisplayID = args.DisplayID;
|
|
149
|
+
const result = await client.post("/Contact/Customer", body);
|
|
150
|
+
return { content: [{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: JSON.stringify(result, null, 2)
|
|
153
|
+
}] };
|
|
154
|
+
});
|
|
155
|
+
register("myob_update_customer", {
|
|
156
|
+
description: "Update an existing customer contact in a connected MYOB Business.",
|
|
157
|
+
inputSchema: {
|
|
158
|
+
myobBusinessId: myobBusinessIdField,
|
|
159
|
+
uid: z.string().describe("The customer UID (GUID) to update"),
|
|
160
|
+
data: z.record(z.string(), z.unknown()).describe("Customer data fields to update")
|
|
161
|
+
}
|
|
162
|
+
}, async (args) => {
|
|
163
|
+
const result = await resolveClient(args.myobBusinessId).put(`/Contact/Customer/${args.uid}`, args.data);
|
|
164
|
+
return { content: [{
|
|
165
|
+
type: "text",
|
|
166
|
+
text: JSON.stringify(result, null, 2)
|
|
167
|
+
}] };
|
|
168
|
+
});
|
|
169
|
+
register("myob_list_suppliers", {
|
|
170
|
+
description: "List supplier contacts from a connected MYOB Business.",
|
|
171
|
+
inputSchema: {
|
|
172
|
+
myobBusinessId: myobBusinessIdField,
|
|
173
|
+
filter: z.string().optional().describe("OData $filter expression"),
|
|
174
|
+
top: z.string().optional().describe("Max records to return"),
|
|
175
|
+
skip: z.string().optional().describe("Number of records to skip")
|
|
176
|
+
}
|
|
177
|
+
}, async (args) => {
|
|
178
|
+
const client = resolveClient(args.myobBusinessId);
|
|
179
|
+
const params = {};
|
|
180
|
+
if (args.filter) params.$filter = args.filter;
|
|
181
|
+
if (args.top) params.$top = args.top;
|
|
182
|
+
if (args.skip) params.$skip = args.skip;
|
|
183
|
+
const result = await client.get("/Contact/Supplier", Object.keys(params).length > 0 ? params : void 0);
|
|
184
|
+
return { content: [{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: JSON.stringify(result, null, 2)
|
|
187
|
+
}] };
|
|
188
|
+
});
|
|
189
|
+
register("myob_get_supplier", {
|
|
190
|
+
description: "Get a specific supplier contact by UID from a connected MYOB Business.",
|
|
191
|
+
inputSchema: {
|
|
192
|
+
myobBusinessId: myobBusinessIdField,
|
|
193
|
+
uid: z.string().describe("The supplier UID (GUID)")
|
|
194
|
+
}
|
|
195
|
+
}, async (args) => {
|
|
196
|
+
const result = await resolveClient(args.myobBusinessId).get(`/Contact/Supplier/${args.uid}`);
|
|
197
|
+
return { content: [{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: JSON.stringify(result, null, 2)
|
|
200
|
+
}] };
|
|
201
|
+
});
|
|
202
|
+
register("myob_create_supplier", {
|
|
203
|
+
description: "Create a new supplier contact in a connected MYOB Business.",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
myobBusinessId: myobBusinessIdField,
|
|
206
|
+
CompanyName: z.string().optional().describe("Company name"),
|
|
207
|
+
FirstName: z.string().optional().describe("First name"),
|
|
208
|
+
LastName: z.string().optional().describe("Last name"),
|
|
209
|
+
IsIndividual: z.boolean().optional().describe("true for individual, false for company"),
|
|
210
|
+
DisplayID: z.string().optional().describe("Display ID"),
|
|
211
|
+
data: z.record(z.string(), z.unknown()).optional().describe("Full supplier data object")
|
|
212
|
+
}
|
|
213
|
+
}, async (args) => {
|
|
214
|
+
const client = resolveClient(args.myobBusinessId);
|
|
215
|
+
const body = args.data ?? {};
|
|
216
|
+
if (args.CompanyName && !body.CompanyName) body.CompanyName = args.CompanyName;
|
|
217
|
+
if (args.FirstName && !body.FirstName) body.FirstName = args.FirstName;
|
|
218
|
+
if (args.LastName && !body.LastName) body.LastName = args.LastName;
|
|
219
|
+
if (args.IsIndividual !== void 0 && body.IsIndividual === void 0) body.IsIndividual = args.IsIndividual;
|
|
220
|
+
if (args.DisplayID && !body.DisplayID) body.DisplayID = args.DisplayID;
|
|
221
|
+
const result = await client.post("/Contact/Supplier", body);
|
|
222
|
+
return { content: [{
|
|
223
|
+
type: "text",
|
|
224
|
+
text: JSON.stringify(result, null, 2)
|
|
225
|
+
}] };
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/tools/sales.ts
|
|
230
|
+
function registerSalesTools(server, resolveClient) {
|
|
231
|
+
const register = server.registerTool.bind(server);
|
|
232
|
+
register("myob_list_invoices", {
|
|
233
|
+
description: "List sale invoices from a connected MYOB Business.",
|
|
234
|
+
inputSchema: {
|
|
235
|
+
myobBusinessId: myobBusinessIdField,
|
|
236
|
+
filter: z.string().optional().describe("OData $filter expression (e.g. \"Status eq 'Open'\")"),
|
|
237
|
+
top: z.string().optional().describe("Max records to return"),
|
|
238
|
+
skip: z.string().optional().describe("Number of records to skip")
|
|
239
|
+
}
|
|
240
|
+
}, async (args) => {
|
|
241
|
+
const client = resolveClient(args.myobBusinessId);
|
|
242
|
+
const params = {};
|
|
243
|
+
if (args.filter) params.$filter = args.filter;
|
|
244
|
+
if (args.top) params.$top = args.top;
|
|
245
|
+
if (args.skip) params.$skip = args.skip;
|
|
246
|
+
const result = await client.get("/Sale/Invoice", Object.keys(params).length > 0 ? params : void 0);
|
|
247
|
+
return { content: [{
|
|
248
|
+
type: "text",
|
|
249
|
+
text: JSON.stringify(result, null, 2)
|
|
250
|
+
}] };
|
|
251
|
+
});
|
|
252
|
+
register("myob_get_invoice", {
|
|
253
|
+
description: "Get a specific sale invoice by UID from a connected MYOB Business.",
|
|
254
|
+
inputSchema: {
|
|
255
|
+
myobBusinessId: myobBusinessIdField,
|
|
256
|
+
uid: z.string().describe("The invoice UID (GUID)")
|
|
257
|
+
}
|
|
258
|
+
}, async (args) => {
|
|
259
|
+
const result = await resolveClient(args.myobBusinessId).get(`/Sale/Invoice/${args.uid}`);
|
|
260
|
+
return { content: [{
|
|
261
|
+
type: "text",
|
|
262
|
+
text: JSON.stringify(result, null, 2)
|
|
263
|
+
}] };
|
|
264
|
+
});
|
|
265
|
+
register("myob_create_invoice", {
|
|
266
|
+
description: "Create a new sale invoice in a connected MYOB Business. Provide customer, line items, and dates.",
|
|
267
|
+
inputSchema: {
|
|
268
|
+
myobBusinessId: myobBusinessIdField,
|
|
269
|
+
data: z.record(z.string(), z.unknown()).describe("Full invoice data object including Customer, Date, Lines, etc.")
|
|
270
|
+
}
|
|
271
|
+
}, async (args) => {
|
|
272
|
+
const result = await resolveClient(args.myobBusinessId).post("/Sale/Invoice", args.data);
|
|
273
|
+
return { content: [{
|
|
274
|
+
type: "text",
|
|
275
|
+
text: JSON.stringify(result, null, 2)
|
|
276
|
+
}] };
|
|
277
|
+
});
|
|
278
|
+
register("myob_list_quotes", {
|
|
279
|
+
description: "List sale quotes from a connected MYOB Business.",
|
|
280
|
+
inputSchema: {
|
|
281
|
+
myobBusinessId: myobBusinessIdField,
|
|
282
|
+
filter: z.string().optional().describe("OData $filter expression"),
|
|
283
|
+
top: z.string().optional().describe("Max records to return"),
|
|
284
|
+
skip: z.string().optional().describe("Number of records to skip")
|
|
285
|
+
}
|
|
286
|
+
}, async (args) => {
|
|
287
|
+
const client = resolveClient(args.myobBusinessId);
|
|
288
|
+
const params = {};
|
|
289
|
+
if (args.filter) params.$filter = args.filter;
|
|
290
|
+
if (args.top) params.$top = args.top;
|
|
291
|
+
if (args.skip) params.$skip = args.skip;
|
|
292
|
+
const result = await client.get("/Sale/Quote", Object.keys(params).length > 0 ? params : void 0);
|
|
293
|
+
return { content: [{
|
|
294
|
+
type: "text",
|
|
295
|
+
text: JSON.stringify(result, null, 2)
|
|
296
|
+
}] };
|
|
297
|
+
});
|
|
298
|
+
register("myob_create_quote", {
|
|
299
|
+
description: "Create a new sale quote in a connected MYOB Business.",
|
|
300
|
+
inputSchema: {
|
|
301
|
+
myobBusinessId: myobBusinessIdField,
|
|
302
|
+
data: z.record(z.string(), z.unknown()).describe("Full quote data object including Customer, Date, Lines, etc.")
|
|
303
|
+
}
|
|
304
|
+
}, async (args) => {
|
|
305
|
+
const result = await resolveClient(args.myobBusinessId).post("/Sale/Quote", args.data);
|
|
306
|
+
return { content: [{
|
|
307
|
+
type: "text",
|
|
308
|
+
text: JSON.stringify(result, null, 2)
|
|
309
|
+
}] };
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/tools/purchases.ts
|
|
314
|
+
function registerPurchaseTools(server, resolveClient) {
|
|
315
|
+
const register = server.registerTool.bind(server);
|
|
316
|
+
register("myob_list_bills", {
|
|
317
|
+
description: "List purchase bills from a connected MYOB Business.",
|
|
318
|
+
inputSchema: {
|
|
319
|
+
myobBusinessId: myobBusinessIdField,
|
|
320
|
+
filter: z.string().optional().describe("OData $filter expression"),
|
|
321
|
+
top: z.string().optional().describe("Max records to return"),
|
|
322
|
+
skip: z.string().optional().describe("Number of records to skip")
|
|
323
|
+
}
|
|
324
|
+
}, async (args) => {
|
|
325
|
+
const client = resolveClient(args.myobBusinessId);
|
|
326
|
+
const params = {};
|
|
327
|
+
if (args.filter) params.$filter = args.filter;
|
|
328
|
+
if (args.top) params.$top = args.top;
|
|
329
|
+
if (args.skip) params.$skip = args.skip;
|
|
330
|
+
const result = await client.get("/Purchase/Bill", Object.keys(params).length > 0 ? params : void 0);
|
|
331
|
+
return { content: [{
|
|
332
|
+
type: "text",
|
|
333
|
+
text: JSON.stringify(result, null, 2)
|
|
334
|
+
}] };
|
|
335
|
+
});
|
|
336
|
+
register("myob_get_bill", {
|
|
337
|
+
description: "Get a specific purchase bill by UID from a connected MYOB Business.",
|
|
338
|
+
inputSchema: {
|
|
339
|
+
myobBusinessId: myobBusinessIdField,
|
|
340
|
+
uid: z.string().describe("The bill UID (GUID)")
|
|
341
|
+
}
|
|
342
|
+
}, async (args) => {
|
|
343
|
+
const result = await resolveClient(args.myobBusinessId).get(`/Purchase/Bill/${args.uid}`);
|
|
344
|
+
return { content: [{
|
|
345
|
+
type: "text",
|
|
346
|
+
text: JSON.stringify(result, null, 2)
|
|
347
|
+
}] };
|
|
348
|
+
});
|
|
349
|
+
register("myob_create_bill", {
|
|
350
|
+
description: "Create a new purchase bill in a connected MYOB Business.",
|
|
351
|
+
inputSchema: {
|
|
352
|
+
myobBusinessId: myobBusinessIdField,
|
|
353
|
+
data: z.record(z.string(), z.unknown()).describe("Full bill data object including Supplier, Date, Lines, etc.")
|
|
354
|
+
}
|
|
355
|
+
}, async (args) => {
|
|
356
|
+
const result = await resolveClient(args.myobBusinessId).post("/Purchase/Bill", args.data);
|
|
357
|
+
return { content: [{
|
|
358
|
+
type: "text",
|
|
359
|
+
text: JSON.stringify(result, null, 2)
|
|
360
|
+
}] };
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region src/tools/general-ledger.ts
|
|
365
|
+
function registerGeneralLedgerTools(server, resolveClient) {
|
|
366
|
+
const register = server.registerTool.bind(server);
|
|
367
|
+
register("myob_list_accounts", {
|
|
368
|
+
description: "List chart of accounts (ledger accounts) from a connected MYOB Business. NOT the connected businesses themselves — for that, use myob_list_businesses.",
|
|
369
|
+
inputSchema: {
|
|
370
|
+
myobBusinessId: myobBusinessIdField,
|
|
371
|
+
filter: z.string().optional().describe("OData $filter expression (e.g. \"Type eq 'Bank'\")"),
|
|
372
|
+
top: z.string().optional().describe("Max records to return"),
|
|
373
|
+
skip: z.string().optional().describe("Number of records to skip")
|
|
374
|
+
}
|
|
375
|
+
}, async (args) => {
|
|
376
|
+
const client = resolveClient(args.myobBusinessId);
|
|
377
|
+
const params = {};
|
|
378
|
+
if (args.filter) params.$filter = args.filter;
|
|
379
|
+
if (args.top) params.$top = args.top;
|
|
380
|
+
if (args.skip) params.$skip = args.skip;
|
|
381
|
+
const result = await client.get("/GeneralLedger/Account", Object.keys(params).length > 0 ? params : void 0);
|
|
382
|
+
return { content: [{
|
|
383
|
+
type: "text",
|
|
384
|
+
text: JSON.stringify(result, null, 2)
|
|
385
|
+
}] };
|
|
386
|
+
});
|
|
387
|
+
register("myob_get_account", {
|
|
388
|
+
description: "Get a specific general ledger account by UID from a connected MYOB Business.",
|
|
389
|
+
inputSchema: {
|
|
390
|
+
myobBusinessId: myobBusinessIdField,
|
|
391
|
+
uid: z.string().describe("The account UID (GUID)")
|
|
392
|
+
}
|
|
393
|
+
}, async (args) => {
|
|
394
|
+
const result = await resolveClient(args.myobBusinessId).get(`/GeneralLedger/Account/${args.uid}`);
|
|
395
|
+
return { content: [{
|
|
396
|
+
type: "text",
|
|
397
|
+
text: JSON.stringify(result, null, 2)
|
|
398
|
+
}] };
|
|
399
|
+
});
|
|
400
|
+
register("myob_list_journal_entries", {
|
|
401
|
+
description: "List journal transactions from a connected MYOB Business general ledger.",
|
|
402
|
+
inputSchema: {
|
|
403
|
+
myobBusinessId: myobBusinessIdField,
|
|
404
|
+
filter: z.string().optional().describe("OData $filter expression"),
|
|
405
|
+
top: z.string().optional().describe("Max records to return"),
|
|
406
|
+
skip: z.string().optional().describe("Number of records to skip")
|
|
407
|
+
}
|
|
408
|
+
}, async (args) => {
|
|
409
|
+
const client = resolveClient(args.myobBusinessId);
|
|
410
|
+
const params = {};
|
|
411
|
+
if (args.filter) params.$filter = args.filter;
|
|
412
|
+
if (args.top) params.$top = args.top;
|
|
413
|
+
if (args.skip) params.$skip = args.skip;
|
|
414
|
+
const result = await client.get("/GeneralLedger/JournalTransaction", Object.keys(params).length > 0 ? params : void 0);
|
|
415
|
+
return { content: [{
|
|
416
|
+
type: "text",
|
|
417
|
+
text: JSON.stringify(result, null, 2)
|
|
418
|
+
}] };
|
|
419
|
+
});
|
|
420
|
+
register("myob_create_journal_entry", {
|
|
421
|
+
description: "Create a new journal transaction in a connected MYOB Business.",
|
|
422
|
+
inputSchema: {
|
|
423
|
+
myobBusinessId: myobBusinessIdField,
|
|
424
|
+
data: z.record(z.string(), z.unknown()).describe("Full journal transaction data object including Lines, DateOccurred, Memo, etc.")
|
|
425
|
+
}
|
|
426
|
+
}, async (args) => {
|
|
427
|
+
const result = await resolveClient(args.myobBusinessId).post("/GeneralLedger/JournalTransaction", args.data);
|
|
428
|
+
return { content: [{
|
|
429
|
+
type: "text",
|
|
430
|
+
text: JSON.stringify(result, null, 2)
|
|
431
|
+
}] };
|
|
432
|
+
});
|
|
433
|
+
register("myob_list_tax_codes", {
|
|
434
|
+
description: "List tax codes from a connected MYOB Business (read-only for MYOB Essentials).",
|
|
435
|
+
inputSchema: {
|
|
436
|
+
myobBusinessId: myobBusinessIdField,
|
|
437
|
+
filter: z.string().optional().describe("OData $filter expression")
|
|
438
|
+
}
|
|
439
|
+
}, async (args) => {
|
|
440
|
+
const client = resolveClient(args.myobBusinessId);
|
|
441
|
+
const params = {};
|
|
442
|
+
if (args.filter) params.$filter = args.filter;
|
|
443
|
+
const result = await client.get("/GeneralLedger/TaxCode", Object.keys(params).length > 0 ? params : void 0);
|
|
444
|
+
return { content: [{
|
|
445
|
+
type: "text",
|
|
446
|
+
text: JSON.stringify(result, null, 2)
|
|
447
|
+
}] };
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/tools/inventory.ts
|
|
452
|
+
function registerInventoryTools(server, resolveClient) {
|
|
453
|
+
const register = server.registerTool.bind(server);
|
|
454
|
+
register("myob_list_items", {
|
|
455
|
+
description: "List inventory items from a connected MYOB Business.",
|
|
456
|
+
inputSchema: {
|
|
457
|
+
myobBusinessId: myobBusinessIdField,
|
|
458
|
+
filter: z.string().optional().describe("OData $filter expression"),
|
|
459
|
+
top: z.string().optional().describe("Max records to return"),
|
|
460
|
+
skip: z.string().optional().describe("Number of records to skip")
|
|
461
|
+
}
|
|
462
|
+
}, async (args) => {
|
|
463
|
+
const client = resolveClient(args.myobBusinessId);
|
|
464
|
+
const params = {};
|
|
465
|
+
if (args.filter) params.$filter = args.filter;
|
|
466
|
+
if (args.top) params.$top = args.top;
|
|
467
|
+
if (args.skip) params.$skip = args.skip;
|
|
468
|
+
const result = await client.get("/Inventory/Item", Object.keys(params).length > 0 ? params : void 0);
|
|
469
|
+
return { content: [{
|
|
470
|
+
type: "text",
|
|
471
|
+
text: JSON.stringify(result, null, 2)
|
|
472
|
+
}] };
|
|
473
|
+
});
|
|
474
|
+
register("myob_get_item", {
|
|
475
|
+
description: "Get a specific inventory item by UID from a connected MYOB Business.",
|
|
476
|
+
inputSchema: {
|
|
477
|
+
myobBusinessId: myobBusinessIdField,
|
|
478
|
+
uid: z.string().describe("The item UID (GUID)")
|
|
479
|
+
}
|
|
480
|
+
}, async (args) => {
|
|
481
|
+
const result = await resolveClient(args.myobBusinessId).get(`/Inventory/Item/${args.uid}`);
|
|
482
|
+
return { content: [{
|
|
483
|
+
type: "text",
|
|
484
|
+
text: JSON.stringify(result, null, 2)
|
|
485
|
+
}] };
|
|
486
|
+
});
|
|
487
|
+
register("myob_create_item", {
|
|
488
|
+
description: "Create a new inventory item in a connected MYOB Business.",
|
|
489
|
+
inputSchema: {
|
|
490
|
+
myobBusinessId: myobBusinessIdField,
|
|
491
|
+
data: z.record(z.string(), z.unknown()).describe("Full item data object including Name, Number, SellingDetails, BuyingDetails, etc.")
|
|
492
|
+
}
|
|
493
|
+
}, async (args) => {
|
|
494
|
+
const result = await resolveClient(args.myobBusinessId).post("/Inventory/Item", args.data);
|
|
495
|
+
return { content: [{
|
|
496
|
+
type: "text",
|
|
497
|
+
text: JSON.stringify(result, null, 2)
|
|
498
|
+
}] };
|
|
499
|
+
});
|
|
500
|
+
register("myob_update_item", {
|
|
501
|
+
description: "Update an existing inventory item in a connected MYOB Business.",
|
|
502
|
+
inputSchema: {
|
|
503
|
+
myobBusinessId: myobBusinessIdField,
|
|
504
|
+
uid: z.string().describe("The item UID (GUID) to update"),
|
|
505
|
+
data: z.record(z.string(), z.unknown()).describe("Item data fields to update")
|
|
506
|
+
}
|
|
507
|
+
}, async (args) => {
|
|
508
|
+
const result = await resolveClient(args.myobBusinessId).put(`/Inventory/Item/${args.uid}`, args.data);
|
|
509
|
+
return { content: [{
|
|
510
|
+
type: "text",
|
|
511
|
+
text: JSON.stringify(result, null, 2)
|
|
512
|
+
}] };
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
//#endregion
|
|
516
|
+
//#region src/tools/payments.ts
|
|
517
|
+
function registerPaymentTools(server, resolveClient) {
|
|
518
|
+
const register = server.registerTool.bind(server);
|
|
519
|
+
register("myob_list_customer_payments", {
|
|
520
|
+
description: "List customer payments (receipts) from a connected MYOB Business.",
|
|
521
|
+
inputSchema: {
|
|
522
|
+
myobBusinessId: myobBusinessIdField,
|
|
523
|
+
filter: z.string().optional().describe("OData $filter expression"),
|
|
524
|
+
top: z.string().optional().describe("Max records to return"),
|
|
525
|
+
skip: z.string().optional().describe("Number of records to skip")
|
|
526
|
+
}
|
|
527
|
+
}, async (args) => {
|
|
528
|
+
const client = resolveClient(args.myobBusinessId);
|
|
529
|
+
const params = {};
|
|
530
|
+
if (args.filter) params.$filter = args.filter;
|
|
531
|
+
if (args.top) params.$top = args.top;
|
|
532
|
+
if (args.skip) params.$skip = args.skip;
|
|
533
|
+
const result = await client.get("/Sale/CustomerPayment", Object.keys(params).length > 0 ? params : void 0);
|
|
534
|
+
return { content: [{
|
|
535
|
+
type: "text",
|
|
536
|
+
text: JSON.stringify(result, null, 2)
|
|
537
|
+
}] };
|
|
538
|
+
});
|
|
539
|
+
register("myob_record_customer_payment", {
|
|
540
|
+
description: "Record a customer payment against invoices in a connected MYOB Business.",
|
|
541
|
+
inputSchema: {
|
|
542
|
+
myobBusinessId: myobBusinessIdField,
|
|
543
|
+
data: z.record(z.string(), z.unknown()).describe("Full customer payment data including Customer, Account, Invoices, AmountReceived, etc.")
|
|
544
|
+
}
|
|
545
|
+
}, async (args) => {
|
|
546
|
+
const result = await resolveClient(args.myobBusinessId).post("/Sale/CustomerPayment", args.data);
|
|
547
|
+
return { content: [{
|
|
548
|
+
type: "text",
|
|
549
|
+
text: JSON.stringify(result, null, 2)
|
|
550
|
+
}] };
|
|
551
|
+
});
|
|
552
|
+
register("myob_list_supplier_payments", {
|
|
553
|
+
description: "List supplier payments from a connected MYOB Business.",
|
|
554
|
+
inputSchema: {
|
|
555
|
+
myobBusinessId: myobBusinessIdField,
|
|
556
|
+
filter: z.string().optional().describe("OData $filter expression"),
|
|
557
|
+
top: z.string().optional().describe("Max records to return"),
|
|
558
|
+
skip: z.string().optional().describe("Number of records to skip")
|
|
559
|
+
}
|
|
560
|
+
}, async (args) => {
|
|
561
|
+
const client = resolveClient(args.myobBusinessId);
|
|
562
|
+
const params = {};
|
|
563
|
+
if (args.filter) params.$filter = args.filter;
|
|
564
|
+
if (args.top) params.$top = args.top;
|
|
565
|
+
if (args.skip) params.$skip = args.skip;
|
|
566
|
+
const result = await client.get("/Purchase/SupplierPayment", Object.keys(params).length > 0 ? params : void 0);
|
|
567
|
+
return { content: [{
|
|
568
|
+
type: "text",
|
|
569
|
+
text: JSON.stringify(result, null, 2)
|
|
570
|
+
}] };
|
|
571
|
+
});
|
|
572
|
+
register("myob_record_supplier_payment", {
|
|
573
|
+
description: "Record a supplier payment against bills in a connected MYOB Business.",
|
|
574
|
+
inputSchema: {
|
|
575
|
+
myobBusinessId: myobBusinessIdField,
|
|
576
|
+
data: z.record(z.string(), z.unknown()).describe("Full supplier payment data including Supplier, Account, Bills, AmountPaid, etc.")
|
|
577
|
+
}
|
|
578
|
+
}, async (args) => {
|
|
579
|
+
const result = await resolveClient(args.myobBusinessId).post("/Purchase/SupplierPayment", args.data);
|
|
580
|
+
return { content: [{
|
|
581
|
+
type: "text",
|
|
582
|
+
text: JSON.stringify(result, null, 2)
|
|
583
|
+
}] };
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
//#endregion
|
|
587
|
+
//#region src/server.ts
|
|
588
|
+
/**
|
|
589
|
+
* MYOB Business MCP Server (Pattern A multi-account)
|
|
590
|
+
*
|
|
591
|
+
* Custom MCP server implementing MYOB Business API tools directly
|
|
592
|
+
* (unlike the Xero proxy, which wraps a child MCP). Every tool that
|
|
593
|
+
* touches credentials requires the LLM to pass `myobBusinessId`
|
|
594
|
+
* explicitly — the server keeps one `MyobClient` per connected MYOB
|
|
595
|
+
* business and dispatches per-call.
|
|
596
|
+
*
|
|
597
|
+
* Pattern A locked in PR 7.1 of channels-and-credential-driven-
|
|
598
|
+
* integrations. See `packages/mcp-bundler/DEVELOPING.md` for the
|
|
599
|
+
* contract; `services/connect/DEVELOPING.md` for the provider table.
|
|
600
|
+
*
|
|
601
|
+
* Architecture:
|
|
602
|
+
* OpenClaw ←(stdio)→ this server ←(https)→ MYOB Business API
|
|
603
|
+
*/
|
|
604
|
+
const clients = /* @__PURE__ */ new Map();
|
|
605
|
+
let refreshTimer = null;
|
|
606
|
+
let inFlightRefresh = null;
|
|
607
|
+
const REFRESH_INTERVAL_MS = 900 * 1e3;
|
|
608
|
+
const REFRESH_RETRY_MS = 60 * 1e3;
|
|
609
|
+
function log(msg) {
|
|
610
|
+
process.stderr.write(`[myob-mcp-server] ${msg}\n`);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Resolve a `myobBusinessId` selector to its `MyobClient`. Throws if
|
|
614
|
+
* unknown — the LLM should call `myob_list_businesses` to discover
|
|
615
|
+
* the valid set. This is the per-call dispatch contract for Pattern A.
|
|
616
|
+
*/
|
|
617
|
+
function resolveMyobClient(myobBusinessId) {
|
|
618
|
+
const c = clients.get(myobBusinessId);
|
|
619
|
+
if (!c) throw new Error(`Unknown myobBusinessId: ${myobBusinessId}. Call myob_list_businesses to see the connected businesses on this agent.`);
|
|
620
|
+
return c;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Single fan-out point for token refresh. Used by:
|
|
624
|
+
* - the scheduled timer (performRefresh)
|
|
625
|
+
* - the `myob_refresh_token` tool
|
|
626
|
+
* - every cached `MyobClient`'s `onRefresh` callback (on 401 retry)
|
|
627
|
+
*
|
|
628
|
+
* Concurrent callers share one in-flight `refreshMYOBToken()` request
|
|
629
|
+
* via `inFlightRefresh`, and the resolved token is fanned out to every
|
|
630
|
+
* cached client so siblings don't re-hit the network on their own 401s.
|
|
631
|
+
*
|
|
632
|
+
* v1 limitation: `apiClient.refreshMYOBToken()` refreshes the primary
|
|
633
|
+
* connection's token only and we fan it across all clients. This works
|
|
634
|
+
* as long as MYOB access tokens are interchangeable across businesses
|
|
635
|
+
* sharing a partner appId (true today). Per-connection refresh is a v2
|
|
636
|
+
* follow-up — see DEVELOPING.md.
|
|
637
|
+
*/
|
|
638
|
+
async function refreshAndFanOut(apiClient) {
|
|
639
|
+
if (inFlightRefresh) return inFlightRefresh;
|
|
640
|
+
inFlightRefresh = (async () => {
|
|
641
|
+
try {
|
|
642
|
+
const { accessToken } = await apiClient.refreshMYOBToken();
|
|
643
|
+
for (const client of clients.values()) client.updateToken(accessToken);
|
|
644
|
+
return accessToken;
|
|
645
|
+
} finally {
|
|
646
|
+
inFlightRefresh = null;
|
|
647
|
+
}
|
|
648
|
+
})();
|
|
649
|
+
return inFlightRefresh;
|
|
650
|
+
}
|
|
651
|
+
async function performRefresh(apiClient) {
|
|
652
|
+
log("Refreshing MYOB access tokens across all connected businesses...");
|
|
653
|
+
await refreshAndFanOut(apiClient);
|
|
654
|
+
log("Token refresh applied to all clients");
|
|
655
|
+
}
|
|
656
|
+
function scheduleRefresh(apiClient) {
|
|
657
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
658
|
+
refreshTimer = setTimeout(() => {
|
|
659
|
+
(async () => {
|
|
660
|
+
try {
|
|
661
|
+
await performRefresh(apiClient);
|
|
662
|
+
scheduleRefresh(apiClient);
|
|
663
|
+
} catch (err) {
|
|
664
|
+
log(`Token refresh failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
665
|
+
refreshTimer = setTimeout(() => {
|
|
666
|
+
scheduleRefresh(apiClient);
|
|
667
|
+
}, REFRESH_RETRY_MS);
|
|
668
|
+
}
|
|
669
|
+
})();
|
|
670
|
+
}, REFRESH_INTERVAL_MS);
|
|
671
|
+
}
|
|
672
|
+
async function main() {
|
|
673
|
+
const config = resolveConfig();
|
|
674
|
+
const apiClient = new AgentApiClient({
|
|
675
|
+
apiKey: config.apiKey,
|
|
676
|
+
apiUrl: config.apiUrl
|
|
677
|
+
});
|
|
678
|
+
const { accounts } = await apiClient.getMYOBAccounts();
|
|
679
|
+
if (accounts.length === 0) log("No MYOB businesses connected — server will start with myob_list_businesses + myob_refresh_token only");
|
|
680
|
+
for (const acct of accounts) {
|
|
681
|
+
if (clients.has(acct.myobBusinessId)) {
|
|
682
|
+
log(`Duplicate myobBusinessId ${acct.myobBusinessId} returned by getMYOBAccounts() — keeping the first cached client`);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
clients.set(acct.myobBusinessId, new MyobClient({
|
|
686
|
+
accessToken: acct.accessToken,
|
|
687
|
+
clientId: acct.clientId,
|
|
688
|
+
businessId: acct.myobBusinessId,
|
|
689
|
+
onRefresh: async () => {
|
|
690
|
+
return { accessToken: await refreshAndFanOut(apiClient) };
|
|
691
|
+
}
|
|
692
|
+
}));
|
|
693
|
+
log(`Cached client for business ${acct.myobBusinessId} (${acct.displayName ?? "no display name"})`);
|
|
694
|
+
}
|
|
695
|
+
const server = new McpServer({
|
|
696
|
+
name: "myob-mcp-server",
|
|
697
|
+
version: "2.0.0"
|
|
698
|
+
});
|
|
699
|
+
registerContactTools(server, resolveMyobClient);
|
|
700
|
+
registerSalesTools(server, resolveMyobClient);
|
|
701
|
+
registerPurchaseTools(server, resolveMyobClient);
|
|
702
|
+
registerGeneralLedgerTools(server, resolveMyobClient);
|
|
703
|
+
registerInventoryTools(server, resolveMyobClient);
|
|
704
|
+
registerPaymentTools(server, resolveMyobClient);
|
|
705
|
+
const registerTool = server.registerTool.bind(server);
|
|
706
|
+
registerTool("myob_list_businesses", { description: "List the MYOB Business accounts (companies) the agent has connected. Returns one entry per OAuth connection — use the returned myobBusinessId values as the `myobBusinessId` selector arg on every other myob_* tool." }, () => {
|
|
707
|
+
const summaries = accounts.map((a) => ({
|
|
708
|
+
myobBusinessId: a.myobBusinessId,
|
|
709
|
+
displayName: a.displayName,
|
|
710
|
+
connectedAt: a.connectedAt
|
|
711
|
+
}));
|
|
712
|
+
return { content: [{
|
|
713
|
+
type: "text",
|
|
714
|
+
text: JSON.stringify({ businesses: summaries }, null, 2)
|
|
715
|
+
}] };
|
|
716
|
+
});
|
|
717
|
+
registerTool("myob_refresh_token", { description: "Force-refresh the MYOB OAuth2 access token. Affects every connected MYOB business (tokens are interchangeable across businesses sharing a partner appId). Use this if API calls are failing with authentication errors." }, async () => {
|
|
718
|
+
try {
|
|
719
|
+
await performRefresh(apiClient);
|
|
720
|
+
return { content: [{
|
|
721
|
+
type: "text",
|
|
722
|
+
text: JSON.stringify({ refreshed: true })
|
|
723
|
+
}] };
|
|
724
|
+
} catch (err) {
|
|
725
|
+
return {
|
|
726
|
+
content: [{
|
|
727
|
+
type: "text",
|
|
728
|
+
text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) })
|
|
729
|
+
}],
|
|
730
|
+
isError: true
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
scheduleRefresh(apiClient);
|
|
735
|
+
const transport = new StdioServerTransport();
|
|
736
|
+
await server.connect(transport);
|
|
737
|
+
log(`MYOB MCP server running with ${String(accounts.length)} connected business(es) and Pattern A selector enforcement`);
|
|
738
|
+
}
|
|
739
|
+
for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, () => {
|
|
740
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
741
|
+
process.exit(0);
|
|
742
|
+
});
|
|
743
|
+
main().catch((err) => {
|
|
744
|
+
log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
745
|
+
process.exit(1);
|
|
746
|
+
});
|
|
747
|
+
//#endregion
|
|
748
|
+
export { resolveMyobClient };
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alfe.ai/myob-mcp",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "MYOB Business MCP server — direct API integration with Alfe OAuth credentials and automatic token refresh",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"myob-mcp-server": "./dist/server.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/server.d.ts",
|
|
13
|
+
"import": "./dist/server.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
21
|
+
"zod": "^4.0.5",
|
|
22
|
+
"@alfe.ai/config": "0.1.0",
|
|
23
|
+
"@alfe.ai/agent-api-client": "0.3.0"
|
|
24
|
+
},
|
|
25
|
+
"license": "UNLICENSED",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsdown",
|
|
28
|
+
"dev": "tsdown --watch",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"lint": "eslint ."
|
|
31
|
+
}
|
|
32
|
+
}
|