xero-mcp 1.3.0 → 2.0.0-beta
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/LICENSE +21 -21
- package/README.md +129 -109
- package/build/Tools/Accounting/BankTransactions.js +91 -3
- package/build/Tools/Accounting/Contacts.js +3 -3
- package/build/Tools/Accounting/Invoices.js +91 -0
- package/build/Tools/McpToolsFactory.js +6 -4
- package/build/{Tools/Utils → Utils}/sanitizeValues.js +19 -11
- package/build/{Tools/Utils → Utils}/sanitizeValues.test.js +72 -12
- package/build/XeroApiClient.js +1 -1
- package/build/XeroMcpServer.js +32 -1
- package/build/index.js +0 -0
- package/package.json +47 -47
- /package/build/{Tools/Utils → Utils}/convertToCamelCase.js +0 -0
- /package/build/{Tools/Utils → Utils}/convertToCamelCase.test.js +0 -0
- /package/build/{Tools/Utils → Utils}/parseArrayValues.js +0 -0
- /package/build/{Tools/Utils → Utils}/parseArrayValues.test.js +0 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 John Zhang
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 John Zhang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,109 +1,129 @@
|
|
|
1
|
-
# Xero MCP Server
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-
[](https://smithery.ai/server/@john-zhang-dev/xero-mcp)
|
|
5
|
-
|
|
6
|
-
This MCP server allows Clients to interact with [Xero Accounting Software](https://www.xero.com).
|
|
7
|
-
|
|
8
|
-
## Get Started
|
|
9
|
-
|
|
10
|
-
1. Make sure [node](https://nodejs.org) and [Claude Desktop](https://claude.ai/download) are installed.
|
|
11
|
-
|
|
12
|
-
2. Create an OAuth 2.0 app in Xero to get a _CLIENT_ID_ and _CLIENT_SECRET_.
|
|
13
|
-
|
|
14
|
-
- Create a free Xero user account (if you don't have one)
|
|
15
|
-
- Login to Xero Developer center https://developer.xero.com/app/manage/
|
|
16
|
-
- Click New app
|
|
17
|
-
- Enter a name for your app
|
|
18
|
-
- Select Web app
|
|
19
|
-
- Provide a valid URL (can be anything valid eg. https://www.myapp.com)
|
|
20
|
-
- Enter redirect URI: `http://localhost:5000/callback`
|
|
21
|
-
- Tick to Accept the Terms & Conditions and click Create app
|
|
22
|
-
- On the left-hand side of the screen select Configuration
|
|
23
|
-
- Click Generate a secret
|
|
24
|
-
|
|
25
|
-
3. Modify `claude_desktop_config.json` file
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- `
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- `
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
- `
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
- `
|
|
68
|
-
|
|
69
|
-
Retrieves
|
|
70
|
-
|
|
71
|
-
- `
|
|
72
|
-
|
|
73
|
-
Retrieves
|
|
74
|
-
|
|
75
|
-
- `
|
|
76
|
-
|
|
77
|
-
Retrieves
|
|
78
|
-
|
|
79
|
-
- `
|
|
80
|
-
|
|
81
|
-
Retrieves
|
|
82
|
-
|
|
83
|
-
- `
|
|
84
|
-
|
|
85
|
-
Retrieves
|
|
86
|
-
|
|
87
|
-
- `
|
|
88
|
-
|
|
89
|
-
Retrieves
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
1
|
+
# Xero MCP Server
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](https://smithery.ai/server/@john-zhang-dev/xero-mcp)
|
|
5
|
+
|
|
6
|
+
This MCP server allows Clients to interact with [Xero Accounting Software](https://www.xero.com).
|
|
7
|
+
|
|
8
|
+
## Get Started
|
|
9
|
+
|
|
10
|
+
1. Make sure [node](https://nodejs.org) and [Claude Desktop](https://claude.ai/download) are installed.
|
|
11
|
+
|
|
12
|
+
2. Create an OAuth 2.0 app in Xero to get a _CLIENT_ID_ and _CLIENT_SECRET_.
|
|
13
|
+
|
|
14
|
+
- Create a free Xero user account (if you don't have one)
|
|
15
|
+
- Login to Xero Developer center https://developer.xero.com/app/manage/
|
|
16
|
+
- Click New app
|
|
17
|
+
- Enter a name for your app
|
|
18
|
+
- Select Web app
|
|
19
|
+
- Provide a valid URL (can be anything valid eg. https://www.myapp.com)
|
|
20
|
+
- Enter redirect URI: `http://localhost:5000/callback`
|
|
21
|
+
- Tick to Accept the Terms & Conditions and click Create app
|
|
22
|
+
- On the left-hand side of the screen select Configuration
|
|
23
|
+
- Click Generate a secret
|
|
24
|
+
|
|
25
|
+
3. Modify `claude_desktop_config.json` file:
|
|
26
|
+
|
|
27
|
+
Go to Settings -> Developers -> Local MCP Servers -> Edit Config
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"xero-mcp": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["-y", "xero-mcp@latest"],
|
|
35
|
+
"env": {
|
|
36
|
+
"XERO_CLIENT_ID": "YOUR_CLIENT_ID",
|
|
37
|
+
"XERO_CLIENT_SECRET": "YOUR_CLIENT_SECRET",
|
|
38
|
+
"XERO_REDIRECT_URI": "http://localhost:5000/callback"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
4. Restart Claude Desktop, or from Claude Desktop Menu:
|
|
46
|
+
|
|
47
|
+
Developer -> Reload MCP Configuration
|
|
48
|
+
|
|
49
|
+
5. When the Client decides to access a Xero tool for the first time, a Xero login page will pop up to ask your consent. Complete the auth flow and manually close the web page (as the Xero page will not auto close in this version)
|
|
50
|
+
|
|
51
|
+
**Privacy alert: after completing the Xero OAuth2 flow, your Xero data may go through the LLM that you use. If you are doing testing you should authorize to your [Xero Demo Company](https://central.xero.com/s/article/Use-the-demo-company).**
|
|
52
|
+
|
|
53
|
+
## Tools
|
|
54
|
+
|
|
55
|
+
- `authenticate`
|
|
56
|
+
|
|
57
|
+
Authenticate with Xero using OAuth2
|
|
58
|
+
|
|
59
|
+
- `create_bank_transactions`
|
|
60
|
+
|
|
61
|
+
Creates one or more spent or received money transaction
|
|
62
|
+
|
|
63
|
+
- `create_contacts`
|
|
64
|
+
|
|
65
|
+
Creates one or multiple contacts in a Xero organisation
|
|
66
|
+
|
|
67
|
+
- `get_balance_sheet`
|
|
68
|
+
|
|
69
|
+
Retrieves report for balancesheet
|
|
70
|
+
|
|
71
|
+
- `get_bank_transaction`
|
|
72
|
+
|
|
73
|
+
Retrieves a single spent or received money transaction by its Xero bank transaction ID
|
|
74
|
+
|
|
75
|
+
- `get_invoice`
|
|
76
|
+
|
|
77
|
+
Retrieves a single sales invoice or purchase bill by its Xero invoice ID
|
|
78
|
+
|
|
79
|
+
- `list_accounts`
|
|
80
|
+
|
|
81
|
+
Retrieves the full chart of accounts
|
|
82
|
+
|
|
83
|
+
- `list_bank_transactions`
|
|
84
|
+
|
|
85
|
+
Retrieves any spent or received money transactions
|
|
86
|
+
|
|
87
|
+
- `list_contacts`
|
|
88
|
+
|
|
89
|
+
Retrieves all contacts in a Xero organisation
|
|
90
|
+
|
|
91
|
+
- `list_invoices`
|
|
92
|
+
|
|
93
|
+
Retrieves sales invoices or purchase bills
|
|
94
|
+
|
|
95
|
+
- `list_organisations`
|
|
96
|
+
|
|
97
|
+
Retrieves Xero organisation details
|
|
98
|
+
|
|
99
|
+
- `list_payments`
|
|
100
|
+
|
|
101
|
+
Retrieves payments for invoices and credit notes
|
|
102
|
+
|
|
103
|
+
- `list_quotes`
|
|
104
|
+
|
|
105
|
+
Retrieves sales quotes
|
|
106
|
+
|
|
107
|
+
- `update_bank_transaction`
|
|
108
|
+
|
|
109
|
+
Updates an existing spent or received money transaction (e.g. line items, contact, bank account) by bank transaction ID
|
|
110
|
+
|
|
111
|
+
- `update_invoice`
|
|
112
|
+
|
|
113
|
+
Updates an existing sales invoice or purchase bill (typically a draft), including line items and account codes
|
|
114
|
+
|
|
115
|
+
## Examples
|
|
116
|
+
|
|
117
|
+
- "Visualize my financial position over the last month"
|
|
118
|
+
|
|
119
|
+
<img src="https://github.com/john-zhang-dev/assets/blob/main/xero-mcp/demo1.jpg?raw=true" width=50% height=50%>
|
|
120
|
+
|
|
121
|
+
- "Track my spendings over last week"
|
|
122
|
+
|
|
123
|
+
<img src="https://github.com/john-zhang-dev/assets/blob/main/xero-mcp/demo2.jpg?raw=true" width=50% height=50%>
|
|
124
|
+
|
|
125
|
+
- "Add all transactions from the monthly statement into my revenue account (account code 201) as receive money"
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
@@ -1,9 +1,44 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { XeroClientSession } from "../../XeroApiClient.js";
|
|
3
3
|
import { XeroAccountingApiSchema } from "../../Resources/xero_accounting.js";
|
|
4
|
-
import { parseArrayValues } from "
|
|
5
|
-
import { convertToCamelCase } from "
|
|
6
|
-
import { sanitizeObject } from "
|
|
4
|
+
import { parseArrayValues } from "../../Utils/parseArrayValues.js";
|
|
5
|
+
import { convertToCamelCase } from "../../Utils/convertToCamelCase.js";
|
|
6
|
+
import { sanitizeObject } from "../../Utils/sanitizeValues.js";
|
|
7
|
+
export const GetBankTransactionTool = {
|
|
8
|
+
requestSchema: {
|
|
9
|
+
name: "get_bank_transaction",
|
|
10
|
+
description: "Retrieves a single spent or received money transaction by its Xero bank transaction ID",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
bankTransactionID: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Xero-generated unique identifier for the bank transaction (UUID)",
|
|
17
|
+
},
|
|
18
|
+
unitdp: {
|
|
19
|
+
type: "number",
|
|
20
|
+
description: "Optional. Unit decimal places (e.g. 4) for unit amounts on line items",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
required: ["bankTransactionID"],
|
|
24
|
+
},
|
|
25
|
+
output: { content: [{ type: "text", text: z.string() }] },
|
|
26
|
+
},
|
|
27
|
+
requestHandler: async (request) => {
|
|
28
|
+
const bankTransactionID = request.params.arguments
|
|
29
|
+
?.bankTransactionID;
|
|
30
|
+
const unitdp = request.params.arguments?.unitdp;
|
|
31
|
+
const response = await XeroClientSession.xeroClient.accountingApi.getBankTransaction(XeroClientSession.activeTenantId(), bankTransactionID, unitdp);
|
|
32
|
+
return {
|
|
33
|
+
content: [
|
|
34
|
+
{
|
|
35
|
+
type: "text",
|
|
36
|
+
text: JSON.stringify(response.body.bankTransactions ?? []),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
};
|
|
7
42
|
export const ListBankTransactionsTool = {
|
|
8
43
|
requestSchema: {
|
|
9
44
|
name: "list_bank_transactions",
|
|
@@ -56,3 +91,56 @@ export const CreateBankTransactionsTool = {
|
|
|
56
91
|
return { content: [{ type: "text", text: JSON.stringify(response.body) }] };
|
|
57
92
|
},
|
|
58
93
|
};
|
|
94
|
+
export const UpdateBankTransactionTool = {
|
|
95
|
+
requestSchema: {
|
|
96
|
+
name: "update_bank_transaction",
|
|
97
|
+
description: "Updates an existing spent or received money transaction by its Xero bank transaction ID",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
bankTransactionID: {
|
|
102
|
+
type: "string",
|
|
103
|
+
description: "Xero generated unique identifier for the bank transaction (UUID)",
|
|
104
|
+
},
|
|
105
|
+
bankTransactions: {
|
|
106
|
+
type: "object",
|
|
107
|
+
description: "BankTransactions payload containing an array of bank transaction objects",
|
|
108
|
+
properties: XeroAccountingApiSchema.components.schemas.BankTransactions.properties,
|
|
109
|
+
example: '{ bankTransactions: [{ type: "SPEND", date: "2026-01-01", reference: "Expense Update", subTotal: 100, total: 115, totalTax: 15, lineItems: [{ accountCode: "401", description: "Taxi fare", lineAmount: 115 }], contact: { contactID: "00000000-0000-0000-0000-000000000000" }, bankAccount: { accountID: "6f7594f2-f059-4d56-9e67-47ac9733bfe9" }, status: "AUTHORISED" }]}',
|
|
110
|
+
},
|
|
111
|
+
unitdp: {
|
|
112
|
+
type: "number",
|
|
113
|
+
description: "Optional. Unit decimal places (e.g. 4) for unit amounts on line items",
|
|
114
|
+
},
|
|
115
|
+
idempotencyKey: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "Optional idempotency key. Allows safe retries without duplicating processing",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
required: ["bankTransactionID", "bankTransactions"],
|
|
121
|
+
},
|
|
122
|
+
output: { content: [{ type: "text", text: z.string() }] },
|
|
123
|
+
},
|
|
124
|
+
requestHandler: async (request) => {
|
|
125
|
+
const rawInputData = request.params.arguments;
|
|
126
|
+
const parsedData = parseArrayValues(rawInputData);
|
|
127
|
+
const bankTransactionID = parsedData?.bankTransactionID;
|
|
128
|
+
const unitdp = parsedData?.unitdp;
|
|
129
|
+
const idempotencyKey = parsedData?.idempotencyKey;
|
|
130
|
+
const rawBankTransactionsPayload = parsedData?.bankTransactions;
|
|
131
|
+
const bankTransactionsPayload = sanitizeObject(convertToCamelCase(rawBankTransactionsPayload));
|
|
132
|
+
if (!bankTransactionID) {
|
|
133
|
+
// Should be prevented by request schema, but keep a hard guard.
|
|
134
|
+
throw new Error("Missing required parameter: bankTransactionID");
|
|
135
|
+
}
|
|
136
|
+
const response = await XeroClientSession.xeroClient.accountingApi.updateBankTransaction(XeroClientSession.activeTenantId(), bankTransactionID, bankTransactionsPayload, unitdp, idempotencyKey);
|
|
137
|
+
return {
|
|
138
|
+
content: [
|
|
139
|
+
{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: JSON.stringify(response.body ?? response.response?.status),
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { XeroClientSession } from "../../XeroApiClient.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { XeroAccountingApiSchema } from "../../Resources/xero_accounting.js";
|
|
4
|
-
import { parseArrayValues } from "
|
|
5
|
-
import { convertToCamelCase } from "
|
|
6
|
-
import { sanitizeObject } from "
|
|
4
|
+
import { parseArrayValues } from "../../Utils/parseArrayValues.js";
|
|
5
|
+
import { convertToCamelCase } from "../../Utils/convertToCamelCase.js";
|
|
6
|
+
import { sanitizeObject } from "../../Utils/sanitizeValues.js";
|
|
7
7
|
export const ListContactsTool = {
|
|
8
8
|
requestSchema: {
|
|
9
9
|
name: "list_contacts",
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { XeroClientSession } from "../../XeroApiClient.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { XeroAccountingApiSchema } from "../../Resources/xero_accounting.js";
|
|
4
|
+
import { parseArrayValues } from "../../Utils/parseArrayValues.js";
|
|
5
|
+
import { convertToCamelCase } from "../../Utils/convertToCamelCase.js";
|
|
6
|
+
import { sanitizeObject } from "../../Utils/sanitizeValues.js";
|
|
3
7
|
export const ListInvoicesTool = {
|
|
4
8
|
requestSchema: {
|
|
5
9
|
name: "list_invoices",
|
|
@@ -32,3 +36,90 @@ export const ListInvoicesTool = {
|
|
|
32
36
|
};
|
|
33
37
|
},
|
|
34
38
|
};
|
|
39
|
+
export const GetInvoiceTool = {
|
|
40
|
+
requestSchema: {
|
|
41
|
+
name: "get_invoice",
|
|
42
|
+
description: "Retrieves a single sales invoice or purchase bill by its Xero invoice ID",
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: {
|
|
46
|
+
invoiceID: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Xero-generated unique identifier for the invoice (UUID)",
|
|
49
|
+
},
|
|
50
|
+
unitdp: {
|
|
51
|
+
type: "number",
|
|
52
|
+
description: "Optional. Unit decimal places (e.g. 4) for unit amounts on line items",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
required: ["invoiceID"],
|
|
56
|
+
},
|
|
57
|
+
output: { content: [{ type: "text", text: z.string() }] },
|
|
58
|
+
},
|
|
59
|
+
requestHandler: async (request) => {
|
|
60
|
+
const invoiceID = request.params.arguments?.invoiceID;
|
|
61
|
+
const unitdp = request.params.arguments?.unitdp;
|
|
62
|
+
const response = await XeroClientSession.xeroClient.accountingApi.getInvoice(XeroClientSession.activeTenantId(), invoiceID, unitdp);
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: JSON.stringify(response.body.invoices ?? []),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
export const UpdateInvoiceTool = {
|
|
74
|
+
requestSchema: {
|
|
75
|
+
name: "update_invoice",
|
|
76
|
+
description: "Updates an existing sales invoice or purchase bill (typically a draft) to change fields like line items and account codes",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
invoiceID: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "Xero generated unique identifier for the invoice (UUID)",
|
|
83
|
+
},
|
|
84
|
+
invoices: {
|
|
85
|
+
type: "object",
|
|
86
|
+
description: "Invoices payload containing an array of invoice objects",
|
|
87
|
+
properties: XeroAccountingApiSchema.components.schemas.Invoices.properties,
|
|
88
|
+
example: '{ invoices: [{ type: "ACCREC", contact: { contactId: "00000000-0000-0000-0000-000000000000" }, date: "2026-01-01", dueDate: "2026-01-15", lineItems: [{ description: "Service", quantity: 1, unitAmount: 100, accountCode: "400", tracking: [] }], reference: "Website Design", status: "DRAFT" }]}',
|
|
89
|
+
},
|
|
90
|
+
unitdp: {
|
|
91
|
+
type: "number",
|
|
92
|
+
description: "Optional. Unit decimal places (e.g. 4) for unit amounts on line items",
|
|
93
|
+
},
|
|
94
|
+
idempotencyKey: {
|
|
95
|
+
type: "string",
|
|
96
|
+
description: "Optional idempotency key. Allows safe retries without duplicating processing",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
required: ["invoiceID", "invoices"],
|
|
100
|
+
},
|
|
101
|
+
output: { content: [{ type: "text", text: z.string() }] },
|
|
102
|
+
},
|
|
103
|
+
requestHandler: async (request) => {
|
|
104
|
+
const rawInputData = request.params.arguments;
|
|
105
|
+
const parsedData = parseArrayValues(rawInputData);
|
|
106
|
+
const invoiceID = parsedData?.invoiceID;
|
|
107
|
+
const unitdp = parsedData?.unitdp;
|
|
108
|
+
const idempotencyKey = parsedData?.idempotencyKey;
|
|
109
|
+
const rawInvoicesPayload = parsedData?.invoices;
|
|
110
|
+
const invoicesPayload = sanitizeObject(convertToCamelCase(rawInvoicesPayload));
|
|
111
|
+
if (!invoiceID) {
|
|
112
|
+
// Should be prevented by request schema, but keep a hard guard.
|
|
113
|
+
throw new Error("Missing required parameter: invoiceID");
|
|
114
|
+
}
|
|
115
|
+
const response = await XeroClientSession.xeroClient.accountingApi.updateInvoice(XeroClientSession.activeTenantId(), invoiceID, invoicesPayload, unitdp, idempotencyKey);
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: "text",
|
|
120
|
+
text: JSON.stringify(response.body ?? response.response?.status),
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { ListAccountsTool } from "./Accounting/Accounts.js";
|
|
2
2
|
import { AuthenticateTool } from "./Authenticate.js";
|
|
3
|
-
import { CreateBankTransactionsTool, ListBankTransactionsTool } from "./Accounting/BankTransactions.js";
|
|
3
|
+
import { CreateBankTransactionsTool, GetBankTransactionTool, ListBankTransactionsTool, UpdateBankTransactionTool, } from "./Accounting/BankTransactions.js";
|
|
4
4
|
import { CreateContactsTool, ListContactsTool } from "./Accounting/Contacts.js";
|
|
5
|
-
import { ListInvoicesTool } from "./Accounting/Invoices.js";
|
|
6
|
-
import { ListJournalsTool } from "./Accounting/Journals.js";
|
|
5
|
+
import { GetInvoiceTool, ListInvoicesTool, UpdateInvoiceTool, } from "./Accounting/Invoices.js";
|
|
7
6
|
import { ListOrganisationsTool } from "./Accounting/Organisations.js";
|
|
8
7
|
import { ListPaymentsTool } from "./Accounting/Payments.js";
|
|
9
8
|
import { ListQuotesTool } from "./Accounting/Quotes.js";
|
|
@@ -14,14 +13,17 @@ export const McpToolsFactory = (function () {
|
|
|
14
13
|
CreateBankTransactionsTool,
|
|
15
14
|
CreateContactsTool,
|
|
16
15
|
GetBalanceSheetTool,
|
|
16
|
+
GetBankTransactionTool,
|
|
17
|
+
GetInvoiceTool,
|
|
17
18
|
ListAccountsTool,
|
|
18
19
|
ListBankTransactionsTool,
|
|
19
20
|
ListContactsTool,
|
|
20
21
|
ListInvoicesTool,
|
|
21
|
-
ListJournalsTool,
|
|
22
22
|
ListOrganisationsTool,
|
|
23
23
|
ListPaymentsTool,
|
|
24
24
|
ListQuotesTool,
|
|
25
|
+
UpdateBankTransactionTool,
|
|
26
|
+
UpdateInvoiceTool,
|
|
25
27
|
// register new tools here alphabetically
|
|
26
28
|
];
|
|
27
29
|
return {
|
|
@@ -3,25 +3,33 @@
|
|
|
3
3
|
* 1. Removing javascript protocol
|
|
4
4
|
* 2. Removing HTML tags
|
|
5
5
|
* 3. Escaping special characters
|
|
6
|
-
* 4.
|
|
6
|
+
* 4. Preventing SQL injections
|
|
7
|
+
* 5. Trimming whitespace
|
|
7
8
|
*/
|
|
8
9
|
export function sanitizeValue(value) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
if (typeof value !== 'string') {
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
let result = value;
|
|
14
|
+
// Remove javascript protocol
|
|
15
|
+
result = result.replace(/javascript:/gi, '');
|
|
16
|
+
// Remove HTML tags
|
|
17
|
+
result = result.replace(/<[^>]*>/g, '');
|
|
18
|
+
// Escape HTML special characters
|
|
19
|
+
result = result.replace(/[&<>]/g, char => {
|
|
16
20
|
const escapeMap = {
|
|
17
21
|
'&': '&',
|
|
18
22
|
'<': '<',
|
|
19
23
|
'>': '>'
|
|
20
24
|
};
|
|
21
25
|
return escapeMap[char];
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
});
|
|
27
|
+
// Prevent SQL injection - escape single quotes
|
|
28
|
+
result = result.replace(/(['"])/g, match => {
|
|
29
|
+
return match === "'" ? "''" : match;
|
|
30
|
+
});
|
|
31
|
+
// Trim whitespace
|
|
32
|
+
return result.trim();
|
|
25
33
|
}
|
|
26
34
|
/**
|
|
27
35
|
* Sanitizes all string values in an object recursively
|
|
@@ -14,6 +14,19 @@ describe('sanitizeValue', () => {
|
|
|
14
14
|
expect(sanitizeValue('<')).toBe('<');
|
|
15
15
|
expect(sanitizeValue('>')).toBe('>');
|
|
16
16
|
});
|
|
17
|
+
it('prevents SQL injection', () => {
|
|
18
|
+
// Basic SQL injection patterns
|
|
19
|
+
expect(sanitizeValue("O'Connor")).toBe("O''Connor");
|
|
20
|
+
expect(sanitizeValue("' OR '1'='1")).toBe("'' OR ''1''=''1");
|
|
21
|
+
expect(sanitizeValue("'; DROP TABLE users; --")).toBe("''; DROP TABLE users; --");
|
|
22
|
+
// More complex SQL injection patterns
|
|
23
|
+
expect(sanitizeValue("' UNION SELECT username, password FROM users --")).toBe("'' UNION SELECT username, password FROM users --");
|
|
24
|
+
expect(sanitizeValue("' OR 1=1; UPDATE users SET password='hacked' WHERE username='admin'; --")).toBe("'' OR 1=1; UPDATE users SET password=''hacked'' WHERE username=''admin''; --");
|
|
25
|
+
expect(sanitizeValue("admin' --")).toBe("admin'' --");
|
|
26
|
+
expect(sanitizeValue("1' OR '1' = '1")).toBe("1'' OR ''1'' = ''1");
|
|
27
|
+
// Double quotes should remain unchanged (handled by parameterized queries)
|
|
28
|
+
expect(sanitizeValue('User "Admin"')).toBe('User "Admin"');
|
|
29
|
+
});
|
|
17
30
|
it('trims whitespace', () => {
|
|
18
31
|
expect(sanitizeValue(' hello ')).toBe('hello');
|
|
19
32
|
expect(sanitizeValue('\n\t\r hello \n\t\r')).toBe('hello');
|
|
@@ -21,11 +34,14 @@ describe('sanitizeValue', () => {
|
|
|
21
34
|
it('handles multiple sanitization requirements', () => {
|
|
22
35
|
expect(sanitizeValue(' javascript:alert("<script>") ')).toBe('alert("")');
|
|
23
36
|
expect(sanitizeValue('<div>Hello & World</div>')).toBe('Hello & World');
|
|
37
|
+
expect(sanitizeValue(" <script>alert('DROP TABLE users');</script> ")).toBe("alert(''DROP TABLE users'');");
|
|
38
|
+
expect(sanitizeValue(" <img src='x' onerror='alert(\"1\")'>admin' OR '1'='1 ")).toBe("admin'' OR ''1''=''1");
|
|
24
39
|
});
|
|
25
40
|
});
|
|
26
41
|
describe('sanitizeObject', () => {
|
|
27
42
|
it('handles primitive values', () => {
|
|
28
43
|
expect(sanitizeObject(' <script>alert("xss")</script> ')).toBe('alert("xss")');
|
|
44
|
+
expect(sanitizeObject("O'Connor")).toBe("O''Connor");
|
|
29
45
|
expect(sanitizeObject(123)).toBe(123);
|
|
30
46
|
expect(sanitizeObject(true)).toBe(true);
|
|
31
47
|
expect(sanitizeObject(null)).toBe(null);
|
|
@@ -35,12 +51,16 @@ describe('sanitizeObject', () => {
|
|
|
35
51
|
const input = [
|
|
36
52
|
' <script>alert("xss")</script> ',
|
|
37
53
|
'javascript:alert("xss")',
|
|
38
|
-
'Hello & World'
|
|
54
|
+
'Hello & World',
|
|
55
|
+
"admin' OR '1'='1",
|
|
56
|
+
"'; DROP TABLE users; --"
|
|
39
57
|
];
|
|
40
58
|
const expected = [
|
|
41
59
|
'alert("xss")',
|
|
42
60
|
'alert("xss")',
|
|
43
|
-
'Hello & World'
|
|
61
|
+
'Hello & World',
|
|
62
|
+
"admin'' OR ''1''=''1",
|
|
63
|
+
"''; DROP TABLE users; --"
|
|
44
64
|
];
|
|
45
65
|
expect(sanitizeObject(input)).toEqual(expected);
|
|
46
66
|
});
|
|
@@ -49,14 +69,26 @@ describe('sanitizeObject', () => {
|
|
|
49
69
|
name: ' <script>alert("xss")</script> ',
|
|
50
70
|
address: {
|
|
51
71
|
street: 'javascript:alert("xss")',
|
|
52
|
-
city: 'Hello & World'
|
|
72
|
+
city: 'Hello & World',
|
|
73
|
+
country: "O'Connor",
|
|
74
|
+
postalCode: "123' OR '1'='1"
|
|
75
|
+
},
|
|
76
|
+
credentials: {
|
|
77
|
+
username: "admin' --",
|
|
78
|
+
password: "'; UPDATE users SET password='hacked'; --"
|
|
53
79
|
}
|
|
54
80
|
};
|
|
55
81
|
const expected = {
|
|
56
82
|
name: 'alert("xss")',
|
|
57
83
|
address: {
|
|
58
84
|
street: 'alert("xss")',
|
|
59
|
-
city: 'Hello & World'
|
|
85
|
+
city: 'Hello & World',
|
|
86
|
+
country: "O''Connor",
|
|
87
|
+
postalCode: "123'' OR ''1''=''1"
|
|
88
|
+
},
|
|
89
|
+
credentials: {
|
|
90
|
+
username: "admin'' --",
|
|
91
|
+
password: "''; UPDATE users SET password=''hacked''; --"
|
|
60
92
|
}
|
|
61
93
|
};
|
|
62
94
|
expect(sanitizeObject(input)).toEqual(expected);
|
|
@@ -64,11 +96,19 @@ describe('sanitizeObject', () => {
|
|
|
64
96
|
it('handles arrays of objects', () => {
|
|
65
97
|
const input = [
|
|
66
98
|
{ name: '<script>alert("xss")</script>' },
|
|
67
|
-
{ address: 'javascript:alert("xss")' }
|
|
99
|
+
{ address: 'javascript:alert("xss")' },
|
|
100
|
+
{
|
|
101
|
+
username: "admin' OR '1'='1",
|
|
102
|
+
query: "'; DROP TABLE users; --"
|
|
103
|
+
}
|
|
68
104
|
];
|
|
69
105
|
const expected = [
|
|
70
106
|
{ name: 'alert("xss")' },
|
|
71
|
-
{ address: 'alert("xss")' }
|
|
107
|
+
{ address: 'alert("xss")' },
|
|
108
|
+
{
|
|
109
|
+
username: "admin'' OR ''1''=''1",
|
|
110
|
+
query: "''; DROP TABLE users; --"
|
|
111
|
+
}
|
|
72
112
|
];
|
|
73
113
|
expect(sanitizeObject(input)).toEqual(expected);
|
|
74
114
|
});
|
|
@@ -77,7 +117,12 @@ describe('sanitizeObject', () => {
|
|
|
77
117
|
level1: {
|
|
78
118
|
level2: {
|
|
79
119
|
level3: {
|
|
80
|
-
value: '<script>alert("xss")</script>'
|
|
120
|
+
value: '<script>alert("xss")</script>',
|
|
121
|
+
sqlInjection: {
|
|
122
|
+
query: "'; DROP TABLE users; --",
|
|
123
|
+
condition: "admin' OR '1'='1",
|
|
124
|
+
update: "'; UPDATE users SET password='hacked'; --"
|
|
125
|
+
}
|
|
81
126
|
}
|
|
82
127
|
}
|
|
83
128
|
}
|
|
@@ -86,7 +131,12 @@ describe('sanitizeObject', () => {
|
|
|
86
131
|
level1: {
|
|
87
132
|
level2: {
|
|
88
133
|
level3: {
|
|
89
|
-
value: 'alert("xss")'
|
|
134
|
+
value: 'alert("xss")',
|
|
135
|
+
sqlInjection: {
|
|
136
|
+
query: "''; DROP TABLE users; --",
|
|
137
|
+
condition: "admin'' OR ''1''=''1",
|
|
138
|
+
update: "''; UPDATE users SET password=''hacked''; --"
|
|
139
|
+
}
|
|
90
140
|
}
|
|
91
141
|
}
|
|
92
142
|
}
|
|
@@ -107,12 +157,17 @@ describe('sanitizeObject', () => {
|
|
|
107
157
|
array: [
|
|
108
158
|
'javascript:alert("xss")',
|
|
109
159
|
456,
|
|
110
|
-
false
|
|
160
|
+
false,
|
|
161
|
+
"admin' OR '1'='1"
|
|
111
162
|
],
|
|
112
163
|
object: {
|
|
113
164
|
string: 'Hello & World',
|
|
114
165
|
number: 789,
|
|
115
|
-
boolean: false
|
|
166
|
+
boolean: false,
|
|
167
|
+
sqlInjection: {
|
|
168
|
+
query: "'; DROP TABLE users; --",
|
|
169
|
+
username: "admin' --"
|
|
170
|
+
}
|
|
116
171
|
}
|
|
117
172
|
};
|
|
118
173
|
const expected = {
|
|
@@ -124,12 +179,17 @@ describe('sanitizeObject', () => {
|
|
|
124
179
|
array: [
|
|
125
180
|
'alert("xss")',
|
|
126
181
|
456,
|
|
127
|
-
false
|
|
182
|
+
false,
|
|
183
|
+
"admin'' OR ''1''=''1"
|
|
128
184
|
],
|
|
129
185
|
object: {
|
|
130
186
|
string: 'Hello & World',
|
|
131
187
|
number: 789,
|
|
132
|
-
boolean: false
|
|
188
|
+
boolean: false,
|
|
189
|
+
sqlInjection: {
|
|
190
|
+
query: "''; DROP TABLE users; --",
|
|
191
|
+
username: "admin'' --"
|
|
192
|
+
}
|
|
133
193
|
}
|
|
134
194
|
};
|
|
135
195
|
expect(sanitizeObject(input)).toEqual(expected);
|
package/build/XeroApiClient.js
CHANGED
|
@@ -3,7 +3,7 @@ import "dotenv/config";
|
|
|
3
3
|
const client_id = process.env.XERO_CLIENT_ID;
|
|
4
4
|
const client_secret = process.env.XERO_CLIENT_SECRET;
|
|
5
5
|
const redirectUrl = process.env.XERO_REDIRECT_URI;
|
|
6
|
-
const scopes = "offline_access openid profile accounting.
|
|
6
|
+
const scopes = "offline_access openid profile accounting.settings accounting.contacts accounting.invoices accounting.banktransactions accounting.payments.read accounting.reports.balancesheet.read";
|
|
7
7
|
if (!client_id || !client_secret || !redirectUrl) {
|
|
8
8
|
throw Error("Environment Variables not all set - please check your .env file in the project root or create one!");
|
|
9
9
|
}
|
package/build/XeroMcpServer.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { CallToolRequestSchema, ErrorCode, ListResourceTemplatesRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { McpToolsFactory } from "./Tools/McpToolsFactory.js";
|
|
5
5
|
import { XeroAuthMiddleware } from "./Middlewares/XeroAuthMiddleware.js";
|
|
6
6
|
import { ErrorMiddleware } from "./Middlewares/ErrorMiddleware.js";
|
|
7
|
+
import { XeroAccountingApiSchema } from "./Resources/xero_accounting.js";
|
|
8
|
+
const ACCOUNTING_OPENAPI_RESOURCE_URI = "xero-mcp://accounting/openapi.json";
|
|
7
9
|
export class XeroMcpServer {
|
|
8
10
|
mcpServer;
|
|
9
11
|
constructor() {
|
|
@@ -13,11 +15,13 @@ export class XeroMcpServer {
|
|
|
13
15
|
}, {
|
|
14
16
|
capabilities: {
|
|
15
17
|
tools: {},
|
|
18
|
+
resources: {},
|
|
16
19
|
},
|
|
17
20
|
});
|
|
18
21
|
}
|
|
19
22
|
async start() {
|
|
20
23
|
this.configureTools();
|
|
24
|
+
this.configureResources();
|
|
21
25
|
const transport = new StdioServerTransport();
|
|
22
26
|
await this.mcpServer.connect(transport);
|
|
23
27
|
console.error("Xero MCP server running on stdio");
|
|
@@ -50,4 +54,31 @@ export class XeroMcpServer {
|
|
|
50
54
|
});
|
|
51
55
|
});
|
|
52
56
|
}
|
|
57
|
+
configureResources() {
|
|
58
|
+
this.mcpServer.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
59
|
+
resources: [
|
|
60
|
+
{
|
|
61
|
+
uri: ACCOUNTING_OPENAPI_RESOURCE_URI,
|
|
62
|
+
name: "Xero Accounting API (OpenAPI)",
|
|
63
|
+
description: "OpenAPI 3.0 document for the Xero Accounting API (paths, operations, schemas). Use when you need request/response shapes or endpoint details beyond the bundled tools.",
|
|
64
|
+
mimeType: "application/json",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
}));
|
|
68
|
+
this.mcpServer.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [] }));
|
|
69
|
+
this.mcpServer.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
70
|
+
if (request.params.uri !== ACCOUNTING_OPENAPI_RESOURCE_URI) {
|
|
71
|
+
throw new McpError(ErrorCode.InvalidParams, `Resource not found: ${request.params.uri}`);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
contents: [
|
|
75
|
+
{
|
|
76
|
+
uri: ACCOUNTING_OPENAPI_RESOURCE_URI,
|
|
77
|
+
mimeType: "application/json",
|
|
78
|
+
text: JSON.stringify(XeroAccountingApiSchema),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
53
84
|
}
|
package/build/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "xero-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A Model Context Protocol server allows Clients to interact with Xero",
|
|
5
|
-
"author": "Jianyang Zhang",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "build/index.js",
|
|
8
|
-
"bin": {
|
|
9
|
-
"xero-mcp": "./build/index.js"
|
|
10
|
-
},
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
|
13
|
-
"start:dev": "tsx src/index.ts",
|
|
14
|
-
"test": "jest"
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"build",
|
|
18
|
-
"README.md"
|
|
19
|
-
],
|
|
20
|
-
"keywords": [
|
|
21
|
-
"mcp",
|
|
22
|
-
"xero",
|
|
23
|
-
"modelcontextprotocol",
|
|
24
|
-
"AI",
|
|
25
|
-
"accounting"
|
|
26
|
-
],
|
|
27
|
-
"repository": {
|
|
28
|
-
"type": "git",
|
|
29
|
-
"url": "git+https://github.com/john-zhang-dev/xero-mcp.git"
|
|
30
|
-
},
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"@modelcontextprotocol/sdk": "^1.7.0",
|
|
33
|
-
"dotenv": "^16.4.7",
|
|
34
|
-
"open": "^10.1.0",
|
|
35
|
-
"xero-node": "^
|
|
36
|
-
"zod": "^3.24.2"
|
|
37
|
-
},
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"@types/jest": "^29.5.14",
|
|
40
|
-
"@types/node": "^
|
|
41
|
-
"jest": "^29.7.0",
|
|
42
|
-
"ts-jest": "^29.3.0",
|
|
43
|
-
"ts-node": "^10.9.2",
|
|
44
|
-
"tsx": "^4.19.3",
|
|
45
|
-
"typescript": "^5.8.2"
|
|
46
|
-
},
|
|
47
|
-
"license": "MIT"
|
|
1
|
+
{
|
|
2
|
+
"name": "xero-mcp",
|
|
3
|
+
"version": "2.0.0-beta",
|
|
4
|
+
"description": "A Model Context Protocol server allows Clients to interact with Xero",
|
|
5
|
+
"author": "Jianyang Zhang",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "build/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"xero-mcp": "./build/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
|
13
|
+
"start:dev": "tsx src/index.ts",
|
|
14
|
+
"test": "jest"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"build",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"xero",
|
|
23
|
+
"modelcontextprotocol",
|
|
24
|
+
"AI",
|
|
25
|
+
"accounting"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/john-zhang-dev/xero-mcp.git"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.7.0",
|
|
33
|
+
"dotenv": "^16.4.7",
|
|
34
|
+
"open": "^10.1.0",
|
|
35
|
+
"xero-node": "^14.0.0",
|
|
36
|
+
"zod": "^3.24.2"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/jest": "^29.5.14",
|
|
40
|
+
"@types/node": "^25.5.0",
|
|
41
|
+
"jest": "^29.7.0",
|
|
42
|
+
"ts-jest": "^29.3.0",
|
|
43
|
+
"ts-node": "^10.9.2",
|
|
44
|
+
"tsx": "^4.19.3",
|
|
45
|
+
"typescript": "^5.8.2"
|
|
46
|
+
},
|
|
47
|
+
"license": "MIT"
|
|
48
48
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|