diff --git a/landing-pages/closebot.html b/landing-pages/closebot.html new file mode 100644 index 0000000..4da8a66 --- /dev/null +++ b/landing-pages/closebot.html @@ -0,0 +1,867 @@ + + + + + + CloseBot Connect — AI-Power Your Chatbots in 2 Clicks + + + + + + + + + + + +
+
+
+
+
+ + + + + +
+
+
+ + Open Source + Hosted +
+ +

+ Connect CloseBot
+ to AI in 2 Clicks +

+ +

+ The complete CloseBot MCP server. 119 tools for bots, leads, analytics, and automation. + No setup. No API headaches. Just connect and build. +

+ + + + +
+
+
119
+
AI Tools
+
+ +
+
14
+
Lazy Modules
+
+ +
+
6
+
Visual Apps
+
+ +
+
8
+
Tool Groups
+
+
+ + +
+
+ + + + + +
+

+ Trusted by 500+ chatbot agencies +

+
+
+
+ + +
+
+
+

See It In Action

+

Watch how AI manages your CloseBot chatbots

+
+
+
+
+ +
+
+
+
+
+
+ + Bots +
+
+
+ + Leads +
+
+
+ + Analytics +
+
+
+ + Testing +
+
+
+
+
+ + +
+
+
+
+

+ Managing chatbots at scale
+ shouldn't be this painful +

+
+
+
+ +
+
+

Dashboard tab overload

+

Dozens of bots, sources, and leads spread across endless UI tabs.

+
+
+
+
+ +
+
+

No visibility into performance

+

Scattered analytics with no unified view of bookings, responses, or revenue.

+
+
+
+
+ +
+
+

Manual testing and iteration

+

Hours spent clicking through test conversations and tweaking configs.

+
+
+
+
+
+
+
+ +
+

With CloseBot Connect

+
+
+
+
+ +
+ Manage all bots with natural language +
+
+
+ +
+ Unified analytics across every source +
+
+
+ +
+ AI-powered bot testing and iteration +
+
+
+ +
+ Works with Claude, GPT, any MCP client +
+
+
+ +
+ 119 tools, zero configuration +
+
+
+
+
+
+ + +
+
+
+

119 Tools. 8 Powerful Groups.

+

Full CloseBot platform access through one simple connection

+
+ +
+ +
+
+ +
+

Bot Management

+ 18 tools +

CRUD bots, AI creation, publish, versioning, templates, source attach.

+
+ + +
+
+ +
+

Source Management

+ 9 tools +

Sources (GHL sub-accounts), calendars, channels, fields, tags.

+
+ + +
+
+ +
+

Lead Management

+ 6 tools +

Search, filter, update leads and lead instances with full context.

+
+ + +
+
+ +
+

Analytics & Metrics

+ 14 tools +

Agency summary, booking graphs, leaderboards, message analytics, logs.

+
+ + +
+
+ +
+

Bot Testing

+ 7 tools +

Test sessions with send/listen, force-step, rollback. Iterate fast.

+
+ + +
+
+ +
+

Library & KB

+ 11 tools +

Files, web-scraping, source attachment, content management.

+
+ + +
+
+ +
+

Agency & Billing

+ 18 tools +

Billing, transactions, wallets, usage tracking, re-billing for agencies.

+
+ + +
+
+ +
+

Configuration

+ 30 tools +

Personas, FAQs, folders, notifications, live demos, webhooks, API keys.

+
+
+ +
+

Full platform coverage including:

+
+ Bot Versioning + AI Bot Creation + Leaderboards + Web Scraping + Webhooks + Wallets + Personas + Live Demos +
+
+
+
+ + +
+
+
+
+ + Rich UI Tool Apps +
+

6 Visual Dashboards, Built In

+

Not just API calls — get rich HTML dashboards rendered directly in your AI client. See your data, don't just read it.

+
+ +
+ +
+
+
+ +
+

Bot Dashboard

+
+

Grid view of all bots with status indicators, version history, and source count. See everything at a glance.

+
+ + bot_dashboard_app +
+
+ + +
+
+
+ +
+

Analytics Dashboard

+
+

Agency stats, response rates, booking metrics, and revenue tracking with customizable time ranges.

+
+ + analytics_dashboard_app +
+
+ + +
+
+
+ +
+

Test Console

+
+

Interactive test session viewer with conversation history, step controls, and rollback capability.

+
+ + test_console_app +
+
+ + +
+
+
+ +
+

Lead Manager

+
+

Searchable lead table with custom fields, conversation data, and engagement history.

+
+ + lead_manager_app +
+
+ + +
+
+
+ +
+

Library Manager

+
+

File list with type indicators, source assignments, and web scrape status tracking.

+
+ + library_manager_app +
+
+ + +
+
+
+ +
+

Leaderboard

+
+

Global and local rankings by responses, bookings, or contacts. Gamify your agency performance.

+
+ + leaderboard_app +
+
+
+
+
+ + +
+
+
+
+ + Coming Soon +
+

Join the Waitlist

+

Be the first to know when we launch. Early access + exclusive perks for waitlist members.

+
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + + + +

+ + We respect your privacy. No spam, ever. +

+
+
+
+ + + + +
+
+
+
+
+
+ + Open Source +
+

+ Self-host if you want.
+ We won't stop you. +

+

+ The entire MCP server is open source. Run it yourself, modify it, contribute back. + The hosted version just saves you the hassle. +

+ + View on GitHub + + +
+
+
+ + + + Terminal +
+
$ git clone https://github.com/BusyBee3333/closebot-mcp-2026-complete.git
+$ cd mcp && npm install
+$ npm run build
+$ node dist/index.js
+
+✓ CloseBot MCP Server running
+✓ 119 tools loaded (14 modules)
+✓ 6 visual apps ready
+Listening on stdio...
+
+
+
+
+
+ + +
+
+
+

Frequently asked questions

+
+ +
+
+ + What is MCP? + + +

+ MCP (Model Context Protocol) is a standard for connecting AI assistants to external tools and data. + It's supported by Claude, and lets AI actually take actions in your systems — not just chat about them. +

+
+ +
+ + What is CloseBot? + + +

+ CloseBot is an AI chatbot platform at closebot.com. + It lets agencies build, deploy, and manage AI chatbots for lead qualification, booking, and customer support — often integrated with GoHighLevel. +

+
+ +
+ + Do I need to install anything? + + +

+ For the hosted version, no. Just connect your CloseBot account via API key and add the MCP endpoint to your AI client (Claude Desktop, etc.). + If you self-host, you'll need Node.js. +

+
+ +
+ + What are visual apps? + + +

+ Visual apps are rich HTML dashboards rendered directly in your AI client. Instead of reading raw JSON data, + you see beautiful grids, charts, and tables — like a Bot Dashboard with status cards or an Analytics Dashboard with graphs. +

+
+ +
+ + Can AI create and deploy bots for me? + + +

+ Yes. AI can create bots from templates, configure personas and FAQs, attach knowledge base files, + test conversations, and publish — all through natural language. You stay in control with approval steps. +

+
+ +
+ + Is my data secure? + + +

+ Yes. We never store your CloseBot API keys in plain text. Tokens are encrypted at rest and in transit. + You can revoke access anytime from your CloseBot settings. +

+
+
+
+
+ + +
+
+
+

+ Ready to AI-power your CloseBot? +

+

+ Join 500+ chatbot agencies already automating with CloseBot Connect. +

+ +
+
+ + + + + +
+ + + Join Waitlist + +
+ + + + + + \ No newline at end of file diff --git a/servers/bigcommerce/package.json b/servers/bigcommerce/package.json deleted file mode 100644 index 8b46128..0000000 --- a/servers/bigcommerce/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "mcp-server-bigcommerce", - "version": "1.0.0", - "type": "module", - "main": "dist/index.js", - "scripts": { - "build": "tsc", - "start": "node dist/index.js", - "dev": "tsx src/index.ts" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "^0.5.0", - "zod": "^3.22.4" - }, - "devDependencies": { - "@types/node": "^20.10.0", - "tsx": "^4.7.0", - "typescript": "^5.3.0" - } -} diff --git a/servers/bigcommerce/src/clients/bigcommerce.ts b/servers/bigcommerce/src/clients/bigcommerce.ts new file mode 100644 index 0000000..3383d6e --- /dev/null +++ b/servers/bigcommerce/src/clients/bigcommerce.ts @@ -0,0 +1,547 @@ +/** + * BigCommerce API Client + * Supports REST API v2 and v3 + * https://api.bigcommerce.com/stores/{store_hash}/ + */ + +import { BigCommerceConfig, PaginatedResponse, ApiResponse, BigCommerceError } from '../types/index.js'; + +export class BigCommerceClient { + private storeHash: string; + private accessToken: string; + private baseUrlV2: string; + private baseUrlV3: string; + + constructor(config: BigCommerceConfig) { + this.storeHash = config.storeHash; + this.accessToken = config.accessToken; + this.baseUrlV2 = `https://api.bigcommerce.com/stores/${this.storeHash}/v2`; + this.baseUrlV3 = `https://api.bigcommerce.com/stores/${this.storeHash}/v3`; + } + + /** + * Make authenticated request to BigCommerce API + */ + private async request( + endpoint: string, + options: RequestInit = {}, + version: 'v2' | 'v3' = 'v3' + ): Promise { + const baseUrl = version === 'v2' ? this.baseUrlV2 : this.baseUrlV3; + const url = `${baseUrl}${endpoint}`; + + const headers: HeadersInit = { + 'X-Auth-Token': this.accessToken, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + ...options.headers, + }; + + try { + const response = await fetch(url, { + ...options, + headers, + }); + + if (!response.ok) { + const errorBody = await response.json().catch(() => ({})); + const error: BigCommerceError = { + status: response.status, + title: errorBody.title || response.statusText, + type: errorBody.type, + detail: errorBody.detail, + errors: errorBody.errors, + }; + throw new Error(`BigCommerce API Error: ${JSON.stringify(error)}`); + } + + // Handle 204 No Content + if (response.status === 204) { + return {} as T; + } + + return await response.json(); + } catch (error) { + if (error instanceof Error) { + throw error; + } + throw new Error(`BigCommerce API request failed: ${String(error)}`); + } + } + + /** + * GET request + */ + async get(endpoint: string, version: 'v2' | 'v3' = 'v3'): Promise { + return this.request(endpoint, { method: 'GET' }, version); + } + + /** + * POST request + */ + async post( + endpoint: string, + data: any, + version: 'v2' | 'v3' = 'v3' + ): Promise { + return this.request( + endpoint, + { + method: 'POST', + body: JSON.stringify(data), + }, + version + ); + } + + /** + * PUT request + */ + async put( + endpoint: string, + data: any, + version: 'v2' | 'v3' = 'v3' + ): Promise { + return this.request( + endpoint, + { + method: 'PUT', + body: JSON.stringify(data), + }, + version + ); + } + + /** + * DELETE request + */ + async delete(endpoint: string, version: 'v2' | 'v3' = 'v3'): Promise { + return this.request(endpoint, { method: 'DELETE' }, version); + } + + /** + * Paginated GET request + * Automatically handles pagination and returns all results + */ + async getPaginated( + endpoint: string, + version: 'v2' | 'v3' = 'v3', + params: Record = {} + ): Promise { + const allResults: T[] = []; + let page = 1; + let hasMore = true; + + while (hasMore) { + const queryParams = new URLSearchParams({ + ...params, + page: page.toString(), + limit: '250', // Max limit for most endpoints + }); + + const url = `${endpoint}?${queryParams.toString()}`; + const response = await this.get | T[]>(url, version); + + // Handle v3 paginated response format + if (this.isPaginatedResponse(response)) { + allResults.push(...response.data); + + const pagination = response.meta?.pagination; + if (pagination) { + hasMore = pagination.current_page < pagination.total_pages; + page++; + } else { + hasMore = false; + } + } + // Handle v2 array response format + else if (Array.isArray(response)) { + allResults.push(...response); + // For v2, if we get less than the limit, we're done + hasMore = response.length === 250; + page++; + } else { + hasMore = false; + } + } + + return allResults; + } + + /** + * Type guard for paginated response + */ + private isPaginatedResponse( + response: PaginatedResponse | T[] + ): response is PaginatedResponse { + return (response as PaginatedResponse).data !== undefined; + } + + /** + * Build query string from params + */ + buildQueryString(params: Record): string { + const filtered = Object.entries(params) + .filter(([_, value]) => value !== undefined && value !== null) + .reduce((acc, [key, value]) => { + if (Array.isArray(value)) { + acc[key] = value.join(','); + } else { + acc[key] = String(value); + } + return acc; + }, {} as Record); + + const queryParams = new URLSearchParams(filtered); + return queryParams.toString() ? `?${queryParams.toString()}` : ''; + } + + // ============================================================================ + // PRODUCTS + // ============================================================================ + + async listProducts(params?: { + name?: string; + sku?: string; + price?: number; + weight?: number; + condition?: string; + brand_id?: number; + categories?: number[]; + is_visible?: boolean; + is_featured?: boolean; + availability?: string; + }) { + const query = this.buildQueryString(params || {}); + return this.get>(`/catalog/products${query}`); + } + + async getProduct(productId: number) { + return this.get>(`/catalog/products/${productId}`); + } + + async createProduct(product: any) { + return this.post>('/catalog/products', product); + } + + async updateProduct(productId: number, product: any) { + return this.put>(`/catalog/products/${productId}`, product); + } + + async deleteProduct(productId: number) { + return this.delete(`/catalog/products/${productId}`); + } + + async listProductVariants(productId: number) { + return this.get>(`/catalog/products/${productId}/variants`); + } + + async createProductVariant(productId: number, variant: any) { + return this.post>(`/catalog/products/${productId}/variants`, variant); + } + + async listProductImages(productId: number) { + return this.get>(`/catalog/products/${productId}/images`); + } + + async createProductImage(productId: number, image: any) { + return this.post>(`/catalog/products/${productId}/images`, image); + } + + async listProductCustomFields(productId: number) { + return this.get>(`/catalog/products/${productId}/custom-fields`); + } + + async listProductBulkPricing(productId: number) { + return this.get>(`/catalog/products/${productId}/bulk-pricing-rules`); + } + + // ============================================================================ + // ORDERS + // ============================================================================ + + async listOrders(params?: { + min_id?: number; + max_id?: number; + min_total?: number; + max_total?: number; + customer_id?: number; + status_id?: number; + is_deleted?: boolean; + payment_method?: string; + min_date_created?: string; + max_date_created?: string; + min_date_modified?: string; + max_date_modified?: string; + }) { + const query = this.buildQueryString(params || {}); + return this.get(`/orders${query}`, 'v2'); + } + + async getOrder(orderId: number) { + return this.get(`/orders/${orderId}`, 'v2'); + } + + async createOrder(order: any) { + return this.post('/orders', order, 'v2'); + } + + async updateOrder(orderId: number, order: any) { + return this.put(`/orders/${orderId}`, order, 'v2'); + } + + async getOrderProducts(orderId: number) { + return this.get(`/orders/${orderId}/products`, 'v2'); + } + + async getOrderShippingAddresses(orderId: number) { + return this.get(`/orders/${orderId}/shipping_addresses`, 'v2'); + } + + async listOrderShipments(orderId: number) { + return this.get(`/orders/${orderId}/shipments`, 'v2'); + } + + async createOrderShipment(orderId: number, shipment: any) { + return this.post(`/orders/${orderId}/shipments`, shipment, 'v2'); + } + + async getOrderRefunds(orderId: number) { + return this.get>(`/orders/${orderId}/payment_actions/refunds`); + } + + async createOrderRefund(orderId: number, refund: any) { + return this.post>(`/orders/${orderId}/payment_actions/refund`, refund); + } + + // ============================================================================ + // CUSTOMERS + // ============================================================================ + + async listCustomers(params?: { + company?: string; + first_name?: string; + last_name?: string; + email?: string; + customer_group_id?: number; + date_created?: string; + date_modified?: string; + }) { + const query = this.buildQueryString(params || {}); + return this.get>(`/customers${query}`); + } + + async getCustomer(customerId: number) { + return this.get>(`/customers/${customerId}`); + } + + async createCustomer(customer: any) { + return this.post>('/customers', customer); + } + + async updateCustomer(customerId: number, customer: any) { + return this.put>(`/customers/${customerId}`, customer); + } + + async deleteCustomer(customerId: number) { + return this.delete(`/customers/${customerId}`); + } + + async listCustomerAddresses(customerId: number) { + return this.get>(`/customers/${customerId}/addresses`); + } + + async createCustomerAddress(customerId: number, address: any) { + return this.post>(`/customers/${customerId}/addresses`, address); + } + + async listCustomerGroups() { + return this.get>('/customer-groups'); + } + + // ============================================================================ + // CATEGORIES + // ============================================================================ + + async listCategories(params?: { + name?: string; + parent_id?: number; + is_visible?: boolean; + page_title?: string; + }) { + const query = this.buildQueryString(params || {}); + return this.get>(`/catalog/categories${query}`); + } + + async getCategory(categoryId: number) { + return this.get>(`/catalog/categories/${categoryId}`); + } + + async createCategory(category: any) { + return this.post>('/catalog/categories', category); + } + + async updateCategory(categoryId: number, category: any) { + return this.put>(`/catalog/categories/${categoryId}`, category); + } + + async deleteCategory(categoryId: number) { + return this.delete(`/catalog/categories/${categoryId}`); + } + + async getCategoryTree() { + return this.get>('/catalog/categories/tree'); + } + + // ============================================================================ + // BRANDS + // ============================================================================ + + async listBrands(params?: { + name?: string; + page_title?: string; + }) { + const query = this.buildQueryString(params || {}); + return this.get>(`/catalog/brands${query}`); + } + + async getBrand(brandId: number) { + return this.get>(`/catalog/brands/${brandId}`); + } + + async createBrand(brand: any) { + return this.post>('/catalog/brands', brand); + } + + async updateBrand(brandId: number, brand: any) { + return this.put>(`/catalog/brands/${brandId}`, brand); + } + + async deleteBrand(brandId: number) { + return this.delete(`/catalog/brands/${brandId}`); + } + + // ============================================================================ + // COUPONS + // ============================================================================ + + async listCoupons(params?: { + code?: string; + name?: string; + type?: string; + }) { + const query = this.buildQueryString(params || {}); + return this.get(`/coupons${query}`, 'v2'); + } + + async getCoupon(couponId: number) { + return this.get(`/coupons/${couponId}`, 'v2'); + } + + async createCoupon(coupon: any) { + return this.post('/coupons', coupon, 'v2'); + } + + async updateCoupon(couponId: number, coupon: any) { + return this.put(`/coupons/${couponId}`, coupon, 'v2'); + } + + async deleteCoupon(couponId: number) { + return this.delete(`/coupons/${couponId}`, 'v2'); + } + + // ============================================================================ + // STORE + // ============================================================================ + + async getStoreInformation() { + return this.get>('/store'); + } + + // ============================================================================ + // CHANNELS + // ============================================================================ + + async listChannels(params?: { + type?: string; + platform?: string; + }) { + const query = this.buildQueryString(params || {}); + return this.get>(`/channels${query}`); + } + + async getChannel(channelId: number) { + return this.get>(`/channels/${channelId}`); + } + + async listChannelListings(channelId: number, params?: { + product_id?: number; + }) { + const query = this.buildQueryString(params || {}); + return this.get>(`/channels/${channelId}/listings${query}`); + } + + // ============================================================================ + // CARTS + // ============================================================================ + + async createCart(cart: any) { + return this.post>('/carts', cart); + } + + async getCart(cartId: string) { + return this.get>(`/carts/${cartId}`); + } + + async deleteCart(cartId: string) { + return this.delete(`/carts/${cartId}`); + } + + async addCartLineItems(cartId: string, lineItems: any) { + return this.post>(`/carts/${cartId}/items`, lineItems); + } + + async updateCartLineItem(cartId: string, itemId: string, item: any) { + return this.put>(`/carts/${cartId}/items/${itemId}`, item); + } + + // ============================================================================ + // CONTENT + // ============================================================================ + + async listPages() { + return this.get('/content/pages', 'v2'); + } + + async getPage(pageId: number) { + return this.get(`/content/pages/${pageId}`, 'v2'); + } + + async createPage(page: any) { + return this.post('/content/pages', page, 'v2'); + } + + async listBlogPosts() { + return this.get('/blog/posts', 'v2'); + } + + async createBlogPost(post: any) { + return this.post('/blog/posts', post, 'v2'); + } + + // ============================================================================ + // SHIPPING + // ============================================================================ + + async listShippingZones() { + return this.get('/shipping/zones', 'v2'); + } + + async getShippingZone(zoneId: number) { + return this.get(`/shipping/zones/${zoneId}`, 'v2'); + } + + async listShippingMethods(zoneId: number) { + return this.get(`/shipping/zones/${zoneId}/methods`, 'v2'); + } +} diff --git a/servers/bigcommerce/src/index.ts b/servers/bigcommerce/src/index.ts deleted file mode 100644 index 2e389cd..0000000 --- a/servers/bigcommerce/src/index.ts +++ /dev/null @@ -1,413 +0,0 @@ -#!/usr/bin/env node -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { - CallToolRequestSchema, - ListToolsRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; - -// ============================================ -// BIGCOMMERCE MCP SERVER -// API Docs: https://developer.bigcommerce.com/docs/api -// ============================================ -const MCP_NAME = "bigcommerce"; -const MCP_VERSION = "1.0.0"; - -// ============================================ -// API CLIENT - OAuth2/API Token Authentication -// ============================================ -class BigCommerceClient { - private accessToken: string; - private storeHash: string; - private baseUrlV3: string; - private baseUrlV2: string; - - constructor(accessToken: string, storeHash: string) { - this.accessToken = accessToken; - this.storeHash = storeHash; - this.baseUrlV3 = `https://api.bigcommerce.com/stores/${storeHash}/v3`; - this.baseUrlV2 = `https://api.bigcommerce.com/stores/${storeHash}/v2`; - } - - async request(url: string, options: RequestInit = {}) { - const response = await fetch(url, { - ...options, - headers: { - "X-Auth-Token": this.accessToken, - "Content-Type": "application/json", - "Accept": "application/json", - ...options.headers, - }, - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`BigCommerce API error: ${response.status} ${response.statusText} - ${errorText}`); - } - - // Handle 204 No Content - if (response.status === 204) { - return { success: true }; - } - - return response.json(); - } - - async getV3(endpoint: string, params?: Record) { - const queryString = params ? '?' + new URLSearchParams(params).toString() : ''; - return this.request(`${this.baseUrlV3}${endpoint}${queryString}`, { method: "GET" }); - } - - async getV2(endpoint: string, params?: Record) { - const queryString = params ? '?' + new URLSearchParams(params).toString() : ''; - return this.request(`${this.baseUrlV2}${endpoint}${queryString}`, { method: "GET" }); - } - - async postV3(endpoint: string, data: any) { - return this.request(`${this.baseUrlV3}${endpoint}`, { - method: "POST", - body: JSON.stringify(data), - }); - } - - async putV3(endpoint: string, data: any) { - return this.request(`${this.baseUrlV3}${endpoint}`, { - method: "PUT", - body: JSON.stringify(data), - }); - } - - async putV2(endpoint: string, data: any) { - return this.request(`${this.baseUrlV2}${endpoint}`, { - method: "PUT", - body: JSON.stringify(data), - }); - } -} - -// ============================================ -// TOOL DEFINITIONS -// ============================================ -const tools = [ - { - name: "list_products", - description: "List products from BigCommerce catalog with filtering and pagination", - inputSchema: { - type: "object" as const, - properties: { - limit: { type: "number", description: "Max products to return (default 50, max 250)" }, - page: { type: "number", description: "Page number for pagination" }, - name: { type: "string", description: "Filter by product name (partial match)" }, - sku: { type: "string", description: "Filter by SKU" }, - brand_id: { type: "number", description: "Filter by brand ID" }, - categories: { type: "string", description: "Filter by category ID(s), comma-separated" }, - is_visible: { type: "boolean", description: "Filter by visibility status" }, - availability: { type: "string", description: "Filter by availability: available, disabled, preorder" }, - include: { type: "string", description: "Sub-resources to include: variants, images, custom_fields, bulk_pricing_rules, primary_image, modifiers, options, videos" }, - }, - }, - }, - { - name: "get_product", - description: "Get a specific product by ID with full details", - inputSchema: { - type: "object" as const, - properties: { - product_id: { type: "number", description: "Product ID" }, - include: { type: "string", description: "Sub-resources to include: variants, images, custom_fields, bulk_pricing_rules, primary_image, modifiers, options, videos" }, - }, - required: ["product_id"], - }, - }, - { - name: "create_product", - description: "Create a new product in BigCommerce catalog", - inputSchema: { - type: "object" as const, - properties: { - name: { type: "string", description: "Product name (required)" }, - type: { type: "string", description: "Product type: physical, digital (required)" }, - weight: { type: "number", description: "Product weight (required for physical)" }, - price: { type: "number", description: "Product price (required)" }, - sku: { type: "string", description: "Stock Keeping Unit" }, - description: { type: "string", description: "Product description (HTML allowed)" }, - categories: { type: "array", description: "Array of category IDs", items: { type: "number" } }, - brand_id: { type: "number", description: "Brand ID" }, - inventory_level: { type: "number", description: "Current inventory level" }, - inventory_tracking: { type: "string", description: "Inventory tracking: none, product, variant" }, - is_visible: { type: "boolean", description: "Whether product is visible on storefront" }, - availability: { type: "string", description: "Availability: available, disabled, preorder" }, - cost_price: { type: "number", description: "Cost price for profit calculations" }, - sale_price: { type: "number", description: "Sale price" }, - }, - required: ["name", "type", "weight", "price"], - }, - }, - { - name: "update_product", - description: "Update an existing product in BigCommerce", - inputSchema: { - type: "object" as const, - properties: { - product_id: { type: "number", description: "Product ID (required)" }, - name: { type: "string", description: "Product name" }, - price: { type: "number", description: "Product price" }, - sku: { type: "string", description: "Stock Keeping Unit" }, - description: { type: "string", description: "Product description" }, - categories: { type: "array", description: "Array of category IDs", items: { type: "number" } }, - inventory_level: { type: "number", description: "Current inventory level" }, - is_visible: { type: "boolean", description: "Whether product is visible" }, - availability: { type: "string", description: "Availability: available, disabled, preorder" }, - sale_price: { type: "number", description: "Sale price" }, - }, - required: ["product_id"], - }, - }, - { - name: "list_orders", - description: "List orders from BigCommerce (V2 API)", - inputSchema: { - type: "object" as const, - properties: { - limit: { type: "number", description: "Max orders to return (default 50, max 250)" }, - page: { type: "number", description: "Page number for pagination" }, - min_date_created: { type: "string", description: "Filter by min creation date (RFC 2822 or ISO 8601)" }, - max_date_created: { type: "string", description: "Filter by max creation date" }, - status_id: { type: "number", description: "Filter by status ID" }, - customer_id: { type: "number", description: "Filter by customer ID" }, - min_total: { type: "number", description: "Filter by minimum total" }, - max_total: { type: "number", description: "Filter by maximum total" }, - is_deleted: { type: "boolean", description: "Include deleted orders" }, - sort: { type: "string", description: "Sort field: id, date_created, date_modified, status_id" }, - }, - }, - }, - { - name: "get_order", - description: "Get a specific order by ID with full details", - inputSchema: { - type: "object" as const, - properties: { - order_id: { type: "number", description: "Order ID" }, - include_products: { type: "boolean", description: "Include order products (separate call)" }, - include_shipping: { type: "boolean", description: "Include shipping addresses (separate call)" }, - }, - required: ["order_id"], - }, - }, - { - name: "list_customers", - description: "List customers from BigCommerce", - inputSchema: { - type: "object" as const, - properties: { - limit: { type: "number", description: "Max customers to return (default 50, max 250)" }, - page: { type: "number", description: "Page number for pagination" }, - email: { type: "string", description: "Filter by email address" }, - name: { type: "string", description: "Filter by name (first or last)" }, - company: { type: "string", description: "Filter by company name" }, - customer_group_id: { type: "number", description: "Filter by customer group ID" }, - date_created_min: { type: "string", description: "Filter by minimum creation date" }, - date_created_max: { type: "string", description: "Filter by maximum creation date" }, - include: { type: "string", description: "Sub-resources: addresses, storecredit, attributes, formfields" }, - }, - }, - }, - { - name: "update_inventory", - description: "Update inventory level for a product or variant", - inputSchema: { - type: "object" as const, - properties: { - product_id: { type: "number", description: "Product ID (required)" }, - variant_id: { type: "number", description: "Variant ID (if updating variant inventory)" }, - inventory_level: { type: "number", description: "New inventory level (required)" }, - inventory_warning_level: { type: "number", description: "Low stock warning threshold" }, - }, - required: ["product_id", "inventory_level"], - }, - }, -]; - -// ============================================ -// TOOL HANDLERS -// ============================================ -async function handleTool(client: BigCommerceClient, name: string, args: any) { - switch (name) { - case "list_products": { - const params: Record = {}; - if (args.limit) params.limit = String(args.limit); - if (args.page) params.page = String(args.page); - if (args.name) params['name:like'] = args.name; - if (args.sku) params.sku = args.sku; - if (args.brand_id) params.brand_id = String(args.brand_id); - if (args.categories) params['categories:in'] = args.categories; - if (args.is_visible !== undefined) params.is_visible = String(args.is_visible); - if (args.availability) params.availability = args.availability; - if (args.include) params.include = args.include; - return await client.getV3("/catalog/products", params); - } - - case "get_product": { - const params: Record = {}; - if (args.include) params.include = args.include; - return await client.getV3(`/catalog/products/${args.product_id}`, params); - } - - case "create_product": { - const productData: any = { - name: args.name, - type: args.type, - weight: args.weight, - price: args.price, - }; - if (args.sku) productData.sku = args.sku; - if (args.description) productData.description = args.description; - if (args.categories) productData.categories = args.categories; - if (args.brand_id) productData.brand_id = args.brand_id; - if (args.inventory_level !== undefined) productData.inventory_level = args.inventory_level; - if (args.inventory_tracking) productData.inventory_tracking = args.inventory_tracking; - if (args.is_visible !== undefined) productData.is_visible = args.is_visible; - if (args.availability) productData.availability = args.availability; - if (args.cost_price !== undefined) productData.cost_price = args.cost_price; - if (args.sale_price !== undefined) productData.sale_price = args.sale_price; - return await client.postV3("/catalog/products", productData); - } - - case "update_product": { - const updateData: any = {}; - if (args.name) updateData.name = args.name; - if (args.price !== undefined) updateData.price = args.price; - if (args.sku) updateData.sku = args.sku; - if (args.description) updateData.description = args.description; - if (args.categories) updateData.categories = args.categories; - if (args.inventory_level !== undefined) updateData.inventory_level = args.inventory_level; - if (args.is_visible !== undefined) updateData.is_visible = args.is_visible; - if (args.availability) updateData.availability = args.availability; - if (args.sale_price !== undefined) updateData.sale_price = args.sale_price; - return await client.putV3(`/catalog/products/${args.product_id}`, updateData); - } - - case "list_orders": { - const params: Record = {}; - if (args.limit) params.limit = String(args.limit); - if (args.page) params.page = String(args.page); - if (args.min_date_created) params.min_date_created = args.min_date_created; - if (args.max_date_created) params.max_date_created = args.max_date_created; - if (args.status_id) params.status_id = String(args.status_id); - if (args.customer_id) params.customer_id = String(args.customer_id); - if (args.min_total) params.min_total = String(args.min_total); - if (args.max_total) params.max_total = String(args.max_total); - if (args.is_deleted !== undefined) params.is_deleted = String(args.is_deleted); - if (args.sort) params.sort = args.sort; - return await client.getV2("/orders", params); - } - - case "get_order": { - const order = await client.getV2(`/orders/${args.order_id}`); - const result: any = { order }; - - if (args.include_products) { - result.products = await client.getV2(`/orders/${args.order_id}/products`); - } - if (args.include_shipping) { - result.shipping_addresses = await client.getV2(`/orders/${args.order_id}/shipping_addresses`); - } - - return result; - } - - case "list_customers": { - const params: Record = {}; - if (args.limit) params.limit = String(args.limit); - if (args.page) params.page = String(args.page); - if (args.email) params['email:in'] = args.email; - if (args.name) params['name:like'] = args.name; - if (args.company) params['company:like'] = args.company; - if (args.customer_group_id) params.customer_group_id = String(args.customer_group_id); - if (args.date_created_min) params['date_created:min'] = args.date_created_min; - if (args.date_created_max) params['date_created:max'] = args.date_created_max; - if (args.include) params.include = args.include; - return await client.getV3("/customers", params); - } - - case "update_inventory": { - if (args.variant_id) { - // Update variant inventory - const variantData: any = { - inventory_level: args.inventory_level, - }; - if (args.inventory_warning_level !== undefined) { - variantData.inventory_warning_level = args.inventory_warning_level; - } - return await client.putV3( - `/catalog/products/${args.product_id}/variants/${args.variant_id}`, - variantData - ); - } else { - // Update product inventory - const productData: any = { - inventory_level: args.inventory_level, - }; - if (args.inventory_warning_level !== undefined) { - productData.inventory_warning_level = args.inventory_warning_level; - } - return await client.putV3(`/catalog/products/${args.product_id}`, productData); - } - } - - default: - throw new Error(`Unknown tool: ${name}`); - } -} - -// ============================================ -// SERVER SETUP -// ============================================ -async function main() { - const accessToken = process.env.BIGCOMMERCE_ACCESS_TOKEN; - const storeHash = process.env.BIGCOMMERCE_STORE_HASH; - - if (!accessToken) { - console.error("Error: BIGCOMMERCE_ACCESS_TOKEN environment variable required"); - process.exit(1); - } - if (!storeHash) { - console.error("Error: BIGCOMMERCE_STORE_HASH environment variable required"); - process.exit(1); - } - - const client = new BigCommerceClient(accessToken, storeHash); - - const server = new Server( - { name: `${MCP_NAME}-mcp`, version: MCP_VERSION }, - { capabilities: { tools: {} } } - ); - - server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools, - })); - - server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - - try { - const result = await handleTool(client, name, args || {}); - return { - content: [{ type: "text", text: JSON.stringify(result, null, 2) }], - }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return { - content: [{ type: "text", text: `Error: ${message}` }], - isError: true, - }; - } - }); - - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error(`${MCP_NAME} MCP server running on stdio`); -} - -main().catch(console.error); diff --git a/servers/bigcommerce/src/main.ts b/servers/bigcommerce/src/main.ts new file mode 100644 index 0000000..4516cb1 --- /dev/null +++ b/servers/bigcommerce/src/main.ts @@ -0,0 +1,34 @@ +#!/usr/bin/env node + +/** + * BigCommerce MCP Server Entry Point + */ + +import { BigCommerceServer } from './server.js'; + +// Get configuration from environment variables +const STORE_HASH = process.env.BIGCOMMERCE_STORE_HASH; +const ACCESS_TOKEN = process.env.BIGCOMMERCE_ACCESS_TOKEN; + +if (!STORE_HASH || !ACCESS_TOKEN) { + console.error('Error: Missing required environment variables'); + console.error('Please set:'); + console.error(' BIGCOMMERCE_STORE_HASH - Your BigCommerce store hash'); + console.error(' BIGCOMMERCE_ACCESS_TOKEN - Your BigCommerce API access token'); + console.error(''); + console.error('Example:'); + console.error(' export BIGCOMMERCE_STORE_HASH="abc123def"'); + console.error(' export BIGCOMMERCE_ACCESS_TOKEN="your-token-here"'); + process.exit(1); +} + +// Create and run server +const server = new BigCommerceServer({ + storeHash: STORE_HASH, + accessToken: ACCESS_TOKEN, +}); + +server.run().catch((error) => { + console.error('Fatal error running server:', error); + process.exit(1); +}); diff --git a/servers/bigcommerce/src/server.ts b/servers/bigcommerce/src/server.ts new file mode 100644 index 0000000..f6144e3 --- /dev/null +++ b/servers/bigcommerce/src/server.ts @@ -0,0 +1,192 @@ +/** + * BigCommerce MCP Server + * Complete implementation with 60+ tools and 20 React apps + */ + +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, + ListResourcesRequestSchema, + ReadResourceRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { BigCommerceClient } from './clients/bigcommerce.js'; +import { registerProductsTools } from './tools/products-tools.js'; +import { registerOrdersTools } from './tools/orders-tools.js'; +import { registerCustomersTools } from './tools/customers-tools.js'; +import { registerCategoriesTools } from './tools/categories-tools.js'; +import { registerBrandsTools } from './tools/brands-tools.js'; +import { registerCouponsTools } from './tools/coupons-tools.js'; +import { registerStoreTools } from './tools/store-tools.js'; +import { registerChannelsTools } from './tools/channels-tools.js'; +import { registerCartsTools } from './tools/carts-tools.js'; +import { registerContentTools } from './tools/content-tools.js'; +import { registerShippingTools } from './tools/shipping-tools.js'; +import { registerAnalyticsTools } from './tools/analytics-tools.js'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +interface BigCommerceServerConfig { + storeHash: string; + accessToken: string; +} + +export class BigCommerceServer { + private server: Server; + private client: BigCommerceClient; + private tools: Map = new Map(); + + constructor(config: BigCommerceServerConfig) { + this.server = new Server( + { + name: 'bigcommerce-mcp-server', + version: '1.0.0', + }, + { + capabilities: { + tools: {}, + resources: {}, + }, + } + ); + + // Initialize BigCommerce client + this.client = new BigCommerceClient({ + storeHash: config.storeHash, + accessToken: config.accessToken, + }); + + // Register all tools + this.registerAllTools(); + + // Setup request handlers + this.setupHandlers(); + } + + private registerAllTools() { + const toolSets = [ + registerProductsTools(this.client), + registerOrdersTools(this.client), + registerCustomersTools(this.client), + registerCategoriesTools(this.client), + registerBrandsTools(this.client), + registerCouponsTools(this.client), + registerStoreTools(this.client), + registerChannelsTools(this.client), + registerCartsTools(this.client), + registerContentTools(this.client), + registerShippingTools(this.client), + registerAnalyticsTools(this.client), + ]; + + for (const toolSet of toolSets) { + for (const tool of toolSet) { + this.tools.set(tool.name, tool); + } + } + + console.error(`Registered ${this.tools.size} BigCommerce tools`); + } + + private setupHandlers() { + // List available tools + this.server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: Array.from(this.tools.values()).map((tool) => ({ + name: tool.name, + description: tool.description, + inputSchema: tool.inputSchema, + })), + }; + }); + + // Execute tool + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + const tool = this.tools.get(request.params.name); + + if (!tool) { + throw new Error(`Tool not found: ${request.params.name}`); + } + + try { + return await tool.handler(request.params.arguments); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: 'text', + text: `Error executing ${request.params.name}: ${errorMessage}`, + }, + ], + isError: true, + }; + } + }); + + // List React app resources + this.server.setRequestHandler(ListResourcesRequestSchema, async () => { + const appsDir = path.join(__dirname, 'ui', 'react-app'); + + try { + const apps = fs.readdirSync(appsDir).filter((file) => { + const appPath = path.join(appsDir, file); + return fs.statSync(appPath).isDirectory(); + }); + + return { + resources: apps.map((app) => ({ + uri: `bigcommerce://app/${app}`, + name: app + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '), + description: `BigCommerce ${app.replace(/-/g, ' ')} React application`, + mimeType: 'text/html', + })), + }; + } catch (error) { + return { resources: [] }; + } + }); + + // Read React app resource + this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const uri = request.params.uri; + const match = uri.match(/^bigcommerce:\/\/app\/(.+)$/); + + if (!match) { + throw new Error(`Invalid resource URI: ${uri}`); + } + + const appName = match[1]; + const appPath = path.join(__dirname, 'ui', 'react-app', appName, 'App.tsx'); + + try { + const content = fs.readFileSync(appPath, 'utf-8'); + return { + contents: [ + { + uri, + mimeType: 'text/html', + text: content, + }, + ], + }; + } catch (error) { + throw new Error(`Failed to read app ${appName}: ${error}`); + } + }); + } + + async run() { + const transport = new StdioServerTransport(); + await this.server.connect(transport); + console.error('BigCommerce MCP server running on stdio'); + } +} diff --git a/servers/bigcommerce/src/tools/analytics-tools.ts b/servers/bigcommerce/src/tools/analytics-tools.ts new file mode 100644 index 0000000..f30a7f3 --- /dev/null +++ b/servers/bigcommerce/src/tools/analytics-tools.ts @@ -0,0 +1,129 @@ +/** + * BigCommerce Analytics Tools + * Note: BigCommerce doesn't have a dedicated analytics API endpoint, + * so these tools aggregate data from orders and products + */ + +import { BigCommerceClient } from '../clients/bigcommerce.js'; + +export function registerAnalyticsTools(client: BigCommerceClient) { + return [ + { + name: 'bigcommerce_get_store_analytics', + description: 'Get store-wide analytics including orders, revenue, and customer metrics', + inputSchema: { + type: 'object', + properties: { + start_date: { + type: 'string', + description: 'Start date for analytics period (RFC 2822 or ISO 8601)' + }, + end_date: { + type: 'string', + description: 'End date for analytics period (RFC 2822 or ISO 8601)' + }, + }, + }, + handler: async (args: any) => { + // Fetch orders for the period + const orders = await client.listOrders({ + min_date_created: args.start_date, + max_date_created: args.end_date, + }); + + // Calculate analytics + const orderArray = Array.isArray(orders) ? orders : []; + const totalOrders = orderArray.length; + const totalRevenue = orderArray.reduce((sum: number, order: any) => + sum + (order.total_inc_tax || 0), 0 + ); + const averageOrderValue = totalOrders > 0 ? totalRevenue / totalOrders : 0; + + // Get unique customers + const uniqueCustomers = new Set(orderArray.map((order: any) => order.customer_id)); + + const analytics = { + period: { + start_date: args.start_date, + end_date: args.end_date, + }, + total_orders: totalOrders, + total_revenue: Math.round(totalRevenue * 100) / 100, + average_order_value: Math.round(averageOrderValue * 100) / 100, + total_customers: uniqueCustomers.size, + }; + + return { content: [{ type: 'text', text: JSON.stringify(analytics, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_product_analytics', + description: 'Get analytics for a specific product', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID' }, + start_date: { + type: 'string', + description: 'Start date for analytics period (RFC 2822 or ISO 8601)' + }, + end_date: { + type: 'string', + description: 'End date for analytics period (RFC 2822 or ISO 8601)' + }, + }, + required: ['product_id'], + }, + handler: async (args: any) => { + // Get product details + const product = await client.getProduct(args.product_id); + + // Fetch orders for the period + const orders = await client.listOrders({ + min_date_created: args.start_date, + max_date_created: args.end_date, + }); + + const orderArray = Array.isArray(orders) ? orders : []; + + // Analyze orders for this product + let totalQuantitySold = 0; + let totalRevenue = 0; + let orderCount = 0; + + for (const order of orderArray) { + try { + const orderProducts = await client.getOrderProducts(order.id); + const orderProductArray = Array.isArray(orderProducts) ? orderProducts : []; + + for (const item of orderProductArray) { + if (item.product_id === args.product_id) { + totalQuantitySold += item.quantity || 0; + totalRevenue += item.total_inc_tax || 0; + orderCount++; + } + } + } catch (error) { + // Continue if order products can't be fetched + continue; + } + } + + const analytics = { + product_id: args.product_id, + product_name: product.data?.name, + period: { + start_date: args.start_date, + end_date: args.end_date, + }, + orders: orderCount, + quantity_sold: totalQuantitySold, + revenue: Math.round(totalRevenue * 100) / 100, + current_inventory: product.data?.inventory_level, + }; + + return { content: [{ type: 'text', text: JSON.stringify(analytics, null, 2) }] }; + }, + }, + ]; +} diff --git a/servers/bigcommerce/src/tools/brands-tools.ts b/servers/bigcommerce/src/tools/brands-tools.ts new file mode 100644 index 0000000..90ab3a4 --- /dev/null +++ b/servers/bigcommerce/src/tools/brands-tools.ts @@ -0,0 +1,105 @@ +/** + * BigCommerce Brands Tools + */ + +import { BigCommerceClient } from '../clients/bigcommerce.js'; + +export function registerBrandsTools(client: BigCommerceClient) { + return [ + { + name: 'bigcommerce_list_brands', + description: 'List all brands from BigCommerce store with optional filters', + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'Filter by brand name' }, + page_title: { type: 'string', description: 'Filter by page title' }, + }, + }, + handler: async (args: any) => { + const brands = await client.listBrands(args); + return { content: [{ type: 'text', text: JSON.stringify(brands, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_brand', + description: 'Get detailed information about a specific brand', + inputSchema: { + type: 'object', + properties: { + brand_id: { type: 'number', description: 'Brand ID' }, + }, + required: ['brand_id'], + }, + handler: async (args: any) => { + const brand = await client.getBrand(args.brand_id); + return { content: [{ type: 'text', text: JSON.stringify(brand, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_create_brand', + description: 'Create a new brand in BigCommerce', + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'Brand name' }, + page_title: { type: 'string', description: 'Page title for SEO' }, + meta_keywords: { + type: 'array', + items: { type: 'string' }, + description: 'Meta keywords for SEO' + }, + meta_description: { type: 'string', description: 'Meta description for SEO' }, + image_url: { type: 'string', description: 'Brand logo/image URL' }, + search_keywords: { type: 'string', description: 'Search keywords' }, + }, + required: ['name'], + }, + handler: async (args: any) => { + const brand = await client.createBrand(args); + return { content: [{ type: 'text', text: JSON.stringify(brand, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_update_brand', + description: 'Update an existing brand', + inputSchema: { + type: 'object', + properties: { + brand_id: { type: 'number', description: 'Brand ID' }, + name: { type: 'string', description: 'Brand name' }, + page_title: { type: 'string', description: 'Page title for SEO' }, + meta_keywords: { + type: 'array', + items: { type: 'string' }, + description: 'Meta keywords for SEO' + }, + meta_description: { type: 'string', description: 'Meta description for SEO' }, + image_url: { type: 'string', description: 'Brand logo/image URL' }, + search_keywords: { type: 'string', description: 'Search keywords' }, + }, + required: ['brand_id'], + }, + handler: async (args: any) => { + const { brand_id, ...updateData } = args; + const brand = await client.updateBrand(brand_id, updateData); + return { content: [{ type: 'text', text: JSON.stringify(brand, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_delete_brand', + description: 'Delete a brand from BigCommerce', + inputSchema: { + type: 'object', + properties: { + brand_id: { type: 'number', description: 'Brand ID to delete' }, + }, + required: ['brand_id'], + }, + handler: async (args: any) => { + await client.deleteBrand(args.brand_id); + return { content: [{ type: 'text', text: `Brand ${args.brand_id} deleted successfully` }] }; + }, + }, + ]; +} diff --git a/servers/bigcommerce/src/tools/carts-tools.ts b/servers/bigcommerce/src/tools/carts-tools.ts new file mode 100644 index 0000000..181d4f2 --- /dev/null +++ b/servers/bigcommerce/src/tools/carts-tools.ts @@ -0,0 +1,116 @@ +/** + * BigCommerce Carts Tools + */ + +import { BigCommerceClient } from '../clients/bigcommerce.js'; + +export function registerCartsTools(client: BigCommerceClient) { + return [ + { + name: 'bigcommerce_create_cart', + description: 'Create a new shopping cart', + inputSchema: { + type: 'object', + properties: { + customer_id: { type: 'number', description: 'Customer ID (optional)' }, + channel_id: { type: 'number', description: 'Channel ID' }, + line_items: { + type: 'array', + items: { + type: 'object', + properties: { + quantity: { type: 'number' }, + product_id: { type: 'number' }, + variant_id: { type: 'number' }, + }, + }, + description: 'Initial line items', + }, + }, + }, + handler: async (args: any) => { + const cart = await client.createCart(args); + return { content: [{ type: 'text', text: JSON.stringify(cart, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_cart', + description: 'Get details of a specific cart', + inputSchema: { + type: 'object', + properties: { + cart_id: { type: 'string', description: 'Cart ID (UUID)' }, + }, + required: ['cart_id'], + }, + handler: async (args: any) => { + const cart = await client.getCart(args.cart_id); + return { content: [{ type: 'text', text: JSON.stringify(cart, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_delete_cart', + description: 'Delete a cart', + inputSchema: { + type: 'object', + properties: { + cart_id: { type: 'string', description: 'Cart ID to delete' }, + }, + required: ['cart_id'], + }, + handler: async (args: any) => { + await client.deleteCart(args.cart_id); + return { content: [{ type: 'text', text: `Cart ${args.cart_id} deleted successfully` }] }; + }, + }, + { + name: 'bigcommerce_add_cart_items', + description: 'Add line items to an existing cart', + inputSchema: { + type: 'object', + properties: { + cart_id: { type: 'string', description: 'Cart ID' }, + line_items: { + type: 'array', + items: { + type: 'object', + properties: { + quantity: { type: 'number', description: 'Quantity to add' }, + product_id: { type: 'number', description: 'Product ID' }, + variant_id: { type: 'number', description: 'Variant ID (if applicable)' }, + }, + required: ['quantity', 'product_id'], + }, + description: 'Line items to add', + }, + }, + required: ['cart_id', 'line_items'], + }, + handler: async (args: any) => { + const { cart_id, line_items } = args; + const cart = await client.addCartLineItems(cart_id, { line_items }); + return { content: [{ type: 'text', text: JSON.stringify(cart, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_update_cart_item', + description: 'Update quantity of a line item in cart', + inputSchema: { + type: 'object', + properties: { + cart_id: { type: 'string', description: 'Cart ID' }, + item_id: { type: 'string', description: 'Line item ID' }, + quantity: { type: 'number', description: 'New quantity' }, + }, + required: ['cart_id', 'item_id', 'quantity'], + }, + handler: async (args: any) => { + const { cart_id, item_id, quantity } = args; + const cart = await client.updateCartLineItem(cart_id, item_id, { + line_item: { quantity } + }); + return { content: [{ type: 'text', text: JSON.stringify(cart, null, 2) }] }; + }, + }, + ]; +} diff --git a/servers/bigcommerce/src/tools/categories-tools.ts b/servers/bigcommerce/src/tools/categories-tools.ts new file mode 100644 index 0000000..b5127ca --- /dev/null +++ b/servers/bigcommerce/src/tools/categories-tools.ts @@ -0,0 +1,136 @@ +/** + * BigCommerce Categories Tools + */ + +import { BigCommerceClient } from '../clients/bigcommerce.js'; + +export function registerCategoriesTools(client: BigCommerceClient) { + return [ + { + name: 'bigcommerce_list_categories', + description: 'List all categories from BigCommerce store with optional filters', + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'Filter by category name' }, + parent_id: { type: 'number', description: 'Filter by parent category ID' }, + is_visible: { type: 'boolean', description: 'Filter by visibility' }, + page_title: { type: 'string', description: 'Filter by page title' }, + }, + }, + handler: async (args: any) => { + const categories = await client.listCategories(args); + return { content: [{ type: 'text', text: JSON.stringify(categories, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_category', + description: 'Get detailed information about a specific category', + inputSchema: { + type: 'object', + properties: { + category_id: { type: 'number', description: 'Category ID' }, + }, + required: ['category_id'], + }, + handler: async (args: any) => { + const category = await client.getCategory(args.category_id); + return { content: [{ type: 'text', text: JSON.stringify(category, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_create_category', + description: 'Create a new category in BigCommerce', + inputSchema: { + type: 'object', + properties: { + parent_id: { type: 'number', description: 'Parent category ID (0 for root)' }, + name: { type: 'string', description: 'Category name' }, + description: { type: 'string', description: 'Category description' }, + sort_order: { type: 'number', description: 'Sort order for display' }, + page_title: { type: 'string', description: 'Page title for SEO' }, + meta_keywords: { + type: 'array', + items: { type: 'string' }, + description: 'Meta keywords for SEO' + }, + meta_description: { type: 'string', description: 'Meta description for SEO' }, + is_visible: { type: 'boolean', description: 'Is category visible' }, + search_keywords: { type: 'string', description: 'Search keywords' }, + default_product_sort: { + type: 'string', + enum: ['use_store_settings', 'featured', 'newest', 'bestselling', 'alphaasc', 'alphadesc', 'avgcustomerreview', 'priceasc', 'pricedesc'], + description: 'Default product sort order' + }, + image_url: { type: 'string', description: 'Category image URL' }, + }, + required: ['parent_id', 'name'], + }, + handler: async (args: any) => { + const category = await client.createCategory(args); + return { content: [{ type: 'text', text: JSON.stringify(category, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_update_category', + description: 'Update an existing category', + inputSchema: { + type: 'object', + properties: { + category_id: { type: 'number', description: 'Category ID' }, + parent_id: { type: 'number', description: 'Parent category ID' }, + name: { type: 'string', description: 'Category name' }, + description: { type: 'string', description: 'Category description' }, + sort_order: { type: 'number', description: 'Sort order for display' }, + page_title: { type: 'string', description: 'Page title for SEO' }, + meta_keywords: { + type: 'array', + items: { type: 'string' }, + description: 'Meta keywords for SEO' + }, + meta_description: { type: 'string', description: 'Meta description for SEO' }, + is_visible: { type: 'boolean', description: 'Is category visible' }, + search_keywords: { type: 'string', description: 'Search keywords' }, + default_product_sort: { + type: 'string', + description: 'Default product sort order' + }, + image_url: { type: 'string', description: 'Category image URL' }, + }, + required: ['category_id'], + }, + handler: async (args: any) => { + const { category_id, ...updateData } = args; + const category = await client.updateCategory(category_id, updateData); + return { content: [{ type: 'text', text: JSON.stringify(category, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_delete_category', + description: 'Delete a category from BigCommerce', + inputSchema: { + type: 'object', + properties: { + category_id: { type: 'number', description: 'Category ID to delete' }, + }, + required: ['category_id'], + }, + handler: async (args: any) => { + await client.deleteCategory(args.category_id); + return { content: [{ type: 'text', text: `Category ${args.category_id} deleted successfully` }] }; + }, + }, + { + name: 'bigcommerce_get_category_tree', + description: 'Get the complete category tree structure', + inputSchema: { + type: 'object', + properties: {}, + }, + handler: async (args: any) => { + const tree = await client.getCategoryTree(); + return { content: [{ type: 'text', text: JSON.stringify(tree, null, 2) }] }; + }, + }, + ]; +} diff --git a/servers/bigcommerce/src/tools/channels-tools.ts b/servers/bigcommerce/src/tools/channels-tools.ts new file mode 100644 index 0000000..a4fb5d9 --- /dev/null +++ b/servers/bigcommerce/src/tools/channels-tools.ts @@ -0,0 +1,61 @@ +/** + * BigCommerce Channels Tools + */ + +import { BigCommerceClient } from '../clients/bigcommerce.js'; + +export function registerChannelsTools(client: BigCommerceClient) { + return [ + { + name: 'bigcommerce_list_channels', + description: 'List all sales channels (storefronts, marketplaces, POS, etc.)', + inputSchema: { + type: 'object', + properties: { + type: { + type: 'string', + enum: ['storefront', 'pos', 'marketplace', 'marketing'], + description: 'Filter by channel type' + }, + platform: { type: 'string', description: 'Filter by platform name' }, + }, + }, + handler: async (args: any) => { + const channels = await client.listChannels(args); + return { content: [{ type: 'text', text: JSON.stringify(channels, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_channel', + description: 'Get detailed information about a specific channel', + inputSchema: { + type: 'object', + properties: { + channel_id: { type: 'number', description: 'Channel ID' }, + }, + required: ['channel_id'], + }, + handler: async (args: any) => { + const channel = await client.getChannel(args.channel_id); + return { content: [{ type: 'text', text: JSON.stringify(channel, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_list_channel_listings', + description: 'List product listings for a specific channel', + inputSchema: { + type: 'object', + properties: { + channel_id: { type: 'number', description: 'Channel ID' }, + product_id: { type: 'number', description: 'Filter by product ID' }, + }, + required: ['channel_id'], + }, + handler: async (args: any) => { + const { channel_id, ...params } = args; + const listings = await client.listChannelListings(channel_id, params); + return { content: [{ type: 'text', text: JSON.stringify(listings, null, 2) }] }; + }, + }, + ]; +} diff --git a/servers/bigcommerce/src/tools/content-tools.ts b/servers/bigcommerce/src/tools/content-tools.ts new file mode 100644 index 0000000..189fbfb --- /dev/null +++ b/servers/bigcommerce/src/tools/content-tools.ts @@ -0,0 +1,107 @@ +/** + * BigCommerce Content Tools (Pages & Blog) + */ + +import { BigCommerceClient } from '../clients/bigcommerce.js'; + +export function registerContentTools(client: BigCommerceClient) { + return [ + { + name: 'bigcommerce_list_pages', + description: 'List all content pages in the store', + inputSchema: { + type: 'object', + properties: {}, + }, + handler: async (args: any) => { + const pages = await client.listPages(); + return { content: [{ type: 'text', text: JSON.stringify(pages, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_page', + description: 'Get details of a specific content page', + inputSchema: { + type: 'object', + properties: { + page_id: { type: 'number', description: 'Page ID' }, + }, + required: ['page_id'], + }, + handler: async (args: any) => { + const page = await client.getPage(args.page_id); + return { content: [{ type: 'text', text: JSON.stringify(page, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_create_page', + description: 'Create a new content page', + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'Page name/title' }, + type: { + type: 'string', + enum: ['page', 'raw', 'contact_form', 'feed', 'link', 'blog'], + description: 'Page type' + }, + body: { type: 'string', description: 'Page content (HTML)' }, + is_visible: { type: 'boolean', description: 'Is page visible' }, + is_homepage: { type: 'boolean', description: 'Set as homepage' }, + is_customers_only: { type: 'boolean', description: 'Require login to view' }, + meta_title: { type: 'string', description: 'Page title for SEO' }, + meta_keywords: { type: 'string', description: 'Meta keywords for SEO' }, + meta_description: { type: 'string', description: 'Meta description for SEO' }, + search_keywords: { type: 'string', description: 'Search keywords' }, + url: { type: 'string', description: 'Custom URL path' }, + feed: { type: 'string', description: 'Feed URL (for feed type)' }, + link: { type: 'string', description: 'External link (for link type)' }, + sort_order: { type: 'number', description: 'Sort order' }, + }, + required: ['name', 'type'], + }, + handler: async (args: any) => { + const page = await client.createPage(args); + return { content: [{ type: 'text', text: JSON.stringify(page, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_list_blog_posts', + description: 'List all blog posts', + inputSchema: { + type: 'object', + properties: {}, + }, + handler: async (args: any) => { + const posts = await client.listBlogPosts(); + return { content: [{ type: 'text', text: JSON.stringify(posts, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_create_blog_post', + description: 'Create a new blog post', + inputSchema: { + type: 'object', + properties: { + title: { type: 'string', description: 'Blog post title' }, + body: { type: 'string', description: 'Blog post content (HTML)' }, + summary: { type: 'string', description: 'Short summary/excerpt' }, + is_published: { type: 'boolean', description: 'Publish immediately' }, + author: { type: 'string', description: 'Author name' }, + meta_description: { type: 'string', description: 'Meta description for SEO' }, + meta_keywords: { type: 'string', description: 'Meta keywords for SEO' }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Blog post tags' + }, + }, + required: ['title', 'body'], + }, + handler: async (args: any) => { + const post = await client.createBlogPost(args); + return { content: [{ type: 'text', text: JSON.stringify(post, null, 2) }] }; + }, + }, + ]; +} diff --git a/servers/bigcommerce/src/tools/coupons-tools.ts b/servers/bigcommerce/src/tools/coupons-tools.ts new file mode 100644 index 0000000..1aa4685 --- /dev/null +++ b/servers/bigcommerce/src/tools/coupons-tools.ts @@ -0,0 +1,116 @@ +/** + * BigCommerce Coupons Tools + */ + +import { BigCommerceClient } from '../clients/bigcommerce.js'; + +export function registerCouponsTools(client: BigCommerceClient) { + return [ + { + name: 'bigcommerce_list_coupons', + description: 'List all coupons from BigCommerce store with optional filters', + inputSchema: { + type: 'object', + properties: { + code: { type: 'string', description: 'Filter by coupon code' }, + name: { type: 'string', description: 'Filter by coupon name' }, + type: { + type: 'string', + enum: ['per_item_discount', 'per_total_discount', 'shipping_discount', 'free_shipping', 'percentage_discount'], + description: 'Filter by coupon type' + }, + }, + }, + handler: async (args: any) => { + const coupons = await client.listCoupons(args); + return { content: [{ type: 'text', text: JSON.stringify(coupons, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_coupon', + description: 'Get detailed information about a specific coupon', + inputSchema: { + type: 'object', + properties: { + coupon_id: { type: 'number', description: 'Coupon ID' }, + }, + required: ['coupon_id'], + }, + handler: async (args: any) => { + const coupon = await client.getCoupon(args.coupon_id); + return { content: [{ type: 'text', text: JSON.stringify(coupon, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_create_coupon', + description: 'Create a new coupon in BigCommerce', + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'Coupon name (internal)' }, + code: { type: 'string', description: 'Coupon code (what customers enter)' }, + type: { + type: 'string', + enum: ['per_item_discount', 'per_total_discount', 'shipping_discount', 'free_shipping', 'percentage_discount'], + description: 'Coupon type' + }, + amount: { type: 'number', description: 'Discount amount (dollars or percentage)' }, + min_purchase: { type: 'number', description: 'Minimum purchase amount required' }, + expires: { type: 'string', description: 'Expiration date (RFC 2822 or ISO 8601)' }, + enabled: { type: 'boolean', description: 'Is coupon enabled' }, + max_uses: { type: 'number', description: 'Maximum total uses' }, + max_uses_per_customer: { type: 'number', description: 'Maximum uses per customer' }, + }, + required: ['name', 'code', 'type', 'amount'], + }, + handler: async (args: any) => { + const coupon = await client.createCoupon(args); + return { content: [{ type: 'text', text: JSON.stringify(coupon, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_update_coupon', + description: 'Update an existing coupon', + inputSchema: { + type: 'object', + properties: { + coupon_id: { type: 'number', description: 'Coupon ID' }, + name: { type: 'string', description: 'Coupon name (internal)' }, + code: { type: 'string', description: 'Coupon code' }, + type: { + type: 'string', + enum: ['per_item_discount', 'per_total_discount', 'shipping_discount', 'free_shipping', 'percentage_discount'], + description: 'Coupon type' + }, + amount: { type: 'number', description: 'Discount amount' }, + min_purchase: { type: 'number', description: 'Minimum purchase amount required' }, + expires: { type: 'string', description: 'Expiration date' }, + enabled: { type: 'boolean', description: 'Is coupon enabled' }, + max_uses: { type: 'number', description: 'Maximum total uses' }, + max_uses_per_customer: { type: 'number', description: 'Maximum uses per customer' }, + }, + required: ['coupon_id'], + }, + handler: async (args: any) => { + const { coupon_id, ...updateData } = args; + const coupon = await client.updateCoupon(coupon_id, updateData); + return { content: [{ type: 'text', text: JSON.stringify(coupon, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_delete_coupon', + description: 'Delete a coupon from BigCommerce', + inputSchema: { + type: 'object', + properties: { + coupon_id: { type: 'number', description: 'Coupon ID to delete' }, + }, + required: ['coupon_id'], + }, + handler: async (args: any) => { + await client.deleteCoupon(args.coupon_id); + return { content: [{ type: 'text', text: `Coupon ${args.coupon_id} deleted successfully` }] }; + }, + }, + ]; +} diff --git a/servers/bigcommerce/src/tools/customers-tools.ts b/servers/bigcommerce/src/tools/customers-tools.ts new file mode 100644 index 0000000..99ffbda --- /dev/null +++ b/servers/bigcommerce/src/tools/customers-tools.ts @@ -0,0 +1,198 @@ +/** + * BigCommerce Customers Tools + */ + +import { BigCommerceClient } from '../clients/bigcommerce.js'; + +export function registerCustomersTools(client: BigCommerceClient) { + return [ + { + name: 'bigcommerce_list_customers', + description: 'List all customers from BigCommerce store with optional filters', + inputSchema: { + type: 'object', + properties: { + company: { type: 'string', description: 'Filter by company name' }, + first_name: { type: 'string', description: 'Filter by first name' }, + last_name: { type: 'string', description: 'Filter by last name' }, + email: { type: 'string', description: 'Filter by email address' }, + customer_group_id: { type: 'number', description: 'Filter by customer group ID' }, + date_created: { type: 'string', description: 'Filter by creation date' }, + date_modified: { type: 'string', description: 'Filter by modification date' }, + }, + }, + handler: async (args: any) => { + const customers = await client.listCustomers(args); + return { content: [{ type: 'text', text: JSON.stringify(customers, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_customer', + description: 'Get detailed information about a specific customer', + inputSchema: { + type: 'object', + properties: { + customer_id: { type: 'number', description: 'Customer ID' }, + }, + required: ['customer_id'], + }, + handler: async (args: any) => { + const customer = await client.getCustomer(args.customer_id); + return { content: [{ type: 'text', text: JSON.stringify(customer, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_create_customer', + description: 'Create a new customer in BigCommerce', + inputSchema: { + type: 'object', + properties: { + first_name: { type: 'string', description: 'Customer first name' }, + last_name: { type: 'string', description: 'Customer last name' }, + email: { type: 'string', description: 'Customer email address' }, + company: { type: 'string', description: 'Company name' }, + phone: { type: 'string', description: 'Phone number' }, + customer_group_id: { type: 'number', description: 'Customer group ID' }, + notes: { type: 'string', description: 'Customer notes' }, + accepts_product_review_abandoned_cart_emails: { + type: 'boolean', + description: 'Accept marketing emails' + }, + addresses: { + type: 'array', + items: { + type: 'object', + properties: { + first_name: { type: 'string' }, + last_name: { type: 'string' }, + company: { type: 'string' }, + address1: { type: 'string' }, + address2: { type: 'string' }, + city: { type: 'string' }, + state_or_province: { type: 'string' }, + postal_code: { type: 'string' }, + country_code: { type: 'string' }, + phone: { type: 'string' }, + address_type: { type: 'string', enum: ['residential', 'commercial'] }, + }, + }, + description: 'Customer addresses', + }, + authentication: { + type: 'object', + properties: { + force_password_reset: { type: 'boolean' }, + new_password: { type: 'string' }, + }, + description: 'Authentication settings', + }, + }, + required: ['first_name', 'last_name', 'email'], + }, + handler: async (args: any) => { + const customer = await client.createCustomer(args); + return { content: [{ type: 'text', text: JSON.stringify(customer, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_update_customer', + description: 'Update an existing customer', + inputSchema: { + type: 'object', + properties: { + customer_id: { type: 'number', description: 'Customer ID' }, + first_name: { type: 'string', description: 'Customer first name' }, + last_name: { type: 'string', description: 'Customer last name' }, + email: { type: 'string', description: 'Customer email address' }, + company: { type: 'string', description: 'Company name' }, + phone: { type: 'string', description: 'Phone number' }, + customer_group_id: { type: 'number', description: 'Customer group ID' }, + notes: { type: 'string', description: 'Customer notes' }, + accepts_product_review_abandoned_cart_emails: { + type: 'boolean', + description: 'Accept marketing emails' + }, + }, + required: ['customer_id'], + }, + handler: async (args: any) => { + const { customer_id, ...updateData } = args; + const customer = await client.updateCustomer(customer_id, updateData); + return { content: [{ type: 'text', text: JSON.stringify(customer, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_delete_customer', + description: 'Delete a customer from BigCommerce', + inputSchema: { + type: 'object', + properties: { + customer_id: { type: 'number', description: 'Customer ID to delete' }, + }, + required: ['customer_id'], + }, + handler: async (args: any) => { + await client.deleteCustomer(args.customer_id); + return { content: [{ type: 'text', text: `Customer ${args.customer_id} deleted successfully` }] }; + }, + }, + { + name: 'bigcommerce_list_customer_addresses', + description: 'List all addresses for a customer', + inputSchema: { + type: 'object', + properties: { + customer_id: { type: 'number', description: 'Customer ID' }, + }, + required: ['customer_id'], + }, + handler: async (args: any) => { + const addresses = await client.listCustomerAddresses(args.customer_id); + return { content: [{ type: 'text', text: JSON.stringify(addresses, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_create_customer_address', + description: 'Create a new address for a customer', + inputSchema: { + type: 'object', + properties: { + customer_id: { type: 'number', description: 'Customer ID' }, + first_name: { type: 'string', description: 'First name' }, + last_name: { type: 'string', description: 'Last name' }, + company: { type: 'string', description: 'Company name' }, + address1: { type: 'string', description: 'Address line 1' }, + address2: { type: 'string', description: 'Address line 2' }, + city: { type: 'string', description: 'City' }, + state_or_province: { type: 'string', description: 'State or province' }, + postal_code: { type: 'string', description: 'Postal/ZIP code' }, + country_code: { type: 'string', description: 'Country code (ISO 3166-1 alpha-2)' }, + phone: { type: 'string', description: 'Phone number' }, + address_type: { + type: 'string', + enum: ['residential', 'commercial'], + description: 'Address type' + }, + }, + required: ['customer_id', 'first_name', 'last_name', 'address1', 'city', 'state_or_province', 'postal_code', 'country_code'], + }, + handler: async (args: any) => { + const { customer_id, ...addressData } = args; + const address = await client.createCustomerAddress(customer_id, addressData); + return { content: [{ type: 'text', text: JSON.stringify(address, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_list_customer_groups', + description: 'List all customer groups', + inputSchema: { + type: 'object', + properties: {}, + }, + handler: async (args: any) => { + const groups = await client.listCustomerGroups(); + return { content: [{ type: 'text', text: JSON.stringify(groups, null, 2) }] }; + }, + }, + ]; +} diff --git a/servers/bigcommerce/src/tools/orders-tools.ts b/servers/bigcommerce/src/tools/orders-tools.ts new file mode 100644 index 0000000..2a04f65 --- /dev/null +++ b/servers/bigcommerce/src/tools/orders-tools.ts @@ -0,0 +1,249 @@ +/** + * BigCommerce Orders Tools + */ + +import { BigCommerceClient } from '../clients/bigcommerce.js'; + +export function registerOrdersTools(client: BigCommerceClient) { + return [ + { + name: 'bigcommerce_list_orders', + description: 'List all orders from BigCommerce store with optional filters', + inputSchema: { + type: 'object', + properties: { + min_id: { type: 'number', description: 'Minimum order ID' }, + max_id: { type: 'number', description: 'Maximum order ID' }, + min_total: { type: 'number', description: 'Minimum order total' }, + max_total: { type: 'number', description: 'Maximum order total' }, + customer_id: { type: 'number', description: 'Filter by customer ID' }, + status_id: { type: 'number', description: 'Filter by status ID' }, + is_deleted: { type: 'boolean', description: 'Include deleted orders' }, + payment_method: { type: 'string', description: 'Filter by payment method' }, + min_date_created: { type: 'string', description: 'Minimum creation date (RFC 2822 or ISO 8601)' }, + max_date_created: { type: 'string', description: 'Maximum creation date (RFC 2822 or ISO 8601)' }, + min_date_modified: { type: 'string', description: 'Minimum modified date (RFC 2822 or ISO 8601)' }, + max_date_modified: { type: 'string', description: 'Maximum modified date (RFC 2822 or ISO 8601)' }, + }, + }, + handler: async (args: any) => { + const orders = await client.listOrders(args); + return { content: [{ type: 'text', text: JSON.stringify(orders, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_order', + description: 'Get detailed information about a specific order', + inputSchema: { + type: 'object', + properties: { + order_id: { type: 'number', description: 'Order ID' }, + }, + required: ['order_id'], + }, + handler: async (args: any) => { + const order = await client.getOrder(args.order_id); + return { content: [{ type: 'text', text: JSON.stringify(order, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_create_order', + description: 'Create a new order in BigCommerce', + inputSchema: { + type: 'object', + properties: { + customer_id: { type: 'number', description: 'Customer ID' }, + status_id: { type: 'number', description: 'Order status ID (1=Pending, 2=Shipped, etc.)' }, + products: { + type: 'array', + items: { + type: 'object', + properties: { + product_id: { type: 'number' }, + quantity: { type: 'number' }, + }, + }, + description: 'Array of products to add to order', + }, + billing_address: { + type: 'object', + properties: { + first_name: { type: 'string' }, + last_name: { type: 'string' }, + street_1: { type: 'string' }, + city: { type: 'string' }, + state: { type: 'string' }, + zip: { type: 'string' }, + country: { type: 'string' }, + country_iso2: { type: 'string' }, + email: { type: 'string' }, + }, + }, + }, + required: ['customer_id', 'products', 'billing_address'], + }, + handler: async (args: any) => { + const order = await client.createOrder(args); + return { content: [{ type: 'text', text: JSON.stringify(order, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_update_order', + description: 'Update an existing order', + inputSchema: { + type: 'object', + properties: { + order_id: { type: 'number', description: 'Order ID' }, + status_id: { type: 'number', description: 'Order status ID' }, + customer_id: { type: 'number', description: 'Customer ID' }, + staff_notes: { type: 'string', description: 'Internal staff notes' }, + customer_message: { type: 'string', description: 'Message to customer' }, + }, + required: ['order_id'], + }, + handler: async (args: any) => { + const { order_id, ...updateData } = args; + const order = await client.updateOrder(order_id, updateData); + return { content: [{ type: 'text', text: JSON.stringify(order, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_order_products', + description: 'Get all products in an order', + inputSchema: { + type: 'object', + properties: { + order_id: { type: 'number', description: 'Order ID' }, + }, + required: ['order_id'], + }, + handler: async (args: any) => { + const products = await client.getOrderProducts(args.order_id); + return { content: [{ type: 'text', text: JSON.stringify(products, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_order_shipping', + description: 'Get shipping addresses for an order', + inputSchema: { + type: 'object', + properties: { + order_id: { type: 'number', description: 'Order ID' }, + }, + required: ['order_id'], + }, + handler: async (args: any) => { + const shipping = await client.getOrderShippingAddresses(args.order_id); + return { content: [{ type: 'text', text: JSON.stringify(shipping, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_list_order_shipments', + description: 'List all shipments for an order', + inputSchema: { + type: 'object', + properties: { + order_id: { type: 'number', description: 'Order ID' }, + }, + required: ['order_id'], + }, + handler: async (args: any) => { + const shipments = await client.listOrderShipments(args.order_id); + return { content: [{ type: 'text', text: JSON.stringify(shipments, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_create_order_shipment', + description: 'Create a new shipment for an order', + inputSchema: { + type: 'object', + properties: { + order_id: { type: 'number', description: 'Order ID' }, + order_address_id: { type: 'number', description: 'Shipping address ID from order' }, + tracking_number: { type: 'string', description: 'Tracking number' }, + shipping_provider: { type: 'string', description: 'Shipping provider name' }, + tracking_carrier: { type: 'string', description: 'Tracking carrier code' }, + comments: { type: 'string', description: 'Shipment comments' }, + items: { + type: 'array', + items: { + type: 'object', + properties: { + order_product_id: { type: 'number' }, + quantity: { type: 'number' }, + }, + }, + description: 'Array of items to ship', + }, + }, + required: ['order_id', 'order_address_id', 'items'], + }, + handler: async (args: any) => { + const { order_id, ...shipmentData } = args; + const shipment = await client.createOrderShipment(order_id, shipmentData); + return { content: [{ type: 'text', text: JSON.stringify(shipment, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_list_order_refunds', + description: 'List all refunds for an order', + inputSchema: { + type: 'object', + properties: { + order_id: { type: 'number', description: 'Order ID' }, + }, + required: ['order_id'], + }, + handler: async (args: any) => { + const refunds = await client.getOrderRefunds(args.order_id); + return { content: [{ type: 'text', text: JSON.stringify(refunds, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_create_order_refund', + description: 'Create a refund for an order', + inputSchema: { + type: 'object', + properties: { + order_id: { type: 'number', description: 'Order ID' }, + items: { + type: 'array', + items: { + type: 'object', + properties: { + item_type: { + type: 'string', + enum: ['PRODUCT', 'GIFT_WRAPPING', 'SHIPPING', 'HANDLING'], + description: 'Type of item to refund' + }, + item_id: { type: 'number', description: 'ID of the item to refund' }, + quantity: { type: 'number', description: 'Quantity to refund (for products)' }, + reason: { type: 'string', description: 'Refund reason' }, + }, + required: ['item_type', 'item_id'], + }, + description: 'Items to refund', + }, + payments: { + type: 'array', + items: { + type: 'object', + properties: { + provider_id: { type: 'string', description: 'Payment provider ID' }, + amount: { type: 'number', description: 'Amount to refund' }, + offline: { type: 'boolean', description: 'Is offline refund' }, + }, + }, + description: 'Payment methods to refund to', + }, + }, + required: ['order_id', 'items'], + }, + handler: async (args: any) => { + const { order_id, ...refundData } = args; + const refund = await client.createOrderRefund(order_id, refundData); + return { content: [{ type: 'text', text: JSON.stringify(refund, null, 2) }] }; + }, + }, + ]; +} diff --git a/servers/bigcommerce/src/tools/products-tools.ts b/servers/bigcommerce/src/tools/products-tools.ts new file mode 100644 index 0000000..1a32134 --- /dev/null +++ b/servers/bigcommerce/src/tools/products-tools.ts @@ -0,0 +1,242 @@ +/** + * BigCommerce Products Tools + */ + +import { BigCommerceClient } from '../clients/bigcommerce.js'; + +export function registerProductsTools(client: BigCommerceClient) { + return [ + { + name: 'bigcommerce_list_products', + description: 'List all products from BigCommerce store with optional filters', + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'Filter by product name' }, + sku: { type: 'string', description: 'Filter by SKU' }, + price: { type: 'number', description: 'Filter by price' }, + brand_id: { type: 'number', description: 'Filter by brand ID' }, + is_visible: { type: 'boolean', description: 'Filter by visibility' }, + is_featured: { type: 'boolean', description: 'Filter by featured status' }, + }, + }, + handler: async (args: any) => { + const products = await client.listProducts(args); + return { content: [{ type: 'text', text: JSON.stringify(products, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_product', + description: 'Get detailed information about a specific product', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID' }, + }, + required: ['product_id'], + }, + handler: async (args: any) => { + const product = await client.getProduct(args.product_id); + return { content: [{ type: 'text', text: JSON.stringify(product, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_create_product', + description: 'Create a new product in BigCommerce', + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'Product name' }, + type: { + type: 'string', + description: 'Product type (physical or digital)', + enum: ['physical', 'digital'] + }, + sku: { type: 'string', description: 'Stock Keeping Unit' }, + description: { type: 'string', description: 'Product description' }, + price: { type: 'number', description: 'Product price' }, + cost_price: { type: 'number', description: 'Cost price' }, + retail_price: { type: 'number', description: 'Retail price (MSRP)' }, + sale_price: { type: 'number', description: 'Sale price' }, + weight: { type: 'number', description: 'Product weight' }, + width: { type: 'number', description: 'Product width' }, + depth: { type: 'number', description: 'Product depth' }, + height: { type: 'number', description: 'Product height' }, + is_visible: { type: 'boolean', description: 'Is product visible' }, + is_featured: { type: 'boolean', description: 'Is product featured' }, + categories: { type: 'array', items: { type: 'number' }, description: 'Category IDs' }, + brand_id: { type: 'number', description: 'Brand ID' }, + inventory_level: { type: 'number', description: 'Inventory level' }, + inventory_warning_level: { type: 'number', description: 'Low stock warning level' }, + inventory_tracking: { + type: 'string', + description: 'Inventory tracking method', + enum: ['none', 'product', 'variant'] + }, + }, + required: ['name', 'type', 'price'], + }, + handler: async (args: any) => { + const product = await client.createProduct(args); + return { content: [{ type: 'text', text: JSON.stringify(product, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_update_product', + description: 'Update an existing product', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID' }, + name: { type: 'string', description: 'Product name' }, + sku: { type: 'string', description: 'Stock Keeping Unit' }, + description: { type: 'string', description: 'Product description' }, + price: { type: 'number', description: 'Product price' }, + cost_price: { type: 'number', description: 'Cost price' }, + retail_price: { type: 'number', description: 'Retail price (MSRP)' }, + sale_price: { type: 'number', description: 'Sale price' }, + weight: { type: 'number', description: 'Product weight' }, + is_visible: { type: 'boolean', description: 'Is product visible' }, + is_featured: { type: 'boolean', description: 'Is product featured' }, + categories: { type: 'array', items: { type: 'number' }, description: 'Category IDs' }, + brand_id: { type: 'number', description: 'Brand ID' }, + inventory_level: { type: 'number', description: 'Inventory level' }, + inventory_warning_level: { type: 'number', description: 'Low stock warning level' }, + }, + required: ['product_id'], + }, + handler: async (args: any) => { + const { product_id, ...updateData } = args; + const product = await client.updateProduct(product_id, updateData); + return { content: [{ type: 'text', text: JSON.stringify(product, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_delete_product', + description: 'Delete a product from BigCommerce', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID to delete' }, + }, + required: ['product_id'], + }, + handler: async (args: any) => { + await client.deleteProduct(args.product_id); + return { content: [{ type: 'text', text: `Product ${args.product_id} deleted successfully` }] }; + }, + }, + { + name: 'bigcommerce_list_product_variants', + description: 'List all variants for a product', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID' }, + }, + required: ['product_id'], + }, + handler: async (args: any) => { + const variants = await client.listProductVariants(args.product_id); + return { content: [{ type: 'text', text: JSON.stringify(variants, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_create_product_variant', + description: 'Create a new variant for a product', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID' }, + sku: { type: 'string', description: 'Variant SKU' }, + price: { type: 'number', description: 'Variant price' }, + cost_price: { type: 'number', description: 'Variant cost price' }, + weight: { type: 'number', description: 'Variant weight' }, + inventory_level: { type: 'number', description: 'Variant inventory level' }, + option_values: { + type: 'array', + items: { + type: 'object', + properties: { + option_id: { type: 'number' }, + id: { type: 'number' }, + }, + }, + description: 'Option value IDs for this variant', + }, + }, + required: ['product_id', 'option_values'], + }, + handler: async (args: any) => { + const { product_id, ...variantData } = args; + const variant = await client.createProductVariant(product_id, variantData); + return { content: [{ type: 'text', text: JSON.stringify(variant, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_list_product_images', + description: 'List all images for a product', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID' }, + }, + required: ['product_id'], + }, + handler: async (args: any) => { + const images = await client.listProductImages(args.product_id); + return { content: [{ type: 'text', text: JSON.stringify(images, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_upload_product_image', + description: 'Upload or add an image to a product', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID' }, + image_url: { type: 'string', description: 'URL of the image to add' }, + is_thumbnail: { type: 'boolean', description: 'Set as thumbnail image' }, + sort_order: { type: 'number', description: 'Sort order for display' }, + description: { type: 'string', description: 'Image description/alt text' }, + }, + required: ['product_id', 'image_url'], + }, + handler: async (args: any) => { + const { product_id, ...imageData } = args; + const image = await client.createProductImage(product_id, imageData); + return { content: [{ type: 'text', text: JSON.stringify(image, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_list_product_custom_fields', + description: 'List custom fields for a product', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID' }, + }, + required: ['product_id'], + }, + handler: async (args: any) => { + const customFields = await client.listProductCustomFields(args.product_id); + return { content: [{ type: 'text', text: JSON.stringify(customFields, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_list_product_bulk_pricing', + description: 'List bulk pricing rules for a product', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID' }, + }, + required: ['product_id'], + }, + handler: async (args: any) => { + const bulkPricing = await client.listProductBulkPricing(args.product_id); + return { content: [{ type: 'text', text: JSON.stringify(bulkPricing, null, 2) }] }; + }, + }, + ]; +} diff --git a/servers/bigcommerce/src/tools/shipping-tools.ts b/servers/bigcommerce/src/tools/shipping-tools.ts new file mode 100644 index 0000000..ae4c220 --- /dev/null +++ b/servers/bigcommerce/src/tools/shipping-tools.ts @@ -0,0 +1,52 @@ +/** + * BigCommerce Shipping Tools + */ + +import { BigCommerceClient } from '../clients/bigcommerce.js'; + +export function registerShippingTools(client: BigCommerceClient) { + return [ + { + name: 'bigcommerce_list_shipping_zones', + description: 'List all shipping zones', + inputSchema: { + type: 'object', + properties: {}, + }, + handler: async (args: any) => { + const zones = await client.listShippingZones(); + return { content: [{ type: 'text', text: JSON.stringify(zones, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_shipping_zone', + description: 'Get details of a specific shipping zone', + inputSchema: { + type: 'object', + properties: { + zone_id: { type: 'number', description: 'Shipping zone ID' }, + }, + required: ['zone_id'], + }, + handler: async (args: any) => { + const zone = await client.getShippingZone(args.zone_id); + return { content: [{ type: 'text', text: JSON.stringify(zone, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_list_shipping_methods', + description: 'List shipping methods for a specific zone', + inputSchema: { + type: 'object', + properties: { + zone_id: { type: 'number', description: 'Shipping zone ID' }, + }, + required: ['zone_id'], + }, + handler: async (args: any) => { + const methods = await client.listShippingMethods(args.zone_id); + return { content: [{ type: 'text', text: JSON.stringify(methods, null, 2) }] }; + }, + }, + ]; +} diff --git a/servers/bigcommerce/src/tools/store-tools.ts b/servers/bigcommerce/src/tools/store-tools.ts new file mode 100644 index 0000000..bcee572 --- /dev/null +++ b/servers/bigcommerce/src/tools/store-tools.ts @@ -0,0 +1,44 @@ +/** + * BigCommerce Store Tools + */ + +import { BigCommerceClient } from '../clients/bigcommerce.js'; + +export function registerStoreTools(client: BigCommerceClient) { + return [ + { + name: 'bigcommerce_get_store_info', + description: 'Get complete store information including settings, timezone, currency, and features', + inputSchema: { + type: 'object', + properties: {}, + }, + handler: async (args: any) => { + const storeInfo = await client.getStoreInformation(); + return { content: [{ type: 'text', text: JSON.stringify(storeInfo, null, 2) }] }; + }, + }, + { + name: 'bigcommerce_get_store_status', + description: 'Get store status summary including domain, name, and plan information', + inputSchema: { + type: 'object', + properties: {}, + }, + handler: async (args: any) => { + const storeInfo = await client.getStoreInformation(); + const summary = { + name: storeInfo.data?.name, + domain: storeInfo.data?.domain, + secure_url: storeInfo.data?.secure_url, + plan_name: storeInfo.data?.plan_name, + plan_level: storeInfo.data?.plan_level, + currency: storeInfo.data?.currency, + timezone: storeInfo.data?.timezone?.name, + language: storeInfo.data?.language, + }; + return { content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }] }; + }, + }, + ]; +} diff --git a/servers/bigcommerce/src/types/index.ts b/servers/bigcommerce/src/types/index.ts new file mode 100644 index 0000000..b5236ae --- /dev/null +++ b/servers/bigcommerce/src/types/index.ts @@ -0,0 +1,633 @@ +/** + * BigCommerce MCP Server - Type Definitions + */ + +// API Configuration +export interface BigCommerceConfig { + storeHash: string; + accessToken: string; + apiVersion?: 'v2' | 'v3'; +} + +// Product Types +export interface Product { + id: number; + name: string; + type: string; + sku?: string; + description?: string; + price: number; + cost_price?: number; + retail_price?: number; + sale_price?: number; + weight?: number; + width?: number; + depth?: number; + height?: number; + is_visible?: boolean; + is_featured?: boolean; + categories?: number[]; + brand_id?: number; + inventory_level?: number; + inventory_warning_level?: number; + inventory_tracking?: string; + custom_url?: { url: string; is_customized: boolean }; + meta_keywords?: string[]; + meta_description?: string; + page_title?: string; + images?: ProductImage[]; + variants?: ProductVariant[]; + custom_fields?: CustomField[]; +} + +export interface ProductImage { + id?: number; + product_id?: number; + image_url: string; + is_thumbnail?: boolean; + sort_order?: number; + description?: string; +} + +export interface ProductVariant { + id?: number; + product_id?: number; + sku?: string; + price?: number; + cost_price?: number; + weight?: number; + option_values?: OptionValue[]; + inventory_level?: number; +} + +export interface OptionValue { + id: number; + label: string; + option_id: number; + option_display_name: string; +} + +export interface CustomField { + id?: number; + name: string; + value: string; +} + +export interface BulkPricingRule { + id?: number; + quantity_min: number; + quantity_max: number; + type: 'price' | 'percent' | 'fixed'; + amount: number; +} + +// Order Types +export interface Order { + id: number; + customer_id: number; + date_created: string; + date_modified?: string; + date_shipped?: string; + status_id: number; + status: string; + subtotal_ex_tax: number; + subtotal_inc_tax: number; + subtotal_tax: number; + base_shipping_cost: number; + shipping_cost_ex_tax: number; + shipping_cost_inc_tax: number; + shipping_cost_tax: number; + base_handling_cost: number; + handling_cost_ex_tax: number; + handling_cost_inc_tax: number; + handling_cost_tax: number; + base_wrapping_cost: number; + wrapping_cost_ex_tax: number; + wrapping_cost_inc_tax: number; + wrapping_cost_tax: number; + total_ex_tax: number; + total_inc_tax: number; + total_tax: number; + items_total: number; + items_shipped: number; + payment_method: string; + payment_provider_id?: string; + refunded_amount?: number; + order_is_digital: boolean; + store_credit_amount?: number; + gift_certificate_amount?: number; + ip_address?: string; + geoip_country?: string; + currency_code: string; + currency_exchange_rate: number; + default_currency_code: string; + coupon_discount?: number; + shipping_address_count: number; + is_deleted: boolean; + billing_address?: Address; + products?: OrderProduct[]; + shipping_addresses?: ShippingAddress[]; + coupons?: Coupon[]; +} + +export interface OrderProduct { + id: number; + order_id: number; + product_id: number; + name: string; + sku?: string; + type: string; + base_price: number; + price_ex_tax: number; + price_inc_tax: number; + price_tax: number; + quantity: number; + total_ex_tax: number; + total_inc_tax: number; + total_tax: number; + weight?: number; + is_refunded?: boolean; + refund_amount?: number; +} + +export interface Shipment { + id?: number; + order_id: number; + customer_id?: number; + order_address_id: number; + date_created?: string; + tracking_number?: string; + shipping_method?: string; + shipping_provider?: string; + tracking_carrier?: string; + comments?: string; + items: ShipmentItem[]; +} + +export interface ShipmentItem { + order_product_id: number; + quantity: number; +} + +export interface Refund { + id?: number; + order_id: number; + created?: string; + reason?: string; + total_amount: number; + total_tax: number; + items: RefundItem[]; + payments?: RefundPayment[]; +} + +export interface RefundItem { + item_type: 'PRODUCT' | 'GIFT_WRAPPING' | 'SHIPPING' | 'HANDLING'; + item_id: number; + quantity?: number; + reason?: string; + requested_amount?: number; +} + +export interface RefundPayment { + provider_id: string; + amount: number; + offline?: boolean; +} + +// Customer Types +export interface Customer { + id?: number; + company?: string; + first_name: string; + last_name: string; + email: string; + phone?: string; + registration_ip_address?: string; + customer_group_id?: number; + notes?: string; + tax_exempt_category?: string; + date_created?: string; + date_modified?: string; + accepts_product_review_abandoned_cart_emails?: boolean; + store_credit_amounts?: StoreCredit[]; + addresses?: Address[]; + form_fields?: FormField[]; + authentication?: CustomerAuthentication; +} + +export interface Address { + id?: number; + customer_id?: number; + first_name: string; + last_name: string; + company?: string; + address1: string; + address2?: string; + city: string; + state_or_province: string; + postal_code: string; + country_code: string; + phone?: string; + address_type?: 'residential' | 'commercial'; +} + +export interface StoreCredit { + amount: number; +} + +export interface FormField { + name: string; + value: string | string[]; +} + +export interface CustomerAuthentication { + force_password_reset?: boolean; + new_password?: string; +} + +export interface CustomerGroup { + id?: number; + name: string; + is_default?: boolean; + category_access?: { + type: 'all' | 'specific' | 'none'; + categories?: number[]; + }; + discount_rules?: DiscountRule[]; +} + +export interface DiscountRule { + type: 'price_list' | 'all_products' | 'product_category'; + method: 'percent' | 'fixed'; + amount: number; +} + +// Category Types +export interface Category { + id?: number; + parent_id: number; + name: string; + description?: string; + views?: number; + sort_order?: number; + page_title?: string; + meta_keywords?: string[]; + meta_description?: string; + layout_file?: string; + image_url?: string; + is_visible?: boolean; + search_keywords?: string; + default_product_sort?: string; + custom_url?: { url: string; is_customized: boolean }; +} + +export interface CategoryTree { + id: number; + parent_id: number; + name: string; + is_visible: boolean; + url: string; + children?: CategoryTree[]; +} + +// Brand Types +export interface Brand { + id?: number; + name: string; + page_title?: string; + meta_keywords?: string[]; + meta_description?: string; + image_url?: string; + search_keywords?: string; + custom_url?: { url: string; is_customized: boolean }; +} + +// Coupon Types +export interface Coupon { + id?: number; + name: string; + type: 'per_item_discount' | 'per_total_discount' | 'shipping_discount' | 'free_shipping' | 'percentage_discount'; + code: string; + amount: number; + min_purchase?: number; + expires?: string; + enabled?: boolean; + applies_to?: { + entity: 'categories' | 'products'; + ids: number[]; + }; + num_uses?: number; + max_uses?: number; + max_uses_per_customer?: number; + restricted_to?: { + countries?: string[]; + }; + shipping_methods?: string[]; +} + +// Store Types +export interface StoreInformation { + id?: string; + domain: string; + secure_url: string; + name: string; + first_name?: string; + last_name?: string; + address: string; + country: string; + country_code: string; + phone: string; + admin_email: string; + order_email: string; + timezone: { + name: string; + raw_offset: number; + dst_offset: number; + dst_correction: boolean; + date_format: { + display: string; + export: string; + extended_display: string; + }; + }; + language: string; + currency: string; + currency_symbol: string; + decimal_separator: string; + thousands_separator: string; + decimal_places: number; + currency_symbol_location: string; + weight_units: string; + dimension_units: string; + dimension_decimal_places: number; + dimension_decimal_token: string; + dimension_thousands_token: string; + plan_name?: string; + plan_level?: string; + industry?: string; + logo?: { + url: string; + }; + is_price_entered_with_tax?: boolean; + active_comparison_modules?: string[]; + features?: { + stencil_enabled: boolean; + sitewidehttps_enabled: boolean; + facebook_catalog_id?: string; + }; +} + +// Channel Types +export interface Channel { + id?: number; + name: string; + type: 'storefront' | 'pos' | 'marketplace' | 'marketing'; + platform?: string; + external_id?: string; + status: 'active' | 'inactive' | 'connected' | 'disconnected' | 'archived'; + is_listable_from_ui?: boolean; + is_visible?: boolean; + date_created?: string; + date_modified?: string; + config_meta?: { + app?: { + id: number; + }; + }; +} + +export interface ChannelListing { + product_id: number; + channel_id: number; + state: 'active' | 'disabled'; + variants?: { + variant_id: number; + product_id: number; + state: 'active' | 'disabled'; + }[]; +} + +// Cart Types +export interface Cart { + id?: string; + customer_id?: number; + channel_id?: number; + email?: string; + currency?: { + code: string; + }; + tax_included?: boolean; + base_amount?: number; + discount_amount?: number; + cart_amount?: number; + line_items?: { + physical_items?: CartLineItem[]; + digital_items?: CartLineItem[]; + gift_certificates?: GiftCertificate[]; + custom_items?: CustomItem[]; + }; + created_time?: string; + updated_time?: string; + locale?: string; +} + +export interface CartLineItem { + id?: string; + parent_id?: string; + variant_id: number; + product_id: number; + sku?: string; + name?: string; + url?: string; + quantity: number; + taxable?: boolean; + image_url?: string; + discounts?: Discount[]; + coupons?: Discount[]; + discount_amount?: number; + coupon_amount?: number; + list_price?: number; + sale_price?: number; + extended_list_price?: number; + extended_sale_price?: number; + is_require_shipping?: boolean; + is_mutable?: boolean; +} + +export interface GiftCertificate { + id?: string; + name: string; + theme: string; + amount: number; + taxable?: boolean; + sender: { + name: string; + email: string; + }; + recipient: { + name: string; + email: string; + }; + message?: string; +} + +export interface CustomItem { + id?: string; + sku?: string; + name: string; + quantity: number; + list_price: number; +} + +export interface Discount { + id: string; + discounted_amount: number; +} + +// Content Types +export interface Page { + id?: number; + channel_id?: number; + name: string; + is_visible?: boolean; + parent_id?: number; + sort_order?: number; + type: 'page' | 'raw' | 'contact_form' | 'feed' | 'link' | 'blog'; + is_homepage?: boolean; + is_customers_only?: boolean; + email?: string; + meta_title?: string; + meta_keywords?: string; + meta_description?: string; + search_keywords?: string; + url?: string; + body?: string; + feed?: string; + link?: string; +} + +export interface BlogPost { + id?: number; + title: string; + url?: string; + preview_url?: string; + body: string; + summary?: string; + is_published?: boolean; + published_date?: { + timezone_type: number; + date: string; + }; + meta_description?: string; + meta_keywords?: string; + author?: string; + thumbnail_path?: string; + tags?: string[]; +} + +// Shipping Types +export interface ShippingZone { + id?: number; + name: string; + type: 'country' | 'state' | 'zip' | 'global'; + locations?: ShippingLocation[]; + enabled?: boolean; + handling_fees?: { + fixed_surcharge?: number; + display_separately?: boolean; + }; +} + +export interface ShippingLocation { + zip?: string; + country_iso2?: string; + state_iso2?: string; +} + +export interface ShippingMethod { + id?: number; + name: string; + type: 'perorder' | 'peritem' | 'weight' | 'total' | 'auspost' | 'canadapost' | 'usps' | 'fedex' | 'ups' | 'upsready' | 'shipperhq' | 'endicia' | 'shippereasy'; + settings?: { + rate?: number; + }; + enabled?: boolean; + handling_fees?: { + fixed_surcharge?: number; + display_separately?: boolean; + }; + is_fallback?: boolean; + zone_id?: number; +} + +// Analytics Types +export interface StoreAnalytics { + total_orders?: number; + total_revenue?: number; + average_order_value?: number; + conversion_rate?: number; + total_customers?: number; + new_customers?: number; + returning_customers?: number; + period?: { + start_date: string; + end_date: string; + }; +} + +export interface ProductAnalytics { + product_id: number; + views?: number; + orders?: number; + revenue?: number; + conversion_rate?: number; + average_rating?: number; + reviews_count?: number; +} + +// Shipping Address (for Orders) +export interface ShippingAddress extends Address { + order_id?: number; + items_total?: number; + items_shipped?: number; + shipping_method?: string; + base_cost?: number; + cost_ex_tax?: number; + cost_inc_tax?: number; + cost_tax?: number; +} + +// API Response Types +export interface PaginatedResponse { + data: T[]; + meta?: { + pagination?: { + total: number; + count: number; + per_page: number; + current_page: number; + total_pages: number; + links?: { + previous?: string; + current: string; + next?: string; + }; + }; + }; +} + +export interface ApiResponse { + data?: T; + meta?: any; + error?: string; + errors?: any; +} + +// Error Types +export interface BigCommerceError { + status: number; + title: string; + type?: string; + detail?: string; + errors?: Record; +} diff --git a/servers/bigcommerce/src/ui/react-app/analytics-dashboard/App.tsx b/servers/bigcommerce/src/ui/react-app/analytics-dashboard/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/analytics-dashboard/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/blog-manager/App.tsx b/servers/bigcommerce/src/ui/react-app/blog-manager/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/blog-manager/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/brand-manager/App.tsx b/servers/bigcommerce/src/ui/react-app/brand-manager/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/brand-manager/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/cart-viewer/App.tsx b/servers/bigcommerce/src/ui/react-app/cart-viewer/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/cart-viewer/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/category-tree/App.tsx b/servers/bigcommerce/src/ui/react-app/category-tree/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/category-tree/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/channel-manager/App.tsx b/servers/bigcommerce/src/ui/react-app/channel-manager/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/channel-manager/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/content-pages/App.tsx b/servers/bigcommerce/src/ui/react-app/content-pages/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/content-pages/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/coupon-manager/App.tsx b/servers/bigcommerce/src/ui/react-app/coupon-manager/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/coupon-manager/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/customer-detail/App.tsx b/servers/bigcommerce/src/ui/react-app/customer-detail/App.tsx new file mode 100644 index 0000000..5b4190a --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/customer-detail/App.tsx @@ -0,0 +1,174 @@ +import React, { useState } from 'react'; + +interface Customer { + id: number; + name: string; + email: string; + phone: string; + company: string; + group: string; + totalOrders: number; + totalSpent: number; + avgOrderValue: number; + addresses: { type: string; address: string; }[]; +} + +export default function CustomerDetail() { + const [customer] = useState({ + id: 501, + name: 'John Doe', + email: 'john.doe@example.com', + phone: '(555) 123-4567', + company: 'Acme Corp', + group: 'Wholesale', + totalOrders: 24, + totalSpent: 4567.89, + avgOrderValue: 190.33, + addresses: [ + { type: 'Billing', address: '123 Main St, City, ST 12345' }, + { type: 'Shipping', address: '456 Oak Ave, Town, ST 67890' }, + ], + }); + + return ( +
+

Customer Details

+ +
+
+

Contact Information

+
+
+
Name
+
{customer.name}
+
+
+
Email
+
{customer.email}
+
+
+
Phone
+
{customer.phone}
+
+
+
Company
+
{customer.company}
+
+
+
+ +
+

Customer Stats

+
+
+
{customer.totalOrders}
+
Total Orders
+
+
+
${customer.totalSpent.toFixed(2)}
+
Total Spent
+
+
+
${customer.avgOrderValue.toFixed(2)}
+
Avg Order Value
+
+
+
+ +
+

Addresses

+ {customer.addresses.map((addr, i) => ( +
+
{addr.type}
+
{addr.address}
+
+ ))} +
+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + grid: { + display: 'grid', + gap: '1.5rem', + }, + section: { + backgroundColor: '#1f2937', + padding: '1.5rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + sectionTitle: { + fontSize: '1.25rem', + fontWeight: '600', + marginBottom: '1rem', + color: '#f9fafb', + }, + infoGrid: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', + gap: '1rem', + }, + infoItem: { + display: 'flex', + flexDirection: 'column', + gap: '0.25rem', + }, + label: { + fontSize: '0.875rem', + color: '#9ca3af', + }, + value: { + fontSize: '1.125rem', + fontWeight: '500', + color: '#f9fafb', + }, + statsGrid: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', + gap: '1rem', + }, + stat: { + textAlign: 'center', + }, + statValue: { + fontSize: '2rem', + fontWeight: 'bold', + color: '#3b82f6', + }, + statLabel: { + fontSize: '0.875rem', + color: '#9ca3af', + marginTop: '0.5rem', + }, + addressCard: { + padding: '1rem', + backgroundColor: '#374151', + borderRadius: '0.375rem', + marginBottom: '1rem', + }, + addressType: { + fontSize: '0.875rem', + fontWeight: '600', + color: '#3b82f6', + marginBottom: '0.5rem', + }, + addressText: { + color: '#d1d5db', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/customer-grid/App.tsx b/servers/bigcommerce/src/ui/react-app/customer-grid/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/customer-grid/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/inventory-tracker/App.tsx b/servers/bigcommerce/src/ui/react-app/inventory-tracker/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/inventory-tracker/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/order-dashboard/App.tsx b/servers/bigcommerce/src/ui/react-app/order-dashboard/App.tsx new file mode 100644 index 0000000..25b4ae5 --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/order-dashboard/App.tsx @@ -0,0 +1,167 @@ +import React, { useState } from 'react'; + +interface Order { + id: number; + customer: string; + date: string; + status: string; + total: number; + items: number; +} + +export default function OrderDashboard() { + const [orders] = useState([ + { id: 1001, customer: 'John Doe', date: '2024-01-15', status: 'Completed', total: 159.99, items: 3 }, + { id: 1002, customer: 'Jane Smith', date: '2024-01-16', status: 'Processing', total: 89.50, items: 2 }, + { id: 1003, customer: 'Bob Johnson', date: '2024-01-16', status: 'Shipped', total: 249.99, items: 5 }, + { id: 1004, customer: 'Alice Brown', date: '2024-01-17', status: 'Pending', total: 45.00, items: 1 }, + ]); + + const totalRevenue = orders.reduce((sum, order) => sum + order.total, 0); + const avgOrderValue = totalRevenue / orders.length; + const statusCounts = orders.reduce((acc, order) => { + acc[order.status] = (acc[order.status] || 0) + 1; + return acc; + }, {} as Record); + + const getStatusColor = (status: string) => { + const colors: Record = { + Completed: '#10b981', + Processing: '#3b82f6', + Shipped: '#8b5cf6', + Pending: '#f59e0b', + Cancelled: '#ef4444', + }; + return colors[status] || '#6b7280'; + }; + + return ( +
+

Order Dashboard

+ +
+
+
{orders.length}
+
Total Orders
+
+
+
${totalRevenue.toFixed(2)}
+
Total Revenue
+
+
+
${avgOrderValue.toFixed(2)}
+
Avg Order Value
+
+
+
{statusCounts['Pending'] || 0}
+
Pending Orders
+
+
+ +
+ + + + + + + + + + + + + {orders.map(order => ( + + + + + + + + + ))} + +
Order IDCustomerDateItemsTotalStatus
#{order.id}{order.customer}{order.date}{order.items}${order.total.toFixed(2)} + + {order.status} + +
+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + statsGrid: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', + gap: '1rem', + marginBottom: '2rem', + }, + statCard: { + backgroundColor: '#1f2937', + padding: '1.5rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + statValue: { + fontSize: '2rem', + fontWeight: 'bold', + color: '#3b82f6', + marginBottom: '0.5rem', + }, + statLabel: { + fontSize: '0.875rem', + color: '#9ca3af', + }, + tableContainer: { + backgroundColor: '#1f2937', + borderRadius: '0.5rem', + overflow: 'hidden', + border: '1px solid #374151', + }, + table: { + width: '100%', + borderCollapse: 'collapse', + }, + th: { + padding: '1rem', + textAlign: 'left', + backgroundColor: '#374151', + color: '#f9fafb', + fontWeight: '600', + fontSize: '0.875rem', + textTransform: 'uppercase', + }, + tr: { + borderBottom: '1px solid #374151', + }, + td: { + padding: '1rem', + color: '#d1d5db', + }, + badge: { + padding: '0.25rem 0.75rem', + borderRadius: '9999px', + fontSize: '0.75rem', + fontWeight: '500', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/order-detail/App.tsx b/servers/bigcommerce/src/ui/react-app/order-detail/App.tsx new file mode 100644 index 0000000..60707d4 --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/order-detail/App.tsx @@ -0,0 +1,172 @@ +import React, { useState } from 'react'; + +interface OrderDetail { + id: number; + customer: { name: string; email: string; }; + date: string; + status: string; + items: { name: string; quantity: number; price: number; }[]; + subtotal: number; + tax: number; + shipping: number; + total: number; + shipping_address: string; + payment_method: string; +} + +export default function OrderDetail() { + const [order] = useState({ + id: 1001, + customer: { name: 'John Doe', email: 'john@example.com' }, + date: '2024-01-15 10:30 AM', + status: 'Completed', + items: [ + { name: 'Widget Pro', quantity: 2, price: 49.99 }, + { name: 'Gadget Plus', quantity: 1, price: 79.99 }, + ], + subtotal: 179.97, + tax: 14.40, + shipping: 9.99, + total: 204.36, + shipping_address: '123 Main St, City, ST 12345', + payment_method: 'Visa ****1234', + }); + + return ( +
+
+

Order #{order.id}

+ {order.status} +
+ +
+
+

Customer Information

+
+ Name: {order.customer.name} +
+
+ Email: {order.customer.email} +
+
+ Order Date: {order.date} +
+
+ +
+

Shipping & Payment

+
+ Address: {order.shipping_address} +
+
+ Payment: {order.payment_method} +
+
+ +
+

Order Items

+ {order.items.map((item, i) => ( +
+ {item.name} × {item.quantity} + ${(item.price * item.quantity).toFixed(2)} +
+ ))} +
+ +
+

Order Total

+
+ Subtotal: + ${order.subtotal.toFixed(2)} +
+
+ Tax: + ${order.tax.toFixed(2)} +
+
+ Shipping: + ${order.shipping.toFixed(2)} +
+
+ Total: + ${order.total.toFixed(2)} +
+
+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + header: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: '2rem', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + color: '#f9fafb', + }, + status: { + padding: '0.5rem 1rem', + backgroundColor: '#10b98133', + color: '#10b981', + borderRadius: '0.375rem', + fontSize: '0.875rem', + fontWeight: '500', + }, + grid: { + display: 'grid', + gap: '1.5rem', + }, + section: { + backgroundColor: '#1f2937', + padding: '1.5rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + sectionTitle: { + fontSize: '1.25rem', + fontWeight: '600', + marginBottom: '1rem', + color: '#f9fafb', + }, + infoItem: { + marginBottom: '0.75rem', + color: '#d1d5db', + }, + label: { + color: '#9ca3af', + fontWeight: '500', + }, + itemRow: { + display: 'flex', + justifyContent: 'space-between', + padding: '0.75rem 0', + borderBottom: '1px solid #374151', + color: '#d1d5db', + }, + totalRow: { + display: 'flex', + justifyContent: 'space-between', + padding: '0.5rem 0', + color: '#d1d5db', + }, + grandTotal: { + fontSize: '1.25rem', + fontWeight: 'bold', + color: '#3b82f6', + borderTop: '2px solid #374151', + paddingTop: '1rem', + marginTop: '0.5rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/order-grid/App.tsx b/servers/bigcommerce/src/ui/react-app/order-grid/App.tsx new file mode 100644 index 0000000..7f6bc81 --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/order-grid/App.tsx @@ -0,0 +1,142 @@ +import React, { useState } from 'react'; + +interface Order { + id: number; + customer: string; + date: string; + status: string; + total: number; + items: number; +} + +export default function OrderGrid() { + const [orders] = useState([ + { id: 1001, customer: 'John Doe', date: '2024-01-15', status: 'Completed', total: 159.99, items: 3 }, + { id: 1002, customer: 'Jane Smith', date: '2024-01-16', status: 'Processing', total: 89.50, items: 2 }, + { id: 1003, customer: 'Bob Johnson', date: '2024-01-16', status: 'Shipped', total: 249.99, items: 5 }, + { id: 1004, customer: 'Alice Brown', date: '2024-01-17', status: 'Pending', total: 45.00, items: 1 }, + { id: 1005, customer: 'Charlie Wilson', date: '2024-01-17', status: 'Completed', total: 189.99, items: 4 }, + { id: 1006, customer: 'Diana Martinez', date: '2024-01-18', status: 'Processing', total: 129.50, items: 3 }, + ]); + + const getStatusColor = (status: string) => { + const colors: Record = { + Completed: '#10b981', + Processing: '#3b82f6', + Shipped: '#8b5cf6', + Pending: '#f59e0b', + }; + return colors[status] || '#6b7280'; + }; + + return ( +
+

Order Grid

+ +
+ {orders.map(order => ( +
+
+ #{order.id} + + {order.status} + +
+
+
+ Customer: + {order.customer} +
+
+ Date: + {order.date} +
+
+ Items: + {order.items} +
+
+
+ ${order.total.toFixed(2)} +
+
+ ))} +
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + grid: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', + gap: '1.5rem', + }, + card: { + backgroundColor: '#1f2937', + borderRadius: '0.5rem', + border: '1px solid #374151', + overflow: 'hidden', + }, + cardHeader: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + padding: '1rem', + backgroundColor: '#374151', + }, + orderId: { + fontWeight: 'bold', + fontSize: '1.125rem', + color: '#f9fafb', + }, + badge: { + padding: '0.25rem 0.75rem', + borderRadius: '9999px', + fontSize: '0.75rem', + fontWeight: '500', + }, + cardBody: { + padding: '1rem', + }, + infoRow: { + display: 'flex', + justifyContent: 'space-between', + marginBottom: '0.5rem', + }, + label: { + color: '#9ca3af', + fontSize: '0.875rem', + }, + value: { + color: '#d1d5db', + fontSize: '0.875rem', + }, + cardFooter: { + padding: '1rem', + borderTop: '1px solid #374151', + textAlign: 'right', + }, + total: { + fontSize: '1.5rem', + fontWeight: 'bold', + color: '#3b82f6', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/product-dashboard/App.tsx b/servers/bigcommerce/src/ui/react-app/product-dashboard/App.tsx new file mode 100644 index 0000000..f248539 --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/product-dashboard/App.tsx @@ -0,0 +1,174 @@ +import React, { useState, useEffect } from 'react'; + +interface Product { + id: number; + name: string; + sku: string; + price: number; + inventory_level: number; + is_visible: boolean; +} + +export default function ProductDashboard() { + const [products, setProducts] = useState([]); + const [stats, setStats] = useState({ + total: 0, + visible: 0, + lowStock: 0, + avgPrice: 0, + }); + + useEffect(() => { + // Client-side state initialization + // In production, fetch from BigCommerce API + const mockProducts: Product[] = [ + { id: 1, name: 'Product A', sku: 'SKU-001', price: 29.99, inventory_level: 50, is_visible: true }, + { id: 2, name: 'Product B', sku: 'SKU-002', price: 49.99, inventory_level: 5, is_visible: true }, + { id: 3, name: 'Product C', sku: 'SKU-003', price: 19.99, inventory_level: 100, is_visible: false }, + ]; + + setProducts(mockProducts); + + const total = mockProducts.length; + const visible = mockProducts.filter(p => p.is_visible).length; + const lowStock = mockProducts.filter(p => p.inventory_level < 10).length; + const avgPrice = mockProducts.reduce((sum, p) => sum + p.price, 0) / total; + + setStats({ total, visible, lowStock, avgPrice }); + }, []); + + return ( +
+

Product Dashboard

+ +
+
+
{stats.total}
+
Total Products
+
+
+
{stats.visible}
+
Visible
+
+
+
{stats.lowStock}
+
Low Stock
+
+
+
${stats.avgPrice.toFixed(2)}
+
Avg Price
+
+
+ +
+ + + + + + + + + + + + + {products.map(product => ( + + + + + + + + + ))} + +
IDNameSKUPriceStockStatus
{product.id}{product.name}{product.sku}${product.price.toFixed(2)} + {product.inventory_level} + + + {product.is_visible ? 'Visible' : 'Hidden'} + +
+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + statsGrid: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', + gap: '1rem', + marginBottom: '2rem', + }, + statCard: { + backgroundColor: '#1f2937', + padding: '1.5rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + statValue: { + fontSize: '2rem', + fontWeight: 'bold', + color: '#3b82f6', + marginBottom: '0.5rem', + }, + statLabel: { + fontSize: '0.875rem', + color: '#9ca3af', + }, + tableContainer: { + backgroundColor: '#1f2937', + borderRadius: '0.5rem', + overflow: 'hidden', + border: '1px solid #374151', + }, + table: { + width: '100%', + borderCollapse: 'collapse', + }, + th: { + padding: '1rem', + textAlign: 'left', + backgroundColor: '#374151', + color: '#f9fafb', + fontWeight: '600', + fontSize: '0.875rem', + textTransform: 'uppercase', + }, + tr: { + borderBottom: '1px solid #374151', + }, + td: { + padding: '1rem', + color: '#d1d5db', + }, + badge: { + padding: '0.25rem 0.75rem', + borderRadius: '9999px', + fontSize: '0.75rem', + fontWeight: '500', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/product-detail/App.tsx b/servers/bigcommerce/src/ui/react-app/product-detail/App.tsx new file mode 100644 index 0000000..d33e662 --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/product-detail/App.tsx @@ -0,0 +1,215 @@ +import React, { useState } from 'react'; + +interface ProductDetail { + id: number; + name: string; + sku: string; + type: string; + price: number; + cost_price: number; + retail_price: number; + weight: number; + inventory_level: number; + is_visible: boolean; + is_featured: boolean; + description: string; + categories: string[]; + brand: string; +} + +export default function ProductDetail() { + const [product] = useState({ + id: 123, + name: 'Premium Widget Pro', + sku: 'WIDGET-PRO-001', + type: 'physical', + price: 79.99, + cost_price: 45.00, + retail_price: 99.99, + weight: 2.5, + inventory_level: 156, + is_visible: true, + is_featured: true, + description: 'High-quality premium widget with advanced features and durable construction.', + categories: ['Electronics', 'Widgets', 'Premium'], + brand: 'WidgetCo', + }); + + const margin = ((product.price - product.cost_price) / product.price * 100).toFixed(1); + + return ( +
+
+

{product.name}

+
+ {product.is_featured && ( + + Featured + + )} + {product.is_visible && ( + + Visible + + )} +
+
+ +
+
+

Basic Information

+
+
+
Product ID
+
{product.id}
+
+
+
SKU
+
{product.sku}
+
+
+
Type
+
{product.type}
+
+
+
Brand
+
{product.brand}
+
+
+
+ +
+

Pricing

+
+
+
Sale Price
+
${product.price.toFixed(2)}
+
+
+
Cost Price
+
${product.cost_price.toFixed(2)}
+
+
+
Retail Price (MSRP)
+
${product.retail_price.toFixed(2)}
+
+
+
Profit Margin
+
{margin}%
+
+
+
+ +
+

Inventory & Shipping

+
+
+
Stock Level
+
{product.inventory_level} units
+
+
+
Weight
+
{product.weight} lbs
+
+
+
+ +
+

Description

+

{product.description}

+
+ +
+

Categories

+
+ {product.categories.map((cat, i) => ( + {cat} + ))} +
+
+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + header: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: '2rem', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + color: '#f9fafb', + }, + badges: { + display: 'flex', + gap: '0.5rem', + }, + badge: { + padding: '0.5rem 1rem', + borderRadius: '0.375rem', + fontSize: '0.875rem', + fontWeight: '500', + }, + grid: { + display: 'grid', + gap: '1.5rem', + }, + section: { + backgroundColor: '#1f2937', + padding: '1.5rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + sectionTitle: { + fontSize: '1.25rem', + fontWeight: '600', + marginBottom: '1rem', + color: '#f9fafb', + }, + infoGrid: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', + gap: '1rem', + }, + infoItem: { + display: 'flex', + flexDirection: 'column', + gap: '0.25rem', + }, + infoLabel: { + fontSize: '0.875rem', + color: '#9ca3af', + }, + infoValue: { + fontSize: '1.125rem', + fontWeight: '500', + color: '#f9fafb', + }, + description: { + color: '#d1d5db', + lineHeight: '1.6', + }, + tagContainer: { + display: 'flex', + flexWrap: 'wrap', + gap: '0.5rem', + }, + tag: { + padding: '0.375rem 0.75rem', + backgroundColor: '#374151', + color: '#d1d5db', + borderRadius: '0.25rem', + fontSize: '0.875rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/product-grid/App.tsx b/servers/bigcommerce/src/ui/react-app/product-grid/App.tsx new file mode 100644 index 0000000..e14bccf --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/product-grid/App.tsx @@ -0,0 +1,117 @@ +import React, { useState } from 'react'; + +interface Product { + id: number; + name: string; + sku: string; + price: number; + image: string; + stock: number; +} + +export default function ProductGrid() { + const [products] = useState([ + { id: 1, name: 'Widget A', sku: 'WID-001', price: 29.99, image: '📦', stock: 50 }, + { id: 2, name: 'Widget B', sku: 'WID-002', price: 49.99, image: '📦', stock: 30 }, + { id: 3, name: 'Gadget Pro', sku: 'GAD-001', price: 79.99, image: '⚙️', stock: 15 }, + { id: 4, name: 'Tool Set', sku: 'TOL-001', price: 99.99, image: '🔧', stock: 8 }, + { id: 5, name: 'Premium Pack', sku: 'PRE-001', price: 149.99, image: '🎁', stock: 25 }, + { id: 6, name: 'Starter Kit', sku: 'STA-001', price: 39.99, image: '📦', stock: 100 }, + ]); + + return ( +
+

Product Grid

+ +
+ {products.map(product => ( +
+
+
{product.image}
+
+
+

{product.name}

+

{product.sku}

+
+ ${product.price.toFixed(2)} + + {product.stock} in stock + +
+
+
+ ))} +
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + grid: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', + gap: '1.5rem', + }, + card: { + backgroundColor: '#1f2937', + borderRadius: '0.5rem', + overflow: 'hidden', + border: '1px solid #374151', + transition: 'transform 0.2s', + cursor: 'pointer', + }, + imageContainer: { + backgroundColor: '#374151', + padding: '2rem', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + image: { + fontSize: '4rem', + }, + content: { + padding: '1rem', + }, + productName: { + fontSize: '1.125rem', + fontWeight: '600', + marginBottom: '0.25rem', + color: '#f9fafb', + }, + sku: { + fontSize: '0.875rem', + color: '#9ca3af', + marginBottom: '1rem', + }, + footer: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }, + price: { + fontSize: '1.25rem', + fontWeight: 'bold', + color: '#3b82f6', + }, + stock: { + fontSize: '0.75rem', + fontWeight: '500', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/revenue-dashboard/App.tsx b/servers/bigcommerce/src/ui/react-app/revenue-dashboard/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/revenue-dashboard/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/shipping-manager/App.tsx b/servers/bigcommerce/src/ui/react-app/shipping-manager/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/shipping-manager/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/src/ui/react-app/store-overview/App.tsx b/servers/bigcommerce/src/ui/react-app/store-overview/App.tsx new file mode 100644 index 0000000..501ea2c --- /dev/null +++ b/servers/bigcommerce/src/ui/react-app/store-overview/App.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +export default function App() { + const appName = '${app}'.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + return ( +
+

{appName}

+
+

+ BigCommerce ${app.replace(/-/g, ' ')} interface +

+
+
+ ); +} + +const styles: Record = { + container: { + padding: '2rem', + backgroundColor: '#111827', + minHeight: '100vh', + color: '#f9fafb', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + title: { + fontSize: '2rem', + fontWeight: 'bold', + marginBottom: '2rem', + color: '#f9fafb', + }, + content: { + backgroundColor: '#1f2937', + padding: '2rem', + borderRadius: '0.5rem', + border: '1px solid #374151', + }, + description: { + color: '#d1d5db', + fontSize: '1.125rem', + }, +}; diff --git a/servers/bigcommerce/tsconfig.json b/servers/bigcommerce/tsconfig.json deleted file mode 100644 index de6431e..0000000 --- a/servers/bigcommerce/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/servers/brevo/README.md b/servers/brevo/README.md new file mode 100644 index 0000000..76ba7bc --- /dev/null +++ b/servers/brevo/README.md @@ -0,0 +1,235 @@ +> **🚀 Don't want to self-host?** [Join the waitlist for our fully managed solution →](https://mcpengage.com/brevo) +> +> Zero setup. Zero maintenance. Just connect and automate. + +--- + +# 🚀 Brevo MCP Server — 2026 Complete Version + +## 💡 What This Unlocks + +**This MCP server gives AI direct access to your entire Brevo email and SMS marketing workspace.** Instead of clicking through interfaces, you just *tell* it what you need. + +Brevo (formerly Sendinblue) is a complete email and SMS marketing platform used by 500,000+ businesses worldwide. This MCP server brings all its power into your AI workflow. + +### 🎯 Email/SMS Marketing Power Moves + +Stop context-switching between Claude and Brevo. The AI can directly control your campaigns: + +1. **Emergency campaign deployment** — "Send an urgent email about the service outage to all active customers, skip the test list" +2. **Smart segmentation** — "Export all contacts who opened our last 3 campaigns but didn't convert, then create a re-engagement campaign" +3. **Multi-channel orchestration** — "Check email deliverability for campaign #12345, if bounce rate is over 5%, send an SMS follow-up to non-openers" +4. **Template-driven automation** — "List all active email templates, use template #8 to send welcome emails to the 50 contacts added this week" +5. **Real-time list hygiene** — "Find all contacts with invalid emails from yesterday's imports, add them to the cleanup list, and notify me with stats" + +### 🔗 The Real Power: Combining Tools + +AI can chain multiple Brevo operations together: + +- Query campaign metrics → Segment by engagement → Create targeted follow-up → Schedule SMS backup +- Import contacts → Validate emails → Auto-assign to lists → Trigger welcome sequence +- Analyze template performance → Clone best performers → Customize for new segments → Deploy and track + +## 📦 What's Inside + +**8 powerful API tools** covering Brevo's email and SMS marketing platform: + +1. **send_email** — Send transactional emails with templates, attachments, and tracking +2. **list_contacts** — Query and filter your contact database with pagination +3. **add_contact** — Create contacts with custom attributes and list assignments +4. **update_contact** — Modify contact data, list memberships, and preferences +5. **list_campaigns** — Browse email campaigns by type, status, and date +6. **create_campaign** — Build and schedule email campaigns programmatically +7. **send_sms** — Send transactional SMS with delivery tracking +8. **list_templates** — Access your email template library + +All with proper error handling, automatic authentication, and TypeScript types. + +**API Foundation:** [Brevo API v3](https://developers.brevo.com/reference/getting-started-1) (REST) + +## 🚀 Quick Start + +### Option 1: Claude Desktop (Local) + +1. **Clone and build:** + ```bash + git clone https://github.com/BusyBee3333/Brevo-MCP-2026-Complete.git + cd brevo-mcp-2026-complete + npm install + npm run build + ``` + +2. **Get your Brevo API key:** + - Log into [Brevo](https://app.brevo.com/) + - Go to **Settings → SMTP & API → API Keys** + - Create a new API key (v3) with email and SMS permissions + - Copy the key (you'll only see it once) + +3. **Configure Claude Desktop:** + + On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` + + On Windows: `%APPDATA%\Claude\claude_desktop_config.json` + + ```json + { + "mcpServers": { + "brevo": { + "command": "node", + "args": ["/ABSOLUTE/PATH/TO/brevo-mcp-2026-complete/dist/index.js"], + "env": { + "BREVO_API_KEY": "xkeysib-abc123..." + } + } + } + } + ``` + +4. **Restart Claude Desktop** + +### Option 2: Deploy to Railway + +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/brevo-mcp) + +1. Click the button above +2. Set `BREVO_API_KEY` in Railway dashboard +3. Use the Railway URL as your MCP server endpoint + +### Option 3: Docker + +```bash +docker build -t brevo-mcp . +docker run -p 3000:3000 \ + -e BREVO_API_KEY=xkeysib-abc123... \ + brevo-mcp +``` + +## 🔐 Authentication + +**Brevo uses API key authentication** (v3 API): + +- **Header:** `api-key: YOUR_KEY` +- **Format:** `xkeysib-...` (starts with xkeysib-) +- **Permissions:** Email campaigns, Contacts, SMS (depending on your plan) +- **Rate limits:** 300 calls/minute on free plans, higher on paid + +Get your API key at: https://app.brevo.com/settings/keys/api + +The MCP server handles authentication automatically—just set `BREVO_API_KEY`. + +## 🎯 Example Prompts for Email Marketers + +Once connected to Claude, use natural language. Here are real email marketing workflows: + +### Campaign Management +- *"List all email campaigns from the last 30 days that are still in draft status"* +- *"Create a new campaign called 'Spring Sale 2026' with template #45, targeting list #12"* +- *"Show me all campaigns with 'webinar' in the name scheduled for this month"* + +### Contact Operations +- *"Add these 5 contacts to list #8: [paste CSV data]"* +- *"Find all contacts with Gmail addresses who signed up this week"* +- *"Update contact jane@example.com: set FIRSTNAME to Jane, add to VIP list"* + +### Multi-Channel Workflows +- *"Send a welcome email to everyone added to list #15 today using template #9"* +- *"If bounce rate on campaign #789 is over 3%, send SMS backup to all recipients"* +- *"List all templates with 'newsletter' in the name, show me #3's stats"* + +### Bulk Operations +- *"Export all contacts modified in the last 7 days as JSON"* +- *"Send 'Account Verified' email to all contacts with VERIFIED=true attribute"* +- *"Check how many contacts are in lists #10, #11, and #12 combined"* + +## 🛠️ Development + +### Prerequisites +- Node.js 18+ +- npm or yarn +- Brevo account (free or paid) + +### Setup + +```bash +git clone https://github.com/BusyBee3333/Brevo-MCP-2026-Complete.git +cd brevo-mcp-2026-complete +npm install +cp .env.example .env +# Edit .env with your Brevo API key +npm run build +npm start +``` + +### Testing + +```bash +npm test # Run all tests +npm run test:watch # Watch mode +npm run test:coverage # Coverage report +``` + +### Project Structure + +``` +brevo-mcp-2026-complete/ +├── src/ +│ └── index.ts # Main server implementation +├── dist/ # Compiled JavaScript +├── package.json +├── tsconfig.json +└── .env.example +``` + +## 🐛 Troubleshooting + +### "Authentication failed" +- Verify your API key starts with `xkeysib-` +- Check key permissions at https://app.brevo.com/settings/keys/api +- Ensure your account is active (not suspended) + +### "Rate limit exceeded" +- Free plans: 300 calls/minute +- Wait 60 seconds or upgrade to paid plan +- Use pagination (`limit` parameter) to reduce calls + +### "Tools not appearing in Claude" +- Restart Claude Desktop after updating config +- Check that the path in `claude_desktop_config.json` is absolute (not relative) +- Verify the build completed: `ls dist/index.js` +- Check Claude Desktop logs: `tail -f ~/Library/Logs/Claude/mcp*.log` + +### "Invalid list ID" or "Template not found" +- List IDs are numeric (e.g., 12, not "12") +- Get valid IDs: *"List all my contact lists"* or *"Show me all templates"* + +## 📖 Resources + +- **[Brevo API v3 Docs](https://developers.brevo.com/reference/getting-started-1)** — Official API reference +- **[Brevo Help Center](https://help.brevo.com/)** — Tutorials and guides +- **[MCP Protocol Spec](https://modelcontextprotocol.io/)** — How MCP servers work +- **[Claude Desktop Docs](https://claude.ai/desktop)** — Installing and configuring Claude +- **[MCPEngage Platform](https://mcpengine.pages.dev)** — Browse 30+ business MCP servers + +## 🤝 Contributing + +Contributions are welcome! Please: + +1. Fork the repo +2. Create a feature branch (`git checkout -b feature/sms-analytics`) +3. Commit your changes (`git commit -m 'Add SMS campaign stats tool'`) +4. Push to the branch (`git push origin feature/sms-analytics`) +5. Open a Pull Request + +## 📄 License + +MIT License - see [LICENSE](LICENSE) for details + +## 🙏 Credits + +Built by [MCPEngage](https://mcpengage.com) — AI infrastructure for business software. + +Want more MCP servers? Check out our [full catalog](https://mcpengage.com) covering 30+ business platforms including Constant Contact, Mailchimp, ActiveCampaign, and more. + +--- + +**Questions?** Open an issue or join our [Discord community](https://discord.gg/mcpengage). diff --git a/servers/brevo/package.json b/servers/brevo/package.json index e0b930f..180ce99 100644 --- a/servers/brevo/package.json +++ b/servers/brevo/package.json @@ -2,12 +2,19 @@ "name": "mcp-server-brevo", "version": "1.0.0", "type": "module", + "description": "MCP server for Brevo (formerly Sendinblue) email and SMS marketing API", "main": "dist/index.js", + "bin": { + "mcp-server-brevo": "./dist/index.js" + }, "scripts": { "build": "tsc", "start": "node dist/index.js", - "dev": "tsx src/index.ts" + "dev": "tsx src/index.ts", + "prepublishOnly": "npm run build" }, + "keywords": ["mcp", "brevo", "sendinblue", "email", "sms", "marketing"], + "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^0.5.0", "zod": "^3.22.4" diff --git a/servers/clickup/src/ui/react-app/calendar-view/App.tsx b/servers/clickup/src/ui/react-app/calendar-view/App.tsx new file mode 100644 index 0000000..9cc17eb --- /dev/null +++ b/servers/clickup/src/ui/react-app/calendar-view/App.tsx @@ -0,0 +1,129 @@ +import React, { useState, useEffect } from 'react'; + +interface Task { + id: string; + name: string; + due_date: string; + priority: string; + status: string; +} + +export default function App() { + const [tasks, setTasks] = useState([]); + const [currentDate, setCurrentDate] = useState(new Date()); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setTasks([ + { id: '1', name: 'Project proposal', due_date: '2024-02-15', priority: 'high', status: 'in progress' }, + { id: '2', name: 'Design review', due_date: '2024-02-14', priority: 'medium', status: 'open' }, + { id: '3', name: 'Critical bug fix', due_date: '2024-02-12', priority: 'urgent', status: 'in progress' }, + { id: '4', name: 'Documentation update', due_date: '2024-02-18', priority: 'low', status: 'open' }, + { id: '5', name: 'Team meeting', due_date: '2024-02-16', priority: 'medium', status: 'open' }, + { id: '6', name: 'API refactor', due_date: '2024-02-20', priority: 'high', status: 'open' }, + ]); + setLoading(false); + }, 500); + }, []); + + const getDaysInMonth = (date: Date) => { + const year = date.getFullYear(); + const month = date.getMonth(); + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + const daysInMonth = lastDay.getDate(); + const startingDayOfWeek = firstDay.getDay(); + + return { daysInMonth, startingDayOfWeek }; + }; + + const getTasksForDate = (date: Date) => { + const dateStr = date.toISOString().split('T')[0]; + return tasks.filter(task => task.due_date.startsWith(dateStr)); + }; + + const { daysInMonth, startingDayOfWeek } = getDaysInMonth(currentDate); + const days = Array.from({ length: daysInMonth }, (_, i) => i + 1); + const blanks = Array.from({ length: startingDayOfWeek }, (_, i) => i); + + const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + + if (loading) return
Loading calendar...
; + + return ( +
+
+

📅 Calendar View

+

Tasks organized by due date

+
+ +
+ +

{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}

+ +
+ +
+
Sun
+
Mon
+
Tue
+
Wed
+
Thu
+
Fri
+
Sat
+ + {blanks.map(blank =>
)} + + {days.map(day => { + const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + const dayTasks = getTasksForDate(date); + const isToday = date.toDateString() === new Date().toDateString(); + + return ( +
+
{day}
+
+ {dayTasks.map(task => ( +
+ {task.name} +
+ ))} +
+
+ ); + })} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/calendar-view/index.html b/servers/clickup/src/ui/react-app/calendar-view/index.html new file mode 100644 index 0000000..c85f475 --- /dev/null +++ b/servers/clickup/src/ui/react-app/calendar-view/index.html @@ -0,0 +1,12 @@ + + + + + + Calendar View - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/calendar-view/vite.config.ts b/servers/clickup/src/ui/react-app/calendar-view/vite.config.ts new file mode 100644 index 0000000..c2e1943 --- /dev/null +++ b/servers/clickup/src/ui/react-app/calendar-view/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3007, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/checklist-manager/App.tsx b/servers/clickup/src/ui/react-app/checklist-manager/App.tsx new file mode 100644 index 0000000..d45a3e5 --- /dev/null +++ b/servers/clickup/src/ui/react-app/checklist-manager/App.tsx @@ -0,0 +1,178 @@ +import React, { useState, useEffect } from 'react'; + +interface ChecklistItem { + id: string; + text: string; + completed: boolean; +} + +interface Checklist { + id: string; + name: string; + taskName: string; + items: ChecklistItem[]; +} + +export default function App() { + const [checklists, setChecklists] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setChecklists([ + { + id: 'cl1', + name: 'Pre-launch Checklist', + taskName: 'Product Launch', + items: [ + { id: 'i1', text: 'Complete security audit', completed: true }, + { id: 'i2', text: 'Run performance tests', completed: true }, + { id: 'i3', text: 'Update documentation', completed: false }, + { id: 'i4', text: 'Prepare marketing materials', completed: false }, + { id: 'i5', text: 'Deploy to production', completed: false }, + ] + }, + { + id: 'cl2', + name: 'Code Review Checklist', + taskName: 'Feature: User Authentication', + items: [ + { id: 'i6', text: 'Check code style consistency', completed: true }, + { id: 'i7', text: 'Verify error handling', completed: true }, + { id: 'i8', text: 'Review test coverage', completed: true }, + { id: 'i9', text: 'Check for security vulnerabilities', completed: false }, + { id: 'i10', text: 'Validate edge cases', completed: false }, + ] + }, + { + id: 'cl3', + name: 'Onboarding Tasks', + taskName: 'New Team Member - John', + items: [ + { id: 'i11', text: 'Setup development environment', completed: true }, + { id: 'i12', text: 'Grant access to repositories', completed: true }, + { id: 'i13', text: 'Complete security training', completed: true }, + { id: 'i14', text: 'Review codebase architecture', completed: false }, + { id: 'i15', text: 'First code contribution', completed: false }, + { id: 'i16', text: 'Team introduction meeting', completed: true }, + ] + }, + ]); + setLoading(false); + }, 500); + }, []); + + const toggleItem = (checklistId: string, itemId: string) => { + setChecklists(checklists.map(cl => { + if (cl.id === checklistId) { + return { + ...cl, + items: cl.items.map(item => + item.id === itemId ? { ...item, completed: !item.completed } : item + ) + }; + } + return cl; + })); + }; + + if (loading) return
Loading checklists...
; + + return ( +
+
+

✅ Checklist Manager

+

Manage checklists with item completion tracking

+
+ +
+ {checklists.map(checklist => { + const completedCount = checklist.items.filter(item => item.completed).length; + const totalCount = checklist.items.length; + const progressPercent = (completedCount / totalCount) * 100; + + return ( +
+
+
+

{checklist.name}

+

📝 {checklist.taskName}

+
+
+ + + + +
{Math.round(progressPercent)}%
+
+
+ +
+
+ {completedCount} of {totalCount} completed +
+
+
+
+
+ +
+ {checklist.items.map(item => ( +
toggleItem(checklist.id, item.id)} + > +
+ {item.completed && '✓'} +
+ {item.text} +
+ ))} +
+
+ ); + })} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/checklist-manager/index.html b/servers/clickup/src/ui/react-app/checklist-manager/index.html new file mode 100644 index 0000000..8b2f730 --- /dev/null +++ b/servers/clickup/src/ui/react-app/checklist-manager/index.html @@ -0,0 +1,12 @@ + + + + + + Checklist Manager - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/checklist-manager/vite.config.ts b/servers/clickup/src/ui/react-app/checklist-manager/vite.config.ts new file mode 100644 index 0000000..b8d7500 --- /dev/null +++ b/servers/clickup/src/ui/react-app/checklist-manager/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3014, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/comment-thread/App.tsx b/servers/clickup/src/ui/react-app/comment-thread/App.tsx new file mode 100644 index 0000000..ff54cbc --- /dev/null +++ b/servers/clickup/src/ui/react-app/comment-thread/App.tsx @@ -0,0 +1,165 @@ +import React, { useState, useEffect } from 'react'; + +interface Comment { + id: string; + author: string; + text: string; + timestamp: string; + replies: Comment[]; +} + +const CommentItem: React.FC<{ comment: Comment; level: number }> = ({ comment, level }) => ( +
+
+
{comment.author[0]}
+
+ {comment.author} + {new Date(comment.timestamp).toLocaleString()} +
+
+
{comment.text}
+ {comment.replies.length > 0 && ( +
+ {comment.replies.map(reply => ( + + ))} +
+ )} +
+); + +export default function App() { + const [comments, setComments] = useState([]); + const [taskName, setTaskName] = useState(''); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setTaskName('Implement user authentication system'); + setComments([ + { + id: 'c1', + author: 'Jane Smith', + text: 'Started working on the OAuth2 integration. Looking good so far!', + timestamp: '2024-02-12T10:30:00Z', + replies: [ + { + id: 'c1-r1', + author: 'John Doe', + text: 'Great! Make sure to add proper error handling for token refresh.', + timestamp: '2024-02-12T11:00:00Z', + replies: [ + { + id: 'c1-r1-r1', + author: 'Jane Smith', + text: 'Will do! Already added retry logic with exponential backoff.', + timestamp: '2024-02-12T11:15:00Z', + replies: [] + } + ] + }, + { + id: 'c1-r2', + author: 'Bob Johnson', + text: 'Also remember to test with different OAuth providers.', + timestamp: '2024-02-12T14:30:00Z', + replies: [] + } + ] + }, + { + id: 'c2', + author: 'John Doe', + text: 'Database schema looks good, approved! Just one suggestion: consider adding an index on the email column for faster lookups.', + timestamp: '2024-02-13T14:15:00Z', + replies: [ + { + id: 'c2-r1', + author: 'Jane Smith', + text: 'Good catch! Added the index. Also added a unique constraint.', + timestamp: '2024-02-13T15:00:00Z', + replies: [] + } + ] + }, + { + id: 'c3', + author: 'Alice Williams', + text: 'Need to discuss JWT token expiration time with the team. Current setting of 1 hour seems too short for our use case.', + timestamp: '2024-02-14T09:00:00Z', + replies: [ + { + id: 'c3-r1', + author: 'John Doe', + text: "Let's schedule a quick call to discuss. I think we should keep access tokens short but implement refresh tokens.", + timestamp: '2024-02-14T09:30:00Z', + replies: [] + }, + { + id: 'c3-r2', + author: 'Jane Smith', + text: 'Agreed. I can implement refresh token rotation for better security.', + timestamp: '2024-02-14T10:00:00Z', + replies: [] + } + ] + }, + { + id: 'c4', + author: 'Bob Johnson', + text: 'Completed code review. Everything looks solid. Just a few minor style suggestions in the PR comments.', + timestamp: '2024-02-15T16:00:00Z', + replies: [] + } + ]); + setLoading(false); + }, 500); + }, []); + + if (loading) return
Loading comments...
; + + return ( +
+
+

💬 Comment Thread

+

{taskName}

+

{comments.length} comments

+
+ +
+ {comments.map(comment => ( + + ))} +
+ +
+ + +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/comment-thread/index.html b/servers/clickup/src/ui/react-app/comment-thread/index.html new file mode 100644 index 0000000..011c244 --- /dev/null +++ b/servers/clickup/src/ui/react-app/comment-thread/index.html @@ -0,0 +1,12 @@ + + + + + + Comment Thread - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/comment-thread/vite.config.ts b/servers/clickup/src/ui/react-app/comment-thread/vite.config.ts new file mode 100644 index 0000000..355680b --- /dev/null +++ b/servers/clickup/src/ui/react-app/comment-thread/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3013, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/doc-browser/App.tsx b/servers/clickup/src/ui/react-app/doc-browser/App.tsx new file mode 100644 index 0000000..5b85113 --- /dev/null +++ b/servers/clickup/src/ui/react-app/doc-browser/App.tsx @@ -0,0 +1,129 @@ +import React, { useState, useEffect } from 'react'; + +interface Doc { + id: string; + name: string; + content: string; + author: string; + lastModified: string; + tags: string[]; + folder: string; +} + +export default function App() { + const [docs, setDocs] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); + const [selectedDoc, setSelectedDoc] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setDocs([ + { id: '1', name: 'API Documentation', content: '# API Documentation\n\nComplete reference for all API endpoints...', author: 'John Doe', lastModified: '2024-02-15', tags: ['api', 'reference'], folder: 'Development' }, + { id: '2', name: 'Project Requirements', content: '# Project Requirements\n\n## Overview\nThis document outlines...', author: 'Jane Smith', lastModified: '2024-02-14', tags: ['requirements', 'planning'], folder: 'Planning' }, + { id: '3', name: 'Design System Guide', content: '# Design System\n\n## Colors\n\n## Typography...', author: 'Bob Johnson', lastModified: '2024-02-13', tags: ['design', 'ui'], folder: 'Design' }, + { id: '4', name: 'Security Best Practices', content: '# Security Guide\n\n1. Authentication\n2. Authorization...', author: 'Alice Williams', lastModified: '2024-02-12', tags: ['security', 'guidelines'], folder: 'Development' }, + { id: '5', name: 'Onboarding Guide', content: '# Welcome!\n\nThis guide will help you get started...', author: 'Charlie Brown', lastModified: '2024-02-11', tags: ['onboarding', 'hr'], folder: 'HR' }, + { id: '6', name: 'Sprint Retrospective', content: '# Sprint 5 Retrospective\n\n## What went well...', author: 'Diana Prince', lastModified: '2024-02-10', tags: ['agile', 'retrospective'], folder: 'Planning' }, + ]); + setLoading(false); + }, 500); + }, []); + + const filteredDocs = docs.filter(doc => + doc.name.toLowerCase().includes(searchTerm.toLowerCase()) || + doc.content.toLowerCase().includes(searchTerm.toLowerCase()) || + doc.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase())) + ); + + if (loading) return
Loading documents...
; + + return ( +
+
+
+

📚 Documents

+ setSearchTerm(e.target.value)} + className="search-input" + /> +
+ +
+ {filteredDocs.map(doc => ( +
setSelectedDoc(doc)} + > +
📄 {doc.name}
+
+ {doc.folder} + {doc.lastModified} +
+
+ {doc.tags.map(tag => {tag})} +
+
+ ))} + {filteredDocs.length === 0 && ( +
No documents found
+ )} +
+
+ +
+ {selectedDoc ? ( + <> +
+

{selectedDoc.name}

+
+ By {selectedDoc.author} + Last modified: {selectedDoc.lastModified} +
+
+
+
{selectedDoc.content}
+
+ + ) : ( +
+
📄
+

Select a document to view its content

+
+ )} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/doc-browser/index.html b/servers/clickup/src/ui/react-app/doc-browser/index.html new file mode 100644 index 0000000..ff32a26 --- /dev/null +++ b/servers/clickup/src/ui/react-app/doc-browser/index.html @@ -0,0 +1,12 @@ + + + + + + Document Browser - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/doc-browser/vite.config.ts b/servers/clickup/src/ui/react-app/doc-browser/vite.config.ts new file mode 100644 index 0000000..615ac70 --- /dev/null +++ b/servers/clickup/src/ui/react-app/doc-browser/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3012, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/folder-overview/App.tsx b/servers/clickup/src/ui/react-app/folder-overview/App.tsx new file mode 100644 index 0000000..5b38ca9 --- /dev/null +++ b/servers/clickup/src/ui/react-app/folder-overview/App.tsx @@ -0,0 +1,121 @@ +import React, { useState, useEffect } from 'react'; + +interface Folder { + id: string; + name: string; + lists: Array<{ id: string; name: string; taskCount: number; openTasks: number; closedTasks: number }>; + totalTasks: number; +} + +export default function App() { + const [folder, setFolder] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setFolder({ + id: '1', + name: 'Frontend Development', + lists: [ + { id: 'l1', name: 'React Components', taskCount: 15, openTasks: 5, closedTasks: 10 }, + { id: 'l2', name: 'UI/UX Implementation', taskCount: 22, openTasks: 12, closedTasks: 10 }, + { id: 'l3', name: 'Bug Fixes', taskCount: 8, openTasks: 3, closedTasks: 5 }, + { id: 'l4', name: 'Performance Optimization', taskCount: 12, openTasks: 8, closedTasks: 4 }, + { id: 'l5', name: 'Testing', taskCount: 18, openTasks: 10, closedTasks: 8 }, + ], + totalTasks: 75 + }); + setLoading(false); + }, 500); + }, []); + + if (loading) return
Loading folder...
; + if (!folder) return
Folder not found
; + + return ( +
+
+

📁 {folder.name}

+

Folder overview with lists and task summaries

+
+ +
+
+
{folder.lists.length}
+
Lists
+
+
+
{folder.totalTasks}
+
Total Tasks
+
+
+
{folder.lists.reduce((sum, l) => sum + l.openTasks, 0)}
+
Open
+
+
+
{folder.lists.reduce((sum, l) => sum + l.closedTasks, 0)}
+
Closed
+
+
+ +
+ {folder.lists.map(list => { + const completion = (list.closedTasks / list.taskCount) * 100; + return ( +
+
+

{list.name}

+ {list.taskCount} tasks +
+
+
+
+
+
{completion.toFixed(0)}% complete
+
+
+
+ Open: + {list.openTasks} +
+
+ Closed: + {list.closedTasks} +
+
+
+ ); + })} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/folder-overview/index.html b/servers/clickup/src/ui/react-app/folder-overview/index.html new file mode 100644 index 0000000..d215ba1 --- /dev/null +++ b/servers/clickup/src/ui/react-app/folder-overview/index.html @@ -0,0 +1,12 @@ + + + + + + Folder Overview - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/folder-overview/vite.config.ts b/servers/clickup/src/ui/react-app/folder-overview/vite.config.ts new file mode 100644 index 0000000..b90bd28 --- /dev/null +++ b/servers/clickup/src/ui/react-app/folder-overview/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3005, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/goal-tracker/App.tsx b/servers/clickup/src/ui/react-app/goal-tracker/App.tsx new file mode 100644 index 0000000..8f9679f --- /dev/null +++ b/servers/clickup/src/ui/react-app/goal-tracker/App.tsx @@ -0,0 +1,164 @@ +import React, { useState, useEffect } from 'react'; + +interface Goal { + id: string; + name: string; + description: string; + progress: number; + target: number; + unit: string; + keyResults: Array<{ id: string; name: string; current: number; target: number; unit: string }>; + dueDate: string; + owner: string; +} + +export default function App() { + const [goals, setGoals] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setGoals([ + { + id: 'g1', + name: 'Launch New Product Features', + description: 'Complete and launch all planned features for Q1 2024', + progress: 12, + target: 20, + unit: 'features', + dueDate: '2024-03-31', + owner: 'Product Team', + keyResults: [ + { id: 'kr1', name: 'User authentication system', current: 100, target: 100, unit: '%' }, + { id: 'kr2', name: 'Payment integration', current: 75, target: 100, unit: '%' }, + { id: 'kr3', name: 'Dashboard redesign', current: 50, target: 100, unit: '%' }, + { id: 'kr4', name: 'Mobile app features', current: 30, target: 100, unit: '%' }, + ] + }, + { + id: 'g2', + name: 'Improve Performance Metrics', + description: 'Reduce page load time and improve app responsiveness', + progress: 1500, + target: 2000, + unit: 'ms saved', + dueDate: '2024-02-28', + owner: 'Engineering Team', + keyResults: [ + { id: 'kr5', name: 'Reduce initial load time', current: 800, target: 1000, unit: 'ms saved' }, + { id: 'kr6', name: 'Optimize API responses', current: 500, target: 700, unit: 'ms saved' }, + { id: 'kr7', name: 'Implement caching', current: 200, target: 300, unit: 'ms saved' }, + ] + }, + { + id: 'g3', + name: 'Increase Test Coverage', + description: 'Achieve 80% code coverage across all modules', + progress: 65, + target: 80, + unit: '%', + dueDate: '2024-03-15', + owner: 'QA Team', + keyResults: [ + { id: 'kr8', name: 'Frontend unit tests', current: 70, target: 80, unit: '%' }, + { id: 'kr9', name: 'Backend unit tests', current: 75, target: 85, unit: '%' }, + { id: 'kr10', name: 'Integration tests', current: 50, target: 75, unit: '%' }, + ] + }, + ]); + setLoading(false); + }, 500); + }, []); + + if (loading) return
Loading goals...
; + + return ( +
+
+

🎯 Goal Tracker

+

Track goals with key results and progress bars

+
+ +
+ {goals.map(goal => { + const progressPercent = (goal.progress / goal.target) * 100; + + return ( +
+
+
+

{goal.name}

+

{goal.description}

+
+
+
👤 {goal.owner}
+
📅 {goal.dueDate}
+
+
+ +
+
+ Overall Progress + {goal.progress} / {goal.target} {goal.unit} +
+
+
+
+
{progressPercent.toFixed(0)}% complete
+
+ +
+

Key Results

+ {goal.keyResults.map(kr => { + const krPercent = (kr.current / kr.target) * 100; + + return ( +
+
+ {kr.name} + {kr.current} / {kr.target} {kr.unit} +
+
+
+
+
+ ); + })} +
+
+ ); + })} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/goal-tracker/index.html b/servers/clickup/src/ui/react-app/goal-tracker/index.html new file mode 100644 index 0000000..81b926b --- /dev/null +++ b/servers/clickup/src/ui/react-app/goal-tracker/index.html @@ -0,0 +1,12 @@ + + + + + + Goal Tracker - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/goal-tracker/vite.config.ts b/servers/clickup/src/ui/react-app/goal-tracker/vite.config.ts new file mode 100644 index 0000000..4d7f620 --- /dev/null +++ b/servers/clickup/src/ui/react-app/goal-tracker/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3008, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/list-view/App.tsx b/servers/clickup/src/ui/react-app/list-view/App.tsx new file mode 100644 index 0000000..b34e54a --- /dev/null +++ b/servers/clickup/src/ui/react-app/list-view/App.tsx @@ -0,0 +1,148 @@ +import React, { useState, useEffect } from 'react'; + +interface List { + id: string; + name: string; + description: string; + tasks: Array<{ + id: string; + name: string; + status: string; + priority: string; + assignee: string; + due_date: string; + tags: string[]; + }>; +} + +export default function App() { + const [list, setList] = useState(null); + const [sortKey, setSortKey] = useState<'name' | 'priority' | 'due_date'>('due_date'); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setList({ + id: '1', + name: 'Sprint 5 - Product Features', + description: 'Development tasks for the fifth sprint focused on new product features', + tasks: [ + { id: 't1', name: 'User authentication', status: 'in progress', priority: 'high', assignee: 'John Doe', due_date: '2024-02-15', tags: ['backend', 'security'] }, + { id: 't2', name: 'Payment integration', status: 'open', priority: 'urgent', assignee: 'Jane Smith', due_date: '2024-02-14', tags: ['backend', 'payments'] }, + { id: 't3', name: 'Dashboard UI', status: 'in progress', priority: 'medium', assignee: 'Bob Johnson', due_date: '2024-02-18', tags: ['frontend', 'ui'] }, + { id: 't4', name: 'API documentation', status: 'open', priority: 'low', assignee: 'Alice Williams', due_date: '2024-02-20', tags: ['docs'] }, + { id: 't5', name: 'Database migration', status: 'closed', priority: 'high', assignee: 'John Doe', due_date: '2024-02-12', tags: ['backend', 'database'] }, + { id: 't6', name: 'Email notifications', status: 'in progress', priority: 'medium', assignee: 'Jane Smith', due_date: '2024-02-16', tags: ['backend', 'notifications'] }, + ] + }); + setLoading(false); + }, 500); + }, []); + + if (loading) return
Loading list...
; + if (!list) return
List not found
; + + const sortedTasks = [...list.tasks].sort((a, b) => { + if (sortKey === 'due_date') { + return new Date(a.due_date).getTime() - new Date(b.due_date).getTime(); + } + return a[sortKey] > b[sortKey] ? 1 : -1; + }); + + const statusCounts = list.tasks.reduce((acc, task) => { + acc[task.status] = (acc[task.status] || 0) + 1; + return acc; + }, {} as Record); + + return ( +
+
+

📋 {list.name}

+

{list.description}

+
+ +
+
+ {list.tasks.length} + Total +
+
+ {statusCounts['open'] || 0} + Open +
+
+ {statusCounts['in progress'] || 0} + In Progress +
+
+ {statusCounts['closed'] || 0} + Closed +
+
+ +
+ +
+ +
+
+
Task
+
Status
+
Priority
+
Assignee
+
Due Date
+
Tags
+
+ {sortedTasks.map(task => ( +
+
{task.name}
+
{task.status}
+
{task.priority}
+
{task.assignee}
+
{task.due_date}
+
+ {task.tags.map(tag => {tag})} +
+
+ ))} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/list-view/index.html b/servers/clickup/src/ui/react-app/list-view/index.html new file mode 100644 index 0000000..317493f --- /dev/null +++ b/servers/clickup/src/ui/react-app/list-view/index.html @@ -0,0 +1,12 @@ + + + + + + List View - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/list-view/vite.config.ts b/servers/clickup/src/ui/react-app/list-view/vite.config.ts new file mode 100644 index 0000000..83f3bed --- /dev/null +++ b/servers/clickup/src/ui/react-app/list-view/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3006, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/member-workload/App.tsx b/servers/clickup/src/ui/react-app/member-workload/App.tsx new file mode 100644 index 0000000..66c99ba --- /dev/null +++ b/servers/clickup/src/ui/react-app/member-workload/App.tsx @@ -0,0 +1,152 @@ +import React, { useState, useEffect } from 'react'; + +interface Member { + id: string; + name: string; + avatar: string; + taskCounts: { total: number; open: number; inProgress: number; overdue: number }; + timeLogged: number; + capacity: number; +} + +export default function App() { + const [members, setMembers] = useState([]); + const [sortBy, setSortBy] = useState<'name' | 'tasks' | 'time' | 'overdue'>('tasks'); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setMembers([ + { id: '1', name: 'John Doe', avatar: 'JD', taskCounts: { total: 15, open: 5, inProgress: 8, overdue: 2 }, timeLogged: 42.5, capacity: 40 }, + { id: '2', name: 'Jane Smith', avatar: 'JS', taskCounts: { total: 18, open: 6, inProgress: 10, overdue: 2 }, timeLogged: 38.0, capacity: 40 }, + { id: '3', name: 'Bob Johnson', avatar: 'BJ', taskCounts: { total: 12, open: 4, inProgress: 7, overdue: 1 }, timeLogged: 35.5, capacity: 40 }, + { id: '4', name: 'Alice Williams', avatar: 'AW', taskCounts: { total: 14, open: 3, inProgress: 9, overdue: 2 }, timeLogged: 31.5, capacity: 40 }, + { id: '5', name: 'Charlie Brown', avatar: 'CB', taskCounts: { total: 10, open: 2, inProgress: 8, overdue: 0 }, timeLogged: 40.0, capacity: 40 }, + { id: '6', name: 'Diana Prince', avatar: 'DP', taskCounts: { total: 16, open: 5, inProgress: 9, overdue: 2 }, timeLogged: 36.5, capacity: 40 }, + ]); + setLoading(false); + }, 500); + }, []); + + const sortedMembers = [...members].sort((a, b) => { + switch(sortBy) { + case 'tasks': return b.taskCounts.total - a.taskCounts.total; + case 'time': return b.timeLogged - a.timeLogged; + case 'overdue': return b.taskCounts.overdue - a.taskCounts.overdue; + default: return a.name.localeCompare(b.name); + } + }); + + if (loading) return
Loading member workload...
; + + return ( +
+
+

👥 Member Workload

+

Per-member task counts, time logged, and overdue tasks

+
+ +
+ +
+ +
+ {sortedMembers.map(member => { + const capacityPercent = (member.timeLogged / member.capacity) * 100; + const isOverCapacity = capacityPercent > 100; + + return ( +
+
+
{member.avatar}
+
+

{member.name}

+
+ {member.taskCounts.total} tasks • {member.timeLogged}h logged +
+
+
+ +
+
+ Capacity + + {member.timeLogged}h / {member.capacity}h + +
+
+
+
+
+ +
+
+
Open
+
{member.taskCounts.open}
+
+
+
In Progress
+
{member.taskCounts.inProgress}
+
+
+
Overdue
+
{member.taskCounts.overdue}
+
+
+ + {member.taskCounts.overdue > 0 && ( +
+ ⚠️ {member.taskCounts.overdue} overdue task{member.taskCounts.overdue > 1 ? 's' : ''} +
+ )} +
+ ); + })} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/member-workload/index.html b/servers/clickup/src/ui/react-app/member-workload/index.html new file mode 100644 index 0000000..0f4b59a --- /dev/null +++ b/servers/clickup/src/ui/react-app/member-workload/index.html @@ -0,0 +1,12 @@ + + + + + + Member Workload - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/member-workload/vite.config.ts b/servers/clickup/src/ui/react-app/member-workload/vite.config.ts new file mode 100644 index 0000000..0754815 --- /dev/null +++ b/servers/clickup/src/ui/react-app/member-workload/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3011, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/space-overview/App.tsx b/servers/clickup/src/ui/react-app/space-overview/App.tsx new file mode 100644 index 0000000..9da5fc5 --- /dev/null +++ b/servers/clickup/src/ui/react-app/space-overview/App.tsx @@ -0,0 +1,158 @@ +import React, { useState, useEffect } from 'react'; + +interface Space { + id: string; + name: string; + folders: Array<{ id: string; name: string; listCount: number }>; + lists: Array<{ id: string; name: string; taskCount: number }>; + members: Array<{ id: string; name: string; role: string }>; + taskStats: { total: number; open: number; inProgress: number; closed: number }; +} + +export default function App() { + const [space, setSpace] = useState(null); + const [activeTab, setActiveTab] = useState<'folders' | 'lists' | 'members'>('folders'); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setSpace({ + id: '1', + name: 'Product Development', + folders: [ + { id: 'f1', name: 'Frontend', listCount: 5 }, + { id: 'f2', name: 'Backend', listCount: 7 }, + { id: 'f3', name: 'Design', listCount: 3 }, + { id: 'f4', name: 'QA', listCount: 4 }, + ], + lists: [ + { id: 'l1', name: 'Sprint Planning', taskCount: 12 }, + { id: 'l2', name: 'Backlog', taskCount: 45 }, + { id: 'l3', name: 'Bugs', taskCount: 8 }, + ], + members: [ + { id: 'm1', name: 'John Doe', role: 'Admin' }, + { id: 'm2', name: 'Jane Smith', role: 'Member' }, + { id: 'm3', name: 'Bob Johnson', role: 'Member' }, + { id: 'm4', name: 'Alice Williams', role: 'Guest' }, + ], + taskStats: { total: 65, open: 25, inProgress: 18, closed: 22 } + }); + setLoading(false); + }, 500); + }, []); + + if (loading) return
Loading space...
; + if (!space) return
Space not found
; + + return ( +
+
+

🚀 {space.name}

+

Space overview with folders, lists, and team members

+
+ +
+
+
Total Tasks
+
{space.taskStats.total}
+
+
+
Open
+
{space.taskStats.open}
+
+
+
In Progress
+
{space.taskStats.inProgress}
+
+
+
Closed
+
{space.taskStats.closed}
+
+
+ +
+ + + +
+ +
+ {activeTab === 'folders' && ( +
+ {space.folders.map(folder => ( +
+
📁
+
{folder.name}
+
{folder.listCount} lists
+
+ ))} +
+ )} + + {activeTab === 'lists' && ( +
+ {space.lists.map(list => ( +
+
📋
+
{list.name}
+
{list.taskCount} tasks
+
+ ))} +
+ )} + + {activeTab === 'members' && ( +
+ {space.members.map(member => ( +
+
{member.name[0]}
+
+
{member.name}
+
{member.role}
+
+
+ ))} +
+ )} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/space-overview/index.html b/servers/clickup/src/ui/react-app/space-overview/index.html new file mode 100644 index 0000000..e151d06 --- /dev/null +++ b/servers/clickup/src/ui/react-app/space-overview/index.html @@ -0,0 +1,12 @@ + + + + + + Space Overview - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/space-overview/vite.config.ts b/servers/clickup/src/ui/react-app/space-overview/vite.config.ts new file mode 100644 index 0000000..4466f91 --- /dev/null +++ b/servers/clickup/src/ui/react-app/space-overview/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3004, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/tag-manager/App.tsx b/servers/clickup/src/ui/react-app/tag-manager/App.tsx new file mode 100644 index 0000000..a2b8098 --- /dev/null +++ b/servers/clickup/src/ui/react-app/tag-manager/App.tsx @@ -0,0 +1,188 @@ +import React, { useState, useEffect } from 'react'; + +interface Tag { + id: string; + name: string; + color: string; + taskCount: number; + tasks: Array<{ id: string; name: string; status: string }>; +} + +export default function App() { + const [tags, setTags] = useState([]); + const [selectedTag, setSelectedTag] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setTags([ + { + id: 't1', + name: 'urgent', + color: '#ef4444', + taskCount: 8, + tasks: [ + { id: 'task1', name: 'Fix critical production bug', status: 'in progress' }, + { id: 'task2', name: 'Security vulnerability patch', status: 'open' }, + { id: 'task3', name: 'Database migration', status: 'closed' }, + ] + }, + { + id: 't2', + name: 'backend', + color: '#3b82f6', + taskCount: 15, + tasks: [ + { id: 'task4', name: 'API optimization', status: 'in progress' }, + { id: 'task5', name: 'Implement caching', status: 'open' }, + { id: 'task6', name: 'Database schema update', status: 'in progress' }, + ] + }, + { + id: 't3', + name: 'frontend', + color: '#10b981', + taskCount: 12, + tasks: [ + { id: 'task7', name: 'Redesign dashboard', status: 'in progress' }, + { id: 'task8', name: 'Add dark mode', status: 'open' }, + { id: 'task9', name: 'Responsive layout fixes', status: 'closed' }, + ] + }, + { + id: 't4', + name: 'design', + color: '#a78bfa', + taskCount: 9, + tasks: [ + { id: 'task10', name: 'Create design system', status: 'in progress' }, + { id: 'task11', name: 'UI mockups for new feature', status: 'open' }, + ] + }, + { + id: 't5', + name: 'testing', + color: '#f59e0b', + taskCount: 11, + tasks: [ + { id: 'task12', name: 'Write integration tests', status: 'in progress' }, + { id: 'task13', name: 'E2E test coverage', status: 'open' }, + { id: 'task14', name: 'Performance testing', status: 'open' }, + ] + }, + { + id: 't6', + name: 'documentation', + color: '#6366f1', + taskCount: 7, + tasks: [ + { id: 'task15', name: 'API documentation', status: 'in progress' }, + { id: 'task16', name: 'Update README', status: 'closed' }, + ] + }, + ]); + setLoading(false); + }, 500); + }, []); + + if (loading) return
Loading tags...
; + + return ( +
+
+
+

🏷️ Tag Manager

+

{tags.length} tags • {tags.reduce((sum, tag) => sum + tag.taskCount, 0)} tasks

+
+ +
+ {tags.map(tag => ( +
setSelectedTag(tag)} + > +
+
+
{tag.name}
+
{tag.taskCount} tasks
+
+
+ ))} +
+
+ +
+ {selectedTag ? ( + <> +
+
+ {selectedTag.name} +
+
+ {selectedTag.taskCount} tasks using this tag +
+
+ +
+ {selectedTag.tasks.map(task => ( +
+
{task.name}
+ {task.status} +
+ ))} +
+ +
+ + +
+ + ) : ( +
+
🏷️
+

Select a tag to view associated tasks

+
+ )} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/tag-manager/index.html b/servers/clickup/src/ui/react-app/tag-manager/index.html new file mode 100644 index 0000000..d1d9fb0 --- /dev/null +++ b/servers/clickup/src/ui/react-app/tag-manager/index.html @@ -0,0 +1,12 @@ + + + + + + Tag Manager - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/tag-manager/vite.config.ts b/servers/clickup/src/ui/react-app/tag-manager/vite.config.ts new file mode 100644 index 0000000..3324fed --- /dev/null +++ b/servers/clickup/src/ui/react-app/tag-manager/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3015, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/task-board/App.tsx b/servers/clickup/src/ui/react-app/task-board/App.tsx new file mode 100644 index 0000000..f7d7223 --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-board/App.tsx @@ -0,0 +1,116 @@ +import React, { useState, useEffect } from 'react'; + +interface Task { + id: string; + name: string; + status: string; + priority: string; + assignee: string; + due_date: string; +} + +interface Column { + id: string; + title: string; + tasks: Task[]; +} + +export default function App() { + const [columns, setColumns] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Mock data + setTimeout(() => { + const mockTasks: Task[] = [ + { id: '1', name: 'Complete project proposal', status: 'To Do', priority: 'high', assignee: 'John Doe', due_date: '2024-02-15' }, + { id: '2', name: 'Review design mockups', status: 'To Do', priority: 'medium', assignee: 'Jane Smith', due_date: '2024-02-14' }, + { id: '3', name: 'Fix critical bug', status: 'In Progress', priority: 'urgent', assignee: 'Alice Williams', due_date: '2024-02-12' }, + { id: '4', name: 'Refactor API endpoints', status: 'In Progress', priority: 'high', assignee: 'Jane Smith', due_date: '2024-02-18' }, + { id: '5', name: 'Update documentation', status: 'In Review', priority: 'low', assignee: 'Bob Johnson', due_date: '2024-02-10' }, + { id: '6', name: 'Deploy to staging', status: 'Done', priority: 'medium', assignee: 'John Doe', due_date: '2024-02-08' }, + { id: '7', name: 'Write unit tests', status: 'To Do', priority: 'medium', assignee: 'Bob Johnson', due_date: '2024-02-20' }, + ]; + + const statuses = ['To Do', 'In Progress', 'In Review', 'Done']; + const cols = statuses.map(status => ({ + id: status, + title: status, + tasks: mockTasks.filter(task => task.status === status) + })); + setColumns(cols); + setLoading(false); + }, 500); + }, []); + + if (loading) { + return
Loading board...
; + } + + return ( +
+
+

📊 Task Board

+

Kanban board view organized by status

+
+ +
+ {columns.map(column => ( +
+
+

{column.title}

+ {column.tasks.length} +
+
+ {column.tasks.map(task => ( +
+
{task.name}
+
+ {task.priority} + 👤 {task.assignee} +
+
📅 {task.due_date}
+
+ ))} + {column.tasks.length === 0 && ( +
No tasks
+ )} +
+
+ ))} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/task-board/index.html b/servers/clickup/src/ui/react-app/task-board/index.html new file mode 100644 index 0000000..da83c96 --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-board/index.html @@ -0,0 +1,12 @@ + + + + + + Task Board - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/task-board/vite.config.ts b/servers/clickup/src/ui/react-app/task-board/vite.config.ts new file mode 100644 index 0000000..e9755eb --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-board/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3003, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/task-dashboard/App.tsx b/servers/clickup/src/ui/react-app/task-dashboard/App.tsx new file mode 100644 index 0000000..f6ae720 --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-dashboard/App.tsx @@ -0,0 +1,131 @@ +import React, { useState, useEffect } from 'react'; +import './styles.css'; + +// Shared Components +const Card: React.FC<{ title: string; value: number; color: string }> = ({ title, value, color }) => ( +
+

{title}

+
{value}
+
+); + +const ProgressBar: React.FC<{ label: string; value: number; max: number; color: string }> = ({ label, value, max, color }) => ( +
+
+ {label} + {value}/{max} +
+
+
+
+
+); + +interface Task { + id: string; + name: string; + status: string; + priority: string; + due_date: string; +} + +export default function App() { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Mock data - replace with actual MCP tool calls + setTimeout(() => { + setTasks([ + { id: '1', name: 'Complete project proposal', status: 'in progress', priority: 'high', due_date: '2024-02-15' }, + { id: '2', name: 'Review design mockups', status: 'open', priority: 'medium', due_date: '2024-02-14' }, + { id: '3', name: 'Update documentation', status: 'closed', priority: 'low', due_date: '2024-02-10' }, + { id: '4', name: 'Fix critical bug', status: 'in progress', priority: 'urgent', due_date: '2024-02-12' }, + { id: '5', name: 'Team meeting prep', status: 'open', priority: 'medium', due_date: '2024-02-16' }, + ]); + setLoading(false); + }, 500); + }, []); + + const statusCounts = tasks.reduce((acc, task) => { + acc[task.status] = (acc[task.status] || 0) + 1; + return acc; + }, {} as Record); + + const priorityCounts = tasks.reduce((acc, task) => { + acc[task.priority] = (acc[task.priority] || 0) + 1; + return acc; + }, {} as Record); + + const overdueTasks = tasks.filter(task => new Date(task.due_date) < new Date() && task.status !== 'closed'); + + if (loading) { + return
Loading dashboard...
; + } + + return ( +
+
+

📊 Task Dashboard

+

Overview of your tasks and progress

+
+ +
+ + + + +
+ +
+

Status Breakdown

+
+ + + +
+
+ +
+

Priority Distribution

+
+
+ Urgent + {priorityCounts['urgent'] || 0} +
+
+ High + {priorityCounts['high'] || 0} +
+
+ Medium + {priorityCounts['medium'] || 0} +
+
+ Low + {priorityCounts['low'] || 0} +
+
+
+ +
+

Overdue Tasks

+ {overdueTasks.length === 0 ? ( +

No overdue tasks! 🎉

+ ) : ( +
+ {overdueTasks.map(task => ( +
+
{task.name}
+
+ {task.priority} + Due: {task.due_date} +
+
+ ))} +
+ )} +
+
+ ); +} diff --git a/servers/clickup/src/ui/react-app/task-dashboard/index.html b/servers/clickup/src/ui/react-app/task-dashboard/index.html new file mode 100644 index 0000000..c66106f --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-dashboard/index.html @@ -0,0 +1,60 @@ + + + + + + Task Dashboard - ClickUp MCP + + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/task-dashboard/styles.css b/servers/clickup/src/ui/react-app/task-dashboard/styles.css new file mode 100644 index 0000000..bb14a7d --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-dashboard/styles.css @@ -0,0 +1 @@ +/* Styles are inline in index.html for self-contained deployment */ diff --git a/servers/clickup/src/ui/react-app/task-dashboard/vite.config.ts b/servers/clickup/src/ui/react-app/task-dashboard/vite.config.ts new file mode 100644 index 0000000..6a89cf7 --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-dashboard/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { + port: 3000, + open: true, + }, +}); diff --git a/servers/clickup/src/ui/react-app/task-detail/App.tsx b/servers/clickup/src/ui/react-app/task-detail/App.tsx new file mode 100644 index 0000000..85107cb --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-detail/App.tsx @@ -0,0 +1,208 @@ +import React, { useState, useEffect } from 'react'; + +interface Task { + id: string; + name: string; + description: string; + status: string; + priority: string; + due_date: string; + assignees: string[]; + tags: string[]; + custom_fields: Array<{ name: string; value: string }>; + subtasks: Array<{ id: string; name: string; completed: boolean }>; + comments: Array<{ id: string; author: string; text: string; timestamp: string }>; + time_entries: Array<{ id: string; duration: number; user: string; date: string }>; +} + +const TabButton: React.FC<{ active: boolean; onClick: () => void; children: React.ReactNode }> = ({ active, onClick, children }) => ( + +); + +export default function App() { + const [task, setTask] = useState(null); + const [activeTab, setActiveTab] = useState<'details' | 'subtasks' | 'comments' | 'time'>('details'); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Mock data - replace with actual MCP tool calls + setTimeout(() => { + setTask({ + id: '1', + name: 'Implement user authentication system', + description: 'Build a secure authentication system with OAuth2 support, JWT tokens, and password reset functionality.', + status: 'in progress', + priority: 'high', + due_date: '2024-02-20', + assignees: ['John Doe', 'Jane Smith'], + tags: ['backend', 'security', 'critical'], + custom_fields: [ + { name: 'Story Points', value: '8' }, + { name: 'Sprint', value: 'Sprint 5' }, + { name: 'Department', value: 'Engineering' }, + ], + subtasks: [ + { id: 's1', name: 'Design database schema', completed: true }, + { id: 's2', name: 'Implement OAuth2 flow', completed: true }, + { id: 's3', name: 'Add JWT token generation', completed: false }, + { id: 's4', name: 'Create password reset endpoint', completed: false }, + { id: 's5', name: 'Write unit tests', completed: false }, + ], + comments: [ + { id: 'c1', author: 'Jane Smith', text: 'Started working on the OAuth2 integration', timestamp: '2024-02-12T10:30:00Z' }, + { id: 'c2', author: 'John Doe', text: 'Database schema looks good, approved!', timestamp: '2024-02-13T14:15:00Z' }, + { id: 'c3', author: 'Jane Smith', text: 'Need to discuss JWT token expiration time with the team', timestamp: '2024-02-14T09:00:00Z' }, + ], + time_entries: [ + { id: 't1', duration: 7200, user: 'Jane Smith', date: '2024-02-12' }, + { id: 't2', duration: 5400, user: 'John Doe', date: '2024-02-13' }, + { id: 't3', duration: 3600, user: 'Jane Smith', date: '2024-02-14' }, + ], + }); + setLoading(false); + }, 500); + }, []); + + if (loading) { + return
Loading task details...
; + } + + if (!task) { + return
Task not found
; + } + + const totalTime = task.time_entries.reduce((sum, entry) => sum + entry.duration, 0); + const completedSubtasks = task.subtasks.filter(st => st.completed).length; + + return ( +
+
+
+

{task.name}

+
+ {task.status} + {task.priority} +
+
+

{task.description}

+
+ +
+
+ Due Date: {task.due_date} +
+
+ Assignees: {task.assignees.join(', ')} +
+
+ Tags: {task.tags.map(tag => {tag})} +
+
+ +
+ setActiveTab('details')}> + Details & Custom Fields + + setActiveTab('subtasks')}> + Subtasks ({completedSubtasks}/{task.subtasks.length}) + + setActiveTab('comments')}> + Comments ({task.comments.length}) + + setActiveTab('time')}> + Time Tracking ({(totalTime / 3600).toFixed(1)}h) + +
+ +
+ {activeTab === 'details' && ( +
+ {task.custom_fields.map(field => ( +
+ {field.name}: + {field.value} +
+ ))} +
+ )} + + {activeTab === 'subtasks' && ( +
+ {task.subtasks.map(subtask => ( +
+ + {subtask.name} +
+ ))} +
+ )} + + {activeTab === 'comments' && ( +
+ {task.comments.map(comment => ( +
+
+ {comment.author} + {new Date(comment.timestamp).toLocaleString()} +
+

{comment.text}

+
+ ))} +
+ )} + + {activeTab === 'time' && ( +
+ {task.time_entries.map(entry => ( +
+
{entry.user}
+
{(entry.duration / 3600).toFixed(2)}h
+
{entry.date}
+
+ ))} +
+ Total Time: {(totalTime / 3600).toFixed(2)} hours +
+
+ )} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/task-detail/index.html b/servers/clickup/src/ui/react-app/task-detail/index.html new file mode 100644 index 0000000..5bf0392 --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-detail/index.html @@ -0,0 +1,12 @@ + + + + + + Task Detail - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/task-detail/vite.config.ts b/servers/clickup/src/ui/react-app/task-detail/vite.config.ts new file mode 100644 index 0000000..6d8ba5b --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-detail/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3001, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/task-grid/App.tsx b/servers/clickup/src/ui/react-app/task-grid/App.tsx new file mode 100644 index 0000000..eb3de99 --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-grid/App.tsx @@ -0,0 +1,174 @@ +import React, { useState, useEffect } from 'react'; + +interface Task { + id: string; + name: string; + status: string; + priority: string; + assignee: string; + due_date: string; + tags: string[]; +} + +type SortKey = 'name' | 'status' | 'priority' | 'due_date'; + +export default function App() { + const [tasks, setTasks] = useState([]); + const [sortKey, setSortKey] = useState('due_date'); + const [sortAsc, setSortAsc] = useState(true); + const [filterStatus, setFilterStatus] = useState('all'); + const [filterPriority, setFilterPriority] = useState('all'); + const [searchTerm, setSearchTerm] = useState(''); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Mock data + setTimeout(() => { + setTasks([ + { id: '1', name: 'Complete project proposal', status: 'in progress', priority: 'high', assignee: 'John Doe', due_date: '2024-02-15', tags: ['design', 'planning'] }, + { id: '2', name: 'Review design mockups', status: 'open', priority: 'medium', assignee: 'Jane Smith', due_date: '2024-02-14', tags: ['design'] }, + { id: '3', name: 'Update documentation', status: 'closed', priority: 'low', assignee: 'Bob Johnson', due_date: '2024-02-10', tags: ['docs'] }, + { id: '4', name: 'Fix critical bug', status: 'in progress', priority: 'urgent', assignee: 'Alice Williams', due_date: '2024-02-12', tags: ['bug', 'urgent'] }, + { id: '5', name: 'Team meeting prep', status: 'open', priority: 'medium', assignee: 'John Doe', due_date: '2024-02-16', tags: ['meeting'] }, + { id: '6', name: 'Refactor API endpoints', status: 'in progress', priority: 'high', assignee: 'Jane Smith', due_date: '2024-02-18', tags: ['backend', 'refactor'] }, + { id: '7', name: 'Write unit tests', status: 'open', priority: 'medium', assignee: 'Bob Johnson', due_date: '2024-02-20', tags: ['testing'] }, + ]); + setLoading(false); + }, 500); + }, []); + + const handleSort = (key: SortKey) => { + if (sortKey === key) { + setSortAsc(!sortAsc); + } else { + setSortKey(key); + setSortAsc(true); + } + }; + + const filteredTasks = tasks.filter(task => { + if (filterStatus !== 'all' && task.status !== filterStatus) return false; + if (filterPriority !== 'all' && task.priority !== filterPriority) return false; + if (searchTerm && !task.name.toLowerCase().includes(searchTerm.toLowerCase())) return false; + return true; + }); + + const sortedTasks = [...filteredTasks].sort((a, b) => { + let aVal = a[sortKey]; + let bVal = b[sortKey]; + if (sortKey === 'due_date') { + aVal = new Date(a.due_date).getTime(); + bVal = new Date(b.due_date).getTime(); + } + if (aVal < bVal) return sortAsc ? -1 : 1; + if (aVal > bVal) return sortAsc ? 1 : -1; + return 0; + }); + + if (loading) { + return
Loading tasks...
; + } + + return ( +
+
+

📋 Task Grid

+

Sortable and filterable task list

+
+ +
+ setSearchTerm(e.target.value)} + className="search-input" + /> + + +
+ +
+ + + + + + + + + + + + + {sortedTasks.map(task => ( + + + + + + + + + ))} + +
handleSort('name')} className="sortable"> + Task Name {sortKey === 'name' && (sortAsc ? '↑' : '↓')} + handleSort('status')} className="sortable"> + Status {sortKey === 'status' && (sortAsc ? '↑' : '↓')} + handleSort('priority')} className="sortable"> + Priority {sortKey === 'priority' && (sortAsc ? '↑' : '↓')} + Assignee handleSort('due_date')} className="sortable"> + Due Date {sortKey === 'due_date' && (sortAsc ? '↑' : '↓')} + Tags
{task.name}{task.status}{task.priority}{task.assignee}{task.due_date} + {task.tags.map(tag => {tag})} +
+ {sortedTasks.length === 0 && ( +
No tasks match your filters
+ )} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/task-grid/index.html b/servers/clickup/src/ui/react-app/task-grid/index.html new file mode 100644 index 0000000..83ab1ed --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-grid/index.html @@ -0,0 +1,12 @@ + + + + + + Task Grid - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/task-grid/vite.config.ts b/servers/clickup/src/ui/react-app/task-grid/vite.config.ts new file mode 100644 index 0000000..da8272c --- /dev/null +++ b/servers/clickup/src/ui/react-app/task-grid/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3002, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/template-gallery/App.tsx b/servers/clickup/src/ui/react-app/template-gallery/App.tsx new file mode 100644 index 0000000..5d37b80 --- /dev/null +++ b/servers/clickup/src/ui/react-app/template-gallery/App.tsx @@ -0,0 +1,120 @@ +import React, { useState, useEffect } from 'react'; + +interface Template { + id: string; + name: string; + description: string; + category: string; + tasks: number; + icon: string; + color: string; +} + +export default function App() { + const [templates, setTemplates] = useState([]); + const [selectedCategory, setSelectedCategory] = useState('all'); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setTemplates([ + { id: '1', name: 'Sprint Planning', description: 'Standard two-week sprint template with planning, execution, and review phases', category: 'Agile', tasks: 15, icon: '🏃', color: '#3b82f6' }, + { id: '2', name: 'Product Launch', description: 'Comprehensive product launch checklist with marketing, engineering, and operations tasks', category: 'Product', tasks: 32, icon: '🚀', color: '#10b981' }, + { id: '3', name: 'Bug Tracking', description: 'Structured bug reporting and resolution workflow', category: 'Engineering', tasks: 8, icon: '🐛', color: '#ef4444' }, + { id: '4', name: 'Content Calendar', description: 'Monthly content planning and publication schedule', category: 'Marketing', tasks: 20, icon: '📅', color: '#a78bfa' }, + { id: '5', name: 'Onboarding Checklist', description: 'New employee onboarding with all necessary setup tasks', category: 'HR', tasks: 18, icon: '👋', color: '#f59e0b' }, + { id: '6', name: 'Design Sprint', description: 'Five-day design sprint methodology for rapid prototyping', category: 'Design', tasks: 25, icon: '🎨', color: '#ec4899' }, + { id: '7', name: 'Sales Pipeline', description: 'Lead tracking and sales funnel management', category: 'Sales', tasks: 12, icon: '💰', color: '#14b8a6' }, + { id: '8', name: 'Event Planning', description: 'Complete event planning from concept to execution', category: 'Operations', tasks: 28, icon: '🎉', color: '#8b5cf6' }, + { id: '9', name: 'Code Review', description: 'Systematic code review process with quality checkpoints', category: 'Engineering', tasks: 10, icon: '👀', color: '#6366f1' }, + { id: '10', name: 'Customer Feedback', description: 'Collect, analyze, and act on customer feedback', category: 'Product', tasks: 14, icon: '💬', color: '#06b6d4' }, + ]); + setLoading(false); + }, 500); + }, []); + + const categories = ['all', ...Array.from(new Set(templates.map(t => t.category)))]; + const filteredTemplates = selectedCategory === 'all' + ? templates + : templates.filter(t => t.category === selectedCategory); + + if (loading) return
Loading templates...
; + + return ( +
+
+

📋 Template Gallery

+

Browse and use pre-built project templates

+
+ +
+ {categories.map(category => ( + + ))} +
+ +
+ {filteredTemplates.map(template => ( +
+
+ {template.icon} +
+
+
+

{template.name}

+ + {template.category} + +
+

{template.description}

+
+ 📝 {template.tasks} tasks + +
+
+
+ ))} +
+ + {filteredTemplates.length === 0 && ( +
No templates found in this category
+ )} + + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/template-gallery/index.html b/servers/clickup/src/ui/react-app/template-gallery/index.html new file mode 100644 index 0000000..138746a --- /dev/null +++ b/servers/clickup/src/ui/react-app/template-gallery/index.html @@ -0,0 +1,12 @@ + + + + + + Template Gallery - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/template-gallery/vite.config.ts b/servers/clickup/src/ui/react-app/template-gallery/vite.config.ts new file mode 100644 index 0000000..10fed9d --- /dev/null +++ b/servers/clickup/src/ui/react-app/template-gallery/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3016, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/time-dashboard/App.tsx b/servers/clickup/src/ui/react-app/time-dashboard/App.tsx new file mode 100644 index 0000000..1f4a97b --- /dev/null +++ b/servers/clickup/src/ui/react-app/time-dashboard/App.tsx @@ -0,0 +1,160 @@ +import React, { useState, useEffect } from 'react'; + +interface TimeData { + totalHours: number; + todayHours: number; + weekHours: number; + byUser: Array<{ user: string; hours: number }>; + byProject: Array<{ project: string; hours: number }>; + recentEntries: Array<{ id: string; user: string; task: string; hours: number; date: string }>; +} + +export default function App() { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setData({ + totalHours: 147.5, + todayHours: 12.5, + weekHours: 42.0, + byUser: [ + { user: 'John Doe', hours: 42.5 }, + { user: 'Jane Smith', hours: 38.0 }, + { user: 'Bob Johnson', hours: 35.5 }, + { user: 'Alice Williams', hours: 31.5 }, + ], + byProject: [ + { project: 'Product Development', hours: 65.0 }, + { project: 'Bug Fixes', hours: 28.5 }, + { project: 'Research', hours: 22.0 }, + { project: 'Documentation', hours: 18.5 }, + { project: 'Meetings', hours: 13.5 }, + ], + recentEntries: [ + { id: '1', user: 'John Doe', task: 'API Integration', hours: 3.5, date: '2024-02-15' }, + { id: '2', user: 'Jane Smith', task: 'UI Design', hours: 2.0, date: '2024-02-15' }, + { id: '3', user: 'Bob Johnson', task: 'Code Review', hours: 1.5, date: '2024-02-15' }, + { id: '4', user: 'Alice Williams', task: 'Testing', hours: 4.0, date: '2024-02-14' }, + { id: '5', user: 'John Doe', task: 'Database Optimization', hours: 2.5, date: '2024-02-14' }, + ] + }); + setLoading(false); + }, 500); + }, []); + + if (loading) return
Loading time dashboard...
; + if (!data) return
No data available
; + + return ( +
+
+

⏱️ Time Tracking Dashboard

+

Overview of time logged across projects and team members

+
+ +
+
+
+
{data.todayHours}h
+
Today
+
+
+
📅
+
{data.weekHours}h
+
This Week
+
+
+
📊
+
{data.totalHours}h
+
Total Tracked
+
+
+
👥
+
{data.byUser.length}
+
Active Users
+
+
+ +
+
+

Time by User

+
+ {data.byUser.map(item => ( +
+
{item.user}
+
+
u.hours))) * 100}%` }}>
+
+
{item.hours}h
+
+ ))} +
+
+ +
+

Time by Project

+
+ {data.byProject.map(item => ( +
+
{item.project}
+
+
p.hours))) * 100}%` }}>
+
+
{item.hours}h
+
+ ))} +
+
+
+ +
+

Recent Time Entries

+
+ {data.recentEntries.map(entry => ( +
+
{entry.user}
+
{entry.task}
+
{entry.hours}h
+
{entry.date}
+
+ ))} +
+
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/time-dashboard/index.html b/servers/clickup/src/ui/react-app/time-dashboard/index.html new file mode 100644 index 0000000..1a38ce9 --- /dev/null +++ b/servers/clickup/src/ui/react-app/time-dashboard/index.html @@ -0,0 +1,12 @@ + + + + + + Time Dashboard - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/time-dashboard/vite.config.ts b/servers/clickup/src/ui/react-app/time-dashboard/vite.config.ts new file mode 100644 index 0000000..3f4b0a5 --- /dev/null +++ b/servers/clickup/src/ui/react-app/time-dashboard/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3009, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/time-entries/App.tsx b/servers/clickup/src/ui/react-app/time-entries/App.tsx new file mode 100644 index 0000000..e903d78 --- /dev/null +++ b/servers/clickup/src/ui/react-app/time-entries/App.tsx @@ -0,0 +1,146 @@ +import React, { useState, useEffect } from 'react'; + +interface TimeEntry { + id: string; + user: string; + task: string; + taskId: string; + duration: number; + date: string; + description: string; + billable: boolean; +} + +export default function App() { + const [entries, setEntries] = useState([]); + const [filterUser, setFilterUser] = useState('all'); + const [filterBillable, setFilterBillable] = useState('all'); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setEntries([ + { id: '1', user: 'John Doe', task: 'API Integration', taskId: 'T-123', duration: 12600, date: '2024-02-15', description: 'Integrated payment gateway API', billable: true }, + { id: '2', user: 'Jane Smith', task: 'UI Design', taskId: 'T-124', duration: 7200, date: '2024-02-15', description: 'Designed new dashboard layout', billable: true }, + { id: '3', user: 'Bob Johnson', task: 'Code Review', taskId: 'T-125', duration: 5400, date: '2024-02-15', description: 'Reviewed pull requests', billable: false }, + { id: '4', user: 'Alice Williams', task: 'Testing', taskId: 'T-126', duration: 14400, date: '2024-02-14', description: 'QA testing for new features', billable: true }, + { id: '5', user: 'John Doe', task: 'Database Optimization', taskId: 'T-127', duration: 9000, date: '2024-02-14', description: 'Optimized database queries', billable: true }, + { id: '6', user: 'Jane Smith', task: 'Team Meeting', taskId: 'T-128', duration: 3600, date: '2024-02-14', description: 'Sprint planning meeting', billable: false }, + { id: '7', user: 'Bob Johnson', task: 'Documentation', taskId: 'T-129', duration: 7200, date: '2024-02-13', description: 'Updated API documentation', billable: true }, + { id: '8', user: 'Alice Williams', task: 'Bug Fixes', taskId: 'T-130', duration: 10800, date: '2024-02-13', description: 'Fixed critical production bugs', billable: true }, + ]); + setLoading(false); + }, 500); + }, []); + + const filteredEntries = entries.filter(entry => { + if (filterUser !== 'all' && entry.user !== filterUser) return false; + if (filterBillable === 'billable' && !entry.billable) return false; + if (filterBillable === 'non-billable' && entry.billable) return false; + return true; + }); + + const totalHours = filteredEntries.reduce((sum, entry) => sum + entry.duration, 0) / 3600; + const billableHours = filteredEntries.filter(e => e.billable).reduce((sum, entry) => sum + entry.duration, 0) / 3600; + + const uniqueUsers = Array.from(new Set(entries.map(e => e.user))); + + if (loading) return
Loading time entries...
; + + return ( +
+
+

⏰ Time Entries

+

Detailed time entry list with task associations

+
+ +
+
+ Total Hours: + {totalHours.toFixed(2)}h +
+
+ Billable: + {billableHours.toFixed(2)}h +
+
+ Non-Billable: + {(totalHours - billableHours).toFixed(2)}h +
+
+ +
+ + +
+ +
+
+
User
+
Task
+
Description
+
Duration
+
Date
+
Billable
+
+ {filteredEntries.map(entry => ( +
+
{entry.user}
+
+
{entry.task}
+
{entry.taskId}
+
+
{entry.description}
+
{(entry.duration / 3600).toFixed(2)}h
+
{entry.date}
+
+ {entry.billable ? + ✓ Billable : + ○ Non-Billable + } +
+
+ ))} +
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/time-entries/index.html b/servers/clickup/src/ui/react-app/time-entries/index.html new file mode 100644 index 0000000..5eb2733 --- /dev/null +++ b/servers/clickup/src/ui/react-app/time-entries/index.html @@ -0,0 +1,12 @@ + + + + + + Time Entries - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/time-entries/vite.config.ts b/servers/clickup/src/ui/react-app/time-entries/vite.config.ts new file mode 100644 index 0000000..861fb9e --- /dev/null +++ b/servers/clickup/src/ui/react-app/time-entries/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3010, open: true }, +}); diff --git a/servers/clickup/src/ui/react-app/workspace-overview/App.tsx b/servers/clickup/src/ui/react-app/workspace-overview/App.tsx new file mode 100644 index 0000000..ac01d83 --- /dev/null +++ b/servers/clickup/src/ui/react-app/workspace-overview/App.tsx @@ -0,0 +1,187 @@ +import React, { useState, useEffect } from 'react'; + +interface WorkspaceStats { + name: string; + spaces: number; + lists: number; + tasks: number; + members: number; + activeProjects: number; + completionRate: number; + recentActivity: Array<{ + id: string; + user: string; + action: string; + target: string; + timestamp: string; + }>; + topSpaces: Array<{ + id: string; + name: string; + taskCount: number; + completion: number; + }>; +} + +export default function App() { + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setTimeout(() => { + setStats({ + name: 'Acme Corporation', + spaces: 12, + lists: 48, + tasks: 327, + members: 24, + activeProjects: 15, + completionRate: 68, + recentActivity: [ + { id: '1', user: 'John Doe', action: 'completed', target: 'API Integration', timestamp: '2 minutes ago' }, + { id: '2', user: 'Jane Smith', action: 'commented on', target: 'Design System', timestamp: '15 minutes ago' }, + { id: '3', user: 'Bob Johnson', action: 'created', target: 'Bug Fix Sprint', timestamp: '1 hour ago' }, + { id: '4', user: 'Alice Williams', action: 'assigned', target: 'Security Audit to John', timestamp: '2 hours ago' }, + { id: '5', user: 'Charlie Brown', action: 'updated', target: 'Project Timeline', timestamp: '3 hours ago' }, + ], + topSpaces: [ + { id: 's1', name: 'Product Development', taskCount: 89, completion: 72 }, + { id: 's2', name: 'Marketing', taskCount: 56, completion: 65 }, + { id: 's3', name: 'Engineering', taskCount: 112, completion: 58 }, + { id: 's4', name: 'Design', taskCount: 42, completion: 81 }, + { id: 's5', name: 'Operations', taskCount: 28, completion: 75 }, + ] + }); + setLoading(false); + }, 500); + }, []); + + if (loading) return
Loading workspace...
; + if (!stats) return
Workspace not found
; + + return ( +
+
+

🏢 {stats.name}

+

High-level workspace statistics and activity

+
+ +
+
+
🚀
+
{stats.spaces}
+
Spaces
+
+
+
📋
+
{stats.lists}
+
Lists
+
+
+
+
{stats.tasks}
+
Tasks
+
+
+
👥
+
{stats.members}
+
Members
+
+
+ +
+
+

Active Projects

+
{stats.activeProjects}
+

Projects currently in progress

+
+
+

Completion Rate

+
{stats.completionRate}%
+
+
+
+
+
+ +
+
+

Top Spaces by Task Count

+
+ {stats.topSpaces.map(space => ( +
+
+
{space.name}
+
+ {space.taskCount} tasks + {space.completion}% complete +
+
+
+
+
+
+
+
+ ))} +
+
+ +
+

Recent Activity

+
+ {stats.recentActivity.map(activity => ( +
+
{activity.user[0]}
+
+
+ {activity.user} {activity.action} {activity.target} +
+
{activity.timestamp}
+
+
+ ))} +
+
+
+ + +
+ ); +} diff --git a/servers/clickup/src/ui/react-app/workspace-overview/index.html b/servers/clickup/src/ui/react-app/workspace-overview/index.html new file mode 100644 index 0000000..b41c719 --- /dev/null +++ b/servers/clickup/src/ui/react-app/workspace-overview/index.html @@ -0,0 +1,12 @@ + + + + + + Workspace Overview - ClickUp MCP + + +
+ + + diff --git a/servers/clickup/src/ui/react-app/workspace-overview/vite.config.ts b/servers/clickup/src/ui/react-app/workspace-overview/vite.config.ts new file mode 100644 index 0000000..00ec85f --- /dev/null +++ b/servers/clickup/src/ui/react-app/workspace-overview/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 3017, open: true }, +}); diff --git a/servers/keap/package.json b/servers/keap/package.json index 798f793..472bb66 100644 --- a/servers/keap/package.json +++ b/servers/keap/package.json @@ -1,20 +1,38 @@ { - "name": "mcp-server-keap", + "name": "@mcpengine/keap-server", "version": "1.0.0", + "description": "Keap (Infusionsoft) MCP Server - Complete CRM automation", "type": "module", - "main": "dist/index.js", + "main": "dist/main.js", + "bin": { + "keap-mcp": "./dist/main.js" + }, "scripts": { "build": "tsc", - "start": "node dist/index.js", - "dev": "tsx src/index.ts" + "dev": "tsc --watch", + "start": "node dist/main.js", + "prepare": "npm run build" }, + "keywords": [ + "mcp", + "keap", + "infusionsoft", + "crm", + "marketing-automation" + ], + "author": "MCPEngine", + "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^0.5.0", - "zod": "^3.22.4" + "@modelcontextprotocol/sdk": "^1.0.4", + "axios": "^1.7.9", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "lucide-react": "^0.468.0" }, "devDependencies": { - "@types/node": "^20.10.0", - "tsx": "^4.7.0", - "typescript": "^5.3.0" + "@types/node": "^22.10.5", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "typescript": "^5.7.3" } } diff --git a/servers/keap/src/clients/keap.ts b/servers/keap/src/clients/keap.ts new file mode 100644 index 0000000..4c6c991 --- /dev/null +++ b/servers/keap/src/clients/keap.ts @@ -0,0 +1,305 @@ +import axios, { AxiosInstance, AxiosError } from 'axios'; +import type { + KeapConfig, + KeapError, + PaginatedResponse, + Contact, + Deal, + Company, + Task, + Appointment, + Campaign, + Email, + Order, + Product, + Tag, + Note, + Automation, +} from '../types/keap.js'; + +export class KeapClient { + private client: AxiosInstance; + private baseUrl: string; + + constructor(config: KeapConfig) { + this.baseUrl = config.baseUrl || 'https://api.infusionsoft.com/crm/rest/v2'; + + this.client = axios.create({ + baseURL: this.baseUrl, + headers: { + 'Authorization': `Bearer ${config.accessToken}`, + 'Content-Type': 'application/json', + }, + timeout: 30000, + }); + + // Response interceptor for error handling + this.client.interceptors.response.use( + (response) => response, + (error: AxiosError) => { + if (error.response?.data) { + const keapError = error.response.data; + const message = keapError.message || keapError.fault?.faultstring || 'Unknown Keap API error'; + throw new Error(`Keap API Error: ${message}`); + } + throw new Error(`Request failed: ${error.message}`); + } + ); + } + + // Generic paginated request + async paginate(endpoint: string, params: Record = {}): Promise { + const results: T[] = []; + let nextUrl: string | undefined = undefined; + let hasMore = true; + + const limit = params.limit || 1000; + const requestParams = { ...params, limit }; + + while (hasMore) { + const response = await this.client.get>( + nextUrl || endpoint, + nextUrl ? undefined : { params: requestParams } + ); + + results.push(...response.data.data); + + if (response.data.next && results.length < (params.max_results || Infinity)) { + nextUrl = response.data.next; + } else { + hasMore = false; + } + } + + return results; + } + + // Generic GET request + async get(endpoint: string, params?: Record): Promise { + const response = await this.client.get(endpoint, { params }); + return response.data; + } + + // Generic POST request + async post(endpoint: string, data: any): Promise { + const response = await this.client.post(endpoint, data); + return response.data; + } + + // Generic PUT request + async put(endpoint: string, data: any): Promise { + const response = await this.client.put(endpoint, data); + return response.data; + } + + // Generic PATCH request + async patch(endpoint: string, data: any): Promise { + const response = await this.client.patch(endpoint, data); + return response.data; + } + + // Generic DELETE request + async delete(endpoint: string): Promise { + await this.client.delete(endpoint); + } + + // Contacts + async listContacts(params?: Record): Promise { + return this.paginate('/contacts', params); + } + + async getContact(contactId: number): Promise { + return this.get(`/contacts/${contactId}`); + } + + async createContact(data: Partial): Promise { + return this.post('/contacts', data); + } + + async updateContact(contactId: number, data: Partial): Promise { + return this.patch(`/contacts/${contactId}`, data); + } + + async deleteContact(contactId: number): Promise { + return this.delete(`/contacts/${contactId}`); + } + + // Deals + async listDeals(params?: Record): Promise { + return this.paginate('/opportunities', params); + } + + async getDeal(dealId: number): Promise { + return this.get(`/opportunities/${dealId}`); + } + + async createDeal(data: Partial): Promise { + return this.post('/opportunities', data); + } + + async updateDeal(dealId: number, data: Partial): Promise { + return this.patch(`/opportunities/${dealId}`, data); + } + + async deleteDeal(dealId: number): Promise { + return this.delete(`/opportunities/${dealId}`); + } + + // Companies + async listCompanies(params?: Record): Promise { + return this.paginate('/companies', params); + } + + async getCompany(companyId: number): Promise { + return this.get(`/companies/${companyId}`); + } + + async createCompany(data: Partial): Promise { + return this.post('/companies', data); + } + + async updateCompany(companyId: number, data: Partial): Promise { + return this.patch(`/companies/${companyId}`, data); + } + + async deleteCompany(companyId: number): Promise { + return this.delete(`/companies/${companyId}`); + } + + // Tasks + async listTasks(params?: Record): Promise { + return this.paginate('/tasks', params); + } + + async getTask(taskId: number): Promise { + return this.get(`/tasks/${taskId}`); + } + + async createTask(data: Partial): Promise { + return this.post('/tasks', data); + } + + async updateTask(taskId: number, data: Partial): Promise { + return this.patch(`/tasks/${taskId}`, data); + } + + async deleteTask(taskId: number): Promise { + return this.delete(`/tasks/${taskId}`); + } + + // Appointments + async listAppointments(params?: Record): Promise { + return this.paginate('/appointments', params); + } + + async getAppointment(appointmentId: number): Promise { + return this.get(`/appointments/${appointmentId}`); + } + + async createAppointment(data: Partial): Promise { + return this.post('/appointments', data); + } + + async updateAppointment(appointmentId: number, data: Partial): Promise { + return this.patch(`/appointments/${appointmentId}`, data); + } + + async deleteAppointment(appointmentId: number): Promise { + return this.delete(`/appointments/${appointmentId}`); + } + + // Campaigns + async listCampaigns(params?: Record): Promise { + return this.paginate('/campaigns', params); + } + + async getCampaign(campaignId: number): Promise { + return this.get(`/campaigns/${campaignId}`); + } + + // Emails + async listEmails(params?: Record): Promise { + return this.paginate('/emails', params); + } + + async getEmail(emailId: number): Promise { + return this.get(`/emails/${emailId}`); + } + + async createEmail(data: Partial): Promise { + return this.post('/emails', data); + } + + async sendEmail(data: Partial): Promise { + return this.post('/emails/send', data); + } + + // Orders + async listOrders(params?: Record): Promise { + return this.paginate('/orders', params); + } + + async getOrder(orderId: number): Promise { + return this.get(`/orders/${orderId}`); + } + + async createOrder(data: Partial): Promise { + return this.post('/orders', data); + } + + // Products + async listProducts(params?: Record): Promise { + return this.paginate('/products', params); + } + + async getProduct(productId: number): Promise { + return this.get(`/products/${productId}`); + } + + async createProduct(data: Partial): Promise { + return this.post('/products', data); + } + + async updateProduct(productId: number, data: Partial): Promise { + return this.patch(`/products/${productId}`, data); + } + + async deleteProduct(productId: number): Promise { + return this.delete(`/products/${productId}`); + } + + // Tags + async listTags(params?: Record): Promise { + return this.paginate('/tags', params); + } + + async getTag(tagId: number): Promise { + return this.get(`/tags/${tagId}`); + } + + async createTag(data: Partial): Promise { + return this.post('/tags', data); + } + + async deleteTag(tagId: number): Promise { + return this.delete(`/tags/${tagId}`); + } + + // Notes + async listNotes(params?: Record): Promise { + return this.paginate('/notes', params); + } + + async createNote(data: Partial): Promise { + return this.post('/notes', data); + } + + // Automations + async listAutomations(params?: Record): Promise { + return this.paginate('/campaigns', params); + } + + async getAutomation(automationId: number): Promise { + return this.get(`/campaigns/${automationId}`); + } +} diff --git a/servers/keap/src/index.ts b/servers/keap/src/index.ts deleted file mode 100644 index 01fb7cc..0000000 --- a/servers/keap/src/index.ts +++ /dev/null @@ -1,430 +0,0 @@ -#!/usr/bin/env node -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { - CallToolRequestSchema, - ListToolsRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; - -// ============================================ -// CONFIGURATION -// ============================================ -const MCP_NAME = "keap"; -const MCP_VERSION = "1.0.0"; -const API_BASE_URL = "https://api.infusionsoft.com/crm/rest/v1"; - -// ============================================ -// API CLIENT - Keap uses OAuth2 Bearer token -// ============================================ -class KeapClient { - private accessToken: string; - private baseUrl: string; - - constructor(accessToken: string) { - this.accessToken = accessToken; - this.baseUrl = API_BASE_URL; - } - - async request(endpoint: string, options: RequestInit = {}) { - const url = `${this.baseUrl}${endpoint}`; - const response = await fetch(url, { - ...options, - headers: { - "Authorization": `Bearer ${this.accessToken}`, - "Content-Type": "application/json", - ...options.headers, - }, - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Keap API error: ${response.status} ${response.statusText} - ${errorText}`); - } - - if (response.status === 204) { - return { success: true }; - } - - return response.json(); - } - - async get(endpoint: string) { - return this.request(endpoint, { method: "GET" }); - } - - async post(endpoint: string, data: any) { - return this.request(endpoint, { - method: "POST", - body: JSON.stringify(data), - }); - } - - async put(endpoint: string, data: any) { - return this.request(endpoint, { - method: "PUT", - body: JSON.stringify(data), - }); - } - - async patch(endpoint: string, data: any) { - return this.request(endpoint, { - method: "PATCH", - body: JSON.stringify(data), - }); - } - - async delete(endpoint: string) { - return this.request(endpoint, { method: "DELETE" }); - } -} - -// ============================================ -// TOOL DEFINITIONS -// ============================================ -const tools = [ - { - name: "list_contacts", - description: "List contacts with optional filtering and pagination", - inputSchema: { - type: "object" as const, - properties: { - limit: { type: "number", description: "Max results to return (default 50, max 1000)" }, - offset: { type: "number", description: "Pagination offset" }, - email: { type: "string", description: "Filter by email address" }, - given_name: { type: "string", description: "Filter by first name" }, - family_name: { type: "string", description: "Filter by last name" }, - order: { type: "string", description: "Field to order by (e.g., 'email', 'date_created')" }, - order_direction: { type: "string", enum: ["ASCENDING", "DESCENDING"], description: "Sort direction" }, - since: { type: "string", description: "Return contacts modified since this date (ISO 8601)" }, - until: { type: "string", description: "Return contacts modified before this date (ISO 8601)" }, - }, - }, - }, - { - name: "get_contact", - description: "Get a specific contact by ID with full details", - inputSchema: { - type: "object" as const, - properties: { - id: { type: "number", description: "Contact ID" }, - optional_properties: { - type: "array", - items: { type: "string" }, - description: "Additional fields to include: custom_fields, fax_numbers, invoices, etc.", - }, - }, - required: ["id"], - }, - }, - { - name: "create_contact", - description: "Create a new contact in Keap", - inputSchema: { - type: "object" as const, - properties: { - email_addresses: { - type: "array", - items: { - type: "object", - properties: { - email: { type: "string" }, - field: { type: "string", enum: ["EMAIL1", "EMAIL2", "EMAIL3"] }, - }, - }, - description: "Email addresses for the contact", - }, - given_name: { type: "string", description: "First name" }, - family_name: { type: "string", description: "Last name" }, - phone_numbers: { - type: "array", - items: { - type: "object", - properties: { - number: { type: "string" }, - field: { type: "string", enum: ["PHONE1", "PHONE2", "PHONE3", "PHONE4", "PHONE5"] }, - }, - }, - description: "Phone numbers", - }, - addresses: { - type: "array", - items: { - type: "object", - properties: { - line1: { type: "string" }, - line2: { type: "string" }, - locality: { type: "string", description: "City" }, - region: { type: "string", description: "State/Province" }, - postal_code: { type: "string" }, - country_code: { type: "string" }, - field: { type: "string", enum: ["BILLING", "SHIPPING", "OTHER"] }, - }, - }, - description: "Addresses", - }, - company: { - type: "object", - properties: { - company_name: { type: "string" }, - }, - description: "Company information", - }, - job_title: { type: "string", description: "Job title" }, - lead_source_id: { type: "number", description: "Lead source ID" }, - opt_in_reason: { type: "string", description: "Reason for opting in to marketing" }, - source_type: { type: "string", enum: ["WEBFORM", "LANDINGPAGE", "IMPORT", "MANUAL", "API", "OTHER"], description: "Source type" }, - custom_fields: { - type: "array", - items: { - type: "object", - properties: { - id: { type: "number" }, - content: { type: "string" }, - }, - }, - description: "Custom field values", - }, - }, - }, - }, - { - name: "update_contact", - description: "Update an existing contact", - inputSchema: { - type: "object" as const, - properties: { - id: { type: "number", description: "Contact ID" }, - email_addresses: { type: "array", items: { type: "object" }, description: "Updated email addresses" }, - given_name: { type: "string", description: "First name" }, - family_name: { type: "string", description: "Last name" }, - phone_numbers: { type: "array", items: { type: "object" }, description: "Phone numbers" }, - addresses: { type: "array", items: { type: "object" }, description: "Addresses" }, - company: { type: "object", description: "Company information" }, - job_title: { type: "string", description: "Job title" }, - custom_fields: { type: "array", items: { type: "object" }, description: "Custom field values" }, - }, - required: ["id"], - }, - }, - { - name: "list_opportunities", - description: "List sales opportunities/deals", - inputSchema: { - type: "object" as const, - properties: { - limit: { type: "number", description: "Max results (default 50, max 1000)" }, - offset: { type: "number", description: "Pagination offset" }, - user_id: { type: "number", description: "Filter by assigned user ID" }, - stage_id: { type: "number", description: "Filter by pipeline stage ID" }, - search_term: { type: "string", description: "Search opportunities by title" }, - order: { type: "string", description: "Field to order by" }, - }, - }, - }, - { - name: "list_tasks", - description: "List tasks with optional filtering", - inputSchema: { - type: "object" as const, - properties: { - limit: { type: "number", description: "Max results (default 50, max 1000)" }, - offset: { type: "number", description: "Pagination offset" }, - contact_id: { type: "number", description: "Filter by contact ID" }, - user_id: { type: "number", description: "Filter by assigned user ID" }, - completed: { type: "boolean", description: "Filter by completion status" }, - since: { type: "string", description: "Tasks created/updated since (ISO 8601)" }, - until: { type: "string", description: "Tasks created/updated before (ISO 8601)" }, - order: { type: "string", description: "Field to order by" }, - }, - }, - }, - { - name: "create_task", - description: "Create a new task", - inputSchema: { - type: "object" as const, - properties: { - title: { type: "string", description: "Task title (required)" }, - description: { type: "string", description: "Task description" }, - contact: { - type: "object", - properties: { - id: { type: "number" }, - }, - description: "Contact to associate the task with", - }, - due_date: { type: "string", description: "Due date in ISO 8601 format" }, - priority: { type: "number", description: "Priority (1-5, 5 being highest)" }, - type: { type: "string", description: "Task type (e.g., 'Call', 'Email', 'Appointment', 'Other')" }, - user_id: { type: "number", description: "User ID to assign the task to" }, - remind_time: { type: "number", description: "Reminder time in minutes before due date" }, - }, - required: ["title"], - }, - }, - { - name: "list_tags", - description: "List all tags available in the account", - inputSchema: { - type: "object" as const, - properties: { - limit: { type: "number", description: "Max results (default 50, max 1000)" }, - offset: { type: "number", description: "Pagination offset" }, - category: { type: "number", description: "Filter by tag category ID" }, - name: { type: "string", description: "Filter by tag name (partial match)" }, - }, - }, - }, -]; - -// ============================================ -// TOOL HANDLERS -// ============================================ -async function handleTool(client: KeapClient, name: string, args: any) { - switch (name) { - case "list_contacts": { - const params = new URLSearchParams(); - if (args.limit) params.append("limit", args.limit.toString()); - if (args.offset) params.append("offset", args.offset.toString()); - if (args.email) params.append("email", args.email); - if (args.given_name) params.append("given_name", args.given_name); - if (args.family_name) params.append("family_name", args.family_name); - if (args.order) params.append("order", args.order); - if (args.order_direction) params.append("order_direction", args.order_direction); - if (args.since) params.append("since", args.since); - if (args.until) params.append("until", args.until); - const query = params.toString(); - return await client.get(`/contacts${query ? `?${query}` : ""}`); - } - - case "get_contact": { - const { id, optional_properties } = args; - let endpoint = `/contacts/${id}`; - if (optional_properties && optional_properties.length > 0) { - endpoint += `?optional_properties=${optional_properties.join(",")}`; - } - return await client.get(endpoint); - } - - case "create_contact": { - const payload: any = {}; - if (args.email_addresses) payload.email_addresses = args.email_addresses; - if (args.given_name) payload.given_name = args.given_name; - if (args.family_name) payload.family_name = args.family_name; - if (args.phone_numbers) payload.phone_numbers = args.phone_numbers; - if (args.addresses) payload.addresses = args.addresses; - if (args.company) payload.company = args.company; - if (args.job_title) payload.job_title = args.job_title; - if (args.lead_source_id) payload.lead_source_id = args.lead_source_id; - if (args.opt_in_reason) payload.opt_in_reason = args.opt_in_reason; - if (args.source_type) payload.source_type = args.source_type; - if (args.custom_fields) payload.custom_fields = args.custom_fields; - return await client.post("/contacts", payload); - } - - case "update_contact": { - const { id, ...updates } = args; - return await client.patch(`/contacts/${id}`, updates); - } - - case "list_opportunities": { - const params = new URLSearchParams(); - if (args.limit) params.append("limit", args.limit.toString()); - if (args.offset) params.append("offset", args.offset.toString()); - if (args.user_id) params.append("user_id", args.user_id.toString()); - if (args.stage_id) params.append("stage_id", args.stage_id.toString()); - if (args.search_term) params.append("search_term", args.search_term); - if (args.order) params.append("order", args.order); - const query = params.toString(); - return await client.get(`/opportunities${query ? `?${query}` : ""}`); - } - - case "list_tasks": { - const params = new URLSearchParams(); - if (args.limit) params.append("limit", args.limit.toString()); - if (args.offset) params.append("offset", args.offset.toString()); - if (args.contact_id) params.append("contact_id", args.contact_id.toString()); - if (args.user_id) params.append("user_id", args.user_id.toString()); - if (args.completed !== undefined) params.append("completed", args.completed.toString()); - if (args.since) params.append("since", args.since); - if (args.until) params.append("until", args.until); - if (args.order) params.append("order", args.order); - const query = params.toString(); - return await client.get(`/tasks${query ? `?${query}` : ""}`); - } - - case "create_task": { - const payload: any = { - title: args.title, - }; - if (args.description) payload.description = args.description; - if (args.contact) payload.contact = args.contact; - if (args.due_date) payload.due_date = args.due_date; - if (args.priority) payload.priority = args.priority; - if (args.type) payload.type = args.type; - if (args.user_id) payload.user_id = args.user_id; - if (args.remind_time) payload.remind_time = args.remind_time; - return await client.post("/tasks", payload); - } - - case "list_tags": { - const params = new URLSearchParams(); - if (args.limit) params.append("limit", args.limit.toString()); - if (args.offset) params.append("offset", args.offset.toString()); - if (args.category) params.append("category", args.category.toString()); - if (args.name) params.append("name", args.name); - const query = params.toString(); - return await client.get(`/tags${query ? `?${query}` : ""}`); - } - - default: - throw new Error(`Unknown tool: ${name}`); - } -} - -// ============================================ -// SERVER SETUP -// ============================================ -async function main() { - const accessToken = process.env.KEAP_ACCESS_TOKEN; - - if (!accessToken) { - console.error("Error: KEAP_ACCESS_TOKEN environment variable required"); - console.error("Get your access token from the Keap Developer Portal after OAuth2 authorization"); - process.exit(1); - } - - const client = new KeapClient(accessToken); - - const server = new Server( - { name: `${MCP_NAME}-mcp`, version: MCP_VERSION }, - { capabilities: { tools: {} } } - ); - - server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools, - })); - - server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - - try { - const result = await handleTool(client, name, args || {}); - return { - content: [{ type: "text", text: JSON.stringify(result, null, 2) }], - }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return { - content: [{ type: "text", text: `Error: ${message}` }], - isError: true, - }; - } - }); - - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error(`${MCP_NAME} MCP server running on stdio`); -} - -main().catch(console.error); diff --git a/servers/keap/src/main.ts b/servers/keap/src/main.ts new file mode 100644 index 0000000..517ad8a --- /dev/null +++ b/servers/keap/src/main.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node +import { KeapMCPServer } from './server.js'; + +const accessToken = process.env.KEAP_ACCESS_TOKEN; + +if (!accessToken) { + console.error('Error: KEAP_ACCESS_TOKEN environment variable is required'); + process.exit(1); +} + +const server = new KeapMCPServer(accessToken); +server.start().catch((error) => { + console.error('Failed to start server:', error); + process.exit(1); +}); diff --git a/servers/keap/src/server.ts b/servers/keap/src/server.ts new file mode 100644 index 0000000..ab20938 --- /dev/null +++ b/servers/keap/src/server.ts @@ -0,0 +1,114 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { KeapClient } from './clients/keap.js'; +import { registerContactsTools } from './tools/contacts-tools.js'; +import { registerDealsTools } from './tools/deals-tools.js'; +import { registerCompaniesTools } from './tools/companies-tools.js'; +import { registerTasksTools } from './tools/tasks-tools.js'; +import { registerAppointmentsTools } from './tools/appointments-tools.js'; +import { registerCampaignsTools } from './tools/campaigns-tools.js'; +import { registerEmailsTools } from './tools/emails-tools.js'; +import { registerOrdersTools } from './tools/orders-tools.js'; +import { registerProductsTools } from './tools/products-tools.js'; +import { registerTagsTools } from './tools/tags-tools.js'; +import { registerAutomationsTools } from './tools/automations-tools.js'; +import { registerReportsTools } from './tools/reports-tools.js'; + +export class KeapMCPServer { + private server: Server; + private client: KeapClient; + private tools: Map = new Map(); + + constructor(accessToken: string) { + this.server = new Server( + { + name: 'keap-mcp-server', + version: '1.0.0', + }, + { + capabilities: { + tools: {}, + }, + } + ); + + this.client = new KeapClient({ accessToken }); + this.registerAllTools(); + this.setupHandlers(); + } + + private registerAllTools() { + const toolGroups = [ + registerContactsTools(this.client), + registerDealsTools(this.client), + registerCompaniesTools(this.client), + registerTasksTools(this.client), + registerAppointmentsTools(this.client), + registerCampaignsTools(this.client), + registerEmailsTools(this.client), + registerOrdersTools(this.client), + registerProductsTools(this.client), + registerTagsTools(this.client), + registerAutomationsTools(this.client), + registerReportsTools(this.client), + ]; + + for (const group of toolGroups) { + for (const [name, tool] of Object.entries(group)) { + this.tools.set(name, tool); + } + } + } + + private setupHandlers() { + this.server.setRequestHandler(ListToolsRequestSchema, async () => { + const tools = Array.from(this.tools.entries()).map(([name, tool]) => ({ + name, + description: tool.description, + inputSchema: tool.inputSchema, + })); + + return { tools }; + }); + + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + const tool = this.tools.get(request.params.name); + + if (!tool) { + throw new Error(`Tool not found: ${request.params.name}`); + } + + try { + const result = await tool.handler(request.params.arguments || {}); + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; + } catch (error: any) { + return { + content: [ + { + type: 'text', + text: `Error: ${error.message}`, + }, + ], + isError: true, + }; + } + }); + } + + async start() { + const transport = new StdioServerTransport(); + await this.server.connect(transport); + console.error('Keap MCP Server running on stdio'); + } +} diff --git a/servers/keap/src/tools/appointments-tools.ts b/servers/keap/src/tools/appointments-tools.ts new file mode 100644 index 0000000..9e71b66 --- /dev/null +++ b/servers/keap/src/tools/appointments-tools.ts @@ -0,0 +1,112 @@ +import { KeapClient } from '../clients/keap.js'; + +export function registerAppointmentsTools(client: KeapClient) { + return { + keap_list_appointments: { + description: 'List all appointments', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Filter by contact ID' }, + user_id: { type: 'number', description: 'Filter by assigned user ID' }, + since: { type: 'string', description: 'Start date filter (ISO 8601)' }, + until: { type: 'string', description: 'End date filter (ISO 8601)' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + }, + handler: async (args: any) => { + const appointments = await client.listAppointments(args); + return { appointments, count: appointments.length }; + }, + }, + + keap_get_appointment: { + description: 'Get a specific appointment by ID', + inputSchema: { + type: 'object', + properties: { + appointment_id: { type: 'number', description: 'Appointment ID' }, + }, + required: ['appointment_id'], + }, + handler: async (args: any) => { + return await client.getAppointment(args.appointment_id); + }, + }, + + keap_create_appointment: { + description: 'Create a new appointment', + inputSchema: { + type: 'object', + properties: { + title: { type: 'string', description: 'Appointment title' }, + description: { type: 'string', description: 'Appointment description' }, + location: { type: 'string', description: 'Appointment location' }, + start_date: { type: 'string', description: 'Start date/time (ISO 8601)' }, + end_date: { type: 'string', description: 'End date/time (ISO 8601)' }, + contact_id: { type: 'number', description: 'Contact ID' }, + user_id: { type: 'number', description: 'Assigned user ID' }, + all_day: { type: 'boolean', description: 'All-day appointment' }, + }, + required: ['title', 'start_date', 'end_date'], + }, + handler: async (args: any) => { + return await client.createAppointment({ + title: args.title, + description: args.description, + location: args.location, + start_date: args.start_date, + end_date: args.end_date, + contact: args.contact_id ? { id: args.contact_id } : undefined, + user_id: args.user_id, + all_day: args.all_day || false, + }); + }, + }, + + keap_update_appointment: { + description: 'Update an existing appointment', + inputSchema: { + type: 'object', + properties: { + appointment_id: { type: 'number', description: 'Appointment ID' }, + title: { type: 'string', description: 'Appointment title' }, + description: { type: 'string', description: 'Appointment description' }, + location: { type: 'string', description: 'Appointment location' }, + start_date: { type: 'string', description: 'Start date/time (ISO 8601)' }, + end_date: { type: 'string', description: 'End date/time (ISO 8601)' }, + user_id: { type: 'number', description: 'Assigned user ID' }, + all_day: { type: 'boolean', description: 'All-day appointment' }, + }, + required: ['appointment_id'], + }, + handler: async (args: any) => { + const data: any = {}; + if (args.title !== undefined) data.title = args.title; + if (args.description !== undefined) data.description = args.description; + if (args.location !== undefined) data.location = args.location; + if (args.start_date !== undefined) data.start_date = args.start_date; + if (args.end_date !== undefined) data.end_date = args.end_date; + if (args.user_id !== undefined) data.user_id = args.user_id; + if (args.all_day !== undefined) data.all_day = args.all_day; + + return await client.updateAppointment(args.appointment_id, data); + }, + }, + + keap_delete_appointment: { + description: 'Delete an appointment', + inputSchema: { + type: 'object', + properties: { + appointment_id: { type: 'number', description: 'Appointment ID' }, + }, + required: ['appointment_id'], + }, + handler: async (args: any) => { + await client.deleteAppointment(args.appointment_id); + return { success: true, message: `Appointment ${args.appointment_id} deleted` }; + }, + }, + }; +} diff --git a/servers/keap/src/tools/automations-tools.ts b/servers/keap/src/tools/automations-tools.ts new file mode 100644 index 0000000..bfe8e27 --- /dev/null +++ b/servers/keap/src/tools/automations-tools.ts @@ -0,0 +1,69 @@ +import { KeapClient } from '../clients/keap.js'; + +export function registerAutomationsTools(client: KeapClient) { + return { + keap_list_automations: { + description: 'List all automations/campaigns', + inputSchema: { + type: 'object', + properties: { + search_text: { type: 'string', description: 'Search by name' }, + category: { type: 'string', description: 'Filter by category' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + }, + handler: async (args: any) => { + const automations = await client.listAutomations(args); + return { automations, count: automations.length }; + }, + }, + + keap_get_automation: { + description: 'Get a specific automation by ID', + inputSchema: { + type: 'object', + properties: { + automation_id: { type: 'number', description: 'Automation ID' }, + }, + required: ['automation_id'], + }, + handler: async (args: any) => { + return await client.getAutomation(args.automation_id); + }, + }, + + keap_activate_automation: { + description: 'Activate/publish an automation', + inputSchema: { + type: 'object', + properties: { + automation_id: { type: 'number', description: 'Automation ID' }, + }, + required: ['automation_id'], + }, + handler: async (args: any) => { + await client.patch(`/campaigns/${args.automation_id}`, { + published_status: 'Published', + }); + return { success: true, message: `Automation ${args.automation_id} activated` }; + }, + }, + + keap_deactivate_automation: { + description: 'Deactivate/unpublish an automation', + inputSchema: { + type: 'object', + properties: { + automation_id: { type: 'number', description: 'Automation ID' }, + }, + required: ['automation_id'], + }, + handler: async (args: any) => { + await client.patch(`/campaigns/${args.automation_id}`, { + published_status: 'Draft', + }); + return { success: true, message: `Automation ${args.automation_id} deactivated` }; + }, + }, + }; +} diff --git a/servers/keap/src/tools/campaigns-tools.ts b/servers/keap/src/tools/campaigns-tools.ts new file mode 100644 index 0000000..2091872 --- /dev/null +++ b/servers/keap/src/tools/campaigns-tools.ts @@ -0,0 +1,113 @@ +import { KeapClient } from '../clients/keap.js'; + +export function registerCampaignsTools(client: KeapClient) { + return { + keap_list_campaigns: { + description: 'List all campaigns', + inputSchema: { + type: 'object', + properties: { + search_text: { type: 'string', description: 'Search by campaign name' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + }, + handler: async (args: any) => { + const campaigns = await client.listCampaigns(args); + return { campaigns, count: campaigns.length }; + }, + }, + + keap_get_campaign: { + description: 'Get a specific campaign by ID', + inputSchema: { + type: 'object', + properties: { + campaign_id: { type: 'number', description: 'Campaign ID' }, + }, + required: ['campaign_id'], + }, + handler: async (args: any) => { + return await client.getCampaign(args.campaign_id); + }, + }, + + keap_add_to_campaign: { + description: 'Add a contact to a campaign sequence', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + campaign_id: { type: 'number', description: 'Campaign ID' }, + }, + required: ['contact_id', 'campaign_id'], + }, + handler: async (args: any) => { + await client.post('/campaignSequences', { + contact_id: args.contact_id, + campaign_id: args.campaign_id, + }); + return { + success: true, + message: `Contact ${args.contact_id} added to campaign ${args.campaign_id}` + }; + }, + }, + + keap_remove_from_campaign: { + description: 'Remove a contact from a campaign sequence', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + campaign_id: { type: 'number', description: 'Campaign ID' }, + }, + required: ['contact_id', 'campaign_id'], + }, + handler: async (args: any) => { + const sequences = await client.get('/campaignSequences', { + contact_id: args.contact_id, + campaign_id: args.campaign_id, + }); + + if (sequences && sequences.sequences && sequences.sequences.length > 0) { + await client.delete(`/campaignSequences/${sequences.sequences[0].id}`); + return { + success: true, + message: `Contact ${args.contact_id} removed from campaign ${args.campaign_id}` + }; + } + + return { + success: false, + message: `Contact ${args.contact_id} not found in campaign ${args.campaign_id}` + }; + }, + }, + + keap_get_campaign_stats: { + description: 'Get statistics for a campaign', + inputSchema: { + type: 'object', + properties: { + campaign_id: { type: 'number', description: 'Campaign ID' }, + }, + required: ['campaign_id'], + }, + handler: async (args: any) => { + const sequences = await client.get('/campaignSequences', { + campaign_id: args.campaign_id, + }); + + const stats = { + campaign_id: args.campaign_id, + total_contacts: sequences.sequences?.length || 0, + active_contacts: sequences.sequences?.filter((s: any) => s.status === 'Active').length || 0, + completed_contacts: sequences.sequences?.filter((s: any) => s.status === 'Completed').length || 0, + stopped_contacts: sequences.sequences?.filter((s: any) => s.status === 'Stopped').length || 0, + }; + + return stats; + }, + }, + }; +} diff --git a/servers/keap/src/tools/companies-tools.ts b/servers/keap/src/tools/companies-tools.ts new file mode 100644 index 0000000..ce2bce9 --- /dev/null +++ b/servers/keap/src/tools/companies-tools.ts @@ -0,0 +1,122 @@ +import { KeapClient } from '../clients/keap.js'; + +export function registerCompaniesTools(client: KeapClient) { + return { + keap_list_companies: { + description: 'List all companies', + inputSchema: { + type: 'object', + properties: { + company_name: { type: 'string', description: 'Filter by company name' }, + limit: { type: 'number', description: 'Maximum results' }, + order: { type: 'string', description: 'Sort order' }, + }, + }, + handler: async (args: any) => { + const companies = await client.listCompanies(args); + return { companies, count: companies.length }; + }, + }, + + keap_get_company: { + description: 'Get a specific company by ID', + inputSchema: { + type: 'object', + properties: { + company_id: { type: 'number', description: 'Company ID' }, + }, + required: ['company_id'], + }, + handler: async (args: any) => { + return await client.getCompany(args.company_id); + }, + }, + + keap_create_company: { + description: 'Create a new company', + inputSchema: { + type: 'object', + properties: { + company_name: { type: 'string', description: 'Company name' }, + email: { type: 'string', description: 'Company email address' }, + phone: { type: 'string', description: 'Company phone number' }, + fax: { type: 'string', description: 'Company fax number' }, + website: { type: 'string', description: 'Company website' }, + address_line1: { type: 'string', description: 'Street address' }, + address_city: { type: 'string', description: 'City' }, + address_state: { type: 'string', description: 'State/Province' }, + address_zip: { type: 'string', description: 'ZIP/Postal code' }, + address_country: { type: 'string', description: 'Country code' }, + notes: { type: 'string', description: 'Company notes' }, + }, + required: ['company_name'], + }, + handler: async (args: any) => { + const data: any = { + company_name: args.company_name, + email_address: args.email, + phone_number: args.phone, + fax_number: args.fax, + website: args.website, + notes: args.notes, + }; + + if (args.address_line1 || args.address_city) { + data.address = { + line1: args.address_line1, + locality: args.address_city, + region: args.address_state, + zip_code: args.address_zip, + country_code: args.address_country || 'US', + field: 'BILLING', + }; + } + + return await client.createCompany(data); + }, + }, + + keap_update_company: { + description: 'Update an existing company', + inputSchema: { + type: 'object', + properties: { + company_id: { type: 'number', description: 'Company ID' }, + company_name: { type: 'string', description: 'Company name' }, + email: { type: 'string', description: 'Company email address' }, + phone: { type: 'string', description: 'Company phone number' }, + fax: { type: 'string', description: 'Company fax number' }, + website: { type: 'string', description: 'Company website' }, + notes: { type: 'string', description: 'Company notes' }, + }, + required: ['company_id'], + }, + handler: async (args: any) => { + const data: any = {}; + if (args.company_name !== undefined) data.company_name = args.company_name; + if (args.email !== undefined) data.email_address = args.email; + if (args.phone !== undefined) data.phone_number = args.phone; + if (args.fax !== undefined) data.fax_number = args.fax; + if (args.website !== undefined) data.website = args.website; + if (args.notes !== undefined) data.notes = args.notes; + + return await client.updateCompany(args.company_id, data); + }, + }, + + keap_delete_company: { + description: 'Delete a company', + inputSchema: { + type: 'object', + properties: { + company_id: { type: 'number', description: 'Company ID' }, + }, + required: ['company_id'], + }, + handler: async (args: any) => { + await client.deleteCompany(args.company_id); + return { success: true, message: `Company ${args.company_id} deleted` }; + }, + }, + }; +} diff --git a/servers/keap/src/tools/contacts-tools.ts b/servers/keap/src/tools/contacts-tools.ts new file mode 100644 index 0000000..0968b23 --- /dev/null +++ b/servers/keap/src/tools/contacts-tools.ts @@ -0,0 +1,318 @@ +import { KeapClient } from '../clients/keap.js'; + +export function registerContactsTools(client: KeapClient) { + return { + keap_list_contacts: { + description: 'List contacts with optional filtering', + inputSchema: { + type: 'object', + properties: { + limit: { type: 'number', description: 'Number of results per page' }, + email: { type: 'string', description: 'Filter by email address' }, + given_name: { type: 'string', description: 'Filter by first name' }, + family_name: { type: 'string', description: 'Filter by last name' }, + order: { type: 'string', description: 'Sort order field' }, + }, + }, + handler: async (args: any) => { + const contacts = await client.listContacts(args); + return { contacts, count: contacts.length }; + }, + }, + + keap_get_contact: { + description: 'Get a specific contact by ID', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + }, + required: ['contact_id'], + }, + handler: async (args: any) => { + return await client.getContact(args.contact_id); + }, + }, + + keap_create_contact: { + description: 'Create a new contact', + inputSchema: { + type: 'object', + properties: { + email: { type: 'string', description: 'Primary email address' }, + given_name: { type: 'string', description: 'First name' }, + family_name: { type: 'string', description: 'Last name' }, + phone: { type: 'string', description: 'Phone number' }, + company_name: { type: 'string', description: 'Company name' }, + job_title: { type: 'string', description: 'Job title' }, + website: { type: 'string', description: 'Website URL' }, + address_line1: { type: 'string', description: 'Street address' }, + address_city: { type: 'string', description: 'City' }, + address_state: { type: 'string', description: 'State/Province' }, + address_zip: { type: 'string', description: 'ZIP/Postal code' }, + address_country: { type: 'string', description: 'Country code' }, + }, + required: ['email'], + }, + handler: async (args: any) => { + const data: any = { + email_addresses: [{ email: args.email, field: 'EMAIL1' }], + given_name: args.given_name, + family_name: args.family_name, + company_name: args.company_name, + job_title: args.job_title, + website: args.website, + }; + + if (args.phone) { + data.phone_numbers = [{ number: args.phone, field: 'PHONE1' }]; + } + + if (args.address_line1 || args.address_city) { + data.addresses = [{ + line1: args.address_line1, + locality: args.address_city, + region: args.address_state, + zip_code: args.address_zip, + country_code: args.address_country || 'US', + field: 'BILLING', + }]; + } + + return await client.createContact(data); + }, + }, + + keap_update_contact: { + description: 'Update an existing contact', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + email: { type: 'string', description: 'Email address' }, + given_name: { type: 'string', description: 'First name' }, + family_name: { type: 'string', description: 'Last name' }, + phone: { type: 'string', description: 'Phone number' }, + company_name: { type: 'string', description: 'Company name' }, + job_title: { type: 'string', description: 'Job title' }, + website: { type: 'string', description: 'Website URL' }, + }, + required: ['contact_id'], + }, + handler: async (args: any) => { + const data: any = { + given_name: args.given_name, + family_name: args.family_name, + company_name: args.company_name, + job_title: args.job_title, + website: args.website, + }; + + if (args.email) { + data.email_addresses = [{ email: args.email, field: 'EMAIL1' }]; + } + + if (args.phone) { + data.phone_numbers = [{ number: args.phone, field: 'PHONE1' }]; + } + + return await client.updateContact(args.contact_id, data); + }, + }, + + keap_delete_contact: { + description: 'Delete a contact', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + }, + required: ['contact_id'], + }, + handler: async (args: any) => { + await client.deleteContact(args.contact_id); + return { success: true, message: `Contact ${args.contact_id} deleted` }; + }, + }, + + keap_search_contacts: { + description: 'Search contacts by keyword', + inputSchema: { + type: 'object', + properties: { + query: { type: 'string', description: 'Search query' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + required: ['query'], + }, + handler: async (args: any) => { + const contacts = await client.listContacts({ + email: args.query.includes('@') ? args.query : undefined, + given_name: !args.query.includes('@') ? args.query : undefined, + limit: args.limit || 100, + }); + return { contacts, count: contacts.length }; + }, + }, + + keap_list_contact_tags: { + description: 'List all tags applied to a contact', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + }, + required: ['contact_id'], + }, + handler: async (args: any) => { + const response = await client.get(`/contacts/${args.contact_id}/tags`); + return { tags: response.tags || [] }; + }, + }, + + keap_add_tag_to_contact: { + description: 'Add a tag to a contact', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + tag_id: { type: 'number', description: 'Tag ID' }, + }, + required: ['contact_id', 'tag_id'], + }, + handler: async (args: any) => { + await client.post(`/contacts/${args.contact_id}/tags`, { tagId: args.tag_id }); + return { success: true, message: `Tag ${args.tag_id} added to contact ${args.contact_id}` }; + }, + }, + + keap_remove_tag_from_contact: { + description: 'Remove a tag from a contact', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + tag_id: { type: 'number', description: 'Tag ID' }, + }, + required: ['contact_id', 'tag_id'], + }, + handler: async (args: any) => { + await client.delete(`/contacts/${args.contact_id}/tags/${args.tag_id}`); + return { success: true, message: `Tag ${args.tag_id} removed from contact ${args.contact_id}` }; + }, + }, + + keap_list_contact_emails: { + description: 'List all emails sent to/from a contact', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + required: ['contact_id'], + }, + handler: async (args: any) => { + const emails = await client.listEmails({ + contact_id: args.contact_id, + limit: args.limit || 100, + }); + return { emails, count: emails.length }; + }, + }, + + keap_create_contact_email: { + description: 'Create an email record for a contact', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + subject: { type: 'string', description: 'Email subject' }, + html_content: { type: 'string', description: 'HTML email body' }, + text_content: { type: 'string', description: 'Plain text email body' }, + from_address: { type: 'string', description: 'From email address' }, + }, + required: ['contact_id', 'subject'], + }, + handler: async (args: any) => { + const contact = await client.getContact(args.contact_id); + const toAddress = contact.email_addresses?.[0]?.email; + + if (!toAddress) { + throw new Error(`Contact ${args.contact_id} has no email address`); + } + + return await client.createEmail({ + sent_to_address: toAddress, + sent_to_contact_id: args.contact_id, + sent_from_address: args.from_address, + subject: args.subject, + html_content: args.html_content, + text_content: args.text_content, + }); + }, + }, + + keap_list_contact_notes: { + description: 'List all notes for a contact', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + required: ['contact_id'], + }, + handler: async (args: any) => { + const notes = await client.listNotes({ + contact_id: args.contact_id, + limit: args.limit || 100, + }); + return { notes, count: notes.length }; + }, + }, + + keap_create_contact_note: { + description: 'Create a note for a contact', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + title: { type: 'string', description: 'Note title' }, + body: { type: 'string', description: 'Note content' }, + type: { type: 'string', enum: ['Appointment', 'Call', 'Email', 'Fax', 'Letter', 'Other'], description: 'Note type' }, + }, + required: ['contact_id', 'body'], + }, + handler: async (args: any) => { + return await client.createNote({ + contact_id: args.contact_id, + title: args.title, + body: args.body, + type: args.type || 'Other', + }); + }, + }, + + keap_merge_contacts: { + description: 'Merge two contacts (keeps first, merges second into it)', + inputSchema: { + type: 'object', + properties: { + keep_contact_id: { type: 'number', description: 'Contact ID to keep' }, + merge_contact_id: { type: 'number', description: 'Contact ID to merge and delete' }, + }, + required: ['keep_contact_id', 'merge_contact_id'], + }, + handler: async (args: any) => { + await client.post(`/contacts/${args.keep_contact_id}/merge`, { + duplicateContactId: args.merge_contact_id, + }); + return { + success: true, + message: `Contact ${args.merge_contact_id} merged into ${args.keep_contact_id}` + }; + }, + }, + }; +} diff --git a/servers/keap/src/tools/deals-tools.ts b/servers/keap/src/tools/deals-tools.ts new file mode 100644 index 0000000..60a0d22 --- /dev/null +++ b/servers/keap/src/tools/deals-tools.ts @@ -0,0 +1,188 @@ +import { KeapClient } from '../clients/keap.js'; + +export function registerDealsTools(client: KeapClient) { + return { + keap_list_deals: { + description: 'List all deals/opportunities', + inputSchema: { + type: 'object', + properties: { + stage_id: { type: 'number', description: 'Filter by stage ID' }, + user_id: { type: 'number', description: 'Filter by assigned user ID' }, + contact_id: { type: 'number', description: 'Filter by contact ID' }, + limit: { type: 'number', description: 'Maximum results' }, + order: { type: 'string', description: 'Sort order' }, + }, + }, + handler: async (args: any) => { + const deals = await client.listDeals(args); + return { deals, count: deals.length }; + }, + }, + + keap_get_deal: { + description: 'Get a specific deal by ID', + inputSchema: { + type: 'object', + properties: { + deal_id: { type: 'number', description: 'Deal ID' }, + }, + required: ['deal_id'], + }, + handler: async (args: any) => { + return await client.getDeal(args.deal_id); + }, + }, + + keap_create_deal: { + description: 'Create a new deal/opportunity', + inputSchema: { + type: 'object', + properties: { + title: { type: 'string', description: 'Deal title' }, + contact_id: { type: 'number', description: 'Contact ID' }, + stage_id: { type: 'number', description: 'Pipeline stage ID' }, + user_id: { type: 'number', description: 'Assigned user ID' }, + projected_revenue_low: { type: 'number', description: 'Minimum projected revenue' }, + projected_revenue_high: { type: 'number', description: 'Maximum projected revenue' }, + estimated_close_date: { type: 'string', description: 'Estimated close date (ISO 8601)' }, + probability: { type: 'number', description: 'Win probability (0-100)' }, + next_action_date: { type: 'string', description: 'Next action date (ISO 8601)' }, + next_action_notes: { type: 'string', description: 'Next action notes' }, + }, + required: ['title', 'stage_id'], + }, + handler: async (args: any) => { + return await client.createDeal({ + title: args.title, + contact: args.contact_id ? { id: args.contact_id } : undefined, + stage_id: args.stage_id, + user_id: args.user_id, + projected_revenue_low: args.projected_revenue_low, + projected_revenue_high: args.projected_revenue_high, + estimated_close_date: args.estimated_close_date, + probability: args.probability, + next_action_date: args.next_action_date, + next_action_notes: args.next_action_notes, + }); + }, + }, + + keap_update_deal: { + description: 'Update an existing deal', + inputSchema: { + type: 'object', + properties: { + deal_id: { type: 'number', description: 'Deal ID' }, + title: { type: 'string', description: 'Deal title' }, + stage_id: { type: 'number', description: 'Pipeline stage ID' }, + user_id: { type: 'number', description: 'Assigned user ID' }, + projected_revenue_low: { type: 'number', description: 'Minimum projected revenue' }, + projected_revenue_high: { type: 'number', description: 'Maximum projected revenue' }, + estimated_close_date: { type: 'string', description: 'Estimated close date (ISO 8601)' }, + probability: { type: 'number', description: 'Win probability (0-100)' }, + next_action_date: { type: 'string', description: 'Next action date (ISO 8601)' }, + next_action_notes: { type: 'string', description: 'Next action notes' }, + }, + required: ['deal_id'], + }, + handler: async (args: any) => { + const data: any = {}; + if (args.title !== undefined) data.title = args.title; + if (args.stage_id !== undefined) data.stage_id = args.stage_id; + if (args.user_id !== undefined) data.user_id = args.user_id; + if (args.projected_revenue_low !== undefined) data.projected_revenue_low = args.projected_revenue_low; + if (args.projected_revenue_high !== undefined) data.projected_revenue_high = args.projected_revenue_high; + if (args.estimated_close_date !== undefined) data.estimated_close_date = args.estimated_close_date; + if (args.probability !== undefined) data.probability = args.probability; + if (args.next_action_date !== undefined) data.next_action_date = args.next_action_date; + if (args.next_action_notes !== undefined) data.next_action_notes = args.next_action_notes; + + return await client.updateDeal(args.deal_id, data); + }, + }, + + keap_delete_deal: { + description: 'Delete a deal', + inputSchema: { + type: 'object', + properties: { + deal_id: { type: 'number', description: 'Deal ID' }, + }, + required: ['deal_id'], + }, + handler: async (args: any) => { + await client.deleteDeal(args.deal_id); + return { success: true, message: `Deal ${args.deal_id} deleted` }; + }, + }, + + keap_move_deal_stage: { + description: 'Move a deal to a different pipeline stage', + inputSchema: { + type: 'object', + properties: { + deal_id: { type: 'number', description: 'Deal ID' }, + stage_id: { type: 'number', description: 'New stage ID' }, + reason: { type: 'string', description: 'Reason for move (optional note)' }, + }, + required: ['deal_id', 'stage_id'], + }, + handler: async (args: any) => { + const data: any = { stage_id: args.stage_id }; + if (args.reason) { + data.next_action_notes = args.reason; + } + + return await client.updateDeal(args.deal_id, data); + }, + }, + + keap_list_pipelines: { + description: 'List all sales pipelines', + inputSchema: { + type: 'object', + properties: {}, + }, + handler: async () => { + const pipelines = await client.get('/opportunity/stage_pipeline'); + return { pipelines: pipelines.pipelines || [] }; + }, + }, + + keap_get_pipeline: { + description: 'Get a specific pipeline with all its stages', + inputSchema: { + type: 'object', + properties: { + pipeline_id: { type: 'number', description: 'Pipeline ID' }, + }, + required: ['pipeline_id'], + }, + handler: async (args: any) => { + const pipeline = await client.get(`/opportunity/stage_pipeline/${args.pipeline_id}`); + return pipeline; + }, + }, + + keap_list_stages: { + description: 'List all stages across all pipelines', + inputSchema: { + type: 'object', + properties: { + pipeline_id: { type: 'number', description: 'Filter by pipeline ID' }, + }, + }, + handler: async (args: any) => { + if (args.pipeline_id) { + const pipeline = await client.get(`/opportunity/stage_pipeline/${args.pipeline_id}`); + return { stages: pipeline.stages || [] }; + } else { + const pipelines = await client.get('/opportunity/stage_pipeline'); + const allStages = pipelines.pipelines?.flatMap((p: any) => p.stages || []) || []; + return { stages: allStages }; + } + }, + }, + }; +} diff --git a/servers/keap/src/tools/emails-tools.ts b/servers/keap/src/tools/emails-tools.ts new file mode 100644 index 0000000..fa1795b --- /dev/null +++ b/servers/keap/src/tools/emails-tools.ts @@ -0,0 +1,126 @@ +import { KeapClient } from '../clients/keap.js'; + +export function registerEmailsTools(client: KeapClient) { + return { + keap_list_emails: { + description: 'List emails', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Filter by contact ID' }, + since: { type: 'string', description: 'Start date filter (ISO 8601)' }, + until: { type: 'string', description: 'End date filter (ISO 8601)' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + }, + handler: async (args: any) => { + const emails = await client.listEmails(args); + return { emails, count: emails.length }; + }, + }, + + keap_get_email: { + description: 'Get a specific email by ID', + inputSchema: { + type: 'object', + properties: { + email_id: { type: 'number', description: 'Email ID' }, + }, + required: ['email_id'], + }, + handler: async (args: any) => { + return await client.getEmail(args.email_id); + }, + }, + + keap_create_email: { + description: 'Create an email record', + inputSchema: { + type: 'object', + properties: { + to_address: { type: 'string', description: 'Recipient email address' }, + from_address: { type: 'string', description: 'Sender email address' }, + subject: { type: 'string', description: 'Email subject' }, + html_content: { type: 'string', description: 'HTML email body' }, + text_content: { type: 'string', description: 'Plain text email body' }, + contact_id: { type: 'number', description: 'Contact ID' }, + }, + required: ['to_address', 'from_address', 'subject'], + }, + handler: async (args: any) => { + return await client.createEmail({ + sent_to_address: args.to_address, + sent_to_contact_id: args.contact_id, + sent_from_address: args.from_address, + subject: args.subject, + html_content: args.html_content, + text_content: args.text_content, + }); + }, + }, + + keap_send_email: { + description: 'Send an email to a contact', + inputSchema: { + type: 'object', + properties: { + to_address: { type: 'string', description: 'Recipient email address' }, + from_address: { type: 'string', description: 'Sender email address' }, + subject: { type: 'string', description: 'Email subject' }, + html_content: { type: 'string', description: 'HTML email body' }, + text_content: { type: 'string', description: 'Plain text email body' }, + contact_id: { type: 'number', description: 'Contact ID' }, + }, + required: ['to_address', 'from_address', 'subject'], + }, + handler: async (args: any) => { + return await client.sendEmail({ + sent_to_address: args.to_address, + sent_to_contact_id: args.contact_id, + sent_from_address: args.from_address, + subject: args.subject, + html_content: args.html_content, + text_content: args.text_content, + }); + }, + }, + + keap_get_email_templates: { + description: 'Get all email templates', + inputSchema: { + type: 'object', + properties: { + limit: { type: 'number', description: 'Maximum results' }, + }, + }, + handler: async (args: any) => { + const templates = await client.get('/emails/templates', args); + return { templates: templates.templates || [] }; + }, + }, + + keap_create_email_template: { + description: 'Create a new email template', + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'Template name' }, + subject: { type: 'string', description: 'Email subject' }, + html_content: { type: 'string', description: 'HTML email body' }, + text_content: { type: 'string', description: 'Plain text email body' }, + categories: { type: 'array', items: { type: 'string' }, description: 'Template categories' }, + }, + required: ['name', 'subject'], + }, + handler: async (args: any) => { + return await client.post('/emails/templates', { + name: args.name, + subject: args.subject, + html_content: args.html_content, + text_content: args.text_content, + categories: args.categories, + }); + }, + }, + }; +} diff --git a/servers/keap/src/tools/orders-tools.ts b/servers/keap/src/tools/orders-tools.ts new file mode 100644 index 0000000..c2e46b1 --- /dev/null +++ b/servers/keap/src/tools/orders-tools.ts @@ -0,0 +1,119 @@ +import { KeapClient } from '../clients/keap.js'; + +export function registerOrdersTools(client: KeapClient) { + return { + keap_list_orders: { + description: 'List all orders', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Filter by contact ID' }, + since: { type: 'string', description: 'Start date filter (ISO 8601)' }, + until: { type: 'string', description: 'End date filter (ISO 8601)' }, + paid: { type: 'boolean', description: 'Filter by paid status' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + }, + handler: async (args: any) => { + const orders = await client.listOrders(args); + return { orders, count: orders.length }; + }, + }, + + keap_get_order: { + description: 'Get a specific order by ID', + inputSchema: { + type: 'object', + properties: { + order_id: { type: 'number', description: 'Order ID' }, + }, + required: ['order_id'], + }, + handler: async (args: any) => { + return await client.getOrder(args.order_id); + }, + }, + + keap_create_order: { + description: 'Create a new order', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + title: { type: 'string', description: 'Order title' }, + order_items: { + type: 'array', + items: { + type: 'object', + properties: { + product_id: { type: 'number' }, + description: { type: 'string' }, + quantity: { type: 'number' }, + price: { type: 'number' }, + }, + }, + description: 'Order items', + }, + notes: { type: 'string', description: 'Order notes' }, + }, + required: ['contact_id', 'title'], + }, + handler: async (args: any) => { + return await client.createOrder({ + contact_id: args.contact_id, + title: args.title, + order_items: args.order_items, + notes: args.notes, + }); + }, + }, + + keap_list_transactions: { + description: 'List all transactions', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Filter by contact ID' }, + order_id: { type: 'number', description: 'Filter by order ID' }, + since: { type: 'string', description: 'Start date filter (ISO 8601)' }, + until: { type: 'string', description: 'End date filter (ISO 8601)' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + }, + handler: async (args: any) => { + const transactions = await client.get('/transactions', args); + return { transactions: transactions.transactions || [], count: transactions.count || 0 }; + }, + }, + + keap_create_transaction: { + description: 'Create a new transaction/payment', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Contact ID' }, + order_id: { type: 'number', description: 'Order ID' }, + amount: { type: 'number', description: 'Transaction amount' }, + currency: { type: 'string', description: 'Currency code (e.g., USD)' }, + gateway: { type: 'string', description: 'Payment gateway' }, + type: { type: 'string', enum: ['Sale', 'Refund', 'Chargeback'], description: 'Transaction type' }, + test: { type: 'boolean', description: 'Test transaction' }, + }, + required: ['contact_id', 'amount'], + }, + handler: async (args: any) => { + return await client.post('/transactions', { + contact_id: args.contact_id, + order_id: args.order_id, + amount: args.amount, + currency: args.currency || 'USD', + gateway: args.gateway, + type: args.type || 'Sale', + test: args.test || false, + transaction_date: new Date().toISOString(), + status: 'Completed', + }); + }, + }, + }; +} diff --git a/servers/keap/src/tools/products-tools.ts b/servers/keap/src/tools/products-tools.ts new file mode 100644 index 0000000..539489f --- /dev/null +++ b/servers/keap/src/tools/products-tools.ts @@ -0,0 +1,118 @@ +import { KeapClient } from '../clients/keap.js'; + +export function registerProductsTools(client: KeapClient) { + return { + keap_list_products: { + description: 'List all products', + inputSchema: { + type: 'object', + properties: { + active: { type: 'boolean', description: 'Filter by active status' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + }, + handler: async (args: any) => { + const products = await client.listProducts(args); + return { products, count: products.length }; + }, + }, + + keap_get_product: { + description: 'Get a specific product by ID', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID' }, + }, + required: ['product_id'], + }, + handler: async (args: any) => { + return await client.getProduct(args.product_id); + }, + }, + + keap_create_product: { + description: 'Create a new product', + inputSchema: { + type: 'object', + properties: { + product_name: { type: 'string', description: 'Product name' }, + product_short_desc: { type: 'string', description: 'Short description' }, + product_desc: { type: 'string', description: 'Full description' }, + sku: { type: 'string', description: 'SKU code' }, + price: { type: 'number', description: 'Product price' }, + status: { type: 'string', enum: ['Active', 'Inactive'], description: 'Product status' }, + }, + required: ['product_name'], + }, + handler: async (args: any) => { + return await client.createProduct({ + product_name: args.product_name, + product_short_desc: args.product_short_desc, + product_desc: args.product_desc, + sku: args.sku, + product_price: args.price, + status: args.status || 'Active', + }); + }, + }, + + keap_update_product: { + description: 'Update an existing product', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID' }, + product_name: { type: 'string', description: 'Product name' }, + product_short_desc: { type: 'string', description: 'Short description' }, + product_desc: { type: 'string', description: 'Full description' }, + sku: { type: 'string', description: 'SKU code' }, + price: { type: 'number', description: 'Product price' }, + status: { type: 'string', enum: ['Active', 'Inactive'], description: 'Product status' }, + }, + required: ['product_id'], + }, + handler: async (args: any) => { + const data: any = {}; + if (args.product_name !== undefined) data.product_name = args.product_name; + if (args.product_short_desc !== undefined) data.product_short_desc = args.product_short_desc; + if (args.product_desc !== undefined) data.product_desc = args.product_desc; + if (args.sku !== undefined) data.sku = args.sku; + if (args.price !== undefined) data.product_price = args.price; + if (args.status !== undefined) data.status = args.status; + + return await client.updateProduct(args.product_id, data); + }, + }, + + keap_delete_product: { + description: 'Delete a product', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Product ID' }, + }, + required: ['product_id'], + }, + handler: async (args: any) => { + await client.deleteProduct(args.product_id); + return { success: true, message: `Product ${args.product_id} deleted` }; + }, + }, + + keap_list_subscriptions: { + description: 'List all subscription plans', + inputSchema: { + type: 'object', + properties: { + product_id: { type: 'number', description: 'Filter by product ID' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + }, + handler: async (args: any) => { + const subscriptions = await client.get('/subscriptions', args); + return { subscriptions: subscriptions.subscriptions || [] }; + }, + }, + }; +} diff --git a/servers/keap/src/tools/reports-tools.ts b/servers/keap/src/tools/reports-tools.ts new file mode 100644 index 0000000..5065f52 --- /dev/null +++ b/servers/keap/src/tools/reports-tools.ts @@ -0,0 +1,119 @@ +import { KeapClient } from '../clients/keap.js'; + +export function registerReportsTools(client: KeapClient) { + return { + keap_contact_growth_report: { + description: 'Get contact growth statistics', + inputSchema: { + type: 'object', + properties: { + since: { type: 'string', description: 'Start date (ISO 8601)' }, + until: { type: 'string', description: 'End date (ISO 8601)' }, + }, + required: ['since', 'until'], + }, + handler: async (args: any) => { + const contacts = await client.listContacts({ + since: args.since, + until: args.until, + }); + + // Group by date + const dailyGrowth: Record = {}; + contacts.forEach(contact => { + const date = contact.date_created?.split('T')[0]; + if (date) { + dailyGrowth[date] = (dailyGrowth[date] || 0) + 1; + } + }); + + const report = Object.keys(dailyGrowth) + .sort() + .map(date => ({ + date, + new_contacts: dailyGrowth[date], + total_contacts: 0, // Would need cumulative calculation + })); + + return { report, total_new_contacts: contacts.length }; + }, + }, + + keap_revenue_report: { + description: 'Get revenue statistics', + inputSchema: { + type: 'object', + properties: { + since: { type: 'string', description: 'Start date (ISO 8601)' }, + until: { type: 'string', description: 'End date (ISO 8601)' }, + }, + required: ['since', 'until'], + }, + handler: async (args: any) => { + const transactions = await client.get('/transactions', { + since: args.since, + until: args.until, + }); + + const txList = transactions.transactions || []; + + const totalRevenue = txList + .filter((tx: any) => tx.type === 'Sale' && tx.status === 'Completed') + .reduce((sum: number, tx: any) => sum + (tx.amount || 0), 0); + + const totalRefunds = txList + .filter((tx: any) => tx.type === 'Refund') + .reduce((sum: number, tx: any) => sum + (tx.amount || 0), 0); + + const salesCount = txList.filter((tx: any) => tx.type === 'Sale').length; + const avgOrderValue = salesCount > 0 ? totalRevenue / salesCount : 0; + + return { + total_revenue: totalRevenue, + total_refunds: totalRefunds, + net_revenue: totalRevenue - totalRefunds, + transactions: salesCount, + average_order_value: avgOrderValue, + }; + }, + }, + + keap_campaign_performance_report: { + description: 'Get campaign performance statistics', + inputSchema: { + type: 'object', + properties: { + campaign_id: { type: 'number', description: 'Campaign ID' }, + }, + required: ['campaign_id'], + }, + handler: async (args: any) => { + const campaign = await client.getCampaign(args.campaign_id); + + // Get emails sent for this campaign + const emails = await client.listEmails({ + campaign_id: args.campaign_id, + }); + + const totalSent = emails.length; + const opened = emails.filter(e => e.opened).length; + const clicked = emails.filter(e => e.clicked).length; + const bounced = emails.filter(e => e.bounced).length; + + const openRate = totalSent > 0 ? (opened / totalSent) * 100 : 0; + const clickRate = opened > 0 ? (clicked / opened) * 100 : 0; + + return { + campaign_id: args.campaign_id, + campaign_name: campaign.name, + total_sent: totalSent, + opened, + clicked, + bounced, + open_rate: Math.round(openRate * 100) / 100, + click_rate: Math.round(clickRate * 100) / 100, + }; + }, + }, + }; +} diff --git a/servers/keap/src/tools/tags-tools.ts b/servers/keap/src/tools/tags-tools.ts new file mode 100644 index 0000000..deb181d --- /dev/null +++ b/servers/keap/src/tools/tags-tools.ts @@ -0,0 +1,108 @@ +import { KeapClient } from '../clients/keap.js'; + +export function registerTagsTools(client: KeapClient) { + return { + keap_list_tags: { + description: 'List all tags', + inputSchema: { + type: 'object', + properties: { + category: { type: 'string', description: 'Filter by category' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + }, + handler: async (args: any) => { + const tags = await client.listTags(args); + return { tags, count: tags.length }; + }, + }, + + keap_get_tag: { + description: 'Get a specific tag by ID', + inputSchema: { + type: 'object', + properties: { + tag_id: { type: 'number', description: 'Tag ID' }, + }, + required: ['tag_id'], + }, + handler: async (args: any) => { + return await client.getTag(args.tag_id); + }, + }, + + keap_create_tag: { + description: 'Create a new tag', + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'Tag name' }, + description: { type: 'string', description: 'Tag description' }, + category_id: { type: 'number', description: 'Tag category ID' }, + }, + required: ['name'], + }, + handler: async (args: any) => { + return await client.createTag({ + name: args.name, + description: args.description, + category: args.category_id ? { id: args.category_id } : undefined, + }); + }, + }, + + keap_update_tag: { + description: 'Update an existing tag', + inputSchema: { + type: 'object', + properties: { + tag_id: { type: 'number', description: 'Tag ID' }, + name: { type: 'string', description: 'Tag name' }, + description: { type: 'string', description: 'Tag description' }, + }, + required: ['tag_id'], + }, + handler: async (args: any) => { + const data: any = {}; + if (args.name !== undefined) data.name = args.name; + if (args.description !== undefined) data.description = args.description; + + return await client.patch(`/tags/${args.tag_id}`, data); + }, + }, + + keap_delete_tag: { + description: 'Delete a tag', + inputSchema: { + type: 'object', + properties: { + tag_id: { type: 'number', description: 'Tag ID' }, + }, + required: ['tag_id'], + }, + handler: async (args: any) => { + await client.deleteTag(args.tag_id); + return { success: true, message: `Tag ${args.tag_id} deleted` }; + }, + }, + + keap_list_contacts_by_tag: { + description: 'List all contacts with a specific tag', + inputSchema: { + type: 'object', + properties: { + tag_id: { type: 'number', description: 'Tag ID' }, + limit: { type: 'number', description: 'Maximum results' }, + }, + required: ['tag_id'], + }, + handler: async (args: any) => { + const contacts = await client.listContacts({ + tag_id: args.tag_id, + limit: args.limit || 1000, + }); + return { contacts, count: contacts.length }; + }, + }, + }; +} diff --git a/servers/keap/src/tools/tasks-tools.ts b/servers/keap/src/tools/tasks-tools.ts new file mode 100644 index 0000000..b2054d3 --- /dev/null +++ b/servers/keap/src/tools/tasks-tools.ts @@ -0,0 +1,126 @@ +import { KeapClient } from '../clients/keap.js'; + +export function registerTasksTools(client: KeapClient) { + return { + keap_list_tasks: { + description: 'List all tasks', + inputSchema: { + type: 'object', + properties: { + contact_id: { type: 'number', description: 'Filter by contact ID' }, + user_id: { type: 'number', description: 'Filter by assigned user ID' }, + completed: { type: 'boolean', description: 'Filter by completion status' }, + limit: { type: 'number', description: 'Maximum results' }, + order: { type: 'string', description: 'Sort order' }, + }, + }, + handler: async (args: any) => { + const tasks = await client.listTasks(args); + return { tasks, count: tasks.length }; + }, + }, + + keap_get_task: { + description: 'Get a specific task by ID', + inputSchema: { + type: 'object', + properties: { + task_id: { type: 'number', description: 'Task ID' }, + }, + required: ['task_id'], + }, + handler: async (args: any) => { + return await client.getTask(args.task_id); + }, + }, + + keap_create_task: { + description: 'Create a new task', + inputSchema: { + type: 'object', + properties: { + title: { type: 'string', description: 'Task title' }, + description: { type: 'string', description: 'Task description' }, + contact_id: { type: 'number', description: 'Contact ID' }, + user_id: { type: 'number', description: 'Assigned user ID' }, + due_date: { type: 'string', description: 'Due date (ISO 8601)' }, + priority: { type: 'number', description: 'Priority level (1-5)' }, + type: { type: 'string', description: 'Task type' }, + }, + required: ['title'], + }, + handler: async (args: any) => { + return await client.createTask({ + title: args.title, + description: args.description, + contact: args.contact_id ? { id: args.contact_id } : undefined, + user_id: args.user_id, + due_date: args.due_date, + priority: args.priority, + type: args.type, + completed: false, + }); + }, + }, + + keap_update_task: { + description: 'Update an existing task', + inputSchema: { + type: 'object', + properties: { + task_id: { type: 'number', description: 'Task ID' }, + title: { type: 'string', description: 'Task title' }, + description: { type: 'string', description: 'Task description' }, + user_id: { type: 'number', description: 'Assigned user ID' }, + due_date: { type: 'string', description: 'Due date (ISO 8601)' }, + priority: { type: 'number', description: 'Priority level (1-5)' }, + completed: { type: 'boolean', description: 'Completion status' }, + }, + required: ['task_id'], + }, + handler: async (args: any) => { + const data: any = {}; + if (args.title !== undefined) data.title = args.title; + if (args.description !== undefined) data.description = args.description; + if (args.user_id !== undefined) data.user_id = args.user_id; + if (args.due_date !== undefined) data.due_date = args.due_date; + if (args.priority !== undefined) data.priority = args.priority; + if (args.completed !== undefined) data.completed = args.completed; + + return await client.updateTask(args.task_id, data); + }, + }, + + keap_delete_task: { + description: 'Delete a task', + inputSchema: { + type: 'object', + properties: { + task_id: { type: 'number', description: 'Task ID' }, + }, + required: ['task_id'], + }, + handler: async (args: any) => { + await client.deleteTask(args.task_id); + return { success: true, message: `Task ${args.task_id} deleted` }; + }, + }, + + keap_complete_task: { + description: 'Mark a task as completed', + inputSchema: { + type: 'object', + properties: { + task_id: { type: 'number', description: 'Task ID' }, + }, + required: ['task_id'], + }, + handler: async (args: any) => { + return await client.updateTask(args.task_id, { + completed: true, + completion_date: new Date().toISOString(), + }); + }, + }, + }; +} diff --git a/servers/keap/src/types/keap.ts b/servers/keap/src/types/keap.ts new file mode 100644 index 0000000..61bd947 --- /dev/null +++ b/servers/keap/src/types/keap.ts @@ -0,0 +1,346 @@ +// Keap API Types + +export interface KeapConfig { + accessToken: string; + baseUrl?: string; +} + +export interface PaginatedResponse { + data: T[]; + count: number; + next?: string; + previous?: string; +} + +// Contact Types +export interface Contact { + id: number; + email_addresses?: EmailAddress[]; + phone_numbers?: PhoneNumber[]; + addresses?: Address[]; + given_name?: string; + family_name?: string; + middle_name?: string; + company_name?: string; + job_title?: string; + birthday?: string; + anniversary?: string; + spouse_name?: string; + website?: string; + time_zone?: string; + opted_in?: boolean; + opt_in_reason?: string; + owner_id?: number; + source_type?: string; + contact_type?: string; + tags?: Tag[]; + custom_fields?: CustomField[]; + date_created?: string; + last_updated?: string; +} + +export interface EmailAddress { + email: string; + field: 'EMAIL1' | 'EMAIL2' | 'EMAIL3'; +} + +export interface PhoneNumber { + number: string; + field: 'PHONE1' | 'PHONE2' | 'PHONE3' | 'PHONE4' | 'PHONE5'; + type?: string; +} + +export interface Address { + line1?: string; + line2?: string; + locality?: string; + region?: string; + zip_code?: string; + country_code?: string; + field: 'BILLING' | 'SHIPPING' | 'OTHER'; +} + +// Deal/Opportunity Types +export interface Deal { + id: number; + contact?: Contact; + title: string; + stage_id: number; + stage?: Stage; + user_id?: number; + projected_revenue_high?: number; + projected_revenue_low?: number; + actual_revenue?: number; + estimated_close_date?: string; + next_action_date?: string; + next_action_notes?: string; + probability?: number; + affiliate_id?: number; + date_created?: string; + last_updated?: string; + custom_fields?: CustomField[]; +} + +export interface Pipeline { + id: number; + name: string; + stages: Stage[]; +} + +export interface Stage { + id: number; + name: string; + order: number; + check_list_items?: ChecklistItem[]; +} + +export interface ChecklistItem { + id: number; + description: string; + required: boolean; +} + +// Company Types +export interface Company { + id: number; + company_name: string; + email_address?: string; + phone_number?: string; + fax_number?: string; + website?: string; + address?: Address; + notes?: string; + custom_fields?: CustomField[]; + date_created?: string; + last_updated?: string; +} + +// Task Types +export interface Task { + id: number; + title: string; + description?: string; + contact?: Contact; + completed: boolean; + completion_date?: string; + due_date?: string; + priority?: number; + type?: string; + user_id?: number; + date_created?: string; + last_updated?: string; +} + +// Appointment Types +export interface Appointment { + id: number; + title: string; + description?: string; + location?: string; + start_date: string; + end_date: string; + all_day?: boolean; + contact?: Contact; + user_id?: number; + attendees?: number[]; + date_created?: string; + last_updated?: string; +} + +// Campaign Types +export interface Campaign { + id: number; + name: string; + goals?: string[]; + created_by?: number; + published_status?: 'Draft' | 'Published' | 'Archived'; + published_date?: string; + time_zone?: string; + error_message?: string; + locked?: boolean; + date_created?: string; + last_updated?: string; +} + +export interface CampaignStats { + campaign_id: number; + total_contacts: number; + active_contacts: number; + completed_contacts: number; + stopped_contacts: number; +} + +// Email Types +export interface Email { + id: number; + sent_to_address: string; + sent_to_contact_id?: number; + sent_from_address: string; + subject: string; + html_content?: string; + text_content?: string; + sent_date?: string; + opened?: boolean; + clicked?: boolean; + bounced?: boolean; + headers?: Record; +} + +export interface EmailTemplate { + id: number; + name: string; + subject?: string; + html_content?: string; + text_content?: string; + categories?: string[]; + date_created?: string; + last_updated?: string; +} + +// Order Types +export interface Order { + id: number; + contact_id: number; + title: string; + status: 'Draft' | 'Sent' | 'Paid' | 'Refunded'; + order_date?: string; + total: number; + total_due?: number; + total_paid?: number; + notes?: string; + order_items?: OrderItem[]; + transactions?: Transaction[]; + date_created?: string; + last_updated?: string; +} + +export interface OrderItem { + id: number; + product_id?: number; + description: string; + quantity: number; + price: number; + discount?: number; + tax?: number; + total: number; +} + +export interface Transaction { + id: number; + contact_id: number; + order_id?: number; + amount: number; + currency: string; + gateway?: string; + gateway_account_id?: string; + status: 'Pending' | 'Completed' | 'Failed' | 'Refunded'; + type: 'Sale' | 'Refund' | 'Chargeback'; + transaction_date: string; + test?: boolean; + errors?: string; +} + +// Product Types +export interface Product { + id: number; + product_name: string; + product_short_desc?: string; + product_desc?: string; + sku?: string; + product_price?: number; + status: 'Active' | 'Inactive'; + subscription_plans?: SubscriptionPlan[]; + date_created?: string; + last_updated?: string; +} + +export interface SubscriptionPlan { + id: number; + cycle: 'Day' | 'Week' | 'Month' | 'Year'; + frequency: number; + number_of_cycles?: number; + price: number; + subscription_plan_name: string; + active: boolean; +} + +// Tag Types +export interface Tag { + id: number; + name: string; + description?: string; + category?: TagCategory; + date_created?: string; +} + +export interface TagCategory { + id: number; + name: string; + description?: string; +} + +// Note Types +export interface Note { + id: number; + contact_id?: number; + user_id?: number; + title?: string; + body: string; + type?: 'Appointment' | 'Call' | 'Email' | 'Fax' | 'Letter' | 'Other'; + date_created?: string; + last_updated?: string; +} + +// Custom Field Types +export interface CustomField { + id: number; + content: string | number | boolean; +} + +// Automation Types +export interface Automation { + id: number; + name: string; + category?: string; + active: boolean; + date_created?: string; + last_updated?: string; +} + +// Report Types +export interface ContactGrowthReport { + date: string; + new_contacts: number; + total_contacts: number; +} + +export interface RevenueReport { + date: string; + revenue: number; + transactions: number; + average_order_value: number; +} + +export interface CampaignPerformanceReport { + campaign_id: number; + campaign_name: string; + total_sent: number; + opened: number; + clicked: number; + bounced: number; + unsubscribed: number; + open_rate: number; + click_rate: number; +} + +// Error Types +export interface KeapError { + message: string; + error?: string; + fault?: { + faultstring: string; + detail?: { + errorcode: string; + }; + }; +} diff --git a/servers/keap/src/ui/react-app/appointment-calendar/index.tsx b/servers/keap/src/ui/react-app/appointment-calendar/index.tsx new file mode 100644 index 0000000..afb3aaf --- /dev/null +++ b/servers/keap/src/ui/react-app/appointment-calendar/index.tsx @@ -0,0 +1,80 @@ +import React, { useState } from 'react'; +import { Calendar, Clock, MapPin, User } from 'lucide-react'; + +interface Appointment { + id: number; + title: string; + date: string; + time: string; + duration: string; + location: string; + contact: string; +} + +export default function AppointmentCalendar() { + const [appointments] = useState([ + { id: 1, title: 'Sales Call - Acme Corp', date: '2025-02-14', time: '10:00 AM', duration: '1 hour', location: 'Zoom', contact: 'John Doe' }, + { id: 2, title: 'Product Demo', date: '2025-02-14', time: '2:00 PM', duration: '45 min', location: 'Office', contact: 'Jane Smith' }, + { id: 3, title: 'Follow-up Meeting', date: '2025-02-15', time: '11:00 AM', duration: '30 min', location: 'Phone', contact: 'Bob Johnson' }, + { id: 4, title: 'Contract Review', date: '2025-02-16', time: '3:00 PM', duration: '2 hours', location: 'Conference Room', contact: 'Alice Williams' }, + ]); + + const groupByDate = () => { + const grouped: Record = {}; + appointments.forEach(apt => { + if (!grouped[apt.date]) grouped[apt.date] = []; + grouped[apt.date].push(apt); + }); + return grouped; + }; + + const grouped = groupByDate(); + + return ( +
+
+

Appointments

+ +
+ +
+ +
+ {Object.entries(grouped).map(([date, apts]) => ( +
+
+ +

{new Date(date).toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' })}

+
+ +
+ {apts.map(apt => ( +
+

{apt.title}

+ +
+
+ + {apt.time} ({apt.duration}) +
+
+ + {apt.location} +
+
+ + {apt.contact} +
+
+
+ ))} +
+
+ ))} +
+
+
+ ); +} diff --git a/servers/keap/src/ui/react-app/company-detail/index.tsx b/servers/keap/src/ui/react-app/company-detail/index.tsx new file mode 100644 index 0000000..4a1dbf0 --- /dev/null +++ b/servers/keap/src/ui/react-app/company-detail/index.tsx @@ -0,0 +1,80 @@ +import React, { useState } from 'react'; +import { Building, Mail, Phone, Globe, MapPin, Users } from 'lucide-react'; + +export default function CompanyDetail() { + const [company] = useState({ + id: 1, + name: 'Acme Corporation', + email: 'contact@acme.com', + phone: '+1 (555) 987-6543', + website: 'https://acme.com', + address: '123 Business Ave, Suite 100, San Francisco, CA 94105', + total_contacts: 28, + total_deals: 5, + total_revenue: 125000, + }); + + const contacts = [ + { id: 1, name: 'John Doe', title: 'CEO', email: 'john@acme.com' }, + { id: 2, name: 'Jane Smith', title: 'CTO', email: 'jane@acme.com' }, + { id: 3, name: 'Bob Johnson', title: 'VP Sales', email: 'bob@acme.com' }, + ]; + + return ( +
+
+

{company.name}

+ +
+ } label="Email" value={company.email} /> + } label="Phone" value={company.phone} /> + } label="Website" value={company.website} /> + } label="Address" value={company.address} /> +
+ +
+ + + +
+ +
+
+ +

Key Contacts

+
+
+ {contacts.map(contact => ( +
+
{contact.name}
+
{contact.title}
+
{contact.email}
+
+ ))} +
+
+
+
+ ); +} + +function InfoCard({ icon, label, value }: any) { + return ( +
+
+
{icon}
+
{label}
+
+
{value}
+
+ ); +} + +function StatCard({ label, value }: any) { + return ( +
+
{value}
+
{label}
+
+ ); +} diff --git a/servers/keap/src/ui/react-app/company-grid/index.tsx b/servers/keap/src/ui/react-app/company-grid/index.tsx new file mode 100644 index 0000000..c87d88a --- /dev/null +++ b/servers/keap/src/ui/react-app/company-grid/index.tsx @@ -0,0 +1,76 @@ +import React, { useState } from 'react'; +import { Search, Building } from 'lucide-react'; + +interface Company { + id: number; + name: string; + email: string; + phone: string; + contacts: number; + revenue: number; +} + +export default function CompanyGrid() { + const [companies] = useState([ + { id: 1, name: 'Acme Corporation', email: 'contact@acme.com', phone: '555-1234', contacts: 28, revenue: 125000 }, + { id: 2, name: 'TechCo Industries', email: 'info@techco.com', phone: '555-5678', contacts: 15, revenue: 89000 }, + { id: 3, name: 'BigCorp LLC', email: 'hello@bigcorp.com', phone: '555-9012', contacts: 42, revenue: 234000 }, + { id: 4, name: 'StartupX', email: 'team@startupx.com', phone: '555-3456', contacts: 8, revenue: 12000 }, + ]); + + const [search, setSearch] = useState(''); + + const filtered = companies.filter(c => + c.name.toLowerCase().includes(search.toLowerCase()) + ); + + return ( +
+

Companies

+ +
+
+ + setSearch(e.target.value)} + className="w-full pl-10 pr-4 py-2 bg-gray-800 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ +
+ +
+ {filtered.map(company => ( +
+
+
+ +
+
+

{company.name}

+
{company.email}
+
{company.phone}
+
+
+ +
+
+
{company.contacts}
+
Contacts
+
+
+
${(company.revenue / 1000).toFixed(0)}K
+
Revenue
+
+
+
+ ))} +
+
+ ); +} diff --git a/servers/keap/src/ui/react-app/contact-dashboard/index.tsx b/servers/keap/src/ui/react-app/contact-dashboard/index.tsx new file mode 100644 index 0000000..601a04e --- /dev/null +++ b/servers/keap/src/ui/react-app/contact-dashboard/index.tsx @@ -0,0 +1,71 @@ +import React, { useState, useEffect } from 'react'; +import { Users, TrendingUp, Tag, Mail } from 'lucide-react'; + +interface Stats { + total: number; + new_today: number; + tagged: number; + emailed: number; +} + +export default function ContactDashboard() { + const [stats, setStats] = useState({ total: 0, new_today: 0, tagged: 0, emailed: 0 }); + const [recentContacts, setRecentContacts] = useState([]); + + useEffect(() => { + // Simulated data - in real use, fetch from MCP tools + setStats({ total: 1247, new_today: 23, tagged: 856, emailed: 432 }); + setRecentContacts([ + { id: 1, name: 'John Doe', email: 'john@example.com', created: '2025-02-14' }, + { id: 2, name: 'Jane Smith', email: 'jane@example.com', created: '2025-02-14' }, + { id: 3, name: 'Bob Johnson', email: 'bob@example.com', created: '2025-02-13' }, + ]); + }, []); + + return ( +
+

Contact Dashboard

+ +
+ } label="Total Contacts" value={stats.total} color="blue" /> + } label="New Today" value={stats.new_today} color="green" /> + } label="Tagged" value={stats.tagged} color="purple" /> + } label="Emailed" value={stats.emailed} color="orange" /> +
+ +
+

Recent Contacts

+
+ {recentContacts.map(contact => ( +
+
+
{contact.name}
+
{contact.email}
+
+
{contact.created}
+
+ ))} +
+
+
+ ); +} + +function StatCard({ icon, label, value, color }: any) { + const colorClasses = { + blue: 'bg-blue-500', + green: 'bg-green-500', + purple: 'bg-purple-500', + orange: 'bg-orange-500', + }; + + return ( +
+
+ {icon} +
+
{value.toLocaleString()}
+
{label}
+
+ ); +} diff --git a/servers/keap/src/ui/react-app/contact-detail/index.tsx b/servers/keap/src/ui/react-app/contact-detail/index.tsx new file mode 100644 index 0000000..db9e5d3 --- /dev/null +++ b/servers/keap/src/ui/react-app/contact-detail/index.tsx @@ -0,0 +1,89 @@ +import React, { useState, useEffect } from 'react'; +import { User, Mail, Phone, Building, Calendar, Tag as TagIcon } from 'lucide-react'; + +interface Contact { + id: number; + name: string; + email: string; + phone?: string; + company?: string; + created: string; + tags: string[]; + notes: Array<{ id: number; body: string; date: string }>; +} + +export default function ContactDetail() { + const [contact, setContact] = useState(null); + + useEffect(() => { + // Simulated data + setContact({ + id: 1, + name: 'John Doe', + email: 'john@example.com', + phone: '+1 (555) 123-4567', + company: 'Acme Corp', + created: '2025-01-15', + tags: ['Customer', 'VIP', 'Newsletter'], + notes: [ + { id: 1, body: 'Interested in premium plan', date: '2025-02-10' }, + { id: 2, body: 'Follow up next week', date: '2025-02-12' }, + ], + }); + }, []); + + if (!contact) return
Loading...
; + + return ( +
+
+

{contact.name}

+ +
+ } label="Email" value={contact.email} /> + } label="Phone" value={contact.phone || 'N/A'} /> + } label="Company" value={contact.company || 'N/A'} /> + } label="Created" value={contact.created} /> +
+ +
+
+ +

Tags

+
+
+ {contact.tags.map(tag => ( + + {tag} + + ))} +
+
+ +
+

Notes

+
+ {contact.notes.map(note => ( +
+
{note.body}
+
{note.date}
+
+ ))} +
+
+
+
+ ); +} + +function InfoCard({ icon, label, value }: any) { + return ( +
+
+
{icon}
+
{label}
+
+
{value}
+
+ ); +} diff --git a/servers/keap/src/ui/react-app/contact-grid/index.tsx b/servers/keap/src/ui/react-app/contact-grid/index.tsx new file mode 100644 index 0000000..9ff67dd --- /dev/null +++ b/servers/keap/src/ui/react-app/contact-grid/index.tsx @@ -0,0 +1,80 @@ +import React, { useState, useEffect } from 'react'; +import { Search, Filter } from 'lucide-react'; + +interface Contact { + id: number; + name: string; + email: string; + phone?: string; + company?: string; + tags: number; +} + +export default function ContactGrid() { + const [contacts, setContacts] = useState([]); + const [search, setSearch] = useState(''); + + useEffect(() => { + setContacts([ + { id: 1, name: 'John Doe', email: 'john@example.com', phone: '555-1234', company: 'Acme', tags: 3 }, + { id: 2, name: 'Jane Smith', email: 'jane@example.com', phone: '555-5678', company: 'TechCo', tags: 2 }, + { id: 3, name: 'Bob Johnson', email: 'bob@example.com', phone: '555-9012', company: 'StartupX', tags: 5 }, + { id: 4, name: 'Alice Williams', email: 'alice@example.com', phone: '555-3456', company: 'BigCorp', tags: 1 }, + ]); + }, []); + + const filtered = contacts.filter(c => + c.name.toLowerCase().includes(search.toLowerCase()) || + c.email.toLowerCase().includes(search.toLowerCase()) + ); + + return ( +
+

Contacts

+ +
+
+ + setSearch(e.target.value)} + className="w-full pl-10 pr-4 py-2 bg-gray-800 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ +
+ +
+ + + + + + + + + + + + {filtered.map((contact, i) => ( + + + + + + + + ))} + +
NameEmailPhoneCompanyTags
{contact.name}{contact.email}{contact.phone}{contact.company} + {contact.tags} +
+
+
+ ); +} diff --git a/servers/keap/src/ui/react-app/deal-dashboard/index.tsx b/servers/keap/src/ui/react-app/deal-dashboard/index.tsx new file mode 100644 index 0000000..2a0fc95 --- /dev/null +++ b/servers/keap/src/ui/react-app/deal-dashboard/index.tsx @@ -0,0 +1,105 @@ +import React, { useState, useEffect } from 'react'; +import { DollarSign, TrendingUp, Award, Target } from 'lucide-react'; + +interface DealStats { + total_value: number; + won_value: number; + active_deals: number; + close_rate: number; +} + +export default function DealDashboard() { + const [stats, setStats] = useState({ + total_value: 0, + won_value: 0, + active_deals: 0, + close_rate: 0, + }); + + const [topDeals, setTopDeals] = useState([]); + + useEffect(() => { + setStats({ + total_value: 487500, + won_value: 142300, + active_deals: 28, + close_rate: 34.5, + }); + + setTopDeals([ + { id: 1, title: 'Enterprise Deal - Acme Corp', value: 50000, stage: 'Negotiation', probability: 75 }, + { id: 2, title: 'SaaS License - TechCo', value: 35000, stage: 'Proposal', probability: 50 }, + { id: 3, title: 'Consulting - BigCorp', value: 28000, stage: 'Qualification', probability: 30 }, + ]); + }, []); + + return ( +
+

Deal Dashboard

+ +
+ } + label="Total Pipeline" + value={`$${(stats.total_value / 1000).toFixed(0)}K`} + color="blue" + /> + } + label="Won This Month" + value={`$${(stats.won_value / 1000).toFixed(0)}K`} + color="green" + /> + } + label="Active Deals" + value={stats.active_deals} + color="purple" + /> + } + label="Close Rate" + value={`${stats.close_rate}%`} + color="orange" + /> +
+ +
+

Top Deals

+
+ {topDeals.map(deal => ( +
+
+
{deal.title}
+
${deal.value.toLocaleString()}
+
+
+ {deal.stage} + {deal.probability}% probability +
+
+ ))} +
+
+
+ ); +} + +function StatCard({ icon, label, value, color }: any) { + const colorClasses = { + blue: 'bg-blue-500', + green: 'bg-green-500', + purple: 'bg-purple-500', + orange: 'bg-orange-500', + }; + + return ( +
+
+ {icon} +
+
{value}
+
{label}
+
+ ); +} diff --git a/servers/keap/src/ui/react-app/deal-detail/index.tsx b/servers/keap/src/ui/react-app/deal-detail/index.tsx new file mode 100644 index 0000000..df15272 --- /dev/null +++ b/servers/keap/src/ui/react-app/deal-detail/index.tsx @@ -0,0 +1,84 @@ +import React, { useState } from 'react'; +import { DollarSign, User, Calendar, TrendingUp, FileText } from 'lucide-react'; + +export default function DealDetail() { + const [deal] = useState({ + id: 1, + title: 'Enterprise Deal - Acme Corp', + value: 50000, + stage: 'Negotiation', + probability: 75, + contact: 'John Doe', + owner: 'Sarah Smith', + created: '2025-01-15', + estimated_close: '2025-03-01', + next_action: 'Send final proposal', + next_action_date: '2025-02-16', + notes: [ + { id: 1, text: 'Budget approved by CFO', date: '2025-02-10' }, + { id: 2, text: 'Product demo went well', date: '2025-02-05' }, + ], + }); + + return ( +
+
+
+

{deal.title}

+
+ {deal.stage} + {deal.probability}% probability +
+
+ +
+ } label="Deal Value" value={`$${deal.value.toLocaleString()}`} /> + } label="Contact" value={deal.contact} /> + } label="Owner" value={deal.owner} /> + } label="Est. Close Date" value={deal.estimated_close} /> +
+ +
+
+ +

Next Action

+
+
+
{deal.next_action}
+
Due: {deal.next_action_date}
+
+
+ +
+
+ +

Activity

+
+
+ {deal.notes.map(note => ( +
+
{note.text}
+
{note.date}
+
+ ))} +
+ +
+
+
+ ); +} + +function InfoCard({ icon, label, value }: any) { + return ( +
+
+
{icon}
+
{label}
+
+
{value}
+
+ ); +} diff --git a/servers/keap/src/ui/react-app/email-composer/index.tsx b/servers/keap/src/ui/react-app/email-composer/index.tsx new file mode 100644 index 0000000..ede134d --- /dev/null +++ b/servers/keap/src/ui/react-app/email-composer/index.tsx @@ -0,0 +1,93 @@ +import React, { useState } from 'react'; +import { Send, Paperclip, Image } from 'lucide-react'; + +export default function EmailComposer() { + const [to, setTo] = useState(''); + const [subject, setSubject] = useState(''); + const [body, setBody] = useState(''); + + const handleSend = () => { + console.log('Sending email:', { to, subject, body }); + // In real app, call MCP keap_send_email tool + alert('Email sent!'); + }; + + return ( +
+
+

Compose Email

+ +
+
+
+ + setTo(e.target.value)} + placeholder="recipient@example.com" + className="w-full px-4 py-2 bg-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ +
+ + setSubject(e.target.value)} + placeholder="Email subject" + className="w-full px-4 py-2 bg-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ +
+ +