@armor/zuora-mcp 0.0.0-development
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/.env.example +16 -0
- package/README.md +249 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +73 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +56 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.d.ts +11 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +236 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +11 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +526 -0
- package/dist/resources.js.map +1 -0
- package/dist/setup.d.ts +12 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +188 -0
- package/dist/setup.js.map +1 -0
- package/dist/token-manager.d.ts +34 -0
- package/dist/token-manager.d.ts.map +1 -0
- package/dist/token-manager.js +103 -0
- package/dist/token-manager.js.map +1 -0
- package/dist/tools.d.ts +1096 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +2841 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +758 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/zoql-helpers.d.ts +68 -0
- package/dist/zoql-helpers.d.ts.map +1 -0
- package/dist/zoql-helpers.js +154 -0
- package/dist/zoql-helpers.js.map +1 -0
- package/dist/zuora-client.d.ts +184 -0
- package/dist/zuora-client.d.ts.map +1 -0
- package/dist/zuora-client.js +583 -0
- package/dist/zuora-client.js.map +1 -0
- package/package.json +60 -0
package/dist/setup.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive setup wizard for zuora-mcp.
|
|
3
|
+
*
|
|
4
|
+
* Zero external dependencies — uses only Node built-ins:
|
|
5
|
+
* node:readline/promises, node:fs, node:path, node:os
|
|
6
|
+
*
|
|
7
|
+
* Detects Claude Code and Claude Desktop config files, prompts for
|
|
8
|
+
* Zuora OAuth credentials, and writes MCP server entries using the
|
|
9
|
+
* npx-based format so the server auto-installs on first use.
|
|
10
|
+
*/
|
|
11
|
+
import { createInterface } from "node:readline/promises";
|
|
12
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { homedir, platform } from "node:os";
|
|
15
|
+
const ZUORA_BASE_URLS = [
|
|
16
|
+
{ label: "US Sandbox", url: "https://rest.apisandbox.zuora.com" },
|
|
17
|
+
{ label: "US Production", url: "https://rest.zuora.com" },
|
|
18
|
+
{ label: "EU Sandbox", url: "https://rest.sandbox.eu.zuora.com" },
|
|
19
|
+
{ label: "EU Production", url: "https://rest.eu.zuora.com" },
|
|
20
|
+
];
|
|
21
|
+
function getClaudeCodeConfigPath() {
|
|
22
|
+
return join(homedir(), ".claude", ".mcp.json");
|
|
23
|
+
}
|
|
24
|
+
function getClaudeDesktopConfigPath() {
|
|
25
|
+
const os = platform();
|
|
26
|
+
if (os === "darwin") {
|
|
27
|
+
return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
28
|
+
}
|
|
29
|
+
if (os === "win32") {
|
|
30
|
+
const appData = process.env.APPDATA ?? join(homedir(), "AppData", "Roaming");
|
|
31
|
+
return join(appData, "Claude", "claude_desktop_config.json");
|
|
32
|
+
}
|
|
33
|
+
// Linux / other — XDG_CONFIG_HOME fallback
|
|
34
|
+
const configHome = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
35
|
+
return join(configHome, "Claude", "claude_desktop_config.json");
|
|
36
|
+
}
|
|
37
|
+
function detectTargets() {
|
|
38
|
+
const targets = [
|
|
39
|
+
{
|
|
40
|
+
name: "Claude Code",
|
|
41
|
+
path: getClaudeCodeConfigPath(),
|
|
42
|
+
exists: existsSync(getClaudeCodeConfigPath()),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "Claude Desktop",
|
|
46
|
+
path: getClaudeDesktopConfigPath(),
|
|
47
|
+
exists: existsSync(getClaudeDesktopConfigPath()),
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
return targets;
|
|
51
|
+
}
|
|
52
|
+
function readConfig(configPath) {
|
|
53
|
+
try {
|
|
54
|
+
const raw = readFileSync(configPath, "utf8");
|
|
55
|
+
const parsed = JSON.parse(raw);
|
|
56
|
+
return { mcpServers: parsed.mcpServers ?? {}, ...parsed };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { mcpServers: {} };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function writeConfig(configPath, config) {
|
|
63
|
+
const dir = join(configPath, "..");
|
|
64
|
+
if (!existsSync(dir)) {
|
|
65
|
+
mkdirSync(dir, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
68
|
+
}
|
|
69
|
+
function buildServerEntry(env) {
|
|
70
|
+
return {
|
|
71
|
+
command: "npx",
|
|
72
|
+
args: ["-y", "@armor/zuora-mcp"],
|
|
73
|
+
env: {
|
|
74
|
+
ZUORA_CLIENT_ID: env.clientId,
|
|
75
|
+
ZUORA_CLIENT_SECRET: env.clientSecret,
|
|
76
|
+
ZUORA_BASE_URL: env.baseUrl,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Interactive prompts
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
async function prompt(rl, question, defaultValue) {
|
|
84
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
85
|
+
const answer = (await rl.question(`${question}${suffix}: `)).trim();
|
|
86
|
+
return answer || defaultValue || "";
|
|
87
|
+
}
|
|
88
|
+
async function promptRequired(rl, question) {
|
|
89
|
+
let answer = "";
|
|
90
|
+
while (!answer) {
|
|
91
|
+
answer = (await rl.question(`${question}: `)).trim();
|
|
92
|
+
if (!answer) {
|
|
93
|
+
console.log(" This field is required.");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return answer;
|
|
97
|
+
}
|
|
98
|
+
async function promptBaseUrl(rl) {
|
|
99
|
+
console.log("\nZuora environment:");
|
|
100
|
+
for (let i = 0; i < ZUORA_BASE_URLS.length; i++) {
|
|
101
|
+
console.log(` ${i + 1}. ${ZUORA_BASE_URLS[i].label} (${ZUORA_BASE_URLS[i].url})`);
|
|
102
|
+
}
|
|
103
|
+
console.log(` 5. Custom URL`);
|
|
104
|
+
let choice = "";
|
|
105
|
+
while (!choice) {
|
|
106
|
+
choice = (await rl.question("Select [1-5] (default 1): ")).trim() || "1";
|
|
107
|
+
const num = parseInt(choice, 10);
|
|
108
|
+
if (num >= 1 && num <= 4) {
|
|
109
|
+
return ZUORA_BASE_URLS[num - 1].url;
|
|
110
|
+
}
|
|
111
|
+
if (num === 5) {
|
|
112
|
+
return promptRequired(rl, "Custom Zuora base URL");
|
|
113
|
+
}
|
|
114
|
+
console.log(" Please enter a number between 1 and 5.");
|
|
115
|
+
choice = "";
|
|
116
|
+
}
|
|
117
|
+
return ZUORA_BASE_URLS[0].url; // unreachable but satisfies TS
|
|
118
|
+
}
|
|
119
|
+
async function promptTargets(rl, targets) {
|
|
120
|
+
console.log("\nDetected config locations:");
|
|
121
|
+
const available = [];
|
|
122
|
+
for (const t of targets) {
|
|
123
|
+
const status = t.exists ? "found" : "will create";
|
|
124
|
+
console.log(` - ${t.name}: ${t.path} (${status})`);
|
|
125
|
+
available.push(t);
|
|
126
|
+
}
|
|
127
|
+
if (available.length === 0) {
|
|
128
|
+
console.log(" No config locations detected. Will create Claude Code config.");
|
|
129
|
+
return [{ name: "Claude Code", path: getClaudeCodeConfigPath(), exists: false }];
|
|
130
|
+
}
|
|
131
|
+
if (available.length === 1) {
|
|
132
|
+
const ok = await prompt(rl, `Configure ${available[0].name}? (Y/n)`, "Y");
|
|
133
|
+
return ok.toLowerCase() === "n" ? [] : available;
|
|
134
|
+
}
|
|
135
|
+
const answer = await prompt(rl, "Configure which? (1=Claude Code, 2=Claude Desktop, 3=Both)", "1");
|
|
136
|
+
if (answer === "3" || answer.toLowerCase() === "both")
|
|
137
|
+
return available;
|
|
138
|
+
if (answer === "2")
|
|
139
|
+
return [available[1]];
|
|
140
|
+
return [available[0]];
|
|
141
|
+
}
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Main entry
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
export async function runSetup() {
|
|
146
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
147
|
+
try {
|
|
148
|
+
console.log("=== Zuora MCP Setup ===\n");
|
|
149
|
+
console.log("This wizard configures Claude to use the Zuora billing MCP server.\n");
|
|
150
|
+
// 1. Collect credentials
|
|
151
|
+
const clientId = await promptRequired(rl, "Zuora OAuth Client ID");
|
|
152
|
+
const clientSecret = await promptRequired(rl, "Zuora OAuth Client Secret");
|
|
153
|
+
const baseUrl = await promptBaseUrl(rl);
|
|
154
|
+
// 2. Detect and choose targets
|
|
155
|
+
const allTargets = detectTargets();
|
|
156
|
+
const selectedTargets = await promptTargets(rl, allTargets);
|
|
157
|
+
if (selectedTargets.length === 0) {
|
|
158
|
+
console.log("\nNo targets selected. Exiting.");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// 3. Write config for each target
|
|
162
|
+
const entry = buildServerEntry({ clientId, clientSecret, baseUrl });
|
|
163
|
+
for (const target of selectedTargets) {
|
|
164
|
+
const config = readConfig(target.path);
|
|
165
|
+
// Check for existing zuora entry
|
|
166
|
+
if (config.mcpServers.zuora) {
|
|
167
|
+
const overwrite = await prompt(rl, `\n${target.name} already has a "zuora" entry. Overwrite? (Y/n)`, "Y");
|
|
168
|
+
if (overwrite.toLowerCase() === "n") {
|
|
169
|
+
console.log(` Skipped ${target.name}.`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
config.mcpServers.zuora = entry;
|
|
174
|
+
writeConfig(target.path, config);
|
|
175
|
+
console.log(` Wrote ${target.name} config: ${target.path}`);
|
|
176
|
+
}
|
|
177
|
+
// 4. Print next steps
|
|
178
|
+
console.log("\n=== Setup Complete ===\n");
|
|
179
|
+
console.log("Next steps:");
|
|
180
|
+
console.log(" 1. Restart Claude Code (or Claude Desktop) to load the new server");
|
|
181
|
+
console.log(" 2. Try asking Claude: \"Show me my Zuora account summary\"");
|
|
182
|
+
console.log("\nTo verify the server starts correctly:\n ZUORA_CLIENT_ID=... ZUORA_CLIENT_SECRET=... ZUORA_BASE_URL=... npx @armor/zuora-mcp");
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
rl.close();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 Token Manager for Zuora API
|
|
3
|
+
*
|
|
4
|
+
* Handles token lifecycle with:
|
|
5
|
+
* - Proactive refresh before expiry (60s buffer)
|
|
6
|
+
* - Promise coalescing for concurrent refresh requests
|
|
7
|
+
* - Clean error propagation on auth failures
|
|
8
|
+
*/
|
|
9
|
+
import type { Config } from "./config.js";
|
|
10
|
+
export declare class TokenManager {
|
|
11
|
+
private static readonly EXPIRY_BUFFER_MS;
|
|
12
|
+
private static readonly TOKEN_REQUEST_TIMEOUT_MS;
|
|
13
|
+
private readonly clientId;
|
|
14
|
+
private readonly clientSecret;
|
|
15
|
+
private readonly tokenUrl;
|
|
16
|
+
private readonly debug;
|
|
17
|
+
private tokenData;
|
|
18
|
+
private refreshPromise;
|
|
19
|
+
constructor(config: Config);
|
|
20
|
+
private log;
|
|
21
|
+
/**
|
|
22
|
+
* Get a valid access token. Refreshes proactively before expiry.
|
|
23
|
+
* Concurrent callers coalesce onto a single refresh request.
|
|
24
|
+
*/
|
|
25
|
+
getAccessToken(): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Force a token refresh. Called when a 401 is received mid-session,
|
|
28
|
+
* indicating the token expired between the validity check and the API call.
|
|
29
|
+
*/
|
|
30
|
+
forceRefresh(): Promise<string>;
|
|
31
|
+
private isTokenValid;
|
|
32
|
+
private fetchNewToken;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=token-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../src/token-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQ1C,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAU;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAU;IAE1D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAEhC,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,cAAc,CAAgC;gBAE1C,MAAM,EAAE,MAAM;IAO1B,OAAO,CAAC,GAAG;IAMX;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAkBvC;;;OAGG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAKrC,OAAO,CAAC,YAAY;YAON,aAAa;CAsD5B"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 Token Manager for Zuora API
|
|
3
|
+
*
|
|
4
|
+
* Handles token lifecycle with:
|
|
5
|
+
* - Proactive refresh before expiry (60s buffer)
|
|
6
|
+
* - Promise coalescing for concurrent refresh requests
|
|
7
|
+
* - Clean error propagation on auth failures
|
|
8
|
+
*/
|
|
9
|
+
export class TokenManager {
|
|
10
|
+
static EXPIRY_BUFFER_MS = 60_000;
|
|
11
|
+
static TOKEN_REQUEST_TIMEOUT_MS = 10_000;
|
|
12
|
+
clientId;
|
|
13
|
+
clientSecret;
|
|
14
|
+
tokenUrl;
|
|
15
|
+
debug;
|
|
16
|
+
tokenData = null;
|
|
17
|
+
refreshPromise = null;
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.clientId = config.zuoraClientId;
|
|
20
|
+
this.clientSecret = config.zuoraClientSecret;
|
|
21
|
+
this.tokenUrl = `${config.zuoraBaseUrl}/oauth/token`;
|
|
22
|
+
this.debug = config.debug;
|
|
23
|
+
}
|
|
24
|
+
log(message) {
|
|
25
|
+
if (this.debug) {
|
|
26
|
+
console.error(`[TokenManager] ${message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get a valid access token. Refreshes proactively before expiry.
|
|
31
|
+
* Concurrent callers coalesce onto a single refresh request.
|
|
32
|
+
*/
|
|
33
|
+
async getAccessToken() {
|
|
34
|
+
if (this.isTokenValid()) {
|
|
35
|
+
return this.tokenData.accessToken;
|
|
36
|
+
}
|
|
37
|
+
// Coalesce concurrent refresh calls into one network request
|
|
38
|
+
if (!this.refreshPromise) {
|
|
39
|
+
this.log("Token expired or missing, fetching new token");
|
|
40
|
+
this.refreshPromise = this.fetchNewToken().finally(() => {
|
|
41
|
+
this.refreshPromise = null;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
this.log("Awaiting in-flight token refresh");
|
|
46
|
+
}
|
|
47
|
+
return this.refreshPromise;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Force a token refresh. Called when a 401 is received mid-session,
|
|
51
|
+
* indicating the token expired between the validity check and the API call.
|
|
52
|
+
*/
|
|
53
|
+
async forceRefresh() {
|
|
54
|
+
this.tokenData = null;
|
|
55
|
+
return this.getAccessToken();
|
|
56
|
+
}
|
|
57
|
+
isTokenValid() {
|
|
58
|
+
return (this.tokenData !== null &&
|
|
59
|
+
Date.now() < this.tokenData.expiresAt - TokenManager.EXPIRY_BUFFER_MS);
|
|
60
|
+
}
|
|
61
|
+
async fetchNewToken() {
|
|
62
|
+
const controller = new AbortController();
|
|
63
|
+
const timeoutId = setTimeout(() => controller.abort(), TokenManager.TOKEN_REQUEST_TIMEOUT_MS);
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetch(this.tokenUrl, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
69
|
+
},
|
|
70
|
+
body: new URLSearchParams({
|
|
71
|
+
client_id: this.clientId,
|
|
72
|
+
client_secret: this.clientSecret,
|
|
73
|
+
grant_type: "client_credentials",
|
|
74
|
+
}).toString(),
|
|
75
|
+
signal: controller.signal,
|
|
76
|
+
});
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
const errorText = await response.text().catch(() => "unknown");
|
|
79
|
+
throw new Error(`OAuth token request failed (${response.status}): ${errorText}`);
|
|
80
|
+
}
|
|
81
|
+
const data = (await response.json());
|
|
82
|
+
this.tokenData = {
|
|
83
|
+
accessToken: data.access_token,
|
|
84
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
85
|
+
};
|
|
86
|
+
this.log(`Token acquired, expires in ${data.expires_in}s (scope: ${data.scope})`);
|
|
87
|
+
return this.tokenData.accessToken;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// Scrub credentials from any error messages using global regex
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
92
|
+
const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
93
|
+
const sanitized = message
|
|
94
|
+
.replace(new RegExp(escapeRegex(this.clientId), "g"), "[CLIENT_ID]")
|
|
95
|
+
.replace(new RegExp(escapeRegex(this.clientSecret), "g"), "[CLIENT_SECRET]");
|
|
96
|
+
throw new Error(`Token acquisition failed: ${sanitized}`);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
clearTimeout(timeoutId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=token-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../src/token-manager.ts"],"names":[],"mappings":"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"}
|