Jake Shore 7ee40342c8 Clover: Complete MCP server with 50+ tools and 18 React apps
- API client with Clover REST API v3 integration (OAuth2 + API key auth)
- 50+ comprehensive tools across 10 categories:
  * Orders: list, get, create, update, delete, add/remove line items, discounts, payments, fire order
  * Inventory: items, categories, modifiers, stock management
  * Customers: CRUD, search, addresses, payment cards
  * Employees: CRUD, roles, shifts, clock in/out
  * Payments: list, get, refunds
  * Merchants: settings, devices, tender types
  * Discounts: CRUD operations
  * Taxes: CRUD, tax rates
  * Reports: sales summary, revenue by item/category, employee performance
  * Cash: cash drawer tracking and events

- 18 React MCP apps with full UI:
  * Order management: dashboard, detail, grid
  * Inventory: dashboard, detail, category manager
  * Customer: detail, grid
  * Employee: dashboard, schedule
  * Payment history
  * Analytics: sales dashboard, revenue by item, revenue by category
  * Configuration: discount manager, tax manager, device manager
  * Cash drawer

- Complete TypeScript types for Clover API
- Pagination support with automatic result fetching
- Comprehensive error handling
- Full README with examples and setup guide
2026-02-12 17:42:59 -05:00

106 lines
2.9 KiB
TypeScript

import axios, { AxiosInstance, AxiosError } from 'axios';
import { CloverConfig, PaginatedResponse } from '../types/index.js';
export class CloverClient {
private client: AxiosInstance;
private merchantId: string;
constructor(config: CloverConfig) {
this.merchantId = config.merchantId;
const baseURL = config.environment === 'production'
? 'https://api.clover.com'
: 'https://sandbox.dev.clover.com';
this.client = axios.create({
baseURL,
headers: {
'Content-Type': 'application/json',
...(config.accessToken && { 'Authorization': `Bearer ${config.accessToken}` }),
},
params: {
...(config.apiKey && { access_token: config.apiKey }),
},
});
// Response interceptor for error handling
this.client.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response) {
throw new Error(`Clover API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
} else if (error.request) {
throw new Error('Clover API Error: No response received');
} else {
throw new Error(`Clover API Error: ${error.message}`);
}
}
);
}
// Generic GET method with pagination support
async get<T>(endpoint: string, params: Record<string, any> = {}): Promise<T> {
const url = `/v3/merchants/${this.merchantId}${endpoint}`;
const response = await this.client.get<T>(url, { params });
return response.data;
}
// Generic POST method
async post<T>(endpoint: string, data: any): Promise<T> {
const url = `/v3/merchants/${this.merchantId}${endpoint}`;
const response = await this.client.post<T>(url, data);
return response.data;
}
// Generic PUT method
async put<T>(endpoint: string, data: any): Promise<T> {
const url = `/v3/merchants/${this.merchantId}${endpoint}`;
const response = await this.client.put<T>(url, data);
return response.data;
}
// Generic DELETE method
async delete<T>(endpoint: string): Promise<T> {
const url = `/v3/merchants/${this.merchantId}${endpoint}`;
const response = await this.client.delete<T>(url);
return response.data;
}
// Paginated fetch helper
async fetchPaginated<T>(
endpoint: string,
params: Record<string, any> = {},
limit?: number
): Promise<T[]> {
const allItems: T[] = [];
let offset = 0;
const pageSize = 100;
while (true) {
const response = await this.get<PaginatedResponse<T>>(endpoint, {
...params,
limit: pageSize,
offset,
});
allItems.push(...response.elements);
if (response.elements.length < pageSize || (limit && allItems.length >= limit)) {
break;
}
offset += pageSize;
if (limit && allItems.length >= limit) {
return allItems.slice(0, limit);
}
}
return allItems;
}
getMerchantId(): string {
return this.merchantId;
}
}