@agenttax/mcp-server 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +5 -2
- package/classify.js +100 -0
- package/index.js +16 -40
- package/package.json +7 -4
- package/server.json +2 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
Tax-safety release for `track_payment`. Some results change; read before upgrading.
|
|
6
|
+
|
|
7
|
+
- **Guessed consulting and trading are no longer exempt.** 1.1.1 guessed a work type from `description` and sent it as if you had declared it. A description that looked like consulting ("SOC 2 audit platform subscription", "tax advice") sold to an Arizona buyer came back $0 under the AZ professional-services exemption, and one that looked like trading ("game asset pack", "token swap fee") came back $0 in every state as a financial service. Both guesses are now taxed as SaaS. If the sale really is consulting or trading, pass `work_type` and the exemption applies as before.
|
|
8
|
+
- **New optional `work_type` argument on `track_payment`** (`compute` | `research` | `information_service` | `content` | `consulting` | `trading`). It overrides the description and is treated as a declaration. Pass it for an exact answer.
|
|
9
|
+
- **Guessed compute and research are unchanged** from 1.1.1. They are not exemptions, but those categories can tax less than SaaS in some states (e.g. CT, MD, SC, TN, WV). Pass `work_type` for an exact answer.
|
|
10
|
+
- **`is_b2b` is sent only when you pass it.** 1.1.1 always sent `is_b2b: false`, which silently declared every buyer a consumer. Ohio sellers to consumers: pass `is_b2b: false`, or the sale is now taxed (Ohio exempts SaaS and data processing only for a declared consumer; an unknown buyer is taxed). With `is_b2b` omitted, AgentTax may also auto-detect a business buyer when the counterparty has a KYA principal record marked as a business, and apply B2B rules (e.g. Maryland 3%, Iowa B2B exemption); 1.1.1's explicit `false` blocked that. Pass `is_b2b` yourself if you know the buyer.
|
|
11
|
+
- The `track_payment` response has a new `classification` field saying where the work type came from (`explicit`, `inferred_from_description` or `default`) and, when a consulting or trading guess was not used, a note telling you to resend with `work_type`.
|
|
12
|
+
- No other tool changed. `calculate_tax` and `ingest_transactions` already sent only what you gave them.
|
package/README.md
CHANGED
|
@@ -115,12 +115,15 @@ track_payment({
|
|
|
115
115
|
buyer_state: "TX",
|
|
116
116
|
buyer_zip: "78701",
|
|
117
117
|
description: "MCP tool subscription",
|
|
118
|
+
work_type: "content",
|
|
118
119
|
payment_id: "pi_stripe_abc123",
|
|
119
120
|
source: "stripe"
|
|
120
121
|
})
|
|
121
122
|
```
|
|
122
123
|
|
|
123
|
-
Returns `tax_owed`, `tax_rate`, `taxable`, `transaction_id
|
|
124
|
+
Returns `tax_owed`, `tax_rate`, `taxable`, `transaction_id`, a `classification` (where the work type came from) and a `compliance_note`. Sellers get $0 in states where no nexus is configured; the response says so in `nexus_warning`.
|
|
125
|
+
|
|
126
|
+
**Pass `work_type` explicitly** (`compute` | `research` | `information_service` | `content` | `consulting` | `trading`) when you know what you sold. It is a declaration and gives the exact answer, and it is the only way the consulting or trading exemption applies: for example, AI consulting sold to an Arizona buyer is an exempt professional service (A.R.S. §42-5061(A)(1), TPR 93-48), so `work_type: "consulting"` returns $0 in AZ. Without `work_type` the server guesses from `description`, but a guess never takes the consulting or trading exemption: a description that looks like consulting (e.g. "SOC 2 audit platform subscription") or trading is taxed as SaaS, and `classification.note` tells you to resend with `work_type` if the guess was right. A description that looks like compute or research keeps the 1.1.1 classification, which can tax less than SaaS in some states (e.g. CT, MD, SC, TN, WV); pass `work_type` for an exact answer. Leave `is_b2b` out when you don't know the buyer; `false` declares a consumer. Upgrading from 1.1.1? Some `track_payment` results change; see [CHANGELOG.md](CHANGELOG.md).
|
|
124
127
|
|
|
125
128
|
### calculate_tax
|
|
126
129
|
|
|
@@ -137,7 +140,7 @@ calculate_tax({
|
|
|
137
140
|
})
|
|
138
141
|
```
|
|
139
142
|
|
|
140
|
-
Optional fields: `seller_state` / `seller_zip` (origin-sourced intrastate sales in TX, UT, AZ, TN and some OH sales), `use_context` (Maryland B2B), `digital_content_type` (with `transaction_type: "digital_good"`), `seller_remitting`.
|
|
143
|
+
Optional fields: `seller_state` / `seller_zip` (origin-sourced intrastate sales in TX, UT, AZ, TN and some OH sales), `use_context` (Maryland B2B), `digital_content_type` (with `transaction_type: "digital_good"`), `seller_remitting`. `transaction_type` and `work_type` are sent exactly as given and treated as declared facts; don't guess them (`consulting` is exempt in Arizona).
|
|
141
144
|
|
|
142
145
|
### Nexus
|
|
143
146
|
|
package/classify.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// How track_payment turns its arguments into a POST /api/v1/calculate body.
|
|
2
|
+
// Kept apart from index.js (which starts the stdio server on import) so it can
|
|
3
|
+
// be unit-tested.
|
|
4
|
+
|
|
5
|
+
export const WORK_TYPES = ["compute", "research", "information_service", "content", "consulting", "trading"];
|
|
6
|
+
|
|
7
|
+
// transaction_type sent for each work_type.
|
|
8
|
+
export const WORK_TYPE_TO_TX_TYPE = {
|
|
9
|
+
compute: "compute",
|
|
10
|
+
research: "api_access",
|
|
11
|
+
information_service: "api_access",
|
|
12
|
+
content: "saas",
|
|
13
|
+
consulting: "consulting",
|
|
14
|
+
trading: "digital_good",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Work types that, in the AgentTax engine, can route a sale to an exemption:
|
|
18
|
+
// consulting -> exempt professional service in Arizona (A.R.S. 42-5061(A)(1),
|
|
19
|
+
// TPR 93-48), and the engine also exempts AZ when only
|
|
20
|
+
// transaction_type "consulting" is sent;
|
|
21
|
+
// trading -> financial_service, exempt in every state.
|
|
22
|
+
// The engine grants these only on a DECLARED fact. The public calculate API
|
|
23
|
+
// has no way to mark a work type as guessed, so a keyword match on the
|
|
24
|
+
// description must never send either one. A guess that lands here is
|
|
25
|
+
// treated as unclassified (the same default as a description with no
|
|
26
|
+
// keyword), which is taxed the way the engine taxes SaaS.
|
|
27
|
+
export const EXEMPTION_BEARING_WORK_TYPES = new Set(["consulting", "trading"]);
|
|
28
|
+
|
|
29
|
+
// Guess a work type from a plain-English description. Returns null when no
|
|
30
|
+
// keyword matches. A guess is a hint only: see buildTrackPaymentRequest.
|
|
31
|
+
export function guessWorkType(description) {
|
|
32
|
+
if (!description) return null;
|
|
33
|
+
const d = description.toLowerCase();
|
|
34
|
+
if (/\b(compute|inference|gpu|process|api[\s-]?call|token)\b/.test(d)) return "compute";
|
|
35
|
+
if (/\b(research|analysis|analytics|data[\s-]?(process|feed))\b/.test(d)) return "research";
|
|
36
|
+
if (/\b(consult|consulting|advisory|advice|audit)\b/.test(d)) return "consulting";
|
|
37
|
+
if (/\b(trade|trading|swap|asset)\b/.test(d)) return "trading";
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Returns { body, classification } where body is the calculate request and
|
|
42
|
+
// classification says where the work type came from.
|
|
43
|
+
//
|
|
44
|
+
// explicit work_type -> sent as work_type, with its transaction_type
|
|
45
|
+
// guessed compute / research -> sent as in 1.1.1. Not an exemption, but their
|
|
46
|
+
// categories can tax less than SaaS in some states
|
|
47
|
+
// (e.g. CT, MD, SC, TN, WV); changing that is a
|
|
48
|
+
// tax-value decision, so it is left as is.
|
|
49
|
+
// guessed consulting/trading -> NOT sent; transaction_type "saas", no work_type
|
|
50
|
+
// no guess -> transaction_type "saas", no work_type
|
|
51
|
+
// is_b2b -> sent only when the caller passed a boolean
|
|
52
|
+
export function buildTrackPaymentRequest(params, { now = Date.now } = {}) {
|
|
53
|
+
const counterpartyId = params.payment_id || `payment_${now()}`;
|
|
54
|
+
let workType;
|
|
55
|
+
let transactionType;
|
|
56
|
+
let classification;
|
|
57
|
+
|
|
58
|
+
if (params.work_type) {
|
|
59
|
+
workType = params.work_type;
|
|
60
|
+
transactionType = WORK_TYPE_TO_TX_TYPE[workType] || "saas";
|
|
61
|
+
classification = { source: "explicit", work_type: workType };
|
|
62
|
+
} else {
|
|
63
|
+
const guess = guessWorkType(params.description);
|
|
64
|
+
if (guess && !EXEMPTION_BEARING_WORK_TYPES.has(guess)) {
|
|
65
|
+
workType = guess;
|
|
66
|
+
transactionType = WORK_TYPE_TO_TX_TYPE[guess];
|
|
67
|
+
classification = { source: "inferred_from_description", work_type: guess };
|
|
68
|
+
} else {
|
|
69
|
+
workType = undefined; // let the API default from transaction_type
|
|
70
|
+
transactionType = "saas";
|
|
71
|
+
classification = guess
|
|
72
|
+
? {
|
|
73
|
+
source: "default",
|
|
74
|
+
work_type: null,
|
|
75
|
+
guess_not_used: guess,
|
|
76
|
+
note:
|
|
77
|
+
`The description looks like "${guess}", but a guess never takes the ${guess} exemption, so this sale was ` +
|
|
78
|
+
`taxed as a SaaS / digital service. If the sale really is ${guess}, call track_payment again with ` +
|
|
79
|
+
`work_type: "${guess}".`,
|
|
80
|
+
}
|
|
81
|
+
: { source: "default", work_type: null };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const body = {
|
|
86
|
+
role: "seller",
|
|
87
|
+
amount: params.amount,
|
|
88
|
+
buyer_state: params.buyer_state,
|
|
89
|
+
buyer_zip: params.buyer_zip,
|
|
90
|
+
transaction_type: transactionType,
|
|
91
|
+
counterparty_id: counterpartyId,
|
|
92
|
+
};
|
|
93
|
+
if (workType) body.work_type = workType;
|
|
94
|
+
// Send is_b2b only when the caller said. The API treats a present
|
|
95
|
+
// is_b2b:false as a DECLARED consumer, which takes explicit-only consumer
|
|
96
|
+
// exemptions (e.g. Ohio SaaS, R.C. 5739.01(B)(3)(e)); an unknown buyer must
|
|
97
|
+
// stay unknown so it is taxed conservatively.
|
|
98
|
+
if (typeof params.is_b2b === "boolean") body.is_b2b = params.is_b2b;
|
|
99
|
+
return { body, classification };
|
|
100
|
+
}
|
package/index.js
CHANGED
|
@@ -4,32 +4,13 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
import { WORK_TYPES, buildTrackPaymentRequest } from "./classify.js";
|
|
7
8
|
|
|
8
9
|
const { version: PKG_VERSION } = createRequire(import.meta.url)("./package.json");
|
|
9
10
|
|
|
10
11
|
const BASE_URL = process.env.AGENTTAX_BASE_URL || "https://agenttax.io";
|
|
11
12
|
const API_KEY = process.env.AGENTTAX_API_KEY || "";
|
|
12
13
|
|
|
13
|
-
// Classify work type from a plain-English description.
|
|
14
|
-
// Developers can describe what they sold and we auto-classify for tax purposes.
|
|
15
|
-
function classifyWorkType(description) {
|
|
16
|
-
if (!description) return "content";
|
|
17
|
-
const d = description.toLowerCase();
|
|
18
|
-
if (/\b(compute|inference|gpu|process|api[\s-]?call|token)\b/.test(d)) return "compute";
|
|
19
|
-
if (/\b(research|analysis|analytics|data[\s-]?(process|feed))\b/.test(d)) return "research";
|
|
20
|
-
if (/\b(consult|advisory|advice|audit)\b/.test(d)) return "consulting";
|
|
21
|
-
if (/\b(trade|trading|swap|asset)\b/.test(d)) return "trading";
|
|
22
|
-
return "content"; // Default: SaaS / digital service
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const WORK_TYPE_TO_TX_TYPE = {
|
|
26
|
-
compute: "compute",
|
|
27
|
-
research: "api_access",
|
|
28
|
-
content: "saas",
|
|
29
|
-
consulting: "consulting",
|
|
30
|
-
trading: "digital_good",
|
|
31
|
-
};
|
|
32
|
-
|
|
33
14
|
async function apiCall(method, path, body = null) {
|
|
34
15
|
const headers = { "Content-Type": "application/json" };
|
|
35
16
|
if (API_KEY) headers["X-API-Key"] = API_KEY;
|
|
@@ -71,7 +52,6 @@ const STATES = [
|
|
|
71
52
|
const stateCode = z.preprocess((v) => (typeof v === "string" ? v.toUpperCase() : v), z.enum(STATES));
|
|
72
53
|
const zip5 = z.string().regex(/^\d{5}$/);
|
|
73
54
|
|
|
74
|
-
const WORK_TYPES = ["compute", "research", "information_service", "content", "consulting", "trading"];
|
|
75
55
|
const TRANSACTION_TYPES = [
|
|
76
56
|
"compute", "api_access", "data_purchase", "saas", "ai_labor", "storage",
|
|
77
57
|
"digital_good", "consulting", "data_processing", "cloud_infrastructure",
|
|
@@ -108,7 +88,8 @@ server.registerTool(
|
|
|
108
88
|
description:
|
|
109
89
|
"Calculate US sales tax (seller) or use tax (buyer) for an AI agent transaction. Returns tax amount, rate, jurisdiction, " +
|
|
110
90
|
"audit trail, confidence score, and advisories. Sellers get $0 sales tax in states without configured nexus " +
|
|
111
|
-
"(see configure_nexus); a $0 total means no collection obligation, not necessarily no tax, so read nexus_warning and advisories."
|
|
91
|
+
"(see configure_nexus); a $0 total means no collection obligation, not necessarily no tax, so read nexus_warning and advisories. " +
|
|
92
|
+
"work_type and transaction_type are sent as you give them and treated as declared facts (e.g. consulting is exempt in Arizona), so do not guess them.",
|
|
112
93
|
inputSchema: {
|
|
113
94
|
role: z.enum(["buyer", "seller"]).describe("Your role in the transaction"),
|
|
114
95
|
amount: z.number().positive().max(100_000_000).describe("Transaction amount in USD"),
|
|
@@ -121,7 +102,8 @@ server.registerTool(
|
|
|
121
102
|
seller_zip: zip5.optional()
|
|
122
103
|
.describe("Seller's 5-digit zip. Required for TX/UT/AZ/TN origin-sourced intrastate sales and intrastate OH license/digital_good sales"),
|
|
123
104
|
work_type: z.enum(WORK_TYPES).optional()
|
|
124
|
-
.describe("What the agent's work is economically; drives per-state taxability (compute, research, information_service, content, consulting, trading)"
|
|
105
|
+
.describe("What the agent's work is economically; drives per-state taxability (compute, research, information_service, content, consulting, trading). " +
|
|
106
|
+
"Pass it only when you know it: it is a declaration. consulting is an exempt professional service in Arizona"),
|
|
125
107
|
is_b2b: z.boolean().optional().describe("Business-to-business transaction (triggers state B2B rules, e.g. IA, MD)"),
|
|
126
108
|
use_context: z.enum(["enterprise_system", "individual", "mixed"]).optional()
|
|
127
109
|
.describe("Maryland only, with is_b2b=true: whether the purchase is solely for use in an enterprise computer system"),
|
|
@@ -150,35 +132,28 @@ server.registerTool(
|
|
|
150
132
|
title: "Track a payment you received",
|
|
151
133
|
description:
|
|
152
134
|
"Track a payment you received and calculate your sales tax liability. Call this after receiving any payment — Stripe, x402, or direct. " +
|
|
153
|
-
"
|
|
135
|
+
"Calculates tax owed and logs it to your AgentTax account (requires API key for history). " +
|
|
136
|
+
"Pass work_type when you know what you sold, for an exact answer; it is the only way to get the consulting exemption (AI consulting is exempt in Arizona) or the trading exemption. " +
|
|
137
|
+
"Without it the sale is classified from description: a description that looks like consulting or trading is taxed as SaaS, never exempt; " +
|
|
138
|
+
"one that looks like compute or research keeps that classification, which can tax less than SaaS in some states (e.g. CT, MD, SC, TN, WV).",
|
|
154
139
|
inputSchema: {
|
|
155
140
|
amount: z.number().positive().max(100_000_000).describe("Payment amount in USD"),
|
|
156
141
|
buyer_state: stateCode.describe("2-letter US state where the buyer is located (e.g. TX, NY, CA)"),
|
|
157
142
|
buyer_zip: zip5.optional().describe("Buyer's 5-digit zip code for local tax rates (more precise)"),
|
|
158
|
-
description: z.string().optional().describe("What you sold
|
|
143
|
+
description: z.string().optional().describe("What you sold (e.g. 'API access', 'MCP tool subscription', 'compute credits'). Used only when work_type is not given; a guess never takes the consulting or trading exemption"),
|
|
144
|
+
work_type: z.enum(WORK_TYPES).optional()
|
|
145
|
+
.describe("What you sold, declared: compute, research, information_service, content, consulting, trading. Overrides description. consulting is an exempt professional service in Arizona"),
|
|
159
146
|
payment_id: z.string().regex(/^[A-Za-z0-9_\-.:@]{1,200}$/).optional()
|
|
160
147
|
.describe("Your payment reference ID (Stripe payment_intent ID, x402 receipt, invoice number); letters, digits and _-.:@ only"),
|
|
161
148
|
source: z.enum(["stripe", "x402", "direct", "other"]).optional().describe("Payment processor used"),
|
|
162
|
-
is_b2b: z.boolean().optional().describe("Buyer is a business (affects rates in MD, IA, NJ)"),
|
|
149
|
+
is_b2b: z.boolean().optional().describe("Buyer is a business (affects rates in MD, IA, NJ, OH). Omit if unknown: false declares a consumer"),
|
|
163
150
|
},
|
|
164
151
|
annotations: WRITES,
|
|
165
152
|
},
|
|
166
153
|
async (params) => {
|
|
167
154
|
try {
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
const counterpartyId = params.payment_id || `payment_${Date.now()}`;
|
|
171
|
-
|
|
172
|
-
const result = await apiCall("POST", "/api/v1/calculate", {
|
|
173
|
-
role: "seller",
|
|
174
|
-
amount: params.amount,
|
|
175
|
-
buyer_state: params.buyer_state,
|
|
176
|
-
buyer_zip: params.buyer_zip,
|
|
177
|
-
transaction_type: transactionType,
|
|
178
|
-
work_type: workType,
|
|
179
|
-
counterparty_id: counterpartyId,
|
|
180
|
-
is_b2b: params.is_b2b || false,
|
|
181
|
-
});
|
|
155
|
+
const { body, classification } = buildTrackPaymentRequest(params);
|
|
156
|
+
const result = await apiCall("POST", "/api/v1/calculate", body);
|
|
182
157
|
|
|
183
158
|
if (!result.success) {
|
|
184
159
|
return {
|
|
@@ -197,6 +172,7 @@ server.registerTool(
|
|
|
197
172
|
tax_rate: result.sales_tax?.rate || result.combined_rate || 0,
|
|
198
173
|
taxable: taxOwed > 0,
|
|
199
174
|
work_type: result.work_type,
|
|
175
|
+
classification,
|
|
200
176
|
transaction_id: result.transaction_id,
|
|
201
177
|
nexus_warning: result.nexus_warning,
|
|
202
178
|
compliance_note: taxOwed > 0
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenttax/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"mcpName": "io.github.AgentTax/agenttax-mcp",
|
|
5
|
-
"description": "MCP server for AgentTax
|
|
5
|
+
"description": "MCP server for AgentTax — US sales tax, nexus thresholds, capital gains and 1099-DA tools for AI agents",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
@@ -10,12 +10,15 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"index.js",
|
|
13
|
+
"classify.js",
|
|
13
14
|
"README.md",
|
|
15
|
+
"CHANGELOG.md",
|
|
14
16
|
"LICENSE",
|
|
15
17
|
"server.json"
|
|
16
18
|
],
|
|
17
19
|
"scripts": {
|
|
18
|
-
"start": "node index.js"
|
|
20
|
+
"start": "node index.js",
|
|
21
|
+
"test": "node --test test/track-payment.test.js"
|
|
19
22
|
},
|
|
20
23
|
"keywords": [
|
|
21
24
|
"1099-da",
|
|
@@ -35,7 +38,7 @@
|
|
|
35
38
|
"homepage": "https://agenttax.io",
|
|
36
39
|
"repository": {
|
|
37
40
|
"type": "git",
|
|
38
|
-
"url": "https://github.com/AgentTax/agenttax-mcp.git"
|
|
41
|
+
"url": "git+https://github.com/AgentTax/agenttax-mcp.git"
|
|
39
42
|
},
|
|
40
43
|
"bugs": {
|
|
41
44
|
"url": "https://github.com/AgentTax/agenttax-mcp/issues"
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.AgentTax/agenttax-mcp",
|
|
4
4
|
"title": "AgentTax",
|
|
5
5
|
"description": "US sales/use tax, nexus thresholds, capital gains and 1099-DA tools for AI agent transactions.",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.2.0",
|
|
7
7
|
"websiteUrl": "https://agenttax.io",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/AgentTax/agenttax-mcp",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"identifier": "@agenttax/mcp-server",
|
|
16
|
-
"version": "1.
|
|
16
|
+
"version": "1.2.0",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
},
|