@lehaotech/walmart-mcp 0.5.4
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 +25 -0
- package/CHANGELOG.md +247 -0
- package/LICENSE +21 -0
- package/README.md +344 -0
- package/build/api/advertising/ad-client.d.ts +24 -0
- package/build/api/advertising/ad-client.js +174 -0
- package/build/api/advertising/advertising-api.d.ts +50 -0
- package/build/api/advertising/advertising-api.js +89 -0
- package/build/api/client.d.ts +19 -0
- package/build/api/client.js +150 -0
- package/build/api/feeds/feeds-api.d.ts +15 -0
- package/build/api/feeds/feeds-api.js +53 -0
- package/build/api/fulfillment/fulfillment-api.d.ts +37 -0
- package/build/api/fulfillment/fulfillment-api.js +81 -0
- package/build/api/index.d.ts +44 -0
- package/build/api/index.js +56 -0
- package/build/api/inventory/inventory-api.d.ts +27 -0
- package/build/api/inventory/inventory-api.js +49 -0
- package/build/api/items/items-api.d.ts +33 -0
- package/build/api/items/items-api.js +67 -0
- package/build/api/notifications/notifications-api.d.ts +14 -0
- package/build/api/notifications/notifications-api.js +33 -0
- package/build/api/orders/orders-api.d.ts +32 -0
- package/build/api/orders/orders-api.js +47 -0
- package/build/api/pricing/pricing-api.d.ts +32 -0
- package/build/api/pricing/pricing-api.js +60 -0
- package/build/api/reports/reports-api.d.ts +37 -0
- package/build/api/reports/reports-api.js +51 -0
- package/build/api/returns/returns-api.d.ts +26 -0
- package/build/api/returns/returns-api.js +37 -0
- package/build/api/settings/settings-api.d.ts +9 -0
- package/build/api/settings/settings-api.js +21 -0
- package/build/auth/oauth.d.ts +16 -0
- package/build/auth/oauth.js +125 -0
- package/build/config/environment.d.ts +22 -0
- package/build/config/environment.js +50 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +180 -0
- package/build/scripts/client-configs.d.ts +36 -0
- package/build/scripts/client-configs.js +132 -0
- package/build/scripts/diagnose.d.ts +15 -0
- package/build/scripts/diagnose.js +320 -0
- package/build/scripts/setup.d.ts +17 -0
- package/build/scripts/setup.js +276 -0
- package/build/tools/definitions/advertising.d.ts +664 -0
- package/build/tools/definitions/advertising.js +315 -0
- package/build/tools/definitions/discovery.d.ts +24 -0
- package/build/tools/definitions/discovery.js +65 -0
- package/build/tools/definitions/feeds.d.ts +46 -0
- package/build/tools/definitions/feeds.js +42 -0
- package/build/tools/definitions/fulfillment.d.ts +1127 -0
- package/build/tools/definitions/fulfillment.js +272 -0
- package/build/tools/definitions/inventory.d.ts +392 -0
- package/build/tools/definitions/inventory.js +182 -0
- package/build/tools/definitions/items.d.ts +447 -0
- package/build/tools/definitions/items.js +223 -0
- package/build/tools/definitions/notifications.d.ts +84 -0
- package/build/tools/definitions/notifications.js +73 -0
- package/build/tools/definitions/orders.d.ts +2659 -0
- package/build/tools/definitions/orders.js +298 -0
- package/build/tools/definitions/pricing.d.ts +724 -0
- package/build/tools/definitions/pricing.js +254 -0
- package/build/tools/definitions/reports.d.ts +223 -0
- package/build/tools/definitions/reports.js +144 -0
- package/build/tools/definitions/returns.d.ts +441 -0
- package/build/tools/definitions/returns.js +126 -0
- package/build/tools/definitions/settings.d.ts +100 -0
- package/build/tools/definitions/settings.js +52 -0
- package/build/tools/definitions/shared-schemas.d.ts +40 -0
- package/build/tools/definitions/shared-schemas.js +47 -0
- package/build/tools/definitions/token-management.d.ts +16 -0
- package/build/tools/definitions/token-management.js +41 -0
- package/build/tools/index.d.ts +6924 -0
- package/build/tools/index.js +379 -0
- package/build/utils/api-error.d.ts +41 -0
- package/build/utils/api-error.js +97 -0
- package/build/utils/endpoint-catalog.d.ts +22 -0
- package/build/utils/endpoint-catalog.js +89 -0
- package/build/utils/env-file.d.ts +12 -0
- package/build/utils/env-file.js +27 -0
- package/build/utils/known-issues.d.ts +29 -0
- package/build/utils/known-issues.js +122 -0
- package/build/utils/logger.d.ts +15 -0
- package/build/utils/logger.js +56 -0
- package/build/utils/rate-limiter.d.ts +51 -0
- package/build/utils/rate-limiter.js +109 -0
- package/package.json +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class SettingsApi {
|
|
2
|
+
client;
|
|
3
|
+
constructor(client) {
|
|
4
|
+
this.client = client;
|
|
5
|
+
}
|
|
6
|
+
async getShippingSettings() {
|
|
7
|
+
return await this.client.get('/v3/settings/shipping');
|
|
8
|
+
}
|
|
9
|
+
async updateShippingSettings(data) {
|
|
10
|
+
return await this.client.put('/v3/settings/shipping', data);
|
|
11
|
+
}
|
|
12
|
+
async getFulfillmentCenters() {
|
|
13
|
+
return await this.client.get('/v3/settings/shippingprofile');
|
|
14
|
+
}
|
|
15
|
+
async getPartnerInfo() {
|
|
16
|
+
// Walmart has no dedicated partner endpoint; the seller/partner record is
|
|
17
|
+
// returned as the `partner` object on the shipping-profile settings payload.
|
|
18
|
+
const data = await this.client.get('/v3/settings/shippingprofile');
|
|
19
|
+
return data?.partner ?? data;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type WalmartConfig } from '../config/environment.js';
|
|
2
|
+
export declare class WalmartOAuthClient {
|
|
3
|
+
private config;
|
|
4
|
+
private accessToken;
|
|
5
|
+
private tokenExpiry;
|
|
6
|
+
private isRefreshing;
|
|
7
|
+
private refreshPromise;
|
|
8
|
+
constructor(config: WalmartConfig);
|
|
9
|
+
initialize(): Promise<void>;
|
|
10
|
+
getAccessToken(): Promise<string>;
|
|
11
|
+
refreshToken(): Promise<void>;
|
|
12
|
+
private _doRefresh;
|
|
13
|
+
getTokenDetail(): Promise<object>;
|
|
14
|
+
getTokenInfo(): object;
|
|
15
|
+
private updateEnvFile;
|
|
16
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { getBaseUrl } from '../config/environment.js';
|
|
4
|
+
import { authLogger } from '../utils/logger.js';
|
|
5
|
+
import { upsertEnvVars } from '../utils/env-file.js';
|
|
6
|
+
export class WalmartOAuthClient {
|
|
7
|
+
config;
|
|
8
|
+
accessToken = null;
|
|
9
|
+
tokenExpiry = 0;
|
|
10
|
+
isRefreshing = false;
|
|
11
|
+
refreshPromise = null;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
}
|
|
15
|
+
async initialize() {
|
|
16
|
+
if (this.config.accessToken && this.config.accessTokenExpiry) {
|
|
17
|
+
this.accessToken = this.config.accessToken;
|
|
18
|
+
this.tokenExpiry = this.config.accessTokenExpiry;
|
|
19
|
+
if (Date.now() < this.tokenExpiry - 120_000) {
|
|
20
|
+
authLogger.info('Loaded cached token from .env');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
await this.refreshToken();
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
authLogger.warn('Initial token fetch failed (credentials may not be configured yet)');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async getAccessToken() {
|
|
32
|
+
// Token valid and more than 2 min remaining
|
|
33
|
+
if (this.accessToken && Date.now() < this.tokenExpiry - 120_000) {
|
|
34
|
+
return this.accessToken;
|
|
35
|
+
}
|
|
36
|
+
// Prevent concurrent refresh
|
|
37
|
+
if (this.isRefreshing && this.refreshPromise) {
|
|
38
|
+
await this.refreshPromise;
|
|
39
|
+
return this.accessToken;
|
|
40
|
+
}
|
|
41
|
+
await this.refreshToken();
|
|
42
|
+
return this.accessToken;
|
|
43
|
+
}
|
|
44
|
+
async refreshToken() {
|
|
45
|
+
this.isRefreshing = true;
|
|
46
|
+
this.refreshPromise = this._doRefresh();
|
|
47
|
+
try {
|
|
48
|
+
await this.refreshPromise;
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
this.isRefreshing = false;
|
|
52
|
+
this.refreshPromise = null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async _doRefresh() {
|
|
56
|
+
if (!this.config.clientId || !this.config.clientSecret) {
|
|
57
|
+
throw new Error('Walmart API credentials are not configured. Set WALMART_CLIENT_ID and '
|
|
58
|
+
+ 'WALMART_CLIENT_SECRET in your MCP server "env" (or a .env file), or call the '
|
|
59
|
+
+ 'walmart_set_credentials tool with your Client ID and Secret. '
|
|
60
|
+
+ 'Get credentials at https://developer.walmart.com/. '
|
|
61
|
+
+ 'Run the walmart_setup_guide tool for step-by-step setup.');
|
|
62
|
+
}
|
|
63
|
+
const baseUrl = getBaseUrl(this.config.environment);
|
|
64
|
+
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
|
|
65
|
+
authLogger.info(`Requesting token from ${this.config.environment} environment`);
|
|
66
|
+
try {
|
|
67
|
+
const response = await axios.post(`${baseUrl}/v3/token`, 'grant_type=client_credentials', {
|
|
68
|
+
headers: {
|
|
69
|
+
'Authorization': `Basic ${credentials}`,
|
|
70
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
71
|
+
'Accept': 'application/json',
|
|
72
|
+
'WM_QOS.CORRELATION_ID': randomUUID(),
|
|
73
|
+
'WM_SVC.NAME': this.config.svcName,
|
|
74
|
+
},
|
|
75
|
+
timeout: 15_000,
|
|
76
|
+
});
|
|
77
|
+
this.accessToken = response.data.access_token;
|
|
78
|
+
this.tokenExpiry = Date.now() + response.data.expires_in * 1000;
|
|
79
|
+
authLogger.info(`Token obtained, expires in ${response.data.expires_in}s`);
|
|
80
|
+
this.updateEnvFile({
|
|
81
|
+
WALMART_ACCESS_TOKEN: this.accessToken,
|
|
82
|
+
WALMART_ACCESS_TOKEN_EXPIRY: String(this.tokenExpiry),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const msg = axios.isAxiosError(error)
|
|
87
|
+
? error.response?.data?.error_description || error.message
|
|
88
|
+
: String(error);
|
|
89
|
+
authLogger.error(`Token refresh failed: ${msg}`);
|
|
90
|
+
throw new Error(`Authentication failed: ${msg}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async getTokenDetail() {
|
|
94
|
+
const token = await this.getAccessToken();
|
|
95
|
+
const baseUrl = getBaseUrl(this.config.environment);
|
|
96
|
+
const response = await axios.get(`${baseUrl}/v3/token/detail`, {
|
|
97
|
+
headers: {
|
|
98
|
+
'WM_SEC.ACCESS_TOKEN': token,
|
|
99
|
+
'WM_QOS.CORRELATION_ID': randomUUID(),
|
|
100
|
+
'WM_SVC.NAME': this.config.svcName,
|
|
101
|
+
'Content-Type': 'application/json',
|
|
102
|
+
},
|
|
103
|
+
timeout: 15_000,
|
|
104
|
+
});
|
|
105
|
+
return response.data;
|
|
106
|
+
}
|
|
107
|
+
getTokenInfo() {
|
|
108
|
+
return {
|
|
109
|
+
hasToken: !!this.accessToken,
|
|
110
|
+
expiresAt: this.tokenExpiry ? new Date(this.tokenExpiry).toISOString() : null,
|
|
111
|
+
expiresInSeconds: this.tokenExpiry
|
|
112
|
+
? Math.max(0, Math.round((this.tokenExpiry - Date.now()) / 1000))
|
|
113
|
+
: 0,
|
|
114
|
+
environment: this.config.environment,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
updateEnvFile(updates) {
|
|
118
|
+
try {
|
|
119
|
+
upsertEnvVars(updates);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Silent failure - don't interfere with MCP stdout
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type WalmartEnvironment = 'sandbox' | 'production';
|
|
2
|
+
export type WalmartMarket = 'us' | 'CA' | 'mx';
|
|
3
|
+
export interface WalmartConfig {
|
|
4
|
+
clientId: string;
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
environment: WalmartEnvironment;
|
|
7
|
+
market: WalmartMarket;
|
|
8
|
+
svcName: string;
|
|
9
|
+
consumerChannelType?: string;
|
|
10
|
+
partnerId?: string;
|
|
11
|
+
accessToken?: string;
|
|
12
|
+
accessTokenExpiry?: number;
|
|
13
|
+
logLevel: string;
|
|
14
|
+
enableFileLogging: boolean;
|
|
15
|
+
adConsumerId?: string;
|
|
16
|
+
adPrivateKey?: string;
|
|
17
|
+
adKeyVersion?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function getBaseUrl(env: WalmartEnvironment): string;
|
|
20
|
+
export declare function getAdBaseUrl(env: WalmartEnvironment): string;
|
|
21
|
+
export declare function getConfig(): WalmartConfig;
|
|
22
|
+
export declare function validateConfig(config: WalmartConfig): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as dotenv from 'dotenv';
|
|
2
|
+
dotenv.config();
|
|
3
|
+
const BASE_URLS = {
|
|
4
|
+
production: 'https://marketplace.walmartapis.com',
|
|
5
|
+
sandbox: 'https://sandbox.walmartapis.com',
|
|
6
|
+
};
|
|
7
|
+
const AD_BASE_URLS = {
|
|
8
|
+
production: 'https://developer.api.walmart.com/api-proxy/service/WPA/Api',
|
|
9
|
+
sandbox: 'https://developer.api.stg.walmart.com/api-proxy/service/WPA/Api',
|
|
10
|
+
};
|
|
11
|
+
export function getBaseUrl(env) {
|
|
12
|
+
return BASE_URLS[env];
|
|
13
|
+
}
|
|
14
|
+
export function getAdBaseUrl(env) {
|
|
15
|
+
return AD_BASE_URLS[env];
|
|
16
|
+
}
|
|
17
|
+
export function getConfig() {
|
|
18
|
+
return {
|
|
19
|
+
clientId: process.env.WALMART_CLIENT_ID || '',
|
|
20
|
+
clientSecret: process.env.WALMART_CLIENT_SECRET || '',
|
|
21
|
+
environment: process.env.WALMART_ENVIRONMENT || 'sandbox',
|
|
22
|
+
market: process.env.WALMART_MARKET || 'us',
|
|
23
|
+
svcName: process.env.WALMART_SVC_NAME || 'Walmart Marketplace',
|
|
24
|
+
consumerChannelType: process.env.WALMART_CONSUMER_CHANNEL_TYPE || undefined,
|
|
25
|
+
partnerId: process.env.WALMART_PARTNER_ID || undefined,
|
|
26
|
+
accessToken: process.env.WALMART_ACCESS_TOKEN || undefined,
|
|
27
|
+
accessTokenExpiry: process.env.WALMART_ACCESS_TOKEN_EXPIRY
|
|
28
|
+
? parseInt(process.env.WALMART_ACCESS_TOKEN_EXPIRY, 10)
|
|
29
|
+
: undefined,
|
|
30
|
+
logLevel: process.env.WALMART_LOG_LEVEL || 'info',
|
|
31
|
+
enableFileLogging: process.env.WALMART_ENABLE_FILE_LOGGING === 'true',
|
|
32
|
+
adConsumerId: process.env.WALMART_AD_CONSUMER_ID || undefined,
|
|
33
|
+
adPrivateKey: process.env.WALMART_AD_PRIVATE_KEY || undefined,
|
|
34
|
+
adKeyVersion: process.env.WALMART_AD_KEY_VERSION || '1',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function validateConfig(config) {
|
|
38
|
+
if (!config.clientId || !config.clientSecret) {
|
|
39
|
+
console.error('\n┌─ walmart-mcp: setup required ────────────────────────────────────\n'
|
|
40
|
+
+ '│ No API credentials found. The server will start, but Walmart calls\n'
|
|
41
|
+
+ '│ will fail until you provide credentials. To fix:\n'
|
|
42
|
+
+ '│ 1. Get a Client ID + Secret at https://developer.walmart.com/\n'
|
|
43
|
+
+ '│ 2. Set WALMART_CLIENT_ID and WALMART_CLIENT_SECRET in your MCP\n'
|
|
44
|
+
+ '│ server "env" (or a .env file), then restart — OR call the\n'
|
|
45
|
+
+ '│ walmart_set_credentials tool at runtime.\n'
|
|
46
|
+
+ '│ Run the walmart_setup_guide tool for full setup steps.\n'
|
|
47
|
+
+ `│ Environment: ${config.environment} Market: ${config.market}\n`
|
|
48
|
+
+ '└──────────────────────────────────────────────────────────────────\n');
|
|
49
|
+
}
|
|
50
|
+
}
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { z, ZodError } from 'zod';
|
|
6
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
8
|
+
import { getConfig, validateConfig } from './config/environment.js';
|
|
9
|
+
import { WalmartSellerApi } from './api/index.js';
|
|
10
|
+
import { getToolDefinitions, executeTool } from './tools/index.js';
|
|
11
|
+
import { serverLogger } from './utils/logger.js';
|
|
12
|
+
import { WalmartApiError } from './utils/api-error.js';
|
|
13
|
+
// Resolve package.json once at module load so the MCP server's reported
|
|
14
|
+
// version stays in lockstep with what's actually installed. Avoids drift like
|
|
15
|
+
// the earlier hard-coded '0.3.2' which lagged through five releases.
|
|
16
|
+
function readPackageVersion() {
|
|
17
|
+
try {
|
|
18
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
// build/index.js -> ../package.json. Works the same when running via tsx
|
|
20
|
+
// from src/ because tsx evaluates in place under the repo root.
|
|
21
|
+
const pkgPath = join(here, '..', 'package.json');
|
|
22
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
23
|
+
return pkg.version ?? '0.0.0';
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return '0.0.0';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const PACKAGE_VERSION = readPackageVersion();
|
|
30
|
+
class WalmartMcpServer {
|
|
31
|
+
server;
|
|
32
|
+
api;
|
|
33
|
+
constructor() {
|
|
34
|
+
const config = getConfig();
|
|
35
|
+
validateConfig(config);
|
|
36
|
+
this.server = new McpServer({
|
|
37
|
+
name: 'walmart-mcp',
|
|
38
|
+
version: PACKAGE_VERSION,
|
|
39
|
+
});
|
|
40
|
+
this.api = new WalmartSellerApi(config);
|
|
41
|
+
this.setupHandlers();
|
|
42
|
+
}
|
|
43
|
+
setupHandlers() {
|
|
44
|
+
const tools = getToolDefinitions();
|
|
45
|
+
serverLogger.info(`Registering ${tools.length} tools`);
|
|
46
|
+
for (const toolDef of tools) {
|
|
47
|
+
const hasSchema = Object.keys(toolDef.inputSchema).length > 0;
|
|
48
|
+
this.server.registerTool(toolDef.name, {
|
|
49
|
+
description: toolDef.description,
|
|
50
|
+
inputSchema: hasSchema
|
|
51
|
+
? toolDef.inputSchema
|
|
52
|
+
: undefined,
|
|
53
|
+
}, async (rawArgs) => {
|
|
54
|
+
// Re-parse args through the tool's zod schema. The MCP SDK does shape
|
|
55
|
+
// validation against the inputSchema, but explicit z.object(...).parse
|
|
56
|
+
// here also runs any .refine() business rules and fills in defaults so
|
|
57
|
+
// the downstream dispatcher always sees a fully-resolved payload.
|
|
58
|
+
let args = rawArgs;
|
|
59
|
+
try {
|
|
60
|
+
if (hasSchema) {
|
|
61
|
+
const schemaObj = z.object(toolDef.inputSchema);
|
|
62
|
+
args = schemaObj.parse(rawArgs);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (validationError) {
|
|
66
|
+
if (validationError instanceof ZodError) {
|
|
67
|
+
serverLogger.warn(`Tool ${toolDef.name} input validation failed: ${validationError.message}`);
|
|
68
|
+
return {
|
|
69
|
+
content: [{
|
|
70
|
+
type: 'text',
|
|
71
|
+
text: JSON.stringify({
|
|
72
|
+
error: 'Input validation failed before any Walmart API call.',
|
|
73
|
+
tool: toolDef.name,
|
|
74
|
+
issues: validationError.issues.map((iss) => ({
|
|
75
|
+
path: iss.path,
|
|
76
|
+
message: iss.message,
|
|
77
|
+
code: iss.code,
|
|
78
|
+
})),
|
|
79
|
+
}, null, 2),
|
|
80
|
+
}],
|
|
81
|
+
isError: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
throw validationError;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const result = await executeTool(this.api, toolDef.name, args);
|
|
88
|
+
return {
|
|
89
|
+
content: [{
|
|
90
|
+
type: 'text',
|
|
91
|
+
text: JSON.stringify(result, null, 2),
|
|
92
|
+
}],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
97
|
+
serverLogger.error(`Tool ${toolDef.name} failed: ${errorMsg}`);
|
|
98
|
+
let payload;
|
|
99
|
+
if (error instanceof WalmartApiError) {
|
|
100
|
+
// Attach the MCP tool name so the LLM can correlate the failing
|
|
101
|
+
// call to a tool it knows about. endpoint + hint are already set
|
|
102
|
+
// by the API client's interceptor.
|
|
103
|
+
error.tool = toolDef.name;
|
|
104
|
+
payload = error.toResponse();
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
payload = { error: errorMsg, tool: toolDef.name };
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
content: [{
|
|
111
|
+
type: 'text',
|
|
112
|
+
text: JSON.stringify(payload, null, 2),
|
|
113
|
+
}],
|
|
114
|
+
isError: true,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async run() {
|
|
121
|
+
await this.api.initialize();
|
|
122
|
+
const transport = new StdioServerTransport();
|
|
123
|
+
await this.server.connect(transport);
|
|
124
|
+
serverLogger.info('Walmart MCP Server running on stdio');
|
|
125
|
+
process.on('SIGINT', async () => {
|
|
126
|
+
serverLogger.info('Shutting down...');
|
|
127
|
+
await this.server.close();
|
|
128
|
+
process.exit(0);
|
|
129
|
+
});
|
|
130
|
+
process.on('SIGTERM', async () => {
|
|
131
|
+
serverLogger.info('Shutting down...');
|
|
132
|
+
await this.server.close();
|
|
133
|
+
process.exit(0);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// ============================================================
|
|
138
|
+
// Subcommand dispatch — make a single bin handle:
|
|
139
|
+
// walmart-mcp -> run the MCP server (default)
|
|
140
|
+
// walmart-mcp setup -> run the interactive setup wizard
|
|
141
|
+
// walmart-mcp diagnose -> run the self-check (--export OK)
|
|
142
|
+
// walmart-mcp version -> print the package version
|
|
143
|
+
// Everything after the subcommand is forwarded as process.argv to the
|
|
144
|
+
// child script (so `walmart-mcp diagnose --export` works).
|
|
145
|
+
// ============================================================
|
|
146
|
+
const subcommand = process.argv[2];
|
|
147
|
+
async function dispatch() {
|
|
148
|
+
if (subcommand === 'setup') {
|
|
149
|
+
process.argv = [process.argv[0], 'setup', ...process.argv.slice(3)];
|
|
150
|
+
await import('./scripts/setup.js');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (subcommand === 'diagnose') {
|
|
154
|
+
process.argv = [process.argv[0], 'diagnose', ...process.argv.slice(3)];
|
|
155
|
+
await import('./scripts/diagnose.js');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (subcommand === 'version' || subcommand === '--version' || subcommand === '-v') {
|
|
159
|
+
console.log(`walmart-mcp v${PACKAGE_VERSION}`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
|
|
163
|
+
console.log('walmart-mcp — Walmart Marketplace MCP server');
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log('Usage:');
|
|
166
|
+
console.log(' walmart-mcp Run the MCP server over stdio (default).');
|
|
167
|
+
console.log(' walmart-mcp setup Interactive setup wizard.');
|
|
168
|
+
console.log(' walmart-mcp diagnose Self-check (env / token / config).');
|
|
169
|
+
console.log(' walmart-mcp version Print version.');
|
|
170
|
+
console.log('');
|
|
171
|
+
console.log('Docs: https://github.com/yufakang0826-hue/walmart-mcp');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const server = new WalmartMcpServer();
|
|
175
|
+
await server.run();
|
|
176
|
+
}
|
|
177
|
+
dispatch().catch((error) => {
|
|
178
|
+
console.error('Fatal error:', error);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP client config detection + write helpers used by `npm run setup`.
|
|
3
|
+
*
|
|
4
|
+
* Each client uses a slightly different config schema. Most follow Claude
|
|
5
|
+
* Desktop's `{ mcpServers: { name: { command, args, env } } }` shape, but
|
|
6
|
+
* Cursor and Zed differ.
|
|
7
|
+
*/
|
|
8
|
+
export type ClientId = 'claude-desktop' | 'claude-code-cli' | 'cursor' | 'cline' | 'continue' | 'windsurf' | 'zed';
|
|
9
|
+
export interface ClientSpec {
|
|
10
|
+
id: ClientId;
|
|
11
|
+
name: string;
|
|
12
|
+
/** Get the OS-resolved absolute config path for this client. */
|
|
13
|
+
resolvePath: () => string | null;
|
|
14
|
+
/** How the `mcpServers` block is keyed in this client's config. */
|
|
15
|
+
serverKey: 'mcpServers' | 'servers';
|
|
16
|
+
}
|
|
17
|
+
export declare const CLIENT_SPECS: ReadonlyArray<ClientSpec>;
|
|
18
|
+
/** Returns the subset of clients whose config files already exist. */
|
|
19
|
+
export declare function detectInstalled(): ReadonlyArray<ClientSpec & {
|
|
20
|
+
path: string;
|
|
21
|
+
}>;
|
|
22
|
+
export interface WalmartMcpEntry {
|
|
23
|
+
type: 'stdio';
|
|
24
|
+
command: 'node';
|
|
25
|
+
args: string[];
|
|
26
|
+
env: Record<string, string>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Read existing config (or start from empty), insert / overwrite the
|
|
30
|
+
* `walmart` entry under the appropriate server-key, and write back. Backs up
|
|
31
|
+
* the original to `.before-walmart-setup.bak` on first write.
|
|
32
|
+
*/
|
|
33
|
+
export declare function writeWalmartEntry(spec: ClientSpec, configPath: string, walmartEntry: WalmartMcpEntry): {
|
|
34
|
+
backedUp: boolean;
|
|
35
|
+
overwrote: boolean;
|
|
36
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP client config detection + write helpers used by `npm run setup`.
|
|
3
|
+
*
|
|
4
|
+
* Each client uses a slightly different config schema. Most follow Claude
|
|
5
|
+
* Desktop's `{ mcpServers: { name: { command, args, env } } }` shape, but
|
|
6
|
+
* Cursor and Zed differ.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
9
|
+
import { join, dirname } from 'node:path';
|
|
10
|
+
import { homedir, platform } from 'node:os';
|
|
11
|
+
const APP_DATA = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming');
|
|
12
|
+
const LOCAL_APP_DATA = process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local');
|
|
13
|
+
export const CLIENT_SPECS = [
|
|
14
|
+
{
|
|
15
|
+
id: 'claude-desktop',
|
|
16
|
+
name: 'Claude Desktop',
|
|
17
|
+
serverKey: 'mcpServers',
|
|
18
|
+
resolvePath: () => {
|
|
19
|
+
const plat = platform();
|
|
20
|
+
if (plat === 'darwin') {
|
|
21
|
+
return join(homedir(), 'Library/Application Support/Claude/claude_desktop_config.json');
|
|
22
|
+
}
|
|
23
|
+
if (plat === 'win32') {
|
|
24
|
+
return join(APP_DATA, 'Claude', 'claude_desktop_config.json');
|
|
25
|
+
}
|
|
26
|
+
return join(homedir(), '.config/Claude/claude_desktop_config.json');
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'claude-code-cli',
|
|
31
|
+
name: 'Claude Code CLI',
|
|
32
|
+
serverKey: 'mcpServers',
|
|
33
|
+
resolvePath: () => join(homedir(), '.claude.json'),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'cursor',
|
|
37
|
+
name: 'Cursor',
|
|
38
|
+
serverKey: 'mcpServers',
|
|
39
|
+
resolvePath: () => join(homedir(), '.cursor', 'mcp.json'),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'cline',
|
|
43
|
+
name: 'Cline (VSCode extension)',
|
|
44
|
+
serverKey: 'mcpServers',
|
|
45
|
+
resolvePath: () => {
|
|
46
|
+
const plat = platform();
|
|
47
|
+
const segment = 'User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json';
|
|
48
|
+
if (plat === 'darwin') {
|
|
49
|
+
return join(homedir(), 'Library/Application Support/Code', segment);
|
|
50
|
+
}
|
|
51
|
+
if (plat === 'win32') {
|
|
52
|
+
return join(APP_DATA, 'Code', segment);
|
|
53
|
+
}
|
|
54
|
+
return join(homedir(), '.config/Code', segment);
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'continue',
|
|
59
|
+
name: 'Continue.dev',
|
|
60
|
+
serverKey: 'mcpServers',
|
|
61
|
+
resolvePath: () => join(homedir(), '.continue', 'config.json'),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'windsurf',
|
|
65
|
+
name: 'Windsurf (Codeium)',
|
|
66
|
+
serverKey: 'mcpServers',
|
|
67
|
+
resolvePath: () => join(homedir(), '.codeium', 'windsurf', 'mcp_config.json'),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'zed',
|
|
71
|
+
name: 'Zed Editor',
|
|
72
|
+
serverKey: 'servers',
|
|
73
|
+
resolvePath: () => {
|
|
74
|
+
const plat = platform();
|
|
75
|
+
if (plat === 'darwin') {
|
|
76
|
+
return join(homedir(), 'Library/Application Support/Zed/settings.json');
|
|
77
|
+
}
|
|
78
|
+
if (plat === 'win32') {
|
|
79
|
+
return join(LOCAL_APP_DATA, 'Zed', 'settings.json');
|
|
80
|
+
}
|
|
81
|
+
return join(homedir(), '.config/zed/settings.json');
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
/** Returns the subset of clients whose config files already exist. */
|
|
86
|
+
export function detectInstalled() {
|
|
87
|
+
const found = [];
|
|
88
|
+
for (const spec of CLIENT_SPECS) {
|
|
89
|
+
const p = spec.resolvePath();
|
|
90
|
+
if (p && existsSync(p))
|
|
91
|
+
found.push({ ...spec, path: p });
|
|
92
|
+
}
|
|
93
|
+
return found;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Read existing config (or start from empty), insert / overwrite the
|
|
97
|
+
* `walmart` entry under the appropriate server-key, and write back. Backs up
|
|
98
|
+
* the original to `.before-walmart-setup.bak` on first write.
|
|
99
|
+
*/
|
|
100
|
+
export function writeWalmartEntry(spec, configPath, walmartEntry) {
|
|
101
|
+
let backedUp = false;
|
|
102
|
+
let overwrote = false;
|
|
103
|
+
let config = {};
|
|
104
|
+
if (existsSync(configPath)) {
|
|
105
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
106
|
+
const backup = `${configPath}.before-walmart-setup.bak`;
|
|
107
|
+
writeFileSync(backup, readFileSync(configPath));
|
|
108
|
+
backedUp = true;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
let servers = config[spec.serverKey] ?? {};
|
|
114
|
+
if (spec.id === 'zed') {
|
|
115
|
+
// Zed nests MCP under `mcp.servers`; load the nested level.
|
|
116
|
+
const mcp = config.mcp ?? {};
|
|
117
|
+
servers = mcp.servers ?? {};
|
|
118
|
+
if ('walmart' in servers)
|
|
119
|
+
overwrote = true;
|
|
120
|
+
servers.walmart = walmartEntry;
|
|
121
|
+
mcp.servers = servers;
|
|
122
|
+
config.mcp = mcp;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
if ('walmart' in servers)
|
|
126
|
+
overwrote = true;
|
|
127
|
+
servers.walmart = walmartEntry;
|
|
128
|
+
config[spec.serverKey] = servers;
|
|
129
|
+
}
|
|
130
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
131
|
+
return { backedUp, overwrote };
|
|
132
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* walmart-mcp diagnose
|
|
3
|
+
* ------------------------------------------------------------
|
|
4
|
+
* Self-check script. Run via `npm run diagnose`.
|
|
5
|
+
*
|
|
6
|
+
* Reports environment readiness, credential validity, and MCP-client config
|
|
7
|
+
* presence without revealing credential values. Use `--export` to dump a
|
|
8
|
+
* JSON report suitable for attaching to bug reports.
|
|
9
|
+
*
|
|
10
|
+
* Exit codes:
|
|
11
|
+
* 0 all checks passed (or only warnings)
|
|
12
|
+
* 1 one or more errors — configuration must be fixed before the MCP works
|
|
13
|
+
* 2 fatal (script itself crashed)
|
|
14
|
+
*/
|
|
15
|
+
export {};
|