From 70698fe7149c8721fba8b757125c45f2266058b3 Mon Sep 17 00:00:00 2001 From: seanmtracey Date: Thu, 28 Aug 2025 19:53:39 +0100 Subject: [PATCH] Team 4; --- docs/connectors.md | 133 ++++++ docs/connectors/hstacks.md | 175 ++++++++ .../src/connectors/hstacks.spec.ts | 215 ++++++++++ .../mcp-connectors/src/connectors/hstacks.ts | 396 ++++++++++++++++++ packages/mcp-connectors/src/index.ts | 3 + 5 files changed, 922 insertions(+) create mode 100644 docs/connectors.md create mode 100644 docs/connectors/hstacks.md create mode 100644 packages/mcp-connectors/src/connectors/hstacks.spec.ts create mode 100644 packages/mcp-connectors/src/connectors/hstacks.ts diff --git a/docs/connectors.md b/docs/connectors.md new file mode 100644 index 00000000..e905f734 --- /dev/null +++ b/docs/connectors.md @@ -0,0 +1,133 @@ +# Available Connectors + +This document lists all available MCP connectors and their basic configurations. + +## Infrastructure & DevOps + +### [HStacks](./connectors/hstacks.md) +Deploy and manage cloud infrastructure using hstacks.dev's API. + +**Credentials:** `accessToken` + +**Key Features:** +- Deploy Ubuntu/Debian/Rocky/CentOS servers +- Multi-region deployment (US, Germany, Finland) +- Firewall management +- Volume storage +- Stack lifecycle management + +## Development & Code + +### GitHub +Connect to GitHub repositories and manage issues, pull requests, and code. + +**Credentials:** `token` + +### GitLab +GitLab integration for repository and project management. + +**Credentials:** `token` + +## Project Management + +### Asana +Task and project management with Asana. + +**Credentials:** `apiKey` + +### Jira +Issue tracking and project management with Atlassian Jira. + +**Credentials:** `email`, `apiToken`, `domain` + +### Linear +Modern issue tracking and project management. + +**Credentials:** `apiKey` + +### Todoist +Personal and team task management. + +**Credentials:** `apiToken` + +## Communication + +### Slack +Team communication and workflow automation. + +**Credentials:** `botToken` + +## Documentation & Knowledge + +### Notion +Knowledge management and documentation. + +**Credentials:** `apiKey` + +### Google Drive +File storage and document management. + +**Credentials:** `clientId`, `clientSecret`, `refreshToken` + +## Database & Backend + +### Supabase +Backend-as-a-service with database, auth, and storage. + +**Credentials:** `url`, `anonKey` + +## Analytics & Monitoring + +### Datadog +Infrastructure monitoring and analytics. + +**Credentials:** `apiKey`, `appKey` + +## HR & People + +### HiBob +HR management and employee data. + +**Credentials:** `apiKey` + +### Deel +Global payroll and compliance management. + +**Credentials:** `apiKey` + +## CRM & Sales + +### HubSpot +Customer relationship management and marketing automation. + +**Credentials:** `accessToken` + +### Attio +Modern CRM for growing businesses. + +**Credentials:** `apiKey` + +## Testing & Development Tools + +### Test +Basic test connector for development and testing. + +**Credentials:** None required + +--- + +## Usage + +To start any connector: + +```bash +# Without credentials (test connector) +bun start --connector test + +# With credentials +bun start --connector --credentials '{"key":"value"}' +``` + +The server will be available at `http://localhost:3000/mcp` + +For detailed configuration and usage examples, see the individual connector documentation pages. \ No newline at end of file diff --git a/docs/connectors/hstacks.md b/docs/connectors/hstacks.md new file mode 100644 index 00000000..9668726c --- /dev/null +++ b/docs/connectors/hstacks.md @@ -0,0 +1,175 @@ +# HStacks Connector + +Deploy and manage cloud infrastructure using hstacks.dev's API. + +## Credentials + +- **accessToken** (required): Your hstacks API access token from your hstacks dashboard + +## Setup + +No additional setup required. + +## Tools + +### get_hstacks_stacks_schema +Get the JSON schema that describes all hstacks Stack properties. + +### get_available_hstacks_images +Get a list of all operating system images available for server deployment. + +### get_available_hstacks_locations +Get a list of all locations where servers can be deployed. Optionally filter by server type. + +**Parameters:** +- `serverType` (optional): Server type to filter locations (e.g., 'cax11') + +### get_available_hstacks_servers +Get a list of all server types that can be deployed. Optionally filter by location. + +**Parameters:** +- `location` (optional): Location to filter server types (e.g., 'nbg1') + +### validate_stack +Validate a hstacks Stack JSON configuration before deployment. + +**Parameters:** +- `name` (required): Unique identifier for the stack deployment +- `servers` (required): Array of server instances to deploy +- `firewalls` (required): Array of shared firewall configurations +- `volumes` (required): Array of persistent storage volumes +- `successHook` (optional): Webhook URL called on successful deployment +- `errorHook` (optional): Webhook URL called on deployment failure + +### deploy_stack +Deploy a Stack to hstacks infrastructure. + +**Parameters:** +- `name` (required): Unique identifier for the stack deployment +- `servers` (required): Array of server instances to deploy +- `firewalls` (required): Array of shared firewall configurations +- `volumes` (required): Array of persistent storage volumes +- `successHook` (optional): Webhook URL called on successful deployment +- `errorHook` (optional): Webhook URL called on deployment failure + +### delete_stack +Delete an existing hstacks Stack. + +**Parameters:** +- `stackID` (required): The stack ID to delete + +### get_stack_status +Get the deployment status of a stack by its ID. + +**Parameters:** +- `stackID` (required): The stack ID to check status for + +## Example Usage + +### Deploy a simple web server + +```bash +# Deploy a single Ubuntu server with nginx in Germany +bun start --connector hstacks --credentials '{"accessToken":"your-token-here"}' +``` + +Then use the deploy_stack tool with: + +```json +{ + "name": "my-web-server", + "servers": [ + { + "name": "web-server", + "serverType": "cpx11", + "image": "ubuntu-22.04", + "location": "nbg1", + "firewalls": ["web-firewall"], + "startScript": "#!/bin/bash\napt update\napt install -y nginx\nsystemctl start nginx\nsystemctl enable nginx" + } + ], + "firewalls": [ + { + "name": "web-firewall", + "rules": [ + { + "direction": "inbound", + "protocol": "tcp", + "port": 80, + "sourceIPs": ["0.0.0.0/0"], + "description": "Allow HTTP traffic" + } + ] + } + ], + "volumes": [] +} +``` + +### Multi-region deployment + +Deploy servers in multiple locations for geographic distribution: + +```json +{ + "name": "multi-region-app", + "servers": [ + { + "name": "app-us", + "serverType": "cpx11", + "image": "ubuntu-22.04", + "location": "ash", + "firewalls": ["app-firewall"] + }, + { + "name": "app-eu", + "serverType": "cpx11", + "image": "ubuntu-22.04", + "location": "nbg1", + "firewalls": ["app-firewall"] + } + ], + "firewalls": [ + { + "name": "app-firewall", + "rules": [ + { + "direction": "inbound", + "protocol": "tcp", + "port": 80, + "sourceIPs": ["0.0.0.0/0"] + }, + { + "direction": "inbound", + "protocol": "tcp", + "port": 443, + "sourceIPs": ["0.0.0.0/0"] + } + ] + } + ], + "volumes": [] +} +``` + +## Common Server Types + +- **cpx11**: 1 vCPU, 2GB RAM (shared CPU) +- **cpx21**: 3 vCPUs, 4GB RAM (shared CPU) +- **cpx31**: 2 vCPUs, 8GB RAM (shared CPU) +- **cax11**: 2 vCPUs, 4GB RAM (ARM) +- **ccx13**: 2 vCPUs, 8GB RAM (dedicated CPU) + +## Common Locations + +- **ash**: Ashburn, VA (US East) +- **nbg1**: Nuremberg (Germany) +- **hel1**: Helsinki (Finland) +- **fsn1**: Falkenstein (Germany) + +## Getting Your Access Token + +1. Sign up at [hstacks.dev](https://hstacks.dev) +2. Navigate to your dashboard +3. Generate an API access token +4. Use the token in your connector credentials \ No newline at end of file diff --git a/packages/mcp-connectors/src/connectors/hstacks.spec.ts b/packages/mcp-connectors/src/connectors/hstacks.spec.ts new file mode 100644 index 00000000..7db53e03 --- /dev/null +++ b/packages/mcp-connectors/src/connectors/hstacks.spec.ts @@ -0,0 +1,215 @@ +import { describe, expect, it } from 'vitest'; +import type { MCPToolDefinition } from '@stackone/mcp-config-types'; +import { createMockConnectorContext } from '../__mocks__/context'; +import { HStacksConnectorConfig } from './hstacks'; + +describe('#HStacksConnector', () => { + describe('.GET_HSTACKS_SCHEMA', () => { + describe('when valid credentials are provided', () => { + it('returns the hstacks schema', async () => { + const tool = HStacksConnectorConfig.tools.GET_HSTACKS_SCHEMA as MCPToolDefinition; + const mockContext = createMockConnectorContext({ + credentials: { accessToken: '58b011ed-28db-412e-b49f-e4724123c2a7' }, + }); + + const actual = await tool.handler({}, mockContext); + + expect(actual).toContain('hstacks Stack Configuration'); + }); + }); + }); + + describe('.GET_AVAILABLE_IMAGES', () => { + describe('when valid credentials are provided', () => { + it('returns available images', async () => { + const tool = HStacksConnectorConfig.tools.GET_AVAILABLE_IMAGES as MCPToolDefinition; + const mockContext = createMockConnectorContext({ + credentials: { accessToken: '58b011ed-28db-412e-b49f-e4724123c2a7' }, + }); + + const actual = await tool.handler({}, mockContext); + + expect(typeof actual).toBe('string'); + expect(actual).not.toBe('null'); + }); + }); + + describe('when no credentials are provided', () => { + it('returns an error message', async () => { + const tool = HStacksConnectorConfig.tools.GET_AVAILABLE_IMAGES as MCPToolDefinition; + const mockContext = createMockConnectorContext({ + credentials: {}, + }); + + const actual = await tool.handler({}, mockContext); + + expect(actual).toBe('ERROR: No access token provided in credentials'); + }); + }); + }); + + describe('.GET_AVAILABLE_LOCATIONS', () => { + describe('when valid credentials are provided', () => { + it('returns available locations', async () => { + const tool = HStacksConnectorConfig.tools.GET_AVAILABLE_LOCATIONS as MCPToolDefinition; + const mockContext = createMockConnectorContext({ + credentials: { accessToken: '58b011ed-28db-412e-b49f-e4724123c2a7' }, + }); + + const actual = await tool.handler({}, mockContext); + + expect(typeof actual).toBe('string'); + }); + }); + + describe('when server type filter is provided', () => { + it('filters locations by server type', async () => { + const tool = HStacksConnectorConfig.tools.GET_AVAILABLE_LOCATIONS as MCPToolDefinition; + const mockContext = createMockConnectorContext({ + credentials: { accessToken: '58b011ed-28db-412e-b49f-e4724123c2a7' }, + }); + + const actual = await tool.handler({ serverType: 'cax11' }, mockContext); + + expect(typeof actual).toBe('string'); + }); + }); + }); + + describe('.GET_AVAILABLE_SERVERS', () => { + describe('when valid credentials are provided', () => { + it('returns available server types', async () => { + const tool = HStacksConnectorConfig.tools.GET_AVAILABLE_SERVERS as MCPToolDefinition; + const mockContext = createMockConnectorContext({ + credentials: { accessToken: '58b011ed-28db-412e-b49f-e4724123c2a7' }, + }); + + const actual = await tool.handler({}, mockContext); + + expect(typeof actual).toBe('string'); + }); + }); + }); + + describe('.VALIDATE_STACK', () => { + describe('when valid stack data is provided', () => { + it('validates the stack configuration', async () => { + const tool = HStacksConnectorConfig.tools.VALIDATE_STACK as MCPToolDefinition; + const mockContext = createMockConnectorContext({ + credentials: { accessToken: '58b011ed-28db-412e-b49f-e4724123c2a7' }, + }); + + const stackData = { + name: 'test-stack', + servers: [ + { + name: 'test-server', + serverType: 'cpx11', + image: 'ubuntu-22.04', + location: 'nbg1', + }, + ], + firewalls: [], + volumes: [], + }; + + const actual = await tool.handler(stackData, mockContext); + + expect(typeof actual).toBe('string'); + }); + }); + }); + + describe('.DEPLOY_STACK', () => { + describe('when valid stack data is provided', () => { + it('deploys the stack and returns deployment info', async () => { + const tool = HStacksConnectorConfig.tools.DEPLOY_STACK as MCPToolDefinition; + const mockContext = createMockConnectorContext({ + credentials: { accessToken: '58b011ed-28db-412e-b49f-e4724123c2a7' }, + }); + + const stackData = { + name: 'test-deploy-stack', + servers: [ + { + name: 'test-server', + serverType: 'cpx11', + image: 'ubuntu-22.04', + location: 'nbg1', + firewalls: ['test-firewall'], + }, + ], + firewalls: [ + { + name: 'test-firewall', + rules: [ + { + direction: 'inbound', + protocol: 'tcp', + port: 22, + sourceIPs: ['0.0.0.0/0'], + }, + ], + }, + ], + volumes: [], + }; + + const actual = await tool.handler(stackData, mockContext); + + expect(typeof actual).toBe('string'); + expect(actual).not.toContain('Failed to deploy stack'); + }); + }); + + describe('when no credentials are provided', () => { + it('returns an error message', async () => { + const tool = HStacksConnectorConfig.tools.DEPLOY_STACK as MCPToolDefinition; + const mockContext = createMockConnectorContext({ + credentials: {}, + }); + + const stackData = { + name: 'test-stack', + servers: [], + firewalls: [], + volumes: [], + }; + + const actual = await tool.handler(stackData, mockContext); + + expect(actual).toContain('Failed to deploy stack: No access token provided in credentials'); + }); + }); + }); + + describe('.DELETE_STACK', () => { + describe('when valid stack ID is provided', () => { + it('deletes the stack', async () => { + const tool = HStacksConnectorConfig.tools.DELETE_STACK as MCPToolDefinition; + const mockContext = createMockConnectorContext({ + credentials: { accessToken: '58b011ed-28db-412e-b49f-e4724123c2a7' }, + }); + + const actual = await tool.handler({ stackID: 'test-stack-id' }, mockContext); + + expect(typeof actual).toBe('string'); + }); + }); + }); + + describe('.GET_STACK_STATUS', () => { + describe('when valid stack ID is provided', () => { + it('returns stack status', async () => { + const tool = HStacksConnectorConfig.tools.GET_STACK_STATUS as MCPToolDefinition; + const mockContext = createMockConnectorContext({ + credentials: { accessToken: '58b011ed-28db-412e-b49f-e4724123c2a7' }, + }); + + const actual = await tool.handler({ stackID: 'test-stack-id' }, mockContext); + + expect(typeof actual).toBe('string'); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/mcp-connectors/src/connectors/hstacks.ts b/packages/mcp-connectors/src/connectors/hstacks.ts new file mode 100644 index 00000000..9064b6df --- /dev/null +++ b/packages/mcp-connectors/src/connectors/hstacks.ts @@ -0,0 +1,396 @@ +import { mcpConnectorConfig } from '@stackone/mcp-config-types'; +import { z } from 'zod'; + +const HSTACKS_BASE_URL = 'https://api.hstacks.dev'; +const USER_AGENT = 'hstacks-mcp/0.1'; + +// Type definitions +interface HStacksSchema { + [key: string]: unknown; +} + +interface HStacksImage { + id: string; + name: string; + description?: string; + [key: string]: unknown; +} + +interface HStacksLocation { + id: string; + name: string; + description?: string; + [key: string]: unknown; +} + +interface HStacksServer { + id: string; + name: string; + vcpus: number; + memory: string; + disk: string; + price: string; + [key: string]: unknown; +} + +interface HStacksValidationResponse { + valid: boolean; + errors?: string[]; + [key: string]: unknown; +} + +interface HStacksDeploymentResponse { + stackId: string; + status: string; + [key: string]: unknown; +} + +interface HStacksStatusResponse { + stackId: string; + status: string; + servers?: unknown[]; + [key: string]: unknown; +} + +interface HStacksDeleteResponse { + success: boolean; + message?: string; + [key: string]: unknown; +} + +class HStacksClient { + private headers: { + 'User-Agent': string; + 'Content-Type': string; + 'Access-Token': string; + }; + + constructor(accessToken: string) { + this.headers = { + 'User-Agent': USER_AGENT, + 'Content-Type': 'application/json', + 'Access-Token': accessToken, + }; + } + + async makeRequest(path: string, method = 'GET', body?: unknown): Promise { + const url = `${HSTACKS_BASE_URL}/${path}`; + console.log(`[HStacks] Making ${method} request to: ${url}`); + + const options: RequestInit = { + method, + headers: this.headers, + }; + + // Only add body for non-GET and non-HEAD requests when body is provided + if (body !== undefined && method && !['GET', 'HEAD'].includes(method.toUpperCase())) { + options.body = JSON.stringify(body); + console.log('[HStacks] Request body:', JSON.stringify(body, null, 2)); + } + + try { + console.log('[HStacks] Request headers:', this.headers); + const response = await fetch(url, options); + console.log(`[HStacks] Response status: ${response.status} ${response.statusText}`); + + if (!response.ok) { + const errorText = await response.text(); + console.error('[HStacks] Error response:', errorText); + throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`); + } + + const responseData = (await response.json()) as T; + console.log('[HStacks] Response data:', JSON.stringify(responseData, null, 2)); + return responseData; + } catch (error) { + console.error(`[HStacks] Error making request to ${url}:`, error); + throw error; + } + } + + async getSchema(): Promise { + return this.makeRequest('info/hstacks-schema'); + } + + async getAvailableImages(): Promise { + return this.makeRequest('info/available-images'); + } + + async getAvailableLocations(serverType?: string): Promise { + const endpoint = serverType + ? `info/available-locations/${serverType}` + : 'info/available-locations'; + return this.makeRequest(endpoint); + } + + async getAvailableServers(location?: string): Promise { + const endpoint = location + ? `info/available-servers/${location}` + : 'info/available-servers'; + return this.makeRequest(endpoint); + } + + async validateStack(stackData: unknown): Promise { + return this.makeRequest( + 'stacks/validate', + 'POST', + stackData + ); + } + + async deployStack(stackData: unknown): Promise { + return this.makeRequest( + 'stacks/create', + 'POST', + stackData + ); + } + + async deleteStack(stackId: string): Promise { + return this.makeRequest(`stacks/delete/${stackId}`, 'POST'); + } + + async getStackStatus(stackId: string): Promise { + return this.makeRequest(`stacks/check/${stackId}`); + } +} + +export const HStacksConnectorConfig = mcpConnectorConfig({ + name: 'hstacks', + key: 'hstacks', + version: '1.0.0', + logo: 'https://hstacks.dev/images/logo.jpg', // Update with actual logo URL + credentials: z.object({ + accessToken: z + .string() + .describe('hstacks API Access Token :: Get from your hstacks dashboard'), + }), + setup: z.object({ + // Add any setup options here if needed + }), + examplePrompt: + 'Get available server types, deploy a new stack with Ubuntu servers, check deployment status, and manage stack lifecycle.', + tools: (tool) => ({ + GET_HSTACKS_SCHEMA: tool({ + name: 'get_hstacks_stacks_schema', + description: 'Get the JSON schema that describes all hstacks Stack properties.', + schema: z.object({}), + handler: async (_args, context) => { + try { + const { accessToken } = await context.getCredentials(); + const client = new HStacksClient(accessToken); + const schema = await client.getSchema(); + return JSON.stringify(schema); + } catch (error) { + return `Failed to get hstacks schema: ${error instanceof Error ? error.message : String(error)}`; + } + }, + }), + GET_AVAILABLE_IMAGES: tool({ + name: 'get_available_hstacks_images', + description: + 'Get a list of all of the operating system images available to deploy a server with on hstacks.', + schema: z.object({}), + handler: async (_args, context) => { + try { + console.log('[get_available_hstacks_images] Starting'); + const credentials = await context.getCredentials(); + console.log('[get_available_hstacks_images] Credentials:', credentials); + const { accessToken } = credentials; + + if (!accessToken) { + return 'ERROR: No access token provided in credentials'; + } + + console.log( + '[get_available_hstacks_images] Creating client with token:', + `${accessToken.substring(0, 8)}...` + ); + const client = new HStacksClient(accessToken); + + console.log('[get_available_hstacks_images] Making API call'); + const images = await client.getAvailableImages(); + + console.log('[get_available_hstacks_images] Success, got images:', images); + return JSON.stringify(images); + } catch (error) { + const errorMsg = `ERROR in get_available_hstacks_images: ${error instanceof Error ? error.message : String(error)}`; + console.error(errorMsg); + return errorMsg; + } + }, + }), + GET_AVAILABLE_LOCATIONS: tool({ + name: 'get_available_hstacks_locations', + description: + 'Get a list of all of the locations that we can deploy a server to with hstacks.', + schema: z.object({ + serverType: z + .string() + .optional() + .describe("Optional server type to filter locations (e.g., 'cax11')"), + }), + handler: async (args, context) => { + try { + const { accessToken } = await context.getCredentials(); + const client = new HStacksClient(accessToken); + const locations = await client.getAvailableLocations(args.serverType); + return JSON.stringify(locations); + } catch (error) { + return `Failed to get available locations: ${error instanceof Error ? error.message : String(error)}`; + } + }, + }), + GET_AVAILABLE_SERVERS: tool({ + name: 'get_available_hstacks_servers', + description: + 'Get a list of all of the servers that we can deploy with hstacks, optionally with a given location.', + schema: z.object({ + location: z + .string() + .optional() + .describe("Optional server type to filter locations (e.g., 'cax11')"), + }), + handler: async (args, context) => { + try { + const { accessToken } = await context.getCredentials(); + const client = new HStacksClient(accessToken); + const servers = await client.getAvailableServers(args.location); + return JSON.stringify(servers); + } catch (error) { + return `Failed to get available servers: ${error instanceof Error ? error.message : String(error)}`; + } + }, + }), + VALIDATE_STACK: tool({ + name: 'validate_stack', + description: 'Validate a hstacks Stack json.', + schema: z.object({ + name: z.string().describe('Unique identifier for the stack deployment'), + servers: z.array(z.any()).describe('Collection of server instances to deploy'), + firewalls: z.array(z.any()).describe('Shared firewall configurations'), + volumes: z.array(z.any()).describe('Persistent storage volumes'), + successHook: z + .string() + .optional() + .describe('Webhook URL called on successful deployment'), + errorHook: z + .string() + .optional() + .describe('Webhook URL called on deployment failure'), + }), + handler: async (args, context) => { + try { + const { accessToken } = await context.getCredentials(); + const client = new HStacksClient(accessToken); + + const stackData = { + name: args.name, + servers: args.servers, + firewalls: args.firewalls, + volumes: args.volumes, + successHook: args.successHook || '', + errorHook: args.errorHook || '', + }; + + const validation = await client.validateStack(stackData); + return JSON.stringify(validation); + } catch (error) { + return `Failed to validate stack: ${error instanceof Error ? error.message : String(error)}`; + } + }, + }), + DEPLOY_STACK: tool({ + name: 'deploy_stack', + description: 'Deploy a Stack to hstacks.', + schema: z.object({ + name: z.string().describe('Unique identifier for the stack deployment'), + servers: z.array(z.any()).describe('Collection of server instances to deploy'), + firewalls: z.array(z.any()).describe('Shared firewall configurations'), + volumes: z.array(z.any()).describe('Persistent storage volumes'), + successHook: z + .string() + .optional() + .describe('Webhook URL called on successful deployment'), + errorHook: z + .string() + .optional() + .describe('Webhook URL called on deployment failure'), + }), + handler: async (args, context) => { + try { + console.log('[deploy_stack] Starting deployment'); + const { accessToken } = await context.getCredentials(); + console.log( + '[deploy_stack] Got access token:', + accessToken ? 'present' : 'missing' + ); + + if (!accessToken) { + throw new Error('No access token provided in credentials'); + } + + const client = new HStacksClient(accessToken); + + const stackData = { + name: args.name, + servers: args.servers, + firewalls: args.firewalls, + volumes: args.volumes, + successHook: args.successHook || '', + errorHook: args.errorHook || '', + }; + + console.log( + '[deploy_stack] Deploying stack:', + JSON.stringify(stackData, null, 2) + ); + const deployment = await client.deployStack(stackData); + console.log('[deploy_stack] Got deployment result:', deployment); + + const result = JSON.stringify(deployment); + console.log('[deploy_stack] Returning result:', result); + return result; + } catch (error) { + const errorMessage = `Failed to deploy stack: ${error instanceof Error ? error.message : String(error)}`; + console.error('[deploy_stack] Error:', errorMessage); + return errorMessage; + } + }, + }), + DELETE_STACK: tool({ + name: 'delete_stack', + description: 'Delete a hstacks Stack.', + schema: z.object({ + stackID: z.string().describe('The stackID to check the deployment status of'), + }), + handler: async (args, context) => { + try { + const { accessToken } = await context.getCredentials(); + const client = new HStacksClient(accessToken); + const result = await client.deleteStack(args.stackID); + return JSON.stringify(result); + } catch (error) { + return `Failed to delete stack: ${error instanceof Error ? error.message : String(error)}`; + } + }, + }), + GET_STACK_STATUS: tool({ + name: 'get_stack_status', + description: 'Get the deployment status of a stack by a given StackID', + schema: z.object({ + stackID: z.string().describe('The stackID to check the deployment status of'), + }), + handler: async (args, context) => { + try { + const { accessToken } = await context.getCredentials(); + const client = new HStacksClient(accessToken); + const status = await client.getStackStatus(args.stackID); + return JSON.stringify(status); + } catch (error) { + return `Failed to get stack status: ${error instanceof Error ? error.message : String(error)}`; + } + }, + }), + }), +}); diff --git a/packages/mcp-connectors/src/index.ts b/packages/mcp-connectors/src/index.ts index bd784f61..43f58e61 100644 --- a/packages/mcp-connectors/src/index.ts +++ b/packages/mcp-connectors/src/index.ts @@ -16,6 +16,7 @@ import { FirefliesConnectorConfig } from './connectors/fireflies'; import { GitHubConnectorConfig } from './connectors/github'; import { GoogleDriveConnectorConfig } from './connectors/google-drive'; import { HiBobConnectorConfig } from './connectors/hibob'; +import { HStacksConnectorConfig } from './connectors/hstacks'; import { HubSpotConnectorConfig } from './connectors/hubspot'; import { IncidentConnectorConfig } from './connectors/incident'; import { JiraConnectorConfig } from './connectors/jira'; @@ -59,6 +60,7 @@ export const Connectors: readonly MCPConnectorConfig[] = [ GitHubConnectorConfig, GoogleDriveConnectorConfig, HiBobConnectorConfig, + HStacksConnectorConfig, HubSpotConnectorConfig, IncidentConnectorConfig, FirefliesConnectorConfig, @@ -102,6 +104,7 @@ export { GitHubConnectorConfig, GoogleDriveConnectorConfig, HiBobConnectorConfig, + HStacksConnectorConfig, HubSpotConnectorConfig, IncidentConnectorConfig, FirefliesConnectorConfig,