@agenttax/mcp-server 1.1.1 → 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 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` and a `compliance_note`. Classification comes from `description`. Sellers get $0 in states where no nexus is configured; the response says so in `nexus_warning`.
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
- "Classifies the sale from its description, calculates tax owed, and logs it to your AgentTax account (requires API key for history).",
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 — used to classify the transaction (e.g. 'API access', 'MCP tool subscription', 'compute credits', 'AI consulting')"),
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 workType = classifyWorkType(params.description);
169
- const transactionType = WORK_TYPE_TO_TX_TYPE[workType] || "saas";
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenttax/mcp-server",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "mcpName": "io.github.AgentTax/agenttax-mcp",
5
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",
@@ -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",
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.1.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.1.1",
16
+ "version": "1.2.0",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  },