diff --git a/.env.example b/.env.example index cdd6da5a..8673d1b8 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,12 @@ # Xero API Configuration for Custom Connections XERO_CLIENT_ID=your_client_id_here -XERO_CLIENT_SECRET=your_client_secret_here \ No newline at end of file +XERO_CLIENT_SECRET=your_client_secret_here + +# Optional: space-separated OAuth scopes for custom connections (defaults match README). +# XERO_SCOPES=accounting.transactions accounting.contacts accounting.settings accounting.reports.read payroll.settings payroll.employees payroll.timesheets + +# Optional: use a pre-obtained bearer token instead of client id/secret (takes precedence when set). +# XERO_CLIENT_BEARER_TOKEN= + +# Optional: log to stderr how many MCP tools were registered vs omitted by XERO_SCOPES (custom connections only). +# XERO_MCP_LOG_SCOPE_FILTERING=1 diff --git a/README.md b/README.md index 6c3657fe..7b2814c2 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,38 @@ It is also the recommended approach if you are integrating this into 3rd party M Set up a Custom Connection following these instructions: https://developer.xero.com/documentation/guides/oauth2/custom-connections/ -Currently the following scopes are required for all sessions: [scopes](src/clients/xero-client.ts#L91-L92) +The following [default scopes](src/helpers/scopes.ts) are requested for all custom connection sessions when `XERO_SCOPES` is not set: + +``` +accounting.transactions +accounting.contacts +accounting.settings +accounting.reports.read +payroll.settings +payroll.employees +payroll.timesheets +``` + +You can override these by setting the `XERO_SCOPES` environment variable to a space-separated list of scopes. + +When using **custom connections**, tools are **registered only if** your configured scopes satisfy each tool’s requirements (see table below). That avoids offering payroll (or other) tools when the access token would not have permission. Typos or unknown scope names log a warning but are still sent to the token endpoint. + +Tool registration compares against **custom-connection style** names (e.g. `accounting.transactions`). If you set `XERO_SCOPES` to **granular** names from the bearer-token list below (e.g. `accounting.invoices` only), tools expecting `accounting.transactions` will not register even though your token may be valid—either include the matching broad scope or use bearer token mode (which registers all tools). + +Set `XERO_MCP_LOG_SCOPE_FILTERING=1` to print how many tools were registered vs omitted (to **stderr**; safe for MCP stdio transports). + +When using **bearer token** auth, `XERO_SCOPES` is not applied to token issuance (your token was created elsewhere). **All tools are registered** in that mode—ensure the token’s scopes match the APIs you use. + +##### Scope groups and MCP tools + +| Required scopes (all must be present) | MCP tools | +| --- | --- | +| `accounting.transactions` | Invoices, credit notes, quotes, payments, bank transactions, manual journals (list / create / update) | +| `accounting.contacts` | Contacts, contact groups (list / create / update) | +| `accounting.settings` | Accounts, items, tax rates, tracking categories and options, organisation details | +| `accounting.reports.read` | Profit and loss, balance sheet, trial balance, aged receivables / payables by contact | +| `payroll.settings` and `payroll.employees` | Payroll employees, employee leave, leave types, leave periods, leave balances | +| `payroll.settings` and `payroll.timesheets` | Payroll timesheets (list, get, create, delete, approve, revert, add/update lines) | ##### Integrating the MCP server with Claude Desktop @@ -61,13 +92,16 @@ To add the MCP server to Claude go to Settings > Developer > Edit config and add "args": ["-y", "@xeroapi/xero-mcp-server@latest"], "env": { "XERO_CLIENT_ID": "your_client_id_here", - "XERO_CLIENT_SECRET": "your_client_secret_here" + "XERO_CLIENT_SECRET": "your_client_secret_here", + "XERO_SCOPES": "accounting.transactions accounting.contacts accounting.settings accounting.reports.read" } } } } ``` +The `XERO_SCOPES` variable is optional. If omitted, the default scopes listed above will be used. + NOTE: If you are using [Node Version Manager](https://github.com/nvm-sh/nvm) `"command": "npx"` section change it to be the full path to the executable, ie: `your_home_directory/.nvm/versions/node/v22.14.0/bin/npx` on Mac / Linux or `"your_home_directory\\.nvm\\versions\\node\\v22.14.0\\bin\\npx"` on Windows #### 2. Bearer Token diff --git a/package-lock.json b/package-lock.json index 8ed0d16e..ba4b61c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -399,7 +399,6 @@ "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", @@ -630,7 +629,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1087,7 +1085,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -1311,7 +1308,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -1743,7 +1739,6 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -2331,7 +2326,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2828,7 +2822,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2985,7 +2978,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/clients/xero-client.ts b/src/clients/xero-client.ts index 24ece6e6..07b53453 100644 --- a/src/clients/xero-client.ts +++ b/src/clients/xero-client.ts @@ -8,6 +8,7 @@ import { } from "xero-node"; import { ensureError } from "../helpers/ensure-error.js"; +import { getConfiguredScopeString } from "../helpers/scopes.js"; dotenv.config(); @@ -89,8 +90,7 @@ class CustomConnectionsXeroClient extends MCPXeroClient { } public async getClientCredentialsToken(): Promise { - const scope = - "accounting.transactions accounting.contacts accounting.settings accounting.reports.read payroll.settings payroll.employees payroll.timesheets"; + const scope = getConfiguredScopeString(); const credentials = Buffer.from( `${this.clientId}:${this.clientSecret}`, ).toString("base64"); diff --git a/src/helpers/create-xero-tool.ts b/src/helpers/create-xero-tool.ts index 049d9d91..b8f5d256 100644 --- a/src/helpers/create-xero-tool.ts +++ b/src/helpers/create-xero-tool.ts @@ -8,10 +8,12 @@ export const CreateXeroTool = description: string, schema: Args, handler: ToolCallback, + requiredScopes: string[] = [], ): (() => ToolDefinition) => () => ({ name: name, description: description, schema: schema, handler: handler, + requiredScopes, }); diff --git a/src/helpers/scopes.ts b/src/helpers/scopes.ts new file mode 100644 index 00000000..1f2e0972 --- /dev/null +++ b/src/helpers/scopes.ts @@ -0,0 +1,122 @@ +/** + * OAuth scopes for custom connections and tool gating. + * @see https://developer.xero.com/documentation/guides/oauth2/scopes/ + */ + +/** Space-separated scopes requested for custom connections when `XERO_SCOPES` is unset. */ +export const DEFAULT_SCOPE_STRING = + "accounting.transactions accounting.contacts accounting.settings accounting.reports.read payroll.settings payroll.employees payroll.timesheets"; + +/** + * Known Xero OAuth2 scope strings (custom-connection style + common granular bearer scopes). + * Unknown scopes from `XERO_SCOPES` are still passed through to the token request after a warning. + */ +export const VALID_SCOPES: ReadonlySet = new Set([ + // Accounting — custom connection / legacy + "accounting.transactions", + "accounting.transactions.read", + "accounting.contacts", + "accounting.settings", + "accounting.reports.read", + // Granular accounting (bearer / newer apps) + "accounting.invoices", + "accounting.invoices.read", + "accounting.payments", + "accounting.payments.read", + "accounting.banktransactions", + "accounting.banktransactions.read", + "accounting.manualjournals", + "accounting.manualjournals.read", + "accounting.reports.aged.read", + "accounting.reports.balancesheet.read", + "accounting.reports.profitandloss.read", + "accounting.reports.trialbalance.read", + // Payroll + "payroll.settings", + "payroll.employees", + "payroll.timesheets", + "payroll.payruns", + "payroll.payslip", +]); + +const ENV_KEY = "XERO_SCOPES"; + +function splitScopeString(raw: string): string[] { + return raw + .trim() + .split(/\s+/) + .map((s) => s.trim()) + .filter(Boolean); +} + +/** True when using client id/secret (custom connections); false when bearer token overrides. */ +export function isCustomConnectionsAuthMode(): boolean { + return !process.env.XERO_CLIENT_BEARER_TOKEN?.trim(); +} + +let cachedScopes: Set | undefined; +let cachedScopeString: string | undefined; + +function parseAndValidateScopeString(scopeString: string): Set { + const parts = splitScopeString(scopeString); + if (parts.length === 0) { + throw new Error( + `No OAuth scopes after parsing (whitespace-only or invalid ${ENV_KEY}?). Provide a space-separated list or unset ${ENV_KEY} to use defaults.`, + ); + } + const result = new Set(); + for (const scope of parts) { + if (!VALID_SCOPES.has(scope)) { + console.warn( + `[xero-mcp-server] Unknown OAuth scope "${scope}" (not in built-in allowlist). It will still be sent to Xero. Check for typos.`, + ); + } + result.add(scope); + } + return result; +} + +/** + * Parsed scopes for the current process. Used for custom-connection token requests and tool registration. + * Cached after first call. + */ +export function getConfiguredScopes(): Set { + if (cachedScopes) { + return cachedScopes; + } + const raw = process.env[ENV_KEY]?.trim(); + const scopeString = raw && raw.length > 0 ? raw : DEFAULT_SCOPE_STRING; + cachedScopes = parseAndValidateScopeString(scopeString); + cachedScopeString = [...cachedScopes].join(" "); + return cachedScopes; +} + +/** Space-separated scopes for the identity server token request (custom connections). */ +export function getConfiguredScopeString(): string { + if (cachedScopeString) { + return cachedScopeString; + } + getConfiguredScopes(); + return cachedScopeString!; +} + +/** Whether a tool's required scopes are all present in the configured set. */ +export function scopesSatisfyTool( + configured: Set, + requiredScopes: string[] | undefined, +): boolean { + if (!requiredScopes?.length) { + return true; + } + return requiredScopes.every((s) => configured.has(s)); +} + +/** Declarative scope groups for MCP tools (custom-connection scope names). */ +export const ToolScopes = { + accountingTransactions: ["accounting.transactions"], + accountingContacts: ["accounting.contacts"], + accountingSettings: ["accounting.settings"], + accountingReportsRead: ["accounting.reports.read"], + payrollEmployees: ["payroll.settings", "payroll.employees"], + payrollTimesheets: ["payroll.settings", "payroll.timesheets"], +} as const satisfies Record; diff --git a/src/index.ts b/src/index.ts index bf5c90aa..c4f96402 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ #!/usr/bin/env node +import "dotenv/config"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { XeroMcpServer } from "./server/xero-mcp-server.js"; import { ToolFactory } from "./tools/tool-factory.js"; diff --git a/src/tools/create/create-bank-transaction.tool.ts b/src/tools/create/create-bank-transaction.tool.ts index c2fa4a98..d574a9f9 100644 --- a/src/tools/create/create-bank-transaction.tool.ts +++ b/src/tools/create/create-bank-transaction.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { createXeroBankTransaction } from "../../handlers/create-xero-bank-transaction.handler.js"; import { bankTransactionDeepLink } from "../../consts/deeplinks.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const lineItemSchema = z.object({ description: z.string(), @@ -63,7 +64,8 @@ const CreateBankTransactionTool = CreateXeroTool( }, ], }; - } + }, + ToolScopes.accountingTransactions ); export default CreateBankTransactionTool; \ No newline at end of file diff --git a/src/tools/create/create-contact.tool.ts b/src/tools/create/create-contact.tool.ts index a991257c..41ffe95d 100644 --- a/src/tools/create/create-contact.tool.ts +++ b/src/tools/create/create-contact.tool.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js"; import { ensureError } from "../../helpers/ensure-error.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const CreateContactTool = CreateXeroTool( "create-contact", @@ -61,6 +62,7 @@ const CreateContactTool = CreateXeroTool( }; } }, + ToolScopes.accountingContacts ); export default CreateContactTool; diff --git a/src/tools/create/create-credit-note.tool.ts b/src/tools/create/create-credit-note.tool.ts index 9e199a65..ccc58eae 100644 --- a/src/tools/create/create-credit-note.tool.ts +++ b/src/tools/create/create-credit-note.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { createXeroCreditNote } from "../../handlers/create-xero-credit-note.handler.js"; import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const lineItemSchema = z.object({ description: z.string(), @@ -59,6 +60,7 @@ const CreateCreditNoteTool = CreateXeroTool( ], }; }, + ToolScopes.accountingTransactions ); export default CreateCreditNoteTool; diff --git a/src/tools/create/create-invoice.tool.ts b/src/tools/create/create-invoice.tool.ts index debe407f..b9ab0a52 100644 --- a/src/tools/create/create-invoice.tool.ts +++ b/src/tools/create/create-invoice.tool.ts @@ -3,6 +3,7 @@ import { createXeroInvoice } from "../../handlers/create-xero-invoice.handler.js import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { Invoice } from "xero-node"; +import { ToolScopes } from "../../helpers/scopes.js"; const trackingSchema = z.object({ name: z.string().describe("The name of the tracking category. Can be obtained from the list-tracking-categories tool"), @@ -85,6 +86,7 @@ const CreateInvoiceTool = CreateXeroTool( ], }; }, + ToolScopes.accountingTransactions ); export default CreateInvoiceTool; diff --git a/src/tools/create/create-item.tool.ts b/src/tools/create/create-item.tool.ts index a77cadd6..f65c8076 100644 --- a/src/tools/create/create-item.tool.ts +++ b/src/tools/create/create-item.tool.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { createXeroItem } from "../../handlers/create-xero-item.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const purchaseDetailsSchema = z.object({ unitPrice: z.number(), @@ -77,6 +78,7 @@ const CreateItemTool = CreateXeroTool( ], }; }, + ToolScopes.accountingSettings ); export default CreateItemTool; \ No newline at end of file diff --git a/src/tools/create/create-manual-journal.tool.ts b/src/tools/create/create-manual-journal.tool.ts index 2ec9717d..478088e3 100644 --- a/src/tools/create/create-manual-journal.tool.ts +++ b/src/tools/create/create-manual-journal.tool.ts @@ -4,6 +4,7 @@ import { createXeroManualJournal } from "../../handlers/create-xero-manual-journ import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js"; import { ensureError } from "../../helpers/ensure-error.js"; import { LineAmountTypes, ManualJournal } from "xero-node"; +import { ToolScopes } from "../../helpers/scopes.js"; const CreateManualJournalTool = CreateXeroTool( "create-manual-journal", @@ -145,6 +146,7 @@ const CreateManualJournalTool = CreateXeroTool( }; } }, + ToolScopes.accountingTransactions ); export default CreateManualJournalTool; diff --git a/src/tools/create/create-payment.tool.ts b/src/tools/create/create-payment.tool.ts index e7ec6594..4c238aca 100644 --- a/src/tools/create/create-payment.tool.ts +++ b/src/tools/create/create-payment.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { createXeroPayment } from "../../handlers/create-xero-payment.handler.js"; import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const CreatePaymentTool = CreateXeroTool( "create-payment", @@ -76,6 +77,7 @@ const CreatePaymentTool = CreateXeroTool( ], }; }, + ToolScopes.accountingTransactions ); export default CreatePaymentTool; diff --git a/src/tools/create/create-payroll-timesheet.tool.ts b/src/tools/create/create-payroll-timesheet.tool.ts index 59ec3f98..ce87193f 100644 --- a/src/tools/create/create-payroll-timesheet.tool.ts +++ b/src/tools/create/create-payroll-timesheet.tool.ts @@ -5,6 +5,7 @@ import { createXeroPayrollTimesheet, } from "../../handlers/create-xero-payroll-timesheet.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const CreatePayrollTimesheetTool = CreateXeroTool( "create-timesheet", @@ -51,6 +52,7 @@ This allows you to specify details such as the employee ID, payroll calendar ID, ], }; }, + ToolScopes.payrollTimesheets ); export default CreatePayrollTimesheetTool; \ No newline at end of file diff --git a/src/tools/create/create-quote.tool.ts b/src/tools/create/create-quote.tool.ts index de7ac867..ba024d87 100644 --- a/src/tools/create/create-quote.tool.ts +++ b/src/tools/create/create-quote.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { createXeroQuote } from "../../handlers/create-xero-quote.handler.js"; import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const lineItemSchema = z.object({ description: z.string(), @@ -79,6 +80,7 @@ const CreateQuoteTool = CreateXeroTool( ], }; }, + ToolScopes.accountingTransactions ); export default CreateQuoteTool; diff --git a/src/tools/create/create-tracking-category.tool.ts b/src/tools/create/create-tracking-category.tool.ts index 02cd0d41..ace41db6 100644 --- a/src/tools/create/create-tracking-category.tool.ts +++ b/src/tools/create/create-tracking-category.tool.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { createXeroTrackingCategory } from "../../handlers/create-xero-tracking-category.handler.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const CreateTrackingCategoryTool = CreateXeroTool( "create-tracking-category", @@ -32,7 +33,8 @@ const CreateTrackingCategoryTool = CreateXeroTool( }, ] }; - } + }, + ToolScopes.accountingSettings ); export default CreateTrackingCategoryTool; \ No newline at end of file diff --git a/src/tools/create/create-tracking-options.tool.ts b/src/tools/create/create-tracking-options.tool.ts index 8134e656..540f663e 100644 --- a/src/tools/create/create-tracking-options.tool.ts +++ b/src/tools/create/create-tracking-options.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { formatTrackingOption } from "../../helpers/format-tracking-option.js"; import { createXeroTrackingOptions } from "../../handlers/create-xero-tracking-option.handler.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const CreateTrackingOptionsTool = CreateXeroTool( "create-tracking-options", @@ -34,7 +35,8 @@ const CreateTrackingOptionsTool = CreateXeroTool( }, ] }; - } + }, + ToolScopes.accountingSettings ); export default CreateTrackingOptionsTool; \ No newline at end of file diff --git a/src/tools/delete/delete-payroll-timesheet.tool.ts b/src/tools/delete/delete-payroll-timesheet.tool.ts index 8529a29c..7b0b05b2 100644 --- a/src/tools/delete/delete-payroll-timesheet.tool.ts +++ b/src/tools/delete/delete-payroll-timesheet.tool.ts @@ -4,6 +4,7 @@ import { deleteXeroPayrollTimesheet, } from "../../handlers/delete-xero-payroll-timesheet.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const DeletePayrollTimesheetTool = CreateXeroTool( "delete-timesheet", @@ -35,6 +36,7 @@ const DeletePayrollTimesheetTool = CreateXeroTool( ], }; }, + ToolScopes.payrollTimesheets ); export default DeletePayrollTimesheetTool; \ No newline at end of file diff --git a/src/tools/get/get-payroll-timesheet.tool.ts b/src/tools/get/get-payroll-timesheet.tool.ts index 034c29c3..4a761569 100644 --- a/src/tools/get/get-payroll-timesheet.tool.ts +++ b/src/tools/get/get-payroll-timesheet.tool.ts @@ -4,6 +4,7 @@ import { getXeroPayrollTimesheet, } from "../../handlers/get-xero-payroll-timesheet.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const GetPayrollTimesheetTool = CreateXeroTool( "get-timesheet", @@ -58,6 +59,7 @@ This provides details such as the timesheet ID, employee ID, start and end dates ], }; }, + ToolScopes.payrollTimesheets ); export default GetPayrollTimesheetTool; \ No newline at end of file diff --git a/src/tools/list/list-accounts.tool.ts b/src/tools/list/list-accounts.tool.ts index ec0e3cd7..5a4ec308 100644 --- a/src/tools/list/list-accounts.tool.ts +++ b/src/tools/list/list-accounts.tool.ts @@ -1,5 +1,6 @@ import { listXeroAccounts } from "../../handlers/list-xero-accounts.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListAccountsTool = CreateXeroTool( "list-accounts", @@ -43,6 +44,7 @@ const ListAccountsTool = CreateXeroTool( ], }; }, + ToolScopes.accountingSettings ); export default ListAccountsTool; diff --git a/src/tools/list/list-aged-payables-by-contact.tool.ts b/src/tools/list/list-aged-payables-by-contact.tool.ts index 57366ec8..2cd95364 100644 --- a/src/tools/list/list-aged-payables-by-contact.tool.ts +++ b/src/tools/list/list-aged-payables-by-contact.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { formatAgedReportFilter } from "../../helpers/format-aged-report-filter.js"; import { listXeroAgedPayablesByContact } from "../../handlers/list-aged-payables-by-contact.handler.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListAgedPayablesByContact = CreateXeroTool( "list-aged-payables-by-contact", @@ -53,7 +54,8 @@ const ListAgedPayablesByContact = CreateXeroTool( } ], }; - } + }, + ToolScopes.accountingReportsRead ); export default ListAgedPayablesByContact; \ No newline at end of file diff --git a/src/tools/list/list-aged-receivables-by-contact.tool.ts b/src/tools/list/list-aged-receivables-by-contact.tool.ts index 44c73921..77ad859a 100644 --- a/src/tools/list/list-aged-receivables-by-contact.tool.ts +++ b/src/tools/list/list-aged-receivables-by-contact.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { listXeroAgedReceivablesByContact } from "../../handlers/list-aged-receivables-by-contact.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { formatAgedReportFilter } from "../../helpers/format-aged-report-filter.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListAgedReceivablesByContact = CreateXeroTool( "list-aged-receivables-by-contact", @@ -53,7 +54,8 @@ const ListAgedReceivablesByContact = CreateXeroTool( } ], }; - } + }, + ToolScopes.accountingReportsRead ); export default ListAgedReceivablesByContact; \ No newline at end of file diff --git a/src/tools/list/list-bank-transactions.tool.ts b/src/tools/list/list-bank-transactions.tool.ts index 6ede7919..67053d34 100644 --- a/src/tools/list/list-bank-transactions.tool.ts +++ b/src/tools/list/list-bank-transactions.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { listXeroBankTransactions } from "../../handlers/list-xero-bank-transactions.handler.js"; import { formatLineItem } from "../../helpers/format-line-item.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListBankTransactionsTool = CreateXeroTool( "list-bank-transactions", @@ -62,7 +63,8 @@ const ListBankTransactionsTool = CreateXeroTool( })) || []) ] }; - } + }, + ToolScopes.accountingTransactions ); export default ListBankTransactionsTool; \ No newline at end of file diff --git a/src/tools/list/list-contact-groups.tool.ts b/src/tools/list/list-contact-groups.tool.ts index ce709b5a..82e7c79d 100644 --- a/src/tools/list/list-contact-groups.tool.ts +++ b/src/tools/list/list-contact-groups.tool.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { listXeroContactGroups } from "../../handlers/list-xero-contact-groups.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListContactGroupsTool = CreateXeroTool( "list-contact-groups", @@ -54,6 +55,7 @@ const ListContactGroupsTool = CreateXeroTool( ], }; }, + ToolScopes.accountingContacts ); export default ListContactGroupsTool; diff --git a/src/tools/list/list-contacts.tool.ts b/src/tools/list/list-contacts.tool.ts index 7d022752..d2352237 100644 --- a/src/tools/list/list-contacts.tool.ts +++ b/src/tools/list/list-contacts.tool.ts @@ -1,6 +1,7 @@ import { listXeroContacts } from "../../handlers/list-xero-contacts.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { z } from "zod"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListContactsTool = CreateXeroTool( "list-contacts", @@ -77,6 +78,7 @@ const ListContactsTool = CreateXeroTool( ], }; }, + ToolScopes.accountingContacts ); export default ListContactsTool; diff --git a/src/tools/list/list-credit-notes.tool.ts b/src/tools/list/list-credit-notes.tool.ts index 1eff5b14..8a018729 100644 --- a/src/tools/list/list-credit-notes.tool.ts +++ b/src/tools/list/list-credit-notes.tool.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { listXeroCreditNotes } from "../../handlers/list-xero-credit-notes.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListCreditNotesTool = CreateXeroTool( "list-credit-notes", @@ -70,6 +71,7 @@ const ListCreditNotesTool = CreateXeroTool( ], }; }, + ToolScopes.accountingTransactions ); export default ListCreditNotesTool; diff --git a/src/tools/list/list-invoices.tool.ts b/src/tools/list/list-invoices.tool.ts index ac400fc9..40975861 100644 --- a/src/tools/list/list-invoices.tool.ts +++ b/src/tools/list/list-invoices.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { listXeroInvoices } from "../../handlers/list-xero-invoices.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { formatLineItem } from "../../helpers/format-line-item.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListInvoicesTool = CreateXeroTool( "list-invoices", @@ -91,6 +92,7 @@ const ListInvoicesTool = CreateXeroTool( ], }; }, + ToolScopes.accountingTransactions ); export default ListInvoicesTool; diff --git a/src/tools/list/list-items.tool.ts b/src/tools/list/list-items.tool.ts index 990e1229..8347ae2e 100644 --- a/src/tools/list/list-items.tool.ts +++ b/src/tools/list/list-items.tool.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { listXeroItems } from "../../handlers/list-xero-items.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListItemsTool = CreateXeroTool( "list-items", @@ -54,6 +55,7 @@ const ListItemsTool = CreateXeroTool( ], }; }, + ToolScopes.accountingSettings ); export default ListItemsTool; \ No newline at end of file diff --git a/src/tools/list/list-manual-journals.tool.ts b/src/tools/list/list-manual-journals.tool.ts index 672fc0f0..e7e93ae0 100644 --- a/src/tools/list/list-manual-journals.tool.ts +++ b/src/tools/list/list-manual-journals.tool.ts @@ -2,6 +2,7 @@ import { ManualJournal } from "xero-node"; import { listXeroManualJournals } from "../../handlers/list-xero-manual-journals.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { z } from "zod"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListManualJournalsTool = CreateXeroTool( "list-manual-journals", @@ -93,6 +94,7 @@ If they want the next page, call this tool again with the next page number, modi ], }; }, + ToolScopes.accountingTransactions ); export default ListManualJournalsTool; diff --git a/src/tools/list/list-organisation-details.tool.ts b/src/tools/list/list-organisation-details.tool.ts index 8be5221e..5182ddca 100644 --- a/src/tools/list/list-organisation-details.tool.ts +++ b/src/tools/list/list-organisation-details.tool.ts @@ -1,6 +1,7 @@ import { listXeroOrganisationDetails } from "../../handlers/list-xero-organisation-details.handler.js"; import { getExternalLink } from "../../helpers/get-external-link.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListOrganisationDetailsTool = CreateXeroTool( "list-organisation-details", @@ -102,6 +103,7 @@ const ListOrganisationDetailsTool = CreateXeroTool( ], }; }, + ToolScopes.accountingSettings ); export default ListOrganisationDetailsTool; \ No newline at end of file diff --git a/src/tools/list/list-payments.tool.ts b/src/tools/list/list-payments.tool.ts index b283fd2c..09cbc8ad 100644 --- a/src/tools/list/list-payments.tool.ts +++ b/src/tools/list/list-payments.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { listXeroPayments } from "../../handlers/list-xero-payments.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { Payment } from "xero-node"; +import { ToolScopes } from "../../helpers/scopes.js"; function paymentFormatter(payment: Payment): string { return [ @@ -87,6 +88,7 @@ const ListPaymentsTool = CreateXeroTool( ], }; }, + ToolScopes.accountingTransactions ); export default ListPaymentsTool; diff --git a/src/tools/list/list-payroll-employee-leave-balances.tool.ts b/src/tools/list/list-payroll-employee-leave-balances.tool.ts index 1717a8c8..2e4e0a52 100644 --- a/src/tools/list/list-payroll-employee-leave-balances.tool.ts +++ b/src/tools/list/list-payroll-employee-leave-balances.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { listXeroPayrollEmployeeLeaveBalances } from "../../handlers/list-xero-payroll-employee-leave-balances.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { EmployeeLeaveBalance } from "xero-node/dist/gen/model/payroll-nz/employeeLeaveBalance.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListPayrollEmployeeLeaveBalancesTool = CreateXeroTool( "list-payroll-employee-leave-balances", @@ -44,6 +45,7 @@ const ListPayrollEmployeeLeaveBalancesTool = CreateXeroTool( ], }; }, + ToolScopes.payrollEmployees ); export default ListPayrollEmployeeLeaveBalancesTool; diff --git a/src/tools/list/list-payroll-employee-leave-types.tool.ts b/src/tools/list/list-payroll-employee-leave-types.tool.ts index 4ab96449..37bf80c5 100644 --- a/src/tools/list/list-payroll-employee-leave-types.tool.ts +++ b/src/tools/list/list-payroll-employee-leave-types.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { listXeroPayrollEmployeeLeaveTypes } from "../../handlers/list-xero-payroll-employee-leave-types.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { EmployeeLeaveType } from "xero-node/dist/gen/model/payroll-nz/employeeLeaveType.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListPayrollEmployeeLeaveTypesTool = CreateXeroTool( "list-payroll-employee-leave-types", @@ -62,6 +63,7 @@ const ListPayrollEmployeeLeaveTypesTool = CreateXeroTool( ], }; }, + ToolScopes.payrollEmployees ); export default ListPayrollEmployeeLeaveTypesTool; diff --git a/src/tools/list/list-payroll-employee-leave.tool.ts b/src/tools/list/list-payroll-employee-leave.tool.ts index 15d5dce1..cb43a612 100644 --- a/src/tools/list/list-payroll-employee-leave.tool.ts +++ b/src/tools/list/list-payroll-employee-leave.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { listXeroPayrollEmployeeLeave } from "../../handlers/list-xero-payroll-employee-leave.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { EmployeeLeave } from "xero-node/dist/gen/model/payroll-nz/employeeLeave.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListPayrollEmployeeLeaveTool = CreateXeroTool( "list-payroll-employee-leave", @@ -47,6 +48,7 @@ const ListPayrollEmployeeLeaveTool = CreateXeroTool( ], }; }, + ToolScopes.payrollEmployees ); export default ListPayrollEmployeeLeaveTool; diff --git a/src/tools/list/list-payroll-employees.tool.ts b/src/tools/list/list-payroll-employees.tool.ts index 7a3b8e6a..b5572d11 100644 --- a/src/tools/list/list-payroll-employees.tool.ts +++ b/src/tools/list/list-payroll-employees.tool.ts @@ -1,6 +1,7 @@ import { Employee } from "xero-node/dist/gen/model/payroll-nz/employee.js"; import { listXeroPayrollEmployees } from "../../handlers/list-xero-payroll-employees.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListPayrollEmployeesTool = CreateXeroTool( "list-payroll-employees", @@ -54,6 +55,7 @@ The response presents a complete overview of all staff currently registered in y ], }; }, + ToolScopes.payrollEmployees ); export default ListPayrollEmployeesTool; diff --git a/src/tools/list/list-payroll-leave-periods.tool.ts b/src/tools/list/list-payroll-leave-periods.tool.ts index 60e73b2e..0bf44e60 100644 --- a/src/tools/list/list-payroll-leave-periods.tool.ts +++ b/src/tools/list/list-payroll-leave-periods.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { listXeroPayrollLeavePeriods } from "../../handlers/list-xero-payroll-leave-periods.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { LeavePeriod } from "xero-node/dist/gen/model/payroll-nz/leavePeriod.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListPayrollLeavePeriodsToolTool = CreateXeroTool( "list-payroll-leave-periods", @@ -50,6 +51,7 @@ const ListPayrollLeavePeriodsToolTool = CreateXeroTool( ], }; }, + ToolScopes.payrollEmployees ); export default ListPayrollLeavePeriodsToolTool; diff --git a/src/tools/list/list-payroll-leave-types.tool.ts b/src/tools/list/list-payroll-leave-types.tool.ts index 213f8803..2cc6397d 100644 --- a/src/tools/list/list-payroll-leave-types.tool.ts +++ b/src/tools/list/list-payroll-leave-types.tool.ts @@ -1,6 +1,7 @@ import { listXeroPayrollLeaveTypes } from "../../handlers/list-xero-payroll-leave-types.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { LeaveType } from "xero-node/dist/gen/model/payroll-nz/leaveType.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListPayrollLeaveTypesTool = CreateXeroTool( "list-payroll-leave-types", @@ -44,6 +45,7 @@ const ListPayrollLeaveTypesTool = CreateXeroTool( ], }; }, + ToolScopes.payrollEmployees ); export default ListPayrollLeaveTypesTool; diff --git a/src/tools/list/list-payroll-timesheets.tool.ts b/src/tools/list/list-payroll-timesheets.tool.ts index 5ffd7695..a8002b9b 100644 --- a/src/tools/list/list-payroll-timesheets.tool.ts +++ b/src/tools/list/list-payroll-timesheets.tool.ts @@ -4,6 +4,7 @@ import { listXeroPayrollTimesheets, } from "../../handlers/list-xero-timesheets.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListPayrollTimesheetsTool = CreateXeroTool( "list-timesheets", @@ -48,6 +49,7 @@ This retrieves comprehensive timesheet details including timesheet IDs, employee ], }; }, + ToolScopes.payrollTimesheets ); export default ListPayrollTimesheetsTool; \ No newline at end of file diff --git a/src/tools/list/list-profit-and-loss.tool.ts b/src/tools/list/list-profit-and-loss.tool.ts index 72e6aba1..06ba77b3 100644 --- a/src/tools/list/list-profit-and-loss.tool.ts +++ b/src/tools/list/list-profit-and-loss.tool.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { listXeroProfitAndLoss } from "../../handlers/list-xero-profit-and-loss.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListProfitAndLossTool = CreateXeroTool( "list-profit-and-loss", @@ -57,6 +58,7 @@ const ListProfitAndLossTool = CreateXeroTool( ], }; }, + ToolScopes.accountingReportsRead ); export default ListProfitAndLossTool; \ No newline at end of file diff --git a/src/tools/list/list-quotes.tool.ts b/src/tools/list/list-quotes.tool.ts index ff82cfd6..fae83b29 100644 --- a/src/tools/list/list-quotes.tool.ts +++ b/src/tools/list/list-quotes.tool.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { listXeroQuotes } from "../../handlers/list-xero-quotes.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListQuotesTool = CreateXeroTool( "list-quotes", @@ -72,6 +73,7 @@ const ListQuotesTool = CreateXeroTool( ], }; }, + ToolScopes.accountingTransactions ); export default ListQuotesTool; diff --git a/src/tools/list/list-report-balance-sheet.tool.ts b/src/tools/list/list-report-balance-sheet.tool.ts index e53ddb85..cc98a0b3 100644 --- a/src/tools/list/list-report-balance-sheet.tool.ts +++ b/src/tools/list/list-report-balance-sheet.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { listXeroReportBalanceSheet } from "../../handlers/list-xero-report-balance-sheet.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { ListReportBalanceSheetParams } from "../../types/list-report-balance-sheet-params.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListReportBalanceSheetTool = CreateXeroTool( "list-report-balance-sheet", @@ -44,7 +45,8 @@ const ListReportBalanceSheetTool = CreateXeroTool( }, ], }; - } + }, + ToolScopes.accountingReportsRead ); export default ListReportBalanceSheetTool; \ No newline at end of file diff --git a/src/tools/list/list-tax-rates.tool.ts b/src/tools/list/list-tax-rates.tool.ts index e73d27ad..44360a5c 100644 --- a/src/tools/list/list-tax-rates.tool.ts +++ b/src/tools/list/list-tax-rates.tool.ts @@ -1,5 +1,6 @@ import { listXeroTaxRates } from "../../handlers/list-xero-tax-rates.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListTaxRatesTool = CreateXeroTool( "list-tax-rates", @@ -56,6 +57,7 @@ const ListTaxRatesTool = CreateXeroTool( ], }; }, + ToolScopes.accountingSettings ); export default ListTaxRatesTool; diff --git a/src/tools/list/list-tracking-categories.tool.ts b/src/tools/list/list-tracking-categories.tool.ts index e2b83b4d..bfe0c013 100644 --- a/src/tools/list/list-tracking-categories.tool.ts +++ b/src/tools/list/list-tracking-categories.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { listXeroTrackingCategories } from "../../handlers/list-xero-tracking-categories.handler.js"; import { formatTrackingOption } from "../../helpers/format-tracking-option.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListTrackingCategoriesTool = CreateXeroTool( "list-tracking-categories", @@ -43,7 +44,8 @@ const ListTrackingCategoriesTool = CreateXeroTool( })) || []) ] }; - } + }, + ToolScopes.accountingSettings ); export default ListTrackingCategoriesTool; \ No newline at end of file diff --git a/src/tools/list/list-trial-balance.tool.ts b/src/tools/list/list-trial-balance.tool.ts index bccb6745..3cc83654 100644 --- a/src/tools/list/list-trial-balance.tool.ts +++ b/src/tools/list/list-trial-balance.tool.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { listXeroTrialBalance } from "../../handlers/list-xero-trial-balance.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ListTrialBalanceTool = CreateXeroTool( "list-trial-balance", @@ -45,6 +46,7 @@ const ListTrialBalanceTool = CreateXeroTool( ], }; }, + ToolScopes.accountingReportsRead ); export default ListTrialBalanceTool; \ No newline at end of file diff --git a/src/tools/tool-factory.ts b/src/tools/tool-factory.ts index 37673b52..0e4fcd31 100644 --- a/src/tools/tool-factory.ts +++ b/src/tools/tool-factory.ts @@ -1,26 +1,70 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { ZodRawShapeCompat } from "@modelcontextprotocol/sdk/server/zod-compat.js"; +import { + getConfiguredScopes, + isCustomConnectionsAuthMode, + scopesSatisfyTool, +} from "../helpers/scopes.js"; +import { ToolDefinition } from "../types/tool-definition.js"; import { CreateTools } from "./create/index.js"; import { DeleteTools } from "./delete/index.js"; import { GetTools } from "./get/index.js"; import { ListTools } from "./list/index.js"; import { UpdateTools } from "./update/index.js"; +const SCOPE_FILTER_LOG_ENV = "XERO_MCP_LOG_SCOPE_FILTERING"; + +function registerTools( + server: McpServer, + factories: Array<() => ToolDefinition>, + configuredScopes: Set, + filterByScopes: boolean, +): { registered: number; skipped: number } { + let registered = 0; + let skipped = 0; + for (const factory of factories) { + const tool = factory(); + if (filterByScopes && !scopesSatisfyTool(configuredScopes, tool.requiredScopes)) { + skipped++; + continue; + } + server.tool(tool.name, tool.description, tool.schema, tool.handler); + registered++; + } + return { registered, skipped }; +} + export function ToolFactory(server: McpServer) { + const filterByScopes = isCustomConnectionsAuthMode(); + const configuredScopes = filterByScopes ? getConfiguredScopes() : new Set(); + + let totalRegistered = 0; + let totalSkipped = 0; + for (const list of [ + DeleteTools, + GetTools, + CreateTools, + ListTools, + UpdateTools, + ]) { + const { registered, skipped } = registerTools( + server, + list, + configuredScopes, + filterByScopes, + ); + totalRegistered += registered; + totalSkipped += skipped; + } - DeleteTools.map((tool) => tool()).forEach((tool) => - server.tool(tool.name, tool.description, tool.schema, tool.handler), - ); - GetTools.map((tool) => tool()).forEach((tool) => - server.tool(tool.name, tool.description, tool.schema, tool.handler), - ); - CreateTools.map((tool) => tool()).forEach((tool) => - server.tool(tool.name, tool.description, tool.schema, tool.handler), - ); - ListTools.map((tool) => tool()).forEach((tool) => - server.tool(tool.name, tool.description, tool.schema, tool.handler), - ); - UpdateTools.map((tool) => tool()).forEach((tool) => - server.tool(tool.name, tool.description, tool.schema, tool.handler), - ); + if ( + filterByScopes && + totalSkipped > 0 && + process.env[SCOPE_FILTER_LOG_ENV]?.trim() + ) { + console.error( + `[xero-mcp-server] OAuth scope filtering: registered ${totalRegistered} tool(s), omitted ${totalSkipped}. Unset ${SCOPE_FILTER_LOG_ENV} to hide this message.`, + ); + } } diff --git a/src/tools/update/approve-payroll-timesheet.tool.ts b/src/tools/update/approve-payroll-timesheet.tool.ts index a9daf330..e410c605 100644 --- a/src/tools/update/approve-payroll-timesheet.tool.ts +++ b/src/tools/update/approve-payroll-timesheet.tool.ts @@ -4,6 +4,7 @@ import { approveXeroPayrollTimesheet, } from "../../handlers/approve-xero-payroll-timesheet.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const ApprovePayrollTimesheetTool = CreateXeroTool( "approve-timesheet", @@ -37,6 +38,7 @@ const ApprovePayrollTimesheetTool = CreateXeroTool( ], }; }, + ToolScopes.payrollTimesheets ); export default ApprovePayrollTimesheetTool; \ No newline at end of file diff --git a/src/tools/update/revert-payroll-timesheet.tool.ts b/src/tools/update/revert-payroll-timesheet.tool.ts index 17ac5f4a..0d25d05c 100644 --- a/src/tools/update/revert-payroll-timesheet.tool.ts +++ b/src/tools/update/revert-payroll-timesheet.tool.ts @@ -4,6 +4,7 @@ import { revertXeroPayrollTimesheet, } from "../../handlers/revert-xero-payroll-timesheet.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const RevertPayrollTimesheetTool = CreateXeroTool( "revert-timesheet", @@ -37,6 +38,7 @@ const RevertPayrollTimesheetTool = CreateXeroTool( ], }; }, + ToolScopes.payrollTimesheets ); export default RevertPayrollTimesheetTool; \ No newline at end of file diff --git a/src/tools/update/update-bank-transaction.tool.ts b/src/tools/update/update-bank-transaction.tool.ts index 874c4fff..5ae75c4c 100644 --- a/src/tools/update/update-bank-transaction.tool.ts +++ b/src/tools/update/update-bank-transaction.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { updateXeroBankTransaction } from "../../handlers/update-xero-bank-transaction.handler.js"; import { bankTransactionDeepLink } from "../../consts/deeplinks.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const lineItemSchema = z.object({ description: z.string(), @@ -73,7 +74,8 @@ const UpdateBankTransactionTool = CreateXeroTool( }, ], }; - } + }, + ToolScopes.accountingTransactions ); export default UpdateBankTransactionTool; \ No newline at end of file diff --git a/src/tools/update/update-contact.tool.ts b/src/tools/update/update-contact.tool.ts index 242af186..082da015 100644 --- a/src/tools/update/update-contact.tool.ts +++ b/src/tools/update/update-contact.tool.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js"; import { ensureError } from "../../helpers/ensure-error.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const UpdateContactTool = CreateXeroTool( "update-contact", @@ -105,6 +106,7 @@ const UpdateContactTool = CreateXeroTool( }; } }, + ToolScopes.accountingContacts ); export default UpdateContactTool; diff --git a/src/tools/update/update-credit-note.tool.ts b/src/tools/update/update-credit-note.tool.ts index a3b676c3..ba6230a3 100644 --- a/src/tools/update/update-credit-note.tool.ts +++ b/src/tools/update/update-credit-note.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { updateXeroCreditNote } from "../../handlers/update-xero-credit-note.handler.js"; import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const lineItemSchema = z.object({ description: z.string(), @@ -90,6 +91,7 @@ const UpdateCreditNoteTool = CreateXeroTool( ], }; }, + ToolScopes.accountingTransactions ); export default UpdateCreditNoteTool; \ No newline at end of file diff --git a/src/tools/update/update-invoice.tool.ts b/src/tools/update/update-invoice.tool.ts index 34cb616c..3abac9cf 100644 --- a/src/tools/update/update-invoice.tool.ts +++ b/src/tools/update/update-invoice.tool.ts @@ -3,6 +3,7 @@ import { updateXeroInvoice } from "../../handlers/update-xero-invoice.handler.js import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { Invoice } from "xero-node"; +import { ToolScopes } from "../../helpers/scopes.js"; const trackingSchema = z.object({ name: z.string().describe("The name of the tracking category. Can be obtained from the list-tracking-categories tool"), @@ -114,6 +115,7 @@ const UpdateInvoiceTool = CreateXeroTool( ], }; }, + ToolScopes.accountingTransactions ); export default UpdateInvoiceTool; diff --git a/src/tools/update/update-item.tool.ts b/src/tools/update/update-item.tool.ts index d49f2937..494b8c5a 100644 --- a/src/tools/update/update-item.tool.ts +++ b/src/tools/update/update-item.tool.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { updateXeroItem } from "../../handlers/update-xero-item.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const purchaseDetailsSchema = z.object({ unitPrice: z.number().optional(), @@ -82,6 +83,7 @@ const UpdateItemTool = CreateXeroTool( ], }; }, + ToolScopes.accountingSettings ); export default UpdateItemTool; \ No newline at end of file diff --git a/src/tools/update/update-manual-journal-tool.ts b/src/tools/update/update-manual-journal-tool.ts index 6be48e56..687dea3e 100644 --- a/src/tools/update/update-manual-journal-tool.ts +++ b/src/tools/update/update-manual-journal-tool.ts @@ -4,6 +4,7 @@ import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js"; import { ensureError } from "../../helpers/ensure-error.js"; import { LineAmountTypes, ManualJournal } from "xero-node"; import { updateXeroManualJournal } from "../../handlers/update-xero-manual-journal.handler.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const UpdateManualJournalTool = CreateXeroTool( "update-manual-journal", @@ -119,6 +120,7 @@ const UpdateManualJournalTool = CreateXeroTool( }; } }, + ToolScopes.accountingTransactions ); export default UpdateManualJournalTool; diff --git a/src/tools/update/update-payroll-timesheet-add-line.tool.ts b/src/tools/update/update-payroll-timesheet-add-line.tool.ts index 498efecf..a87d9cc9 100644 --- a/src/tools/update/update-payroll-timesheet-add-line.tool.ts +++ b/src/tools/update/update-payroll-timesheet-add-line.tool.ts @@ -7,6 +7,7 @@ import { updateXeroPayrollTimesheetAddLine, } from "../../handlers/update-xero-payroll-timesheet-add-line.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const AddTimesheetLineTool = CreateXeroTool( "add-timesheet-line", @@ -45,6 +46,7 @@ const AddTimesheetLineTool = CreateXeroTool( ], }; }, + ToolScopes.payrollTimesheets ); export default AddTimesheetLineTool; \ No newline at end of file diff --git a/src/tools/update/update-payroll-timesheet-update-line.tool.ts b/src/tools/update/update-payroll-timesheet-update-line.tool.ts index 58afbb0c..620c557c 100644 --- a/src/tools/update/update-payroll-timesheet-update-line.tool.ts +++ b/src/tools/update/update-payroll-timesheet-update-line.tool.ts @@ -7,6 +7,7 @@ import { updateXeroPayrollTimesheetUpdateLine, } from "../../handlers/update-xero-payroll-timesheet-update-line.handler.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const UpdatePayrollTimesheetLineTool = CreateXeroTool( "update-timesheet-line", @@ -46,6 +47,7 @@ const UpdatePayrollTimesheetLineTool = CreateXeroTool( ], }; }, + ToolScopes.payrollTimesheets ); export default UpdatePayrollTimesheetLineTool; \ No newline at end of file diff --git a/src/tools/update/update-quote.tool.ts b/src/tools/update/update-quote.tool.ts index c7ece6e5..e8c4674e 100644 --- a/src/tools/update/update-quote.tool.ts +++ b/src/tools/update/update-quote.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { updateXeroQuote } from "../../handlers/update-xero-quote.handler.js"; import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const lineItemSchema = z.object({ description: z.string(), @@ -93,6 +94,7 @@ const UpdateQuoteTool = CreateXeroTool( ], }; }, + ToolScopes.accountingTransactions ); export default UpdateQuoteTool; \ No newline at end of file diff --git a/src/tools/update/update-tracking-category.tool.ts b/src/tools/update/update-tracking-category.tool.ts index 9871f188..598b7ab7 100644 --- a/src/tools/update/update-tracking-category.tool.ts +++ b/src/tools/update/update-tracking-category.tool.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { updateXeroTrackingCategory } from "../../handlers/update-xero-tracking-category.handler.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const UpdateTrackingCategoryTool = CreateXeroTool( "update-tracking-category", @@ -34,7 +35,8 @@ const UpdateTrackingCategoryTool = CreateXeroTool( }, ] }; - } + }, + ToolScopes.accountingSettings ); export default UpdateTrackingCategoryTool; \ No newline at end of file diff --git a/src/tools/update/update-tracking-options.tool.ts b/src/tools/update/update-tracking-options.tool.ts index 4d6dabab..794c3eb4 100644 --- a/src/tools/update/update-tracking-options.tool.ts +++ b/src/tools/update/update-tracking-options.tool.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; import { formatTrackingOption } from "../../helpers/format-tracking-option.js"; import { updateXeroTrackingOption } from "../../handlers/update-xero-tracking-options.handler.js"; +import { ToolScopes } from "../../helpers/scopes.js"; const trackingOptionSchema = z.object({ trackingOptionId: z.string(), @@ -40,7 +41,8 @@ const UpdateTrackingOptionsTool = CreateXeroTool( }, ] }; - } + }, + ToolScopes.accountingSettings ); export default UpdateTrackingOptionsTool; \ No newline at end of file diff --git a/src/types/tool-definition.ts b/src/types/tool-definition.ts index 862a8491..efefe980 100644 --- a/src/types/tool-definition.ts +++ b/src/types/tool-definition.ts @@ -11,4 +11,6 @@ export interface ToolDefinition< description: string; schema: Args; handler: ToolCallback; + /** OAuth scopes required for this tool; empty = always register (when not in bearer-token mode). */ + requiredScopes?: string[]; }