This is page 1 of 5. Use http://codebase.md/stumason/coolify-mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .cursor │ └── rules │ ├── 000-cursor-rules.mdc │ ├── 801-feature-workflow.mdc │ ├── 802-coolify-mcp-workflow.mdc │ └── 803-npm-publish-workflow.mdc ├── .eslintrc.json ├── .github │ └── workflows │ └── ci.yml ├── .gitignore ├── .lintstagedrc.json ├── .markdownlint-cli2.jsonc ├── .prettierrc ├── .repomixignore ├── debug.js ├── docs │ ├── coolify-openapi.yaml │ ├── features │ │ ├── 001-core-server-setup.md │ │ ├── 002-server-info-resource.md │ │ ├── 003-project-management.md │ │ ├── 004-environment-management.md │ │ ├── 005-application-deployment.md │ │ ├── 006-database-management.md │ │ ├── 007-service-management.md │ │ ├── 008-mcp-resources-implementation.md │ │ ├── 009-mcp-prompts-implementation.md │ │ ├── 010-private-key-management.md │ │ ├── 011-team-management.md │ │ ├── 012-backup-management.md │ │ ├── 013-npx-config-fix.md │ │ └── future-adrs.md │ ├── mcp-example-clients.md │ ├── mcp-js-readme.md │ └── openapi-chunks │ ├── applications-api.yaml │ ├── databases-api.yaml │ ├── deployments-api.yaml │ ├── private-keys-api.yaml │ ├── projects-api.yaml │ ├── resources-api.yaml │ ├── schemas.yaml │ ├── servers-api.yaml │ ├── services-api.yaml │ ├── teams-api.yaml │ └── untagged-api.yaml ├── jest.config.js ├── package-lock.json ├── package.json ├── README.md ├── repomix-output.xml ├── src │ ├── __tests__ │ │ ├── coolify-client.test.ts │ │ └── resources │ │ ├── application-resources.test.ts │ │ ├── database-resources.test.ts │ │ ├── deployment-resources.test.ts │ │ └── service-resources.test.ts │ ├── index.ts │ ├── lib │ │ ├── coolify-client.ts │ │ ├── mcp-server.ts │ │ └── resource.ts │ ├── resources │ │ ├── application-resources.ts │ │ ├── database-resources.ts │ │ ├── deployment-resources.ts │ │ ├── index.ts │ │ └── service-resources.ts │ └── types │ └── coolify.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.repomixignore: -------------------------------------------------------------------------------- ``` 1 | .cursor/ 2 | .github/ 3 | .husky/ 4 | docs/ 5 | package-lock.json 6 | 7 | ``` -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- ``` 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 100, 6 | "tabWidth": 2 7 | } 8 | ``` -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "src/**/*.ts": ["eslint --fix", "prettier --write"], 3 | "*.json": ["prettier --write"], 4 | "*.md": ["prettier --write", "markdownlint-cli2"] 5 | } 6 | ``` -------------------------------------------------------------------------------- /.markdownlint-cli2.jsonc: -------------------------------------------------------------------------------- ``` 1 | { 2 | "config": { 3 | "line-length": false, 4 | "no-duplicate-heading": false, 5 | "no-inline-html": false, 6 | }, 7 | "ignores": ["node_modules", "dist"], 8 | } 9 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Build output 5 | dist/ 6 | build/ 7 | /lib/ 8 | coverage/ 9 | 10 | # IDE and editor files 11 | .idea/ 12 | .vscode/ 13 | *.swp 14 | *.swo 15 | .DS_Store 16 | Thumbs.db 17 | 18 | # Environment variables 19 | .env 20 | .env.local 21 | .env.*.local 22 | 23 | # Logs 24 | logs/ 25 | *.log 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | 30 | # Test coverage 31 | coverage/ 32 | .nyc_output/ 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional eslint cache 38 | .eslintcache 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | 43 | # Output of 'npm pack' 44 | *.tgz 45 | 46 | # Yarn Integrity file 47 | .yarn-integrity ``` -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "env": { 3 | "node": true, 4 | "es2021": true 5 | }, 6 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "sourceType": "module" 11 | }, 12 | "plugins": ["@typescript-eslint"], 13 | "rules": { 14 | "@typescript-eslint/explicit-function-return-type": "warn", 15 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 16 | "@typescript-eslint/no-explicit-any": "warn" 17 | } 18 | } 19 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Coolify MCP Server 2 | 3 | A Model Context Protocol (MCP) server implementation for [Coolify](https://coolify.io/), enabling AI assistants to interact with your Coolify instances through natural language. 4 | 5 | ## Example Prompts 6 | 7 | Here are example prompts you can use with MCP-compatible AI assistants to interact with your Coolify instance: 8 | 9 | ### Server Management 10 | 11 | ``` 12 | # List and Inspect Servers 13 | - Show me all Coolify servers in my instance 14 | - What's the status of server {uuid}? 15 | - Show me the resources running on server {uuid} 16 | - What domains are configured for server {uuid}? 17 | - Can you validate the connection to server {uuid}? 18 | 19 | # Resource Monitoring 20 | - How much CPU and memory is server {uuid} using? 21 | - List all resources running on server {uuid} 22 | - Show me the current status of all servers 23 | ``` 24 | 25 | ### Project Management 26 | 27 | ``` 28 | # Project Operations 29 | - List all my Coolify projects 30 | - Create a new project called "my-webapp" with description "My web application" 31 | - Show me the details of project {uuid} 32 | - Update project {uuid} to change its name to "new-name" 33 | - Delete project {uuid} 34 | 35 | # Environment Management 36 | - Show me the environments in project {uuid} 37 | - Get details of the production environment in project {uuid} 38 | - What variables are set in the staging environment of project {uuid}? 39 | ``` 40 | 41 | ### Application and Service Management 42 | 43 | ``` 44 | # Application Management 45 | - List all applications 46 | - Show me details of application {uuid} 47 | - Create a new application called "my-nodejs-app" 48 | - Delete application {uuid} 49 | 50 | # Service Operations 51 | - Show me all running services 52 | - Create a new WordPress service: 53 | - Name: my-blog 54 | - Project UUID: {project_uuid} 55 | - Server UUID: {server_uuid} 56 | - Type: wordpress-with-mysql 57 | - What's the status of service {uuid}? 58 | - Delete service {uuid} and clean up its resources 59 | ``` 60 | 61 | ### Database Management 62 | 63 | ``` 64 | # Database Operations 65 | - List all databases 66 | - Show me the configuration of database {uuid} 67 | - Update database {uuid}: 68 | - Increase memory limit to 1GB 69 | - Change public port to 5432 70 | - Update password 71 | - Delete database {uuid} and clean up volumes 72 | 73 | # Database Types 74 | - Create a PostgreSQL database 75 | - Set up a Redis instance 76 | - Configure a MongoDB database 77 | - Initialize a MySQL database 78 | ``` 79 | 80 | ### Deployment Management 81 | 82 | ``` 83 | # Deployment Operations 84 | - Show me all active deployments 85 | - What's the status of deployment {uuid}? 86 | - Deploy application {uuid} 87 | - Force rebuild and deploy application {uuid} 88 | - List recent deployments for application {uuid} 89 | ``` 90 | 91 | ## Installation 92 | 93 | ### Prerequisites 94 | 95 | - Node.js >= 18 96 | - A running Coolify instance 97 | - Coolify API access token 98 | 99 | ### Setup in AI Tools 100 | 101 | #### Claude Desktop 102 | 103 | ```json 104 | "coolify": { 105 | "command": "npx", 106 | "args": [ 107 | "-y", "@masonator/coolify-mcp" 108 | ], 109 | "env": { 110 | "COOLIFY_ACCESS_TOKEN": "0|your-secret-token", 111 | "COOLIFY_BASE_URL": "https://your-coolify-instance.com" 112 | } 113 | } 114 | ``` 115 | 116 | #### Cursor 117 | 118 | ```bash 119 | env COOLIFY_ACCESS_TOKEN:0|your-secret-token COOLIFY_BASE_URL:https://your-coolify-instance.com npx -y @stumason/coolify-mcp 120 | ``` 121 | 122 | ## Development 123 | 124 | ### Local Setup 125 | 126 | ```bash 127 | # Clone the repository 128 | git clone https://github.com/stumason/coolify-mcp.git 129 | cd coolify-mcp 130 | 131 | # Install dependencies 132 | npm install 133 | 134 | # Build the project 135 | npm run build 136 | 137 | # Run tests 138 | npm test 139 | ``` 140 | 141 | ### Environment Variables 142 | 143 | ```bash 144 | # Required 145 | COOLIFY_ACCESS_TOKEN=your_access_token_here 146 | 147 | # Optional (defaults to http://localhost:3000) 148 | COOLIFY_BASE_URL=https://your.coolify.instance 149 | ``` 150 | 151 | ## API Reference 152 | 153 | ### Resource Types 154 | 155 | #### Application 156 | 157 | ```typescript 158 | interface Application { 159 | uuid: string; 160 | name: string; 161 | // Additional properties based on your Coolify instance 162 | } 163 | ``` 164 | 165 | #### Service 166 | 167 | ```typescript 168 | interface Service { 169 | id: number; 170 | uuid: string; 171 | name: string; 172 | type: ServiceType; // Various types like 'wordpress', 'mysql', etc. 173 | status: 'running' | 'stopped' | 'error'; 174 | project_uuid: string; 175 | environment_uuid: string; 176 | server_uuid: string; 177 | domains?: string[]; 178 | } 179 | ``` 180 | 181 | #### Database 182 | 183 | ```typescript 184 | interface Database { 185 | id: number; 186 | uuid: string; 187 | name: string; 188 | type: 'postgresql' | 'mysql' | 'mongodb' | 'redis' | /* other types */; 189 | status: 'running' | 'stopped' | 'error'; 190 | is_public: boolean; 191 | public_port?: number; 192 | // Additional configuration based on database type 193 | } 194 | ``` 195 | 196 | #### Deployment 197 | 198 | ```typescript 199 | interface Deployment { 200 | id: number; 201 | uuid: string; 202 | application_uuid: string; 203 | status: string; 204 | created_at: string; 205 | updated_at: string; 206 | } 207 | ``` 208 | 209 | ## Contributing 210 | 211 | Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change. 212 | 213 | ## License 214 | 215 | MIT 216 | 217 | ## Support 218 | 219 | For support, please: 220 | 221 | 1. Check the [issues](https://github.com/stumason/coolify-mcp/issues) page 222 | 2. Create a new issue if needed 223 | 3. Join the Coolify community 224 | ``` -------------------------------------------------------------------------------- /docs/features/012-backup-management.md: -------------------------------------------------------------------------------- ```markdown 1 | ``` -------------------------------------------------------------------------------- /src/resources/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export * from './database-resources.js'; 2 | export * from './deployment-resources.js'; 3 | export * from './application-resources.js'; 4 | export * from './service-resources.js'; 5 | ``` -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- ```javascript 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | export default { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | moduleNameMapper: { 6 | '^(\\.{1,2}/.*)\\.js$': '$1', 7 | }, 8 | transform: { 9 | '^.+\\.tsx?$': [ 10 | 'ts-jest', 11 | { 12 | useESM: true, 13 | }, 14 | ], 15 | }, 16 | extensionsToTreatAsEsm: ['.ts'], 17 | testPathIgnorePatterns: ['/node_modules/', '/dist/', '\\.d\\.ts$'], 18 | }; 19 | ``` -------------------------------------------------------------------------------- /debug.js: -------------------------------------------------------------------------------- ```javascript 1 | import { CoolifyMcpServer } from './dist/lib/mcp-server.js'; 2 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 3 | 4 | // Enable debug logging 5 | process.env.DEBUG = '*'; 6 | 7 | const server = new CoolifyMcpServer({ 8 | baseUrl: 'https://coolify.dev', // Replace with your actual Coolify URL 9 | accessToken: 'your-actual-token' // Replace with your actual Coolify token 10 | }); 11 | 12 | const transport = new StdioServerTransport(); 13 | await server.connect(transport); ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "experimentalDecorators": true, 13 | "emitDecoratorMetadata": true, 14 | "allowJs": true, 15 | "resolveJsonModule": true 16 | }, 17 | "include": ["src"], 18 | "exclude": ["node_modules", "dist", "tests"] 19 | } 20 | ``` -------------------------------------------------------------------------------- /docs/openapi-chunks/resources-api.yaml: -------------------------------------------------------------------------------- ```yaml 1 | openapi: 3.1.0 2 | info: 3 | title: Coolify 4 | version: '0.1' 5 | paths: 6 | /resources: 7 | get: 8 | tags: 9 | - Resources 10 | summary: List 11 | description: Get all resources. 12 | operationId: list-resources 13 | responses: 14 | '200': 15 | description: Get all resources 16 | content: 17 | application/json: 18 | schema: 19 | type: string 20 | example: Content is very complex. Will be implemented later. 21 | '400': 22 | $ref: '#/components/responses/400' 23 | '401': 24 | $ref: '#/components/responses/401' 25 | security: 26 | - bearerAuth: [] 27 | ``` -------------------------------------------------------------------------------- /docs/features/001-core-server-setup.md: -------------------------------------------------------------------------------- ```markdown 1 | # ADR 001: Core Server Setup 2 | 3 | ## Context 4 | 5 | Need basic MCP server implementation that can authenticate with Coolify and handle basic operations. 6 | 7 | ## Implementation Checklist 8 | 9 | - [x] Project structure setup 10 | 11 | - [x] TypeScript configuration 12 | - [x] ESLint + Prettier 13 | - [x] Jest/Vitest setup 14 | - [x] Basic GitHub Actions CI 15 | 16 | - [x] Core MCP Server 17 | 18 | - [x] Basic server class implementation 19 | - [x] Environment configuration (COOLIFY_ACCESS_TOKEN, COOLIFY_BASE_URL) 20 | - [x] Coolify API client wrapper 21 | - [x] Error handling structure 22 | 23 | - [x] Testing Infrastructure 24 | - [x] Mock Coolify API responses 25 | - [x] Basic integration test framework 26 | 27 | ## Dependencies 28 | 29 | - None (This is our first implementation) 30 | ``` -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [18.x, 20.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: 'npm' 25 | 26 | - name: Install dependencies 27 | run: npm ci 28 | 29 | - name: Security audit 30 | run: npm audit --audit-level=high 31 | 32 | - name: Format check 33 | run: npx prettier --check . 34 | 35 | - name: Lint 36 | run: npm run lint 37 | 38 | - name: Build 39 | run: npm run build 40 | 41 | - name: Test 42 | run: npm test 43 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 4 | import { CoolifyMcpServer } from './lib/mcp-server.js'; 5 | import { CoolifyConfig } from './types/coolify.js'; 6 | 7 | declare const process: NodeJS.Process; 8 | 9 | async function main(): Promise<void> { 10 | const config: CoolifyConfig = { 11 | baseUrl: process.env.COOLIFY_BASE_URL || 'http://localhost:3000', 12 | accessToken: process.env.COOLIFY_ACCESS_TOKEN || '', 13 | }; 14 | 15 | if (!config.accessToken) { 16 | throw new Error('COOLIFY_ACCESS_TOKEN environment variable is required'); 17 | } 18 | 19 | const server = new CoolifyMcpServer(config); 20 | const transport = new StdioServerTransport(); 21 | 22 | await server.connect(transport); 23 | } 24 | 25 | main().catch((error) => { 26 | console.error('Fatal error:', error); 27 | process.exit(1); 28 | }); 29 | ``` -------------------------------------------------------------------------------- /src/resources/deployment-resources.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Resource } from '../lib/resource.js'; 2 | import { CoolifyClient } from '../lib/coolify-client.js'; 3 | import { Deployment } from '../types/coolify.js'; 4 | 5 | export class DeploymentResources { 6 | private client: CoolifyClient; 7 | 8 | constructor(client: CoolifyClient) { 9 | this.client = client; 10 | } 11 | 12 | @Resource('coolify/deployments/list') 13 | async listDeployments(): Promise<Deployment[]> { 14 | // TODO: Implement listDeployments in CoolifyClient 15 | throw new Error('Not implemented'); 16 | } 17 | 18 | @Resource('coolify/deployments/{id}') 19 | async getDeployment(_id: string): Promise<Deployment> { 20 | // TODO: Implement getDeployment in CoolifyClient 21 | throw new Error('Not implemented'); 22 | } 23 | 24 | @Resource('coolify/deploy') 25 | async deploy(params: { uuid: string; forceRebuild?: boolean }): Promise<Deployment> { 26 | return this.client.deployApplication(params.uuid); 27 | } 28 | } 29 | ``` -------------------------------------------------------------------------------- /src/lib/resource.ts: -------------------------------------------------------------------------------- ```typescript 1 | import 'reflect-metadata'; 2 | 3 | /** 4 | * Metadata key for storing the resource URI 5 | */ 6 | const RESOURCE_URI_KEY = Symbol('resourceUri'); 7 | 8 | /** 9 | * Decorator for marking methods as MCP resources. 10 | * @param uri The URI pattern for the resource 11 | */ 12 | export function Resource(uri: string): MethodDecorator { 13 | return function ( 14 | target: object, 15 | propertyKey: string | symbol, 16 | descriptor: PropertyDescriptor, 17 | ): PropertyDescriptor { 18 | // Store the URI pattern in the method's metadata 19 | Reflect.defineMetadata(RESOURCE_URI_KEY, uri, target, propertyKey); 20 | return descriptor; 21 | }; 22 | } 23 | 24 | /** 25 | * Get the resource URI for a decorated method 26 | * @param target The class instance or constructor 27 | * @param propertyKey The method name 28 | * @returns The resource URI or undefined if not a resource 29 | */ 30 | export function getResourceUri(target: object, propertyKey: string | symbol): string | undefined { 31 | return Reflect.getMetadata(RESOURCE_URI_KEY, target, propertyKey); 32 | } 33 | ``` -------------------------------------------------------------------------------- /src/resources/service-resources.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Resource } from '../lib/resource.js'; 2 | import { CoolifyClient } from '../lib/coolify-client.js'; 3 | import { Service, CreateServiceRequest, DeleteServiceOptions } from '../types/coolify.js'; 4 | 5 | export class ServiceResources { 6 | private client: CoolifyClient; 7 | 8 | constructor(client: CoolifyClient) { 9 | this.client = client; 10 | } 11 | 12 | @Resource('coolify/services/list') 13 | async listServices(): Promise<Service[]> { 14 | return this.client.listServices(); 15 | } 16 | 17 | @Resource('coolify/services/{id}') 18 | async getService(id: string): Promise<Service> { 19 | return this.client.getService(id); 20 | } 21 | 22 | @Resource('coolify/services/create') 23 | async createService(data: CreateServiceRequest): Promise<{ uuid: string; domains: string[] }> { 24 | return this.client.createService(data); 25 | } 26 | 27 | @Resource('coolify/services/{id}/delete') 28 | async deleteService(id: string, options?: DeleteServiceOptions): Promise<{ message: string }> { 29 | return this.client.deleteService(id, options); 30 | } 31 | } 32 | ``` -------------------------------------------------------------------------------- /docs/features/002-server-info-resource.md: -------------------------------------------------------------------------------- ```markdown 1 | # ADR 002: Server Information Resources 2 | 3 | ## Context 4 | 5 | First useful feature - ability to get server information and status through MCP resources. 6 | 7 | ## API Endpoints Used 8 | 9 | - GET `/servers` (Line ~500) 10 | 11 | - Lists all servers 12 | - Response: Array of Server objects 13 | - Auth: Bearer token required 14 | 15 | - GET `/servers/{uuid}` (Line ~550) 16 | 17 | - Get server details 18 | - Response: Server object with status 19 | - Auth: Bearer token required 20 | 21 | - GET `/servers/{uuid}/status` (Line ~600) 22 | - Get server health and resource usage 23 | - Response: ServerStatus object 24 | - Auth: Bearer token required 25 | 26 | ## Implementation Checklist 27 | 28 | - [x] Basic Resource Implementation 29 | 30 | - [x] Server info resource (resources://coolify/server/info) 31 | - [x] Basic server details 32 | - [x] Version information 33 | - [x] Server status resource (resources://coolify/server/status) 34 | - [x] Health check 35 | - [x] Resource usage 36 | 37 | - [x] Resource Testing 38 | - [x] Unit tests for resource formatters 39 | - [x] Integration tests with mock data 40 | - [x] Live test with real Coolify instance 41 | 42 | ## Dependencies 43 | 44 | - ADR 001 (Core Server Setup) 45 | ``` -------------------------------------------------------------------------------- /src/resources/database-resources.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Resource } from '../lib/resource.js'; 2 | import { CoolifyClient } from '../lib/coolify-client.js'; 3 | import { Database, DatabaseUpdateRequest } from '../types/coolify.js'; 4 | 5 | export class DatabaseResources { 6 | private client: CoolifyClient; 7 | 8 | constructor(client: CoolifyClient) { 9 | this.client = client; 10 | } 11 | 12 | @Resource('coolify/databases/list') 13 | async listDatabases(): Promise<Database[]> { 14 | return this.client.listDatabases(); 15 | } 16 | 17 | @Resource('coolify/databases/{id}') 18 | async getDatabase(id: string): Promise<Database> { 19 | return this.client.getDatabase(id); 20 | } 21 | 22 | @Resource('coolify/databases/{id}/update') 23 | async updateDatabase(id: string, data: DatabaseUpdateRequest): Promise<Database> { 24 | return this.client.updateDatabase(id, data); 25 | } 26 | 27 | @Resource('coolify/databases/{id}/delete') 28 | async deleteDatabase( 29 | id: string, 30 | options?: { 31 | deleteConfigurations?: boolean; 32 | deleteVolumes?: boolean; 33 | dockerCleanup?: boolean; 34 | deleteConnectedNetworks?: boolean; 35 | }, 36 | ): Promise<{ message: string }> { 37 | return this.client.deleteDatabase(id, options); 38 | } 39 | } 40 | ``` -------------------------------------------------------------------------------- /src/resources/application-resources.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Resource } from '../lib/resource.js'; 2 | import { CoolifyClient } from '../lib/coolify-client.js'; 3 | import { Application, CreateApplicationRequest } from '../types/coolify.js'; 4 | 5 | export class ApplicationResources { 6 | private client: CoolifyClient; 7 | 8 | constructor(client: CoolifyClient) { 9 | this.client = client; 10 | } 11 | 12 | @Resource('coolify/applications/list') 13 | async listApplications(): Promise<Application[]> { 14 | // TODO: Implement listApplications in CoolifyClient 15 | throw new Error('Not implemented'); 16 | } 17 | 18 | @Resource('coolify/applications/{id}') 19 | async getApplication(_id: string): Promise<Application> { 20 | // TODO: Implement getApplication in CoolifyClient 21 | throw new Error('Not implemented'); 22 | } 23 | 24 | @Resource('coolify/applications/create') 25 | async createApplication(_data: CreateApplicationRequest): Promise<{ uuid: string }> { 26 | // TODO: Implement createApplication in CoolifyClient 27 | throw new Error('Not implemented'); 28 | } 29 | 30 | @Resource('coolify/applications/{id}/delete') 31 | async deleteApplication(_id: string): Promise<{ message: string }> { 32 | // TODO: Implement deleteApplication in CoolifyClient 33 | throw new Error('Not implemented'); 34 | } 35 | } 36 | ``` -------------------------------------------------------------------------------- /src/__tests__/resources/application-resources.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ApplicationResources } from '../../resources/application-resources.js'; 2 | import { CoolifyClient } from '../../lib/coolify-client.js'; 3 | import { jest } from '@jest/globals'; 4 | 5 | jest.mock('../../lib/coolify-client.js'); 6 | 7 | describe('ApplicationResources', () => { 8 | let resources: ApplicationResources; 9 | let mockClient: jest.Mocked<CoolifyClient>; 10 | 11 | beforeEach(() => { 12 | mockClient = { 13 | deployApplication: jest.fn(), 14 | } as unknown as jest.Mocked<CoolifyClient>; 15 | 16 | resources = new ApplicationResources(mockClient); 17 | }); 18 | 19 | describe('listApplications', () => { 20 | it('should throw not implemented error', async () => { 21 | await expect(resources.listApplications()).rejects.toThrow('Not implemented'); 22 | }); 23 | }); 24 | 25 | describe('getApplication', () => { 26 | it('should throw not implemented error', async () => { 27 | await expect(resources.getApplication('test-id')).rejects.toThrow('Not implemented'); 28 | }); 29 | }); 30 | 31 | describe('createApplication', () => { 32 | it('should throw not implemented error', async () => { 33 | await expect(resources.createApplication({ name: 'test-app' })).rejects.toThrow( 34 | 'Not implemented', 35 | ); 36 | }); 37 | }); 38 | 39 | describe('deleteApplication', () => { 40 | it('should throw not implemented error', async () => { 41 | await expect(resources.deleteApplication('test-id')).rejects.toThrow('Not implemented'); 42 | }); 43 | }); 44 | }); 45 | ``` -------------------------------------------------------------------------------- /docs/features/011-team-management.md: -------------------------------------------------------------------------------- ```markdown 1 | # ADR 011: Team Management 2 | 3 | ## Context 4 | 5 | Implementation of team management features through MCP resources, allowing users to manage teams and team members in Coolify. 6 | 7 | ## API Endpoints Used 8 | 9 | - GET `/teams` (List) 10 | 11 | - Lists all teams 12 | - Response: Array of Team objects 13 | - Auth: Bearer token required 14 | 15 | - GET `/teams/{id}` (Get) 16 | 17 | - Get team details by ID 18 | - Response: Team object 19 | - Auth: Bearer token required 20 | 21 | - GET `/teams/{id}/members` (List Members) 22 | 23 | - Get team members by team ID 24 | - Response: Array of User objects 25 | - Auth: Bearer token required 26 | 27 | - GET `/teams/current` (Get Current) 28 | 29 | - Get currently authenticated team 30 | - Response: Team object 31 | - Auth: Bearer token required 32 | 33 | - GET `/teams/current/members` (Get Current Members) 34 | - Get currently authenticated team members 35 | - Response: Array of User objects 36 | - Auth: Bearer token required 37 | 38 | ## Implementation Checklist 39 | 40 | - [ ] Basic Team Management 41 | 42 | - [ ] List teams resource 43 | - [ ] Get team details 44 | - [ ] List team members 45 | - [ ] Get current team 46 | - [ ] Get current team members 47 | 48 | - [ ] Team Features 49 | 50 | - [ ] Team information display 51 | - [ ] Member list management 52 | - [ ] Team permissions handling 53 | - [ ] Current team context 54 | 55 | - [ ] Resource Testing 56 | - [ ] Unit tests for team operations 57 | - [ ] Integration tests with mock data 58 | - [ ] Live test with real Coolify instance 59 | - [ ] Permission testing 60 | 61 | ## Dependencies 62 | 63 | - ADR 001 (Core Server Setup) 64 | - ADR 002 (Server Information Resources) 65 | ``` -------------------------------------------------------------------------------- /docs/features/003-project-management.md: -------------------------------------------------------------------------------- ```markdown 1 | # ADR 003: Project Management 2 | 3 | ## Context 4 | 5 | Basic project management functionality - first interactive feature allowing users to create and manage projects. 6 | 7 | ## API Endpoints Used 8 | 9 | - GET `/projects` (Line ~800) 10 | 11 | - Lists all projects 12 | - Response: Array of Project objects 13 | - Auth: Bearer token required 14 | 15 | - POST `/projects` (Line ~850) 16 | 17 | - Create new project 18 | - Request body: { name: string, description?: string } 19 | - Response: Project object 20 | - Auth: Bearer token required 21 | 22 | - GET `/projects/{uuid}` (Line ~900) 23 | 24 | - Get project details 25 | - Response: Project object with relationships 26 | - Auth: Bearer token required 27 | 28 | - DELETE `/projects/{uuid}` (Line ~950) 29 | 30 | - Delete project 31 | - Response: 204 No Content 32 | - Auth: Bearer token required 33 | 34 | - PUT `/projects/{uuid}` (Line ~1000) 35 | - Update project 36 | - Request body: { name?: string, description?: string } 37 | - Response: Updated Project object 38 | - Auth: Bearer token required 39 | 40 | ## Implementation Checklist 41 | 42 | - [x] Project List Resource 43 | 44 | - [x] resources://coolify/projects/list implementation 45 | - [x] Pagination support 46 | - [x] Basic filtering 47 | 48 | - [x] Project Management Tools 49 | 50 | - [x] createProject tool 51 | - [x] deleteProject tool 52 | - [x] updateProject tool 53 | 54 | - [x] Project Detail Resource 55 | 56 | - [x] resources://coolify/projects/{id} implementation 57 | - [x] Project status and configuration 58 | 59 | - [x] Testing 60 | - [x] CRUD operation tests 61 | - [x] Error handling tests 62 | - [x] Resource format tests 63 | 64 | ## Dependencies 65 | 66 | - ADR 001 (Core Server Setup) 67 | - ADR 002 (Server Information Resources) 68 | ``` -------------------------------------------------------------------------------- /docs/features/010-private-key-management.md: -------------------------------------------------------------------------------- ```markdown 1 | # ADR 010: Private Key Management 2 | 3 | ## Context 4 | 5 | Implementation of private key management features through MCP resources, allowing users to manage SSH keys for server access and deployment. 6 | 7 | ## API Endpoints Used 8 | 9 | - GET `/security/keys` (List) 10 | 11 | - Lists all private keys 12 | - Response: Array of PrivateKey objects 13 | - Auth: Bearer token required 14 | 15 | - POST `/security/keys` (Create) 16 | 17 | - Create a new private key 18 | - Required fields: 19 | - private_key 20 | - Optional fields: 21 | - name 22 | - description 23 | - Response: { uuid: string } 24 | - Auth: Bearer token required 25 | 26 | - GET `/security/keys/{uuid}` (Get) 27 | 28 | - Get private key details 29 | - Response: PrivateKey object 30 | - Auth: Bearer token required 31 | 32 | - PATCH `/security/keys` (Update) 33 | 34 | - Update a private key 35 | - Required fields: 36 | - private_key 37 | - Optional fields: 38 | - name 39 | - description 40 | - Response: { uuid: string } 41 | - Auth: Bearer token required 42 | 43 | - DELETE `/security/keys/{uuid}` (Delete) 44 | - Delete a private key 45 | - Response: { message: string } 46 | - Auth: Bearer token required 47 | 48 | ## Implementation Checklist 49 | 50 | - [ ] Basic Key Management 51 | 52 | - [ ] List private keys resource 53 | - [ ] Get private key details 54 | - [ ] Create private key 55 | - [ ] Update private key 56 | - [ ] Delete private key 57 | 58 | - [ ] Security Features 59 | 60 | - [ ] Secure key storage 61 | - [ ] Key validation 62 | - [ ] Usage tracking 63 | - [ ] Access control 64 | 65 | - [ ] Resource Testing 66 | - [ ] Unit tests for key operations 67 | - [ ] Integration tests with mock data 68 | - [ ] Live test with real Coolify instance 69 | - [ ] Security testing 70 | 71 | ## Dependencies 72 | 73 | - ADR 001 (Core Server Setup) 74 | - ADR 002 (Server Information Resources) 75 | ``` -------------------------------------------------------------------------------- /docs/features/004-environment-management.md: -------------------------------------------------------------------------------- ```markdown 1 | # ADR 004: Environment Management 2 | 3 | ## Context 4 | 5 | Environment management within projects - allows retrieving environment information and deploying applications within environments. 6 | 7 | ## API Endpoints Used 8 | 9 | - GET `/projects/{uuid}/{environment_name_or_uuid}` 10 | 11 | - Get environment details by project UUID and environment name/UUID 12 | - Response: Environment object 13 | - Auth: Bearer token required 14 | 15 | - POST `/applications/{uuid}/deploy` 16 | - Deploy an application using its UUID 17 | - Response: Deployment object 18 | - Auth: Bearer token required 19 | 20 | Note: Environment creation and management is handled through the Projects API. Environments are created and configured as part of project setup. 21 | 22 | ## Implementation Status 23 | 24 | ### Completed 25 | 26 | - [x] Environment Detail Resource 27 | 28 | - [x] GET project environment endpoint implemented 29 | - [x] Client method: `getProjectEnvironment` 30 | - [x] MCP tool: `get_project_environment` 31 | 32 | - [x] Application Deployment 33 | - [x] Deploy application endpoint implemented 34 | - [x] Client method: `deployApplication` 35 | - [x] MCP tool: `deploy_application` 36 | 37 | ### Environment Schema 38 | 39 | ```typescript 40 | interface Environment { 41 | id: number; 42 | name: string; 43 | project_id: number; 44 | created_at: string; 45 | updated_at: string; 46 | description: string; 47 | } 48 | ``` 49 | 50 | ## Dependencies 51 | 52 | - ADR 001 (Core Server Setup) 53 | - ADR 003 (Project Management) 54 | 55 | ## Notes 56 | 57 | - Environment management is tightly coupled with projects in the Coolify API 58 | - Environment variables are managed at the application level during application creation/updates 59 | - Direct environment CRUD operations are not available through dedicated endpoints 60 | - Environment information can be retrieved through the project endpoints 61 | ``` -------------------------------------------------------------------------------- /docs/features/006-database-management.md: -------------------------------------------------------------------------------- ```markdown 1 | # ADR 006: Database Management 2 | 3 | ## Context 4 | 5 | Implementation of database management features through MCP resources, allowing users to manage various types of databases (PostgreSQL, MySQL, MariaDB, MongoDB, Redis, etc.). 6 | 7 | ## API Endpoints Used 8 | 9 | - GET `/databases` (List) 10 | 11 | - Lists all databases 12 | - Response: Array of Database objects 13 | - Auth: Bearer token required 14 | 15 | - GET `/databases/{uuid}` (Get) 16 | 17 | - Get database details 18 | - Response: Database object 19 | - Auth: Bearer token required 20 | 21 | - DELETE `/databases/{uuid}` (Delete) 22 | 23 | - Delete database 24 | - Optional query params: 25 | - delete_configurations (boolean, default: true) 26 | - delete_volumes (boolean, default: true) 27 | - docker_cleanup (boolean, default: true) 28 | - delete_connected_networks (boolean, default: true) 29 | - Auth: Bearer token required 30 | 31 | - PATCH `/databases/{uuid}` (Update) 32 | - Update database configuration 33 | - Supports various database types: 34 | - PostgreSQL 35 | - MariaDB 36 | - MySQL 37 | - MongoDB 38 | - Redis 39 | - KeyDB 40 | - Clickhouse 41 | - Dragonfly 42 | 43 | ## Implementation Checklist 44 | 45 | - [x] Basic Database Management 46 | 47 | - [x] List databases resource 48 | - [x] Get database details 49 | - [x] Delete database 50 | - [x] Update database configuration 51 | 52 | - [x] Database Type Support 53 | 54 | - [x] PostgreSQL configuration 55 | - [x] MariaDB configuration 56 | - [x] MySQL configuration 57 | - [x] MongoDB configuration 58 | - [x] Redis configuration 59 | - [x] KeyDB configuration 60 | - [x] Clickhouse configuration 61 | - [x] Dragonfly configuration 62 | 63 | - [x] Resource Testing 64 | - [x] Unit tests for database operations 65 | - [x] Integration tests with mock data 66 | - [x] Live test with real Coolify instance 67 | 68 | ## Dependencies 69 | 70 | - ADR 001 (Core Server Setup) 71 | - ADR 002 (Server Information Resources) 72 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@masonator/coolify-mcp", 3 | "scope": "@masonator", 4 | "version": "0.2.8", 5 | "description": "MCP server implementation for Coolify", 6 | "type": "module", 7 | "main": "./dist/index.js", 8 | "types": "./dist/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "import": "./dist/index.js", 12 | "require": "./dist/index.cjs", 13 | "types": "./dist/index.d.ts" 14 | } 15 | }, 16 | "bin": { 17 | "coolify-mcp": "dist/index.js" 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "build": "tsc && shx chmod +x dist/*.js", 24 | "dev": "tsc --watch", 25 | "test": "NODE_OPTIONS=--experimental-vm-modules jest", 26 | "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch", 27 | "test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage", 28 | "lint": "eslint . --ext .ts", 29 | "lint:fix": "eslint . --ext .ts --fix", 30 | "format": "prettier --write .", 31 | "format:check": "prettier --check .", 32 | "prepare": "husky", 33 | "prepublishOnly": "npm test && npm run lint", 34 | "start": "node dist/index.js" 35 | }, 36 | "keywords": [ 37 | "coolify", 38 | "mcp", 39 | "model-context-protocol" 40 | ], 41 | "author": "Stuart Mason", 42 | "license": "MIT", 43 | "dependencies": { 44 | "@modelcontextprotocol/sdk": "^1.6.1", 45 | "reflect-metadata": "^0.2.2", 46 | "zod": "^3.24.2" 47 | }, 48 | "devDependencies": { 49 | "@types/debug": "^4.1.12", 50 | "@types/jest": "^29.5.14", 51 | "@types/node": "^20.17.23", 52 | "@typescript-eslint/eslint-plugin": "^7.18.0", 53 | "@typescript-eslint/parser": "^7.18.0", 54 | "eslint": "^8.56.0", 55 | "eslint-config-prettier": "^9.1.0", 56 | "husky": "^9.0.11", 57 | "jest": "^29.7.0", 58 | "lint-staged": "^15.2.2", 59 | "markdownlint-cli2": "^0.12.1", 60 | "prettier": "^3.5.3", 61 | "shx": "^0.3.4", 62 | "ts-jest": "^29.2.6", 63 | "typescript": "^5.8.2" 64 | }, 65 | "engines": { 66 | "node": ">=18" 67 | } 68 | } 69 | ``` -------------------------------------------------------------------------------- /docs/features/009-mcp-prompts-implementation.md: -------------------------------------------------------------------------------- ```markdown 1 | # ADR 009: MCP Prompts Implementation 2 | 3 | ## Context 4 | 5 | Create reusable prompt templates for common Coolify workflows to make interactions more efficient. 6 | 7 | ## Implementation Checklist 8 | 9 | - [ ] Deployment Prompts 10 | 11 | - [ ] "deploy-application" prompt 12 | ```typescript 13 | { 14 | applicationId: string, 15 | version?: string, 16 | environment?: string 17 | } 18 | ``` 19 | - [ ] "rollback-deployment" prompt 20 | ```typescript 21 | { 22 | applicationId: string, 23 | deploymentId: string 24 | } 25 | ``` 26 | 27 | - [ ] Configuration Prompts 28 | 29 | - [ ] "configure-database" prompt 30 | ```typescript 31 | { 32 | databaseId: string, 33 | settings: DatabaseSettings 34 | } 35 | ``` 36 | - [ ] "configure-environment" prompt 37 | ```typescript 38 | { 39 | environmentId: string, 40 | variables: Record<string, string> 41 | } 42 | ``` 43 | 44 | - [ ] Service Management Prompts 45 | 46 | - [ ] "setup-service" prompt 47 | ```typescript 48 | { 49 | environmentId: string, 50 | serviceType: string, 51 | configuration: ServiceConfig 52 | } 53 | ``` 54 | - [ ] "troubleshoot-service" prompt 55 | ```typescript 56 | { 57 | serviceId: string, 58 | issueType?: "connectivity" | "performance" | "logs" 59 | } 60 | ``` 61 | 62 | - [ ] Resource Management Prompts 63 | 64 | - [ ] "optimize-resources" prompt 65 | ```typescript 66 | { 67 | resourceId: string, 68 | resourceType: "application" | "service" | "database" 69 | } 70 | ``` 71 | - [ ] "backup-management" prompt 72 | ```typescript 73 | { 74 | resourceId: string, 75 | operation: "create" | "restore" | "list" 76 | } 77 | ``` 78 | 79 | - [ ] Testing 80 | - [ ] Prompt validation tests 81 | - [ ] Response formatting tests 82 | - [ ] Error handling tests 83 | - [ ] Integration tests with actual commands 84 | 85 | ## Dependencies 86 | 87 | - ADR 001 (Core Server Setup) 88 | - ADR 005 (Application Deployment) 89 | - ADR 006 (Database Management) 90 | - ADR 007 (Service Management) 91 | - ADR 008 (MCP Resources Implementation) 92 | ``` -------------------------------------------------------------------------------- /src/__tests__/resources/deployment-resources.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { DeploymentResources } from '../../resources/deployment-resources.js'; 2 | import { CoolifyClient } from '../../lib/coolify-client.js'; 3 | import { jest } from '@jest/globals'; 4 | 5 | jest.mock('../../lib/coolify-client.js'); 6 | 7 | describe('DeploymentResources', () => { 8 | let mockClient: jest.Mocked<CoolifyClient>; 9 | let resources: DeploymentResources; 10 | const mockDeployment = { 11 | id: 1, 12 | uuid: 'test-uuid', 13 | status: 'running', 14 | created_at: '2024-01-01', 15 | updated_at: '2024-01-01', 16 | application_uuid: 'app-uuid', 17 | environment_uuid: 'env-uuid', 18 | }; 19 | 20 | beforeEach(() => { 21 | mockClient = { 22 | deployApplication: jest.fn(), 23 | } as unknown as jest.Mocked<CoolifyClient>; 24 | resources = new DeploymentResources(mockClient); 25 | }); 26 | 27 | describe('listDeployments', () => { 28 | it('should throw not implemented error', async () => { 29 | await expect(resources.listDeployments()).rejects.toThrow('Not implemented'); 30 | }); 31 | }); 32 | 33 | describe('getDeployment', () => { 34 | it('should throw not implemented error', async () => { 35 | await expect(resources.getDeployment('test-id')).rejects.toThrow('Not implemented'); 36 | }); 37 | }); 38 | 39 | describe('deploy', () => { 40 | it('should deploy an application', async () => { 41 | mockClient.deployApplication.mockResolvedValue(mockDeployment); 42 | 43 | const result = await resources.deploy({ uuid: 'test-uuid' }); 44 | 45 | expect(result).toEqual(mockDeployment); 46 | expect(mockClient.deployApplication).toHaveBeenCalledWith('test-uuid'); 47 | }); 48 | 49 | it('should handle deployment errors', async () => { 50 | const error = new Error('Deployment failed'); 51 | mockClient.deployApplication.mockRejectedValue(error); 52 | 53 | await expect(resources.deploy({ uuid: 'test-uuid' })).rejects.toThrow('Deployment failed'); 54 | expect(mockClient.deployApplication).toHaveBeenCalledWith('test-uuid'); 55 | }); 56 | }); 57 | }); 58 | ``` -------------------------------------------------------------------------------- /docs/features/005-application-deployment.md: -------------------------------------------------------------------------------- ```markdown 1 | # ADR 005: Application Deployment 2 | 3 | ## Context 4 | 5 | Core application deployment functionality - allows deploying and managing applications within environments. 6 | 7 | ## API Endpoints Used 8 | 9 | - GET `/applications` (Line 10) 10 | 11 | - Lists all applications 12 | - Query params: environment_uuid (optional) 13 | - Response: Array of Application objects 14 | - Auth: Bearer token required 15 | - ✅ Implemented 16 | 17 | - POST `/applications/public` (Line 31) 18 | 19 | - Create new application from public repository 20 | - Request body: { 21 | project_uuid: string, 22 | environment_uuid: string, 23 | git_repository: string, 24 | git_branch: string, 25 | build_pack: "nixpacks" | "static" | "dockerfile" | "dockercompose", 26 | ports_exposes: string, 27 | name?: string, 28 | ...additional configuration 29 | } 30 | - Response: Application object 31 | - Auth: Bearer token required 32 | - ✅ Implemented 33 | 34 | - GET `/applications/{uuid}` (Line ~1600) 35 | 36 | - Get application details 37 | - Response: Application object with status 38 | - Auth: Bearer token required 39 | - ✅ Implemented 40 | 41 | - DELETE `/applications/{uuid}` (Line ~1650) 42 | 43 | - Delete application 44 | - Response: 204 No Content 45 | - Auth: Bearer token required 46 | - ✅ Implemented 47 | 48 | - POST `/applications/{uuid}/deploy` (Line ~1700) 49 | 50 | - Trigger application deployment 51 | - Response: Deployment object 52 | - Auth: Bearer token required 53 | - ✅ Implemented 54 | 55 | ## Implementation Checklist 56 | 57 | - [x] Application List Resource 58 | 59 | - [x] resources://coolify/applications/list 60 | - [x] Filter by environment/project 61 | - [x] Status information 62 | 63 | - [x] Application Management Tools 64 | 65 | - [x] createApplication tool 66 | - [x] deployApplication tool 67 | - [x] configureApplication tool 68 | - [x] deleteApplication tool 69 | 70 | - [x] Application Monitoring 71 | 72 | - [x] resources://coolify/applications/{id}/status 73 | - [x] Basic metrics 74 | 75 | - [x] Testing 76 | - [x] Deployment workflow tests 77 | - [x] Configuration management tests 78 | 79 | ## Dependencies 80 | 81 | - ADR 001 (Core Server Setup) 82 | - ADR 004 (Environment Management) 83 | ``` -------------------------------------------------------------------------------- /docs/features/future-adrs.md: -------------------------------------------------------------------------------- ```markdown 1 | # Future ADR Considerations 2 | 3 | Based on available Coolify API endpoints, here are potential future features to implement: 4 | 5 | ## Deployment Management 6 | 7 | - Webhook integration for GitHub, GitLab, Bitbucket, Gitea 8 | 9 | - POST `/webhooks/github/{uuid}` (Line ~4000) 10 | - POST `/webhooks/gitlab/{uuid}` (Line ~4050) 11 | - POST `/webhooks/bitbucket/{uuid}` (Line ~4100) 12 | - POST `/webhooks/gitea/{uuid}` (Line ~4150) 13 | 14 | - Build pack support 15 | 16 | - POST `/applications/{uuid}/buildpack` (Line ~4200) 17 | - GET `/buildpacks/templates` (Line ~4250) 18 | 19 | - Custom deployment commands 20 | - POST `/applications/{uuid}/commands/pre-deploy` (Line ~4300) 21 | - POST `/applications/{uuid}/commands/post-deploy` (Line ~4350) 22 | 23 | ## Resource Management 24 | 25 | - Resource limits management 26 | 27 | - PUT `/applications/{uuid}/limits` (Line ~4400) 28 | - PUT `/services/{uuid}/limits` (Line ~4450) 29 | - GET `/resources/usage` (Line ~4500) 30 | 31 | - Health check configuration 32 | - PUT `/applications/{uuid}/health` (Line ~4550) 33 | - GET `/applications/{uuid}/health/status` (Line ~4600) 34 | 35 | ## Network Management 36 | 37 | - Domain management 38 | 39 | - POST `/domains` (Line ~4650) 40 | - GET `/domains/{uuid}/verify` (Line ~4700) 41 | - PUT `/domains/{uuid}/ssl` (Line ~4750) 42 | 43 | - SSL/TLS configuration 44 | - POST `/certificates` (Line ~4800) 45 | - GET `/certificates/{uuid}/status` (Line ~4850) 46 | 47 | ## Build Management 48 | 49 | - Build server configuration 50 | - POST `/build-servers` (Line ~4900) 51 | - GET `/build-servers/{uuid}/status` (Line ~4950) 52 | - PUT `/build-servers/{uuid}/cache` (Line ~5000) 53 | 54 | ## Team Management 55 | 56 | - Team member management 57 | 58 | - POST `/teams` (Line ~5050) 59 | - POST `/teams/{uuid}/members` (Line ~5100) 60 | - PUT `/teams/{uuid}/permissions` (Line ~5150) 61 | 62 | - API key management 63 | - POST `/api-keys` (Line ~5200) 64 | - GET `/api-keys/{uuid}/usage` (Line ~5250) 65 | 66 | ## Monitoring and Logging 67 | 68 | - Resource usage monitoring 69 | 70 | - GET `/monitoring/resources` (Line ~5300) 71 | - GET `/monitoring/alerts` (Line ~5350) 72 | 73 | - Centralized logging 74 | - GET `/logs/aggregate` (Line ~5400) 75 | - POST `/logs/search` (Line ~5450) 76 | 77 | Each of these could be developed into full ADRs once the core functionality is stable. The line numbers reference the OpenAPI specification for implementation details. 78 | ``` -------------------------------------------------------------------------------- /docs/features/007-service-management.md: -------------------------------------------------------------------------------- ```markdown 1 | # ADR 007: Service Management 2 | 3 | ## Context 4 | 5 | Implementation of one-click service management features through MCP resources, allowing users to deploy and manage various pre-configured services. 6 | 7 | ## API Endpoints Used 8 | 9 | - GET `/services` (List) 10 | 11 | - Lists all services 12 | - Response: Array of Service objects 13 | - Auth: Bearer token required 14 | 15 | - POST `/services` (Create) 16 | 17 | - Create a one-click service 18 | - Required fields: 19 | - server_uuid 20 | - project_uuid 21 | - environment_name/uuid 22 | - type (one of many supported service types) 23 | - Optional fields: 24 | - name 25 | - description 26 | - destination_uuid 27 | - instant_deploy 28 | - Auth: Bearer token required 29 | 30 | - GET `/services/{uuid}` (Get) 31 | 32 | - Get service details 33 | - Response: Service object 34 | - Auth: Bearer token required 35 | 36 | - DELETE `/services/{uuid}` (Delete) 37 | - Delete service 38 | - Optional query params: 39 | - delete_configurations (boolean, default: true) 40 | - delete_volumes (boolean, default: true) 41 | - docker_cleanup (boolean, default: true) 42 | - delete_connected_networks (boolean, default: true) 43 | - Auth: Bearer token required 44 | 45 | ## Supported Service Types 46 | 47 | - Development Tools: 48 | 49 | - code-server 50 | - gitea (with various DB options) 51 | - docker-registry 52 | 53 | - CMS & Documentation: 54 | 55 | - wordpress (with various DB options) 56 | - ghost 57 | - mediawiki 58 | - dokuwiki 59 | 60 | - Monitoring & Analytics: 61 | 62 | - grafana 63 | - umami 64 | - glances 65 | - uptime-kuma 66 | 67 | - Collaboration & Communication: 68 | 69 | - rocketchat 70 | - chatwoot 71 | - nextcloud 72 | 73 | - Database Management: 74 | 75 | - phpmyadmin 76 | - nocodb 77 | - directus 78 | 79 | - And many more specialized services 80 | 81 | ## Implementation Checklist 82 | 83 | - [x] Basic Service Management 84 | 85 | - [x] List services resource 86 | - [x] Get service details 87 | - [x] Create service 88 | - [x] Delete service 89 | 90 | - [x] Service Type Support 91 | 92 | - [x] Development tools deployment 93 | - [x] CMS system deployment 94 | - [x] Monitoring tools deployment 95 | - [x] Collaboration tools deployment 96 | - [x] Database tools deployment 97 | 98 | - [x] Resource Testing 99 | - [x] Unit tests for service operations 100 | - [x] Integration tests with mock data 101 | - [x] Live test with real Coolify instance 102 | 103 | ## Dependencies 104 | 105 | - ADR 001 (Core Server Setup) 106 | - ADR 002 (Server Information Resources) 107 | - ADR 003 (Project Management) 108 | ``` -------------------------------------------------------------------------------- /docs/features/008-mcp-resources-implementation.md: -------------------------------------------------------------------------------- ```markdown 1 | # ADR 008: MCP Resources Implementation 2 | 3 | ## Context 4 | 5 | Implement MCP resources for managing Coolify entities through the available API endpoints. 6 | 7 | ## API Endpoints Used 8 | 9 | - Database Management: 10 | 11 | - GET `/databases` - List databases 12 | - GET `/databases/{uuid}` - Get database details 13 | - POST `/databases/{type}` - Create database 14 | - PATCH `/databases/{uuid}` - Update database 15 | - DELETE `/databases/{uuid}` - Delete database 16 | 17 | - Deployment Management: 18 | 19 | - GET `/deployments` - List deployments 20 | - GET `/deployments/{uuid}` - Get deployment details 21 | - GET `/deploy` - Deploy by tag or uuid 22 | 23 | - Application Management: 24 | 25 | - GET `/applications` - List applications 26 | - GET `/applications/{uuid}` - Get application details 27 | - POST `/applications/public` - Create public application 28 | - DELETE `/applications/{uuid}` - Delete application 29 | 30 | - Service Management: 31 | - GET `/services` - List services 32 | - GET `/services/{uuid}` - Get service details 33 | - POST `/services` - Create service 34 | - DELETE `/services/{uuid}` - Delete service 35 | 36 | ## Implementation Checklist 37 | 38 | - [ ] Database Resources 39 | 40 | - [ ] resources://coolify/databases/list 41 | - [ ] resources://coolify/databases/{id} 42 | - [ ] resources://coolify/databases/create/{type} 43 | - [ ] resources://coolify/databases/{id}/update 44 | - [ ] resources://coolify/databases/{id}/delete 45 | 46 | - [ ] Deployment Resources 47 | 48 | - [ ] resources://coolify/deployments/list 49 | - [ ] resources://coolify/deployments/{id} 50 | - [ ] resources://coolify/deploy 51 | - Support for tag-based deployment 52 | - Support for UUID-based deployment 53 | - Force rebuild option 54 | 55 | - [ ] Application Resources 56 | 57 | - [ ] resources://coolify/applications/list 58 | - [ ] resources://coolify/applications/{id} 59 | - [ ] resources://coolify/applications/create 60 | - [ ] resources://coolify/applications/{id}/delete 61 | 62 | - [ ] Service Resources 63 | 64 | - [ ] resources://coolify/services/list 65 | - [ ] resources://coolify/services/{id} 66 | - [ ] resources://coolify/services/create 67 | - [ ] resources://coolify/services/{id}/delete 68 | 69 | - [ ] Testing 70 | - [ ] Database operation tests 71 | - [ ] Deployment operation tests 72 | - [ ] Application operation tests 73 | - [ ] Service operation tests 74 | - [ ] Error handling tests 75 | - [ ] Permission validation tests 76 | 77 | ## Dependencies 78 | 79 | - ADR 001 (Core Server Setup) 80 | - ADR 005 (Application Deployment) 81 | - ADR 006 (Database Management) 82 | - ADR 007 (Service Management) 83 | ``` -------------------------------------------------------------------------------- /src/__tests__/resources/database-resources.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { DatabaseResources } from '../../resources/database-resources.js'; 2 | import { CoolifyClient } from '../../lib/coolify-client.js'; 3 | import { PostgresDatabase } from '../../types/coolify.js'; 4 | import { jest } from '@jest/globals'; 5 | 6 | jest.mock('../../lib/coolify-client.js'); 7 | 8 | describe('DatabaseResources', () => { 9 | let mockClient: jest.Mocked<CoolifyClient>; 10 | let resources: DatabaseResources; 11 | const mockDatabase: PostgresDatabase = { 12 | id: 1, 13 | uuid: 'test-uuid', 14 | name: 'test-db', 15 | description: 'test description', 16 | type: 'postgresql', 17 | status: 'running', 18 | created_at: '2024-01-01', 19 | updated_at: '2024-01-01', 20 | is_public: false, 21 | image: 'postgres:latest', 22 | postgres_user: 'test', 23 | postgres_password: 'test', 24 | postgres_db: 'test', 25 | }; 26 | 27 | beforeEach(() => { 28 | mockClient = { 29 | listDatabases: jest.fn(), 30 | getDatabase: jest.fn(), 31 | updateDatabase: jest.fn(), 32 | deleteDatabase: jest.fn(), 33 | } as unknown as jest.Mocked<CoolifyClient>; 34 | resources = new DatabaseResources(mockClient); 35 | }); 36 | 37 | describe('listDatabases', () => { 38 | it('should return a list of databases', async () => { 39 | mockClient.listDatabases.mockResolvedValue([mockDatabase]); 40 | 41 | const result = await resources.listDatabases(); 42 | 43 | expect(result).toEqual([mockDatabase]); 44 | expect(mockClient.listDatabases).toHaveBeenCalled(); 45 | }); 46 | }); 47 | 48 | describe('getDatabase', () => { 49 | it('should return a database by uuid', async () => { 50 | mockClient.getDatabase.mockResolvedValue(mockDatabase); 51 | 52 | const result = await resources.getDatabase('test-uuid'); 53 | 54 | expect(result).toEqual(mockDatabase); 55 | expect(mockClient.getDatabase).toHaveBeenCalledWith('test-uuid'); 56 | }); 57 | }); 58 | 59 | describe('updateDatabase', () => { 60 | it('should update a database', async () => { 61 | const updateData = { 62 | name: 'updated-db', 63 | description: 'updated description', 64 | }; 65 | 66 | mockClient.updateDatabase.mockResolvedValue({ ...mockDatabase, ...updateData }); 67 | 68 | const result = await resources.updateDatabase('test-uuid', updateData); 69 | 70 | expect(result).toEqual({ ...mockDatabase, ...updateData }); 71 | expect(mockClient.updateDatabase).toHaveBeenCalledWith('test-uuid', updateData); 72 | }); 73 | }); 74 | 75 | describe('deleteDatabase', () => { 76 | it('should delete a database', async () => { 77 | const mockResponse = { message: 'Database deleted successfully' }; 78 | mockClient.deleteDatabase.mockResolvedValue(mockResponse); 79 | 80 | const result = await resources.deleteDatabase('test-uuid', {}); 81 | 82 | expect(result).toEqual(mockResponse); 83 | expect(mockClient.deleteDatabase).toHaveBeenCalledWith('test-uuid', {}); 84 | }); 85 | }); 86 | }); 87 | ``` -------------------------------------------------------------------------------- /docs/openapi-chunks/untagged-api.yaml: -------------------------------------------------------------------------------- ```yaml 1 | openapi: 3.1.0 2 | info: 3 | title: Coolify 4 | version: '0.1' 5 | paths: 6 | /version: 7 | get: 8 | summary: Version 9 | description: Get Coolify version. 10 | operationId: version 11 | responses: 12 | '200': 13 | description: Returns the version of the application 14 | content: 15 | application/json: 16 | schema: 17 | type: string 18 | example: v4.0.0 19 | '400': 20 | $ref: '#/components/responses/400' 21 | '401': 22 | $ref: '#/components/responses/401' 23 | security: 24 | - bearerAuth: [] 25 | /enable: 26 | get: 27 | summary: Enable API 28 | description: Enable API (only with root permissions). 29 | operationId: enable-api 30 | responses: 31 | '200': 32 | description: Enable API. 33 | content: 34 | application/json: 35 | schema: 36 | properties: 37 | message: 38 | type: string 39 | example: API enabled. 40 | type: object 41 | '400': 42 | $ref: '#/components/responses/400' 43 | '401': 44 | $ref: '#/components/responses/401' 45 | '403': 46 | description: You are not allowed to enable the API. 47 | content: 48 | application/json: 49 | schema: 50 | properties: 51 | message: 52 | type: string 53 | example: You are not allowed to enable the API. 54 | type: object 55 | security: 56 | - bearerAuth: [] 57 | /disable: 58 | get: 59 | summary: Disable API 60 | description: Disable API (only with root permissions). 61 | operationId: disable-api 62 | responses: 63 | '200': 64 | description: Disable API. 65 | content: 66 | application/json: 67 | schema: 68 | properties: 69 | message: 70 | type: string 71 | example: API disabled. 72 | type: object 73 | '400': 74 | $ref: '#/components/responses/400' 75 | '401': 76 | $ref: '#/components/responses/401' 77 | '403': 78 | description: You are not allowed to disable the API. 79 | content: 80 | application/json: 81 | schema: 82 | properties: 83 | message: 84 | type: string 85 | example: You are not allowed to disable the API. 86 | type: object 87 | security: 88 | - bearerAuth: [] 89 | /health: 90 | get: 91 | summary: Healthcheck 92 | description: Healthcheck endpoint. 93 | operationId: healthcheck 94 | responses: 95 | '200': 96 | description: Healthcheck endpoint. 97 | content: 98 | application/json: 99 | schema: 100 | type: string 101 | example: OK 102 | '400': 103 | $ref: '#/components/responses/400' 104 | '401': 105 | $ref: '#/components/responses/401' 106 | ``` -------------------------------------------------------------------------------- /src/__tests__/resources/service-resources.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ServiceResources } from '../../resources/service-resources.js'; 2 | import { CoolifyClient } from '../../lib/coolify-client.js'; 3 | import { Service, ServiceType } from '../../types/coolify.js'; 4 | import { jest } from '@jest/globals'; 5 | 6 | jest.mock('../../lib/coolify-client.js'); 7 | 8 | describe('ServiceResources', () => { 9 | let mockClient: jest.Mocked<CoolifyClient>; 10 | let resources: ServiceResources; 11 | const mockService: Service = { 12 | id: 1, 13 | uuid: 'test-uuid', 14 | name: 'test-service', 15 | description: 'test description', 16 | type: 'code-server', 17 | status: 'running', 18 | created_at: '2024-01-01', 19 | updated_at: '2024-01-01', 20 | project_uuid: 'project-uuid', 21 | environment_name: 'test-env', 22 | environment_uuid: 'env-uuid', 23 | server_uuid: 'server-uuid', 24 | domains: ['test.com'], 25 | }; 26 | 27 | beforeEach(() => { 28 | mockClient = { 29 | listServices: jest.fn(), 30 | getService: jest.fn(), 31 | createService: jest.fn(), 32 | deleteService: jest.fn(), 33 | } as unknown as jest.Mocked<CoolifyClient>; 34 | resources = new ServiceResources(mockClient); 35 | }); 36 | 37 | describe('listServices', () => { 38 | it('should return a list of services', async () => { 39 | mockClient.listServices.mockResolvedValue([mockService]); 40 | 41 | const result = await resources.listServices(); 42 | 43 | expect(result).toEqual([mockService]); 44 | expect(mockClient.listServices).toHaveBeenCalled(); 45 | }); 46 | }); 47 | 48 | describe('getService', () => { 49 | it('should return a service by uuid', async () => { 50 | mockClient.getService.mockResolvedValue(mockService); 51 | 52 | const result = await resources.getService('test-uuid'); 53 | 54 | expect(result).toEqual(mockService); 55 | expect(mockClient.getService).toHaveBeenCalledWith('test-uuid'); 56 | }); 57 | }); 58 | 59 | describe('createService', () => { 60 | it('should create a new service', async () => { 61 | const createData = { 62 | name: 'new-service', 63 | type: 'code-server' as ServiceType, 64 | project_uuid: 'project-uuid', 65 | environment_name: 'test-env', 66 | environment_uuid: 'env-uuid', 67 | server_uuid: 'server-uuid', 68 | }; 69 | 70 | const mockResponse = { 71 | uuid: 'new-uuid', 72 | domains: ['new-service.test.com'], 73 | }; 74 | 75 | mockClient.createService.mockResolvedValue(mockResponse); 76 | 77 | const result = await resources.createService(createData); 78 | 79 | expect(result).toEqual(mockResponse); 80 | expect(mockClient.createService).toHaveBeenCalledWith(createData); 81 | }); 82 | }); 83 | 84 | describe('deleteService', () => { 85 | it('should delete a service', async () => { 86 | const mockResponse = { message: 'Service deleted' }; 87 | mockClient.deleteService.mockResolvedValue(mockResponse); 88 | 89 | const result = await resources.deleteService('test-uuid'); 90 | 91 | expect(result).toEqual(mockResponse); 92 | expect(mockClient.deleteService).toHaveBeenCalledWith('test-uuid', undefined); 93 | }); 94 | }); 95 | }); 96 | ``` -------------------------------------------------------------------------------- /docs/openapi-chunks/deployments-api.yaml: -------------------------------------------------------------------------------- ```yaml 1 | openapi: 3.1.0 2 | info: 3 | title: Coolify 4 | version: '0.1' 5 | paths: 6 | /deployments: 7 | get: 8 | tags: 9 | - Deployments 10 | summary: List 11 | description: List currently running deployments 12 | operationId: list-deployments 13 | responses: 14 | '200': 15 | description: Get all currently running deployments. 16 | content: 17 | application/json: 18 | schema: 19 | type: array 20 | items: 21 | $ref: '#/components/schemas/ApplicationDeploymentQueue' 22 | '400': 23 | $ref: '#/components/responses/400' 24 | '401': 25 | $ref: '#/components/responses/401' 26 | security: 27 | - bearerAuth: [] 28 | /deployments/{uuid}: 29 | get: 30 | tags: 31 | - Deployments 32 | summary: Get 33 | description: Get deployment by UUID. 34 | operationId: get-deployment-by-uuid 35 | parameters: 36 | - name: uuid 37 | in: path 38 | description: Deployment UUID 39 | required: true 40 | schema: 41 | type: string 42 | responses: 43 | '200': 44 | description: Get deployment by UUID. 45 | content: 46 | application/json: 47 | schema: 48 | $ref: '#/components/schemas/ApplicationDeploymentQueue' 49 | '400': 50 | $ref: '#/components/responses/400' 51 | '401': 52 | $ref: '#/components/responses/401' 53 | '404': 54 | $ref: '#/components/responses/404' 55 | security: 56 | - bearerAuth: [] 57 | /deploy: 58 | get: 59 | tags: 60 | - Deployments 61 | summary: Deploy 62 | description: Deploy by tag or uuid. `Post` request also accepted. 63 | operationId: deploy-by-tag-or-uuid 64 | parameters: 65 | - name: tag 66 | in: query 67 | description: Tag name(s). Comma separated list is also accepted. 68 | schema: 69 | type: string 70 | - name: uuid 71 | in: query 72 | description: Resource UUID(s). Comma separated list is also accepted. 73 | schema: 74 | type: string 75 | - name: force 76 | in: query 77 | description: Force rebuild (without cache) 78 | schema: 79 | type: boolean 80 | responses: 81 | '200': 82 | description: Get deployment(s) UUID's 83 | content: 84 | application/json: 85 | schema: 86 | properties: 87 | deployments: 88 | type: array 89 | items: 90 | properties: 91 | message: 92 | type: string 93 | resource_uuid: 94 | type: string 95 | deployment_uuid: 96 | type: string 97 | type: object 98 | type: object 99 | '400': 100 | $ref: '#/components/responses/400' 101 | '401': 102 | $ref: '#/components/responses/401' 103 | security: 104 | - bearerAuth: [] 105 | ``` -------------------------------------------------------------------------------- /docs/openapi-chunks/teams-api.yaml: -------------------------------------------------------------------------------- ```yaml 1 | openapi: 3.1.0 2 | info: 3 | title: Coolify 4 | version: '0.1' 5 | paths: 6 | /teams: 7 | get: 8 | tags: 9 | - Teams 10 | summary: List 11 | description: Get all teams. 12 | operationId: list-teams 13 | responses: 14 | '200': 15 | description: List of teams. 16 | content: 17 | application/json: 18 | schema: 19 | type: array 20 | items: 21 | $ref: '#/components/schemas/Team' 22 | '400': 23 | $ref: '#/components/responses/400' 24 | '401': 25 | $ref: '#/components/responses/401' 26 | security: 27 | - bearerAuth: [] 28 | /teams/{id}: 29 | get: 30 | tags: 31 | - Teams 32 | summary: Get 33 | description: Get team by TeamId. 34 | operationId: get-team-by-id 35 | parameters: 36 | - name: id 37 | in: path 38 | description: Team ID 39 | required: true 40 | schema: 41 | type: integer 42 | responses: 43 | '200': 44 | description: List of teams. 45 | content: 46 | application/json: 47 | schema: 48 | $ref: '#/components/schemas/Team' 49 | '400': 50 | $ref: '#/components/responses/400' 51 | '401': 52 | $ref: '#/components/responses/401' 53 | '404': 54 | $ref: '#/components/responses/404' 55 | security: 56 | - bearerAuth: [] 57 | /teams/{id}/members: 58 | get: 59 | tags: 60 | - Teams 61 | summary: Members 62 | description: Get members by TeamId. 63 | operationId: get-members-by-team-id 64 | parameters: 65 | - name: id 66 | in: path 67 | description: Team ID 68 | required: true 69 | schema: 70 | type: integer 71 | responses: 72 | '200': 73 | description: List of members. 74 | content: 75 | application/json: 76 | schema: 77 | type: array 78 | items: 79 | $ref: '#/components/schemas/User' 80 | '400': 81 | $ref: '#/components/responses/400' 82 | '401': 83 | $ref: '#/components/responses/401' 84 | '404': 85 | $ref: '#/components/responses/404' 86 | security: 87 | - bearerAuth: [] 88 | /teams/current: 89 | get: 90 | tags: 91 | - Teams 92 | summary: Authenticated Team 93 | description: Get currently authenticated team. 94 | operationId: get-current-team 95 | responses: 96 | '200': 97 | description: Current Team. 98 | content: 99 | application/json: 100 | schema: 101 | $ref: '#/components/schemas/Team' 102 | '400': 103 | $ref: '#/components/responses/400' 104 | '401': 105 | $ref: '#/components/responses/401' 106 | security: 107 | - bearerAuth: [] 108 | /teams/current/members: 109 | get: 110 | tags: 111 | - Teams 112 | summary: Authenticated Team Members 113 | description: Get currently authenticated team members. 114 | operationId: get-current-team-members 115 | responses: 116 | '200': 117 | description: Currently authenticated team members. 118 | content: 119 | application/json: 120 | schema: 121 | type: array 122 | items: 123 | $ref: '#/components/schemas/User' 124 | '400': 125 | $ref: '#/components/responses/400' 126 | '401': 127 | $ref: '#/components/responses/401' 128 | security: 129 | - bearerAuth: [] 130 | ``` -------------------------------------------------------------------------------- /docs/openapi-chunks/private-keys-api.yaml: -------------------------------------------------------------------------------- ```yaml 1 | openapi: 3.1.0 2 | info: 3 | title: Coolify 4 | version: '0.1' 5 | paths: 6 | /security/keys: 7 | get: 8 | tags: 9 | - Private Keys 10 | summary: List 11 | description: List all private keys. 12 | operationId: list-private-keys 13 | responses: 14 | '200': 15 | description: Get all private keys. 16 | content: 17 | application/json: 18 | schema: 19 | type: array 20 | items: 21 | $ref: '#/components/schemas/PrivateKey' 22 | '400': 23 | $ref: '#/components/responses/400' 24 | '401': 25 | $ref: '#/components/responses/401' 26 | security: 27 | - bearerAuth: [] 28 | post: 29 | tags: 30 | - Private Keys 31 | summary: Create 32 | description: Create a new private key. 33 | operationId: create-private-key 34 | requestBody: 35 | required: true 36 | content: 37 | application/json: 38 | schema: 39 | required: 40 | - private_key 41 | properties: 42 | name: 43 | type: string 44 | description: 45 | type: string 46 | private_key: 47 | type: string 48 | type: object 49 | additionalProperties: false 50 | responses: 51 | '201': 52 | description: The created private key's UUID. 53 | content: 54 | application/json: 55 | schema: 56 | properties: 57 | uuid: 58 | type: string 59 | type: object 60 | '400': 61 | $ref: '#/components/responses/400' 62 | '401': 63 | $ref: '#/components/responses/401' 64 | security: 65 | - bearerAuth: [] 66 | patch: 67 | tags: 68 | - Private Keys 69 | summary: Update 70 | description: Update a private key. 71 | operationId: update-private-key 72 | requestBody: 73 | required: true 74 | content: 75 | application/json: 76 | schema: 77 | required: 78 | - private_key 79 | properties: 80 | name: 81 | type: string 82 | description: 83 | type: string 84 | private_key: 85 | type: string 86 | type: object 87 | additionalProperties: false 88 | responses: 89 | '201': 90 | description: The updated private key's UUID. 91 | content: 92 | application/json: 93 | schema: 94 | properties: 95 | uuid: 96 | type: string 97 | type: object 98 | '400': 99 | $ref: '#/components/responses/400' 100 | '401': 101 | $ref: '#/components/responses/401' 102 | security: 103 | - bearerAuth: [] 104 | /security/keys/{uuid}: 105 | get: 106 | tags: 107 | - Private Keys 108 | summary: Get 109 | description: Get key by UUID. 110 | operationId: get-private-key-by-uuid 111 | parameters: 112 | - name: uuid 113 | in: path 114 | description: Private Key UUID 115 | required: true 116 | schema: 117 | type: string 118 | responses: 119 | '200': 120 | description: Get all private keys. 121 | content: 122 | application/json: 123 | schema: 124 | $ref: '#/components/schemas/PrivateKey' 125 | '400': 126 | $ref: '#/components/responses/400' 127 | '401': 128 | $ref: '#/components/responses/401' 129 | '404': 130 | description: Private Key not found. 131 | security: 132 | - bearerAuth: [] 133 | delete: 134 | tags: 135 | - Private Keys 136 | summary: Delete 137 | description: Delete a private key. 138 | operationId: delete-private-key-by-uuid 139 | parameters: 140 | - name: uuid 141 | in: path 142 | description: Private Key UUID 143 | required: true 144 | schema: 145 | type: string 146 | responses: 147 | '200': 148 | description: Private Key deleted. 149 | content: 150 | application/json: 151 | schema: 152 | properties: 153 | message: 154 | type: string 155 | example: Private Key deleted. 156 | type: object 157 | '400': 158 | $ref: '#/components/responses/400' 159 | '401': 160 | $ref: '#/components/responses/401' 161 | '404': 162 | description: Private Key not found. 163 | security: 164 | - bearerAuth: [] 165 | ``` -------------------------------------------------------------------------------- /docs/openapi-chunks/projects-api.yaml: -------------------------------------------------------------------------------- ```yaml 1 | openapi: 3.1.0 2 | info: 3 | title: Coolify 4 | version: '0.1' 5 | paths: 6 | /projects: 7 | get: 8 | tags: 9 | - Projects 10 | summary: List 11 | description: List projects. 12 | operationId: list-projects 13 | responses: 14 | '200': 15 | description: Get all projects. 16 | content: 17 | application/json: 18 | schema: 19 | type: array 20 | items: 21 | $ref: '#/components/schemas/Project' 22 | '400': 23 | $ref: '#/components/responses/400' 24 | '401': 25 | $ref: '#/components/responses/401' 26 | security: 27 | - bearerAuth: [] 28 | post: 29 | tags: 30 | - Projects 31 | summary: Create 32 | description: Create Project. 33 | operationId: create-project 34 | requestBody: 35 | description: Project created. 36 | required: true 37 | content: 38 | application/json: 39 | schema: 40 | properties: 41 | name: 42 | type: string 43 | description: The name of the project. 44 | description: 45 | type: string 46 | description: The description of the project. 47 | type: object 48 | responses: 49 | '201': 50 | description: Project created. 51 | content: 52 | application/json: 53 | schema: 54 | properties: 55 | uuid: 56 | type: string 57 | example: og888os 58 | description: The UUID of the project. 59 | type: object 60 | '400': 61 | $ref: '#/components/responses/400' 62 | '401': 63 | $ref: '#/components/responses/401' 64 | '404': 65 | $ref: '#/components/responses/404' 66 | security: 67 | - bearerAuth: [] 68 | /projects/{uuid}: 69 | get: 70 | tags: 71 | - Projects 72 | summary: Get 73 | description: Get project by UUID. 74 | operationId: get-project-by-uuid 75 | parameters: 76 | - name: uuid 77 | in: path 78 | description: Project UUID 79 | required: true 80 | schema: 81 | type: string 82 | responses: 83 | '200': 84 | description: Project details 85 | content: 86 | application/json: 87 | schema: 88 | $ref: '#/components/schemas/Project' 89 | '400': 90 | $ref: '#/components/responses/400' 91 | '401': 92 | $ref: '#/components/responses/401' 93 | '404': 94 | description: Project not found. 95 | security: 96 | - bearerAuth: [] 97 | delete: 98 | tags: 99 | - Projects 100 | summary: Delete 101 | description: Delete project by UUID. 102 | operationId: delete-project-by-uuid 103 | parameters: 104 | - name: uuid 105 | in: path 106 | description: UUID of the application. 107 | required: true 108 | schema: 109 | type: string 110 | format: uuid 111 | responses: 112 | '200': 113 | description: Project deleted. 114 | content: 115 | application/json: 116 | schema: 117 | properties: 118 | message: 119 | type: string 120 | example: Project deleted. 121 | type: object 122 | '400': 123 | $ref: '#/components/responses/400' 124 | '401': 125 | $ref: '#/components/responses/401' 126 | '404': 127 | $ref: '#/components/responses/404' 128 | security: 129 | - bearerAuth: [] 130 | patch: 131 | tags: 132 | - Projects 133 | summary: Update 134 | description: Update Project. 135 | operationId: update-project-by-uuid 136 | requestBody: 137 | description: Project updated. 138 | required: true 139 | content: 140 | application/json: 141 | schema: 142 | properties: 143 | name: 144 | type: string 145 | description: The name of the project. 146 | description: 147 | type: string 148 | description: The description of the project. 149 | type: object 150 | responses: 151 | '201': 152 | description: Project updated. 153 | content: 154 | application/json: 155 | schema: 156 | properties: 157 | uuid: 158 | type: string 159 | example: og888os 160 | name: 161 | type: string 162 | example: Project Name 163 | description: 164 | type: string 165 | example: Project Description 166 | type: object 167 | '400': 168 | $ref: '#/components/responses/400' 169 | '401': 170 | $ref: '#/components/responses/401' 171 | '404': 172 | $ref: '#/components/responses/404' 173 | security: 174 | - bearerAuth: [] 175 | /projects/{uuid}/{environment_name_or_uuid}: 176 | get: 177 | tags: 178 | - Projects 179 | summary: Environment 180 | description: Get environment by name or UUID. 181 | operationId: get-environment-by-name-or-uuid 182 | parameters: 183 | - name: uuid 184 | in: path 185 | description: Project UUID 186 | required: true 187 | schema: 188 | type: string 189 | - name: environment_name_or_uuid 190 | in: path 191 | description: Environment name or UUID 192 | required: true 193 | schema: 194 | type: string 195 | responses: 196 | '200': 197 | description: Environment details 198 | content: 199 | application/json: 200 | schema: 201 | $ref: '#/components/schemas/Environment' 202 | '400': 203 | $ref: '#/components/responses/400' 204 | '401': 205 | $ref: '#/components/responses/401' 206 | '404': 207 | $ref: '#/components/responses/404' 208 | security: 209 | - bearerAuth: [] 210 | ``` -------------------------------------------------------------------------------- /src/lib/coolify-client.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | CoolifyConfig, 3 | ErrorResponse, 4 | ServerInfo, 5 | ServerResources, 6 | ServerDomain, 7 | ValidationResponse, 8 | Project, 9 | CreateProjectRequest, 10 | UpdateProjectRequest, 11 | Environment, 12 | Deployment, 13 | Database, 14 | DatabaseUpdateRequest, 15 | Service, 16 | CreateServiceRequest, 17 | DeleteServiceOptions, 18 | } from '../types/coolify.js'; 19 | 20 | export class CoolifyClient { 21 | private baseUrl: string; 22 | private accessToken: string; 23 | 24 | constructor(config: CoolifyConfig) { 25 | if (!config.baseUrl) { 26 | throw new Error('Coolify base URL is required'); 27 | } 28 | if (!config.accessToken) { 29 | throw new Error('Coolify access token is required'); 30 | } 31 | this.baseUrl = config.baseUrl.replace(/\/$/, ''); 32 | this.accessToken = config.accessToken; 33 | } 34 | 35 | private async request<T>(path: string, options: RequestInit = {}): Promise<T> { 36 | try { 37 | const url = `${this.baseUrl}/api/v1${path}`; 38 | const response = await fetch(url, { 39 | headers: { 40 | 'Content-Type': 'application/json', 41 | Authorization: `Bearer ${this.accessToken}`, 42 | }, 43 | ...options, 44 | }); 45 | 46 | const data = await response.json(); 47 | 48 | if (!response.ok) { 49 | const error = data as ErrorResponse; 50 | throw new Error(error.message || `HTTP ${response.status}: ${response.statusText}`); 51 | } 52 | 53 | return data as T; 54 | } catch (error) { 55 | if (error instanceof TypeError && error.message.includes('fetch')) { 56 | throw new Error( 57 | `Failed to connect to Coolify server at ${this.baseUrl}. Please check if the server is running and the URL is correct.`, 58 | ); 59 | } 60 | throw error; 61 | } 62 | } 63 | 64 | async listServers(): Promise<ServerInfo[]> { 65 | return this.request<ServerInfo[]>('/servers'); 66 | } 67 | 68 | async getServer(uuid: string): Promise<ServerInfo> { 69 | return this.request<ServerInfo>(`/servers/${uuid}`); 70 | } 71 | 72 | async getServerResources(uuid: string): Promise<ServerResources> { 73 | return this.request<ServerResources>(`/servers/${uuid}/resources`); 74 | } 75 | 76 | async getServerDomains(uuid: string): Promise<ServerDomain[]> { 77 | return this.request<ServerDomain[]>(`/servers/${uuid}/domains`); 78 | } 79 | 80 | async validateServer(uuid: string): Promise<ValidationResponse> { 81 | return this.request<ValidationResponse>(`/servers/${uuid}/validate`); 82 | } 83 | 84 | async validateConnection(): Promise<void> { 85 | try { 86 | await this.listServers(); 87 | } catch (error) { 88 | throw new Error( 89 | `Failed to connect to Coolify server: ${error instanceof Error ? error.message : 'Unknown error'}`, 90 | ); 91 | } 92 | } 93 | 94 | async listProjects(): Promise<Project[]> { 95 | return this.request<Project[]>('/projects'); 96 | } 97 | 98 | async getProject(uuid: string): Promise<Project> { 99 | return this.request<Project>(`/projects/${uuid}`); 100 | } 101 | 102 | async createProject(project: CreateProjectRequest): Promise<{ uuid: string }> { 103 | return this.request<{ uuid: string }>('/projects', { 104 | method: 'POST', 105 | body: JSON.stringify(project), 106 | }); 107 | } 108 | 109 | async updateProject(uuid: string, project: UpdateProjectRequest): Promise<Project> { 110 | return this.request<Project>(`/projects/${uuid}`, { 111 | method: 'PATCH', 112 | body: JSON.stringify(project), 113 | }); 114 | } 115 | 116 | async deleteProject(uuid: string): Promise<{ message: string }> { 117 | return this.request<{ message: string }>(`/projects/${uuid}`, { 118 | method: 'DELETE', 119 | }); 120 | } 121 | 122 | async getProjectEnvironment( 123 | projectUuid: string, 124 | environmentNameOrUuid: string, 125 | ): Promise<Environment> { 126 | return this.request<Environment>(`/projects/${projectUuid}/${environmentNameOrUuid}`); 127 | } 128 | 129 | async deployApplication(uuid: string): Promise<Deployment> { 130 | const response = await this.request<Deployment>(`/applications/${uuid}/deploy`, { 131 | method: 'POST', 132 | }); 133 | return response; 134 | } 135 | 136 | async listDatabases(): Promise<Database[]> { 137 | return this.request<Database[]>('/databases'); 138 | } 139 | 140 | async getDatabase(uuid: string): Promise<Database> { 141 | return this.request<Database>(`/databases/${uuid}`); 142 | } 143 | 144 | async updateDatabase(uuid: string, data: DatabaseUpdateRequest): Promise<Database> { 145 | return this.request<Database>(`/databases/${uuid}`, { 146 | method: 'PATCH', 147 | body: JSON.stringify(data), 148 | }); 149 | } 150 | 151 | async deleteDatabase( 152 | uuid: string, 153 | options?: { 154 | deleteConfigurations?: boolean; 155 | deleteVolumes?: boolean; 156 | dockerCleanup?: boolean; 157 | deleteConnectedNetworks?: boolean; 158 | }, 159 | ): Promise<{ message: string }> { 160 | const queryParams = new URLSearchParams(); 161 | if (options) { 162 | if (options.deleteConfigurations !== undefined) { 163 | queryParams.set('delete_configurations', options.deleteConfigurations.toString()); 164 | } 165 | if (options.deleteVolumes !== undefined) { 166 | queryParams.set('delete_volumes', options.deleteVolumes.toString()); 167 | } 168 | if (options.dockerCleanup !== undefined) { 169 | queryParams.set('docker_cleanup', options.dockerCleanup.toString()); 170 | } 171 | if (options.deleteConnectedNetworks !== undefined) { 172 | queryParams.set('delete_connected_networks', options.deleteConnectedNetworks.toString()); 173 | } 174 | } 175 | 176 | const queryString = queryParams.toString(); 177 | const url = queryString ? `/databases/${uuid}?${queryString}` : `/databases/${uuid}`; 178 | 179 | return this.request<{ message: string }>(url, { 180 | method: 'DELETE', 181 | }); 182 | } 183 | 184 | async listServices(): Promise<Service[]> { 185 | return this.request<Service[]>('/services'); 186 | } 187 | 188 | async getService(uuid: string): Promise<Service> { 189 | return this.request<Service>(`/services/${uuid}`); 190 | } 191 | 192 | async createService(data: CreateServiceRequest): Promise<{ uuid: string; domains: string[] }> { 193 | return this.request<{ uuid: string; domains: string[] }>('/services', { 194 | method: 'POST', 195 | body: JSON.stringify(data), 196 | }); 197 | } 198 | 199 | async deleteService(uuid: string, options?: DeleteServiceOptions): Promise<{ message: string }> { 200 | const queryParams = new URLSearchParams(); 201 | if (options) { 202 | if (options.deleteConfigurations !== undefined) { 203 | queryParams.set('delete_configurations', options.deleteConfigurations.toString()); 204 | } 205 | if (options.deleteVolumes !== undefined) { 206 | queryParams.set('delete_volumes', options.deleteVolumes.toString()); 207 | } 208 | if (options.dockerCleanup !== undefined) { 209 | queryParams.set('docker_cleanup', options.dockerCleanup.toString()); 210 | } 211 | if (options.deleteConnectedNetworks !== undefined) { 212 | queryParams.set('delete_connected_networks', options.deleteConnectedNetworks.toString()); 213 | } 214 | } 215 | 216 | const queryString = queryParams.toString(); 217 | const url = queryString ? `/services/${uuid}?${queryString}` : `/services/${uuid}`; 218 | 219 | return this.request<{ message: string }>(url, { 220 | method: 'DELETE', 221 | }); 222 | } 223 | 224 | // Add more methods as needed for other endpoints 225 | } 226 | ``` -------------------------------------------------------------------------------- /src/__tests__/coolify-client.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { jest } from '@jest/globals'; 2 | import { CoolifyClient } from '../lib/coolify-client.js'; 3 | import { ServiceType, CreateServiceRequest } from '../types/coolify.js'; 4 | 5 | const mockFetch = jest.fn() as any; 6 | 7 | describe('CoolifyClient', () => { 8 | let client: CoolifyClient; 9 | 10 | const mockServers = [ 11 | { 12 | id: 1, 13 | uuid: 'test-uuid', 14 | name: 'test-server', 15 | status: 'running', 16 | }, 17 | ]; 18 | 19 | const mockServerInfo = { 20 | id: 1, 21 | uuid: 'test-uuid', 22 | name: 'test-server', 23 | status: 'running', 24 | }; 25 | 26 | const mockServerResources = { 27 | resources: [ 28 | { 29 | name: 'memory', 30 | value: '2GB', 31 | }, 32 | { 33 | name: 'disk', 34 | value: '20GB', 35 | }, 36 | ], 37 | }; 38 | 39 | const mockService = { 40 | id: 1, 41 | uuid: 'test-uuid', 42 | name: 'test-service', 43 | type: 'code-server' as ServiceType, 44 | status: 'running', 45 | created_at: '2024-01-01', 46 | updated_at: '2024-01-01', 47 | }; 48 | 49 | const errorResponse = { 50 | message: 'Resource not found', 51 | }; 52 | 53 | beforeEach(() => { 54 | mockFetch.mockClear(); 55 | (global as any).fetch = mockFetch; 56 | client = new CoolifyClient({ 57 | baseUrl: 'http://localhost:3000', 58 | accessToken: 'test-api-key', 59 | }); 60 | }); 61 | 62 | describe('listServers', () => { 63 | it('should return a list of servers', async () => { 64 | mockFetch.mockImplementationOnce( 65 | async () => 66 | ({ 67 | ok: true, 68 | json: async () => mockServers, 69 | }) as Response, 70 | ); 71 | 72 | const servers = await client.listServers(); 73 | expect(servers).toEqual(mockServers); 74 | expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/servers', { 75 | headers: { 76 | 'Content-Type': 'application/json', 77 | Authorization: 'Bearer test-api-key', 78 | }, 79 | }); 80 | }); 81 | 82 | it('should handle errors', async () => { 83 | mockFetch.mockImplementationOnce(() => 84 | Promise.resolve({ 85 | ok: false, 86 | json: async () => errorResponse, 87 | } as Response), 88 | ); 89 | 90 | await expect(client.listServers()).rejects.toThrow('Resource not found'); 91 | }); 92 | }); 93 | 94 | describe('getServer', () => { 95 | it('should get server info', async () => { 96 | mockFetch.mockImplementationOnce(() => 97 | Promise.resolve({ 98 | ok: true, 99 | json: async () => mockServerInfo, 100 | } as Response), 101 | ); 102 | 103 | const result = await client.getServer('test-uuid'); 104 | 105 | expect(result).toEqual(mockServerInfo); 106 | expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/servers/test-uuid', { 107 | headers: { 108 | 'Content-Type': 'application/json', 109 | Authorization: 'Bearer test-api-key', 110 | }, 111 | }); 112 | }); 113 | 114 | it('should handle errors', async () => { 115 | mockFetch.mockImplementationOnce(() => 116 | Promise.resolve({ 117 | ok: false, 118 | json: async () => errorResponse, 119 | } as Response), 120 | ); 121 | 122 | await expect(client.getServer('test-uuid')).rejects.toThrow('Resource not found'); 123 | }); 124 | }); 125 | 126 | describe('getServerResources', () => { 127 | it('should get server resources', async () => { 128 | mockFetch.mockImplementationOnce(() => 129 | Promise.resolve({ 130 | ok: true, 131 | json: async () => mockServerResources, 132 | } as Response), 133 | ); 134 | 135 | const result = await client.getServerResources('test-uuid'); 136 | 137 | expect(result).toEqual(mockServerResources); 138 | expect(mockFetch).toHaveBeenCalledWith( 139 | 'http://localhost:3000/api/v1/servers/test-uuid/resources', 140 | { 141 | headers: { 142 | 'Content-Type': 'application/json', 143 | Authorization: 'Bearer test-api-key', 144 | }, 145 | }, 146 | ); 147 | }); 148 | 149 | it('should handle errors', async () => { 150 | mockFetch.mockImplementationOnce(() => 151 | Promise.resolve({ 152 | ok: false, 153 | json: async () => errorResponse, 154 | } as Response), 155 | ); 156 | 157 | await expect(client.getServerResources('test-uuid')).rejects.toThrow('Resource not found'); 158 | }); 159 | }); 160 | 161 | describe('listServices', () => { 162 | it('should list services', async () => { 163 | mockFetch.mockImplementationOnce(() => 164 | Promise.resolve({ 165 | ok: true, 166 | json: () => Promise.resolve([mockService]), 167 | } as Response), 168 | ); 169 | 170 | const result = await client.listServices(); 171 | 172 | expect(result).toEqual([mockService]); 173 | expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services', { 174 | headers: { 175 | 'Content-Type': 'application/json', 176 | Authorization: 'Bearer test-api-key', 177 | }, 178 | }); 179 | }); 180 | }); 181 | 182 | describe('getService', () => { 183 | it('should get service info', async () => { 184 | mockFetch.mockImplementationOnce(() => 185 | Promise.resolve({ 186 | ok: true, 187 | json: () => Promise.resolve(mockService), 188 | } as Response), 189 | ); 190 | 191 | const result = await client.getService('test-uuid'); 192 | 193 | expect(result).toEqual(mockService); 194 | expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/test-uuid', { 195 | headers: { 196 | 'Content-Type': 'application/json', 197 | Authorization: 'Bearer test-api-key', 198 | }, 199 | }); 200 | }); 201 | }); 202 | 203 | describe('createService', () => { 204 | it('should create a service', async () => { 205 | mockFetch.mockImplementationOnce(() => 206 | Promise.resolve({ 207 | ok: true, 208 | json: () => 209 | Promise.resolve({ 210 | uuid: 'test-uuid', 211 | domains: ['test.com'], 212 | }), 213 | } as Response), 214 | ); 215 | 216 | const createData: CreateServiceRequest = { 217 | name: 'test-service', 218 | type: 'code-server', 219 | project_uuid: 'project-uuid', 220 | environment_uuid: 'env-uuid', 221 | server_uuid: 'server-uuid', 222 | }; 223 | 224 | const result = await client.createService(createData); 225 | 226 | expect(result).toEqual({ 227 | uuid: 'test-uuid', 228 | domains: ['test.com'], 229 | }); 230 | expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services', { 231 | method: 'POST', 232 | headers: { 233 | 'Content-Type': 'application/json', 234 | Authorization: 'Bearer test-api-key', 235 | }, 236 | body: JSON.stringify(createData), 237 | }); 238 | }); 239 | }); 240 | 241 | describe('deleteService', () => { 242 | it('should delete a service', async () => { 243 | mockFetch.mockImplementationOnce(() => 244 | Promise.resolve({ 245 | ok: true, 246 | json: () => Promise.resolve({ message: 'Service deleted' }), 247 | } as Response), 248 | ); 249 | 250 | const result = await client.deleteService('test-uuid'); 251 | 252 | expect(result).toEqual({ message: 'Service deleted' }); 253 | expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/test-uuid', { 254 | method: 'DELETE', 255 | headers: { 256 | 'Content-Type': 'application/json', 257 | Authorization: 'Bearer test-api-key', 258 | }, 259 | }); 260 | }); 261 | }); 262 | 263 | describe('error handling', () => { 264 | it('should handle network errors', async () => { 265 | const errorMessage = 'Network error'; 266 | mockFetch.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); 267 | 268 | await expect(client.listServers()).rejects.toThrow(errorMessage); 269 | }); 270 | }); 271 | }); 272 | ``` -------------------------------------------------------------------------------- /src/types/coolify.ts: -------------------------------------------------------------------------------- ```typescript 1 | export interface CoolifyConfig { 2 | baseUrl: string; 3 | accessToken: string; 4 | } 5 | 6 | export interface ServerInfo { 7 | uuid: string; 8 | name: string; 9 | status: 'running' | 'stopped' | 'error'; 10 | version: string; 11 | resources: { 12 | cpu: number; 13 | memory: number; 14 | disk: number; 15 | }; 16 | } 17 | 18 | export interface ResourceStatus { 19 | id: number; 20 | uuid: string; 21 | name: string; 22 | type: string; 23 | created_at: string; 24 | updated_at: string; 25 | status: string; 26 | } 27 | 28 | export type ServerResources = ResourceStatus[]; 29 | 30 | export interface ErrorResponse { 31 | error: string; 32 | status: number; 33 | message: string; 34 | } 35 | 36 | export interface ServerDomain { 37 | ip: string; 38 | domains: string[]; 39 | } 40 | 41 | export interface ValidationResponse { 42 | message: string; 43 | } 44 | 45 | export interface Environment { 46 | id: number; 47 | uuid: string; 48 | name: string; 49 | project_uuid: string; 50 | variables?: Record<string, string>; 51 | created_at: string; 52 | updated_at: string; 53 | } 54 | 55 | export interface Project { 56 | id: number; 57 | uuid: string; 58 | name: string; 59 | description?: string; 60 | environments?: Environment[]; 61 | } 62 | 63 | export interface CreateProjectRequest { 64 | name: string; 65 | description?: string; 66 | } 67 | 68 | export interface UpdateProjectRequest { 69 | name?: string; 70 | description?: string; 71 | } 72 | 73 | export interface LogEntry { 74 | timestamp: string; 75 | level: string; 76 | message: string; 77 | } 78 | 79 | export interface Deployment { 80 | id: number; 81 | uuid: string; 82 | application_uuid: string; 83 | status: string; 84 | created_at: string; 85 | updated_at: string; 86 | } 87 | 88 | export interface DatabaseBase { 89 | id: number; 90 | uuid: string; 91 | name: string; 92 | description?: string; 93 | type: 94 | | 'postgresql' 95 | | 'mysql' 96 | | 'mariadb' 97 | | 'mongodb' 98 | | 'redis' 99 | | 'keydb' 100 | | 'clickhouse' 101 | | 'dragonfly'; 102 | status: 'running' | 'stopped' | 'error'; 103 | created_at: string; 104 | updated_at: string; 105 | is_public: boolean; 106 | public_port?: number; 107 | image: string; 108 | limits?: { 109 | memory?: string; 110 | memory_swap?: string; 111 | memory_swappiness?: number; 112 | memory_reservation?: string; 113 | cpus?: string; 114 | cpuset?: string; 115 | cpu_shares?: number; 116 | }; 117 | } 118 | 119 | export interface PostgresDatabase extends DatabaseBase { 120 | type: 'postgresql'; 121 | postgres_user: string; 122 | postgres_password: string; 123 | postgres_db: string; 124 | postgres_initdb_args?: string; 125 | postgres_host_auth_method?: string; 126 | postgres_conf?: string; 127 | } 128 | 129 | export interface MySQLDatabase extends DatabaseBase { 130 | type: 'mysql'; 131 | mysql_root_password: string; 132 | mysql_user?: string; 133 | mysql_password?: string; 134 | mysql_database?: string; 135 | } 136 | 137 | export interface MariaDBDatabase extends DatabaseBase { 138 | type: 'mariadb'; 139 | mariadb_root_password: string; 140 | mariadb_user?: string; 141 | mariadb_password?: string; 142 | mariadb_database?: string; 143 | mariadb_conf?: string; 144 | } 145 | 146 | export interface MongoDBDatabase extends DatabaseBase { 147 | type: 'mongodb'; 148 | mongo_initdb_root_username: string; 149 | mongo_initdb_root_password: string; 150 | mongo_initdb_database?: string; 151 | mongo_conf?: string; 152 | } 153 | 154 | export interface RedisDatabase extends DatabaseBase { 155 | type: 'redis'; 156 | redis_password?: string; 157 | redis_conf?: string; 158 | } 159 | 160 | export interface KeyDBDatabase extends DatabaseBase { 161 | type: 'keydb'; 162 | keydb_password?: string; 163 | keydb_conf?: string; 164 | } 165 | 166 | export interface ClickhouseDatabase extends DatabaseBase { 167 | type: 'clickhouse'; 168 | clickhouse_admin_user: string; 169 | clickhouse_admin_password: string; 170 | } 171 | 172 | export interface DragonflyDatabase extends DatabaseBase { 173 | type: 'dragonfly'; 174 | dragonfly_password: string; 175 | } 176 | 177 | export type Database = 178 | | PostgresDatabase 179 | | MySQLDatabase 180 | | MariaDBDatabase 181 | | MongoDBDatabase 182 | | RedisDatabase 183 | | KeyDBDatabase 184 | | ClickhouseDatabase 185 | | DragonflyDatabase; 186 | 187 | export interface DatabaseUpdateRequest { 188 | name?: string; 189 | description?: string; 190 | image?: string; 191 | is_public?: boolean; 192 | public_port?: number; 193 | limits_memory?: string; 194 | limits_memory_swap?: string; 195 | limits_memory_swappiness?: number; 196 | limits_memory_reservation?: string; 197 | limits_cpus?: string; 198 | limits_cpuset?: string; 199 | limits_cpu_shares?: number; 200 | postgres_user?: string; 201 | postgres_password?: string; 202 | postgres_db?: string; 203 | postgres_initdb_args?: string; 204 | postgres_host_auth_method?: string; 205 | postgres_conf?: string; 206 | clickhouse_admin_user?: string; 207 | clickhouse_admin_password?: string; 208 | dragonfly_password?: string; 209 | redis_password?: string; 210 | redis_conf?: string; 211 | keydb_password?: string; 212 | keydb_conf?: string; 213 | mariadb_conf?: string; 214 | mariadb_root_password?: string; 215 | mariadb_user?: string; 216 | mariadb_password?: string; 217 | mariadb_database?: string; 218 | mongo_conf?: string; 219 | mongo_initdb_root_username?: string; 220 | mongo_initdb_root_password?: string; 221 | mongo_initdb_database?: string; 222 | mysql_root_password?: string; 223 | mysql_password?: string; 224 | mysql_user?: string; 225 | mysql_database?: string; 226 | } 227 | 228 | export type ServiceType = 229 | | 'activepieces' 230 | | 'appsmith' 231 | | 'appwrite' 232 | | 'authentik' 233 | | 'babybuddy' 234 | | 'budge' 235 | | 'changedetection' 236 | | 'chatwoot' 237 | | 'classicpress-with-mariadb' 238 | | 'classicpress-with-mysql' 239 | | 'classicpress-without-database' 240 | | 'cloudflared' 241 | | 'code-server' 242 | | 'dashboard' 243 | | 'directus' 244 | | 'directus-with-postgresql' 245 | | 'docker-registry' 246 | | 'docuseal' 247 | | 'docuseal-with-postgres' 248 | | 'dokuwiki' 249 | | 'duplicati' 250 | | 'emby' 251 | | 'embystat' 252 | | 'fider' 253 | | 'filebrowser' 254 | | 'firefly' 255 | | 'formbricks' 256 | | 'ghost' 257 | | 'gitea' 258 | | 'gitea-with-mariadb' 259 | | 'gitea-with-mysql' 260 | | 'gitea-with-postgresql' 261 | | 'glance' 262 | | 'glances' 263 | | 'glitchtip' 264 | | 'grafana' 265 | | 'grafana-with-postgresql' 266 | | 'grocy' 267 | | 'heimdall' 268 | | 'homepage' 269 | | 'jellyfin' 270 | | 'kuzzle' 271 | | 'listmonk' 272 | | 'logto' 273 | | 'mediawiki' 274 | | 'meilisearch' 275 | | 'metabase' 276 | | 'metube' 277 | | 'minio' 278 | | 'moodle' 279 | | 'n8n' 280 | | 'n8n-with-postgresql' 281 | | 'next-image-transformation' 282 | | 'nextcloud' 283 | | 'nocodb' 284 | | 'odoo' 285 | | 'openblocks' 286 | | 'pairdrop' 287 | | 'penpot' 288 | | 'phpmyadmin' 289 | | 'pocketbase' 290 | | 'posthog' 291 | | 'reactive-resume' 292 | | 'rocketchat' 293 | | 'shlink' 294 | | 'slash' 295 | | 'snapdrop' 296 | | 'statusnook' 297 | | 'stirling-pdf' 298 | | 'supabase' 299 | | 'syncthing' 300 | | 'tolgee' 301 | | 'trigger' 302 | | 'trigger-with-external-database' 303 | | 'twenty' 304 | | 'umami' 305 | | 'unleash-with-postgresql' 306 | | 'unleash-without-database' 307 | | 'uptime-kuma' 308 | | 'vaultwarden' 309 | | 'vikunja' 310 | | 'weblate' 311 | | 'whoogle' 312 | | 'wordpress-with-mariadb' 313 | | 'wordpress-with-mysql' 314 | | 'wordpress-without-database'; 315 | 316 | export interface Service { 317 | id: number; 318 | uuid: string; 319 | name: string; 320 | description?: string; 321 | type: ServiceType; 322 | status: 'running' | 'stopped' | 'error'; 323 | created_at: string; 324 | updated_at: string; 325 | project_uuid: string; 326 | environment_name: string; 327 | environment_uuid: string; 328 | server_uuid: string; 329 | destination_uuid?: string; 330 | domains?: string[]; 331 | } 332 | 333 | export interface CreateServiceRequest { 334 | type: ServiceType; 335 | name?: string; 336 | description?: string; 337 | project_uuid: string; 338 | environment_name?: string; 339 | environment_uuid?: string; 340 | server_uuid: string; 341 | destination_uuid?: string; 342 | instant_deploy?: boolean; 343 | } 344 | 345 | export interface DeleteServiceOptions { 346 | deleteConfigurations?: boolean; 347 | deleteVolumes?: boolean; 348 | dockerCleanup?: boolean; 349 | deleteConnectedNetworks?: boolean; 350 | } 351 | 352 | export interface Application { 353 | uuid: string; 354 | name: string; 355 | // Add other application properties as needed 356 | } 357 | 358 | export interface CreateApplicationRequest { 359 | name: string; 360 | // Add other required fields for application creation 361 | } 362 | ``` -------------------------------------------------------------------------------- /docs/openapi-chunks/servers-api.yaml: -------------------------------------------------------------------------------- ```yaml 1 | openapi: 3.1.0 2 | info: 3 | title: Coolify 4 | version: '0.1' 5 | paths: 6 | /servers: 7 | get: 8 | tags: 9 | - Servers 10 | summary: List 11 | description: List all servers. 12 | operationId: list-servers 13 | responses: 14 | '200': 15 | description: Get all servers. 16 | content: 17 | application/json: 18 | schema: 19 | type: array 20 | items: 21 | $ref: '#/components/schemas/Server' 22 | '400': 23 | $ref: '#/components/responses/400' 24 | '401': 25 | $ref: '#/components/responses/401' 26 | security: 27 | - bearerAuth: [] 28 | post: 29 | tags: 30 | - Servers 31 | summary: Create 32 | description: Create Server. 33 | operationId: create-server 34 | requestBody: 35 | description: Server created. 36 | required: true 37 | content: 38 | application/json: 39 | schema: 40 | properties: 41 | name: 42 | type: string 43 | example: My Server 44 | description: The name of the server. 45 | description: 46 | type: string 47 | example: My Server Description 48 | description: The description of the server. 49 | ip: 50 | type: string 51 | example: 127.0.0.1 52 | description: The IP of the server. 53 | port: 54 | type: integer 55 | example: 22 56 | description: The port of the server. 57 | user: 58 | type: string 59 | example: root 60 | description: The user of the server. 61 | private_key_uuid: 62 | type: string 63 | example: og888os 64 | description: The UUID of the private key. 65 | is_build_server: 66 | type: boolean 67 | example: false 68 | description: Is build server. 69 | instant_validate: 70 | type: boolean 71 | example: false 72 | description: Instant validate. 73 | proxy_type: 74 | type: string 75 | enum: 76 | - traefik 77 | - caddy 78 | - none 79 | example: traefik 80 | description: The proxy type. 81 | type: object 82 | responses: 83 | '201': 84 | description: Server created. 85 | content: 86 | application/json: 87 | schema: 88 | properties: 89 | uuid: 90 | type: string 91 | example: og888os 92 | description: The UUID of the server. 93 | type: object 94 | '400': 95 | $ref: '#/components/responses/400' 96 | '401': 97 | $ref: '#/components/responses/401' 98 | '404': 99 | $ref: '#/components/responses/404' 100 | security: 101 | - bearerAuth: [] 102 | /servers/{uuid}: 103 | get: 104 | tags: 105 | - Servers 106 | summary: Get 107 | description: Get server by UUID. 108 | operationId: get-server-by-uuid 109 | parameters: 110 | - name: uuid 111 | in: path 112 | description: Server's UUID 113 | required: true 114 | schema: 115 | type: string 116 | responses: 117 | '200': 118 | description: Get server by UUID 119 | content: 120 | application/json: 121 | schema: 122 | $ref: '#/components/schemas/Server' 123 | '400': 124 | $ref: '#/components/responses/400' 125 | '401': 126 | $ref: '#/components/responses/401' 127 | '404': 128 | $ref: '#/components/responses/404' 129 | security: 130 | - bearerAuth: [] 131 | delete: 132 | tags: 133 | - Servers 134 | summary: Delete 135 | description: Delete server by UUID. 136 | operationId: delete-server-by-uuid 137 | parameters: 138 | - name: uuid 139 | in: path 140 | description: UUID of the server. 141 | required: true 142 | schema: 143 | type: string 144 | format: uuid 145 | responses: 146 | '200': 147 | description: Server deleted. 148 | content: 149 | application/json: 150 | schema: 151 | properties: 152 | message: 153 | type: string 154 | example: Server deleted. 155 | type: object 156 | '400': 157 | $ref: '#/components/responses/400' 158 | '401': 159 | $ref: '#/components/responses/401' 160 | '404': 161 | $ref: '#/components/responses/404' 162 | security: 163 | - bearerAuth: [] 164 | patch: 165 | tags: 166 | - Servers 167 | summary: Update 168 | description: Update Server. 169 | operationId: update-server-by-uuid 170 | parameters: 171 | - name: uuid 172 | in: path 173 | description: Server UUID 174 | required: true 175 | schema: 176 | type: string 177 | requestBody: 178 | description: Server updated. 179 | required: true 180 | content: 181 | application/json: 182 | schema: 183 | properties: 184 | name: 185 | type: string 186 | description: The name of the server. 187 | description: 188 | type: string 189 | description: The description of the server. 190 | ip: 191 | type: string 192 | description: The IP of the server. 193 | port: 194 | type: integer 195 | description: The port of the server. 196 | user: 197 | type: string 198 | description: The user of the server. 199 | private_key_uuid: 200 | type: string 201 | description: The UUID of the private key. 202 | is_build_server: 203 | type: boolean 204 | description: Is build server. 205 | instant_validate: 206 | type: boolean 207 | description: Instant validate. 208 | proxy_type: 209 | type: string 210 | enum: 211 | - traefik 212 | - caddy 213 | - none 214 | description: The proxy type. 215 | type: object 216 | responses: 217 | '201': 218 | description: Server updated. 219 | content: 220 | application/json: 221 | schema: 222 | $ref: '#/components/schemas/Server' 223 | '400': 224 | $ref: '#/components/responses/400' 225 | '401': 226 | $ref: '#/components/responses/401' 227 | '404': 228 | $ref: '#/components/responses/404' 229 | security: 230 | - bearerAuth: [] 231 | /servers/{uuid}/resources: 232 | get: 233 | tags: 234 | - Servers 235 | summary: Resources 236 | description: Get resources by server. 237 | operationId: get-resources-by-server-uuid 238 | parameters: 239 | - name: uuid 240 | in: path 241 | description: Server's UUID 242 | required: true 243 | schema: 244 | type: string 245 | responses: 246 | '200': 247 | description: Get resources by server 248 | content: 249 | application/json: 250 | schema: 251 | type: array 252 | items: 253 | properties: 254 | id: 255 | type: integer 256 | uuid: 257 | type: string 258 | name: 259 | type: string 260 | type: 261 | type: string 262 | created_at: 263 | type: string 264 | updated_at: 265 | type: string 266 | status: 267 | type: string 268 | type: object 269 | '400': 270 | $ref: '#/components/responses/400' 271 | '401': 272 | $ref: '#/components/responses/401' 273 | security: 274 | - bearerAuth: [] 275 | /servers/{uuid}/domains: 276 | get: 277 | tags: 278 | - Servers 279 | summary: Domains 280 | description: Get domains by server. 281 | operationId: get-domains-by-server-uuid 282 | parameters: 283 | - name: uuid 284 | in: path 285 | description: Server's UUID 286 | required: true 287 | schema: 288 | type: string 289 | responses: 290 | '200': 291 | description: Get domains by server 292 | content: 293 | application/json: 294 | schema: 295 | type: array 296 | items: 297 | properties: 298 | ip: 299 | type: string 300 | domains: 301 | type: array 302 | items: 303 | type: string 304 | type: object 305 | '400': 306 | $ref: '#/components/responses/400' 307 | '401': 308 | $ref: '#/components/responses/401' 309 | security: 310 | - bearerAuth: [] 311 | /servers/{uuid}/validate: 312 | get: 313 | tags: 314 | - Servers 315 | summary: Validate 316 | description: Validate server by UUID. 317 | operationId: validate-server-by-uuid 318 | parameters: 319 | - name: uuid 320 | in: path 321 | description: Server UUID 322 | required: true 323 | schema: 324 | type: string 325 | responses: 326 | '201': 327 | description: Server validation started. 328 | content: 329 | application/json: 330 | schema: 331 | properties: 332 | message: 333 | type: string 334 | example: Validation started. 335 | type: object 336 | '400': 337 | $ref: '#/components/responses/400' 338 | '401': 339 | $ref: '#/components/responses/401' 340 | '404': 341 | $ref: '#/components/responses/404' 342 | security: 343 | - bearerAuth: [] 344 | ``` -------------------------------------------------------------------------------- /docs/features/013-npx-config-fix.md: -------------------------------------------------------------------------------- ```markdown 1 | # npx config fix 2 | 3 | I want to be able to have the following config: 4 | 5 | ```json 6 | { 7 | "mcpServers": { 8 | "coolify": { 9 | "command": "npx", 10 | "args": ["-y", "@masonator/coolify-mcp"], 11 | "env": { 12 | "COOLIFY_ACCESS_TOKEN": "token", 13 | "COOLIFY_BASE_URL": "https://url" 14 | } 15 | }, 16 | "github": { 17 | "command": "npx", 18 | "args": ["-y", "@modelcontextprotocol/server-github"], 19 | "env": { 20 | "GITHUB_PERSONAL_ACCESS_TOKEN": "pat" 21 | } 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | The github config is correct, but the coolify config currently does not work. 28 | 29 | I get the following error: 30 | 31 | ``` 32 | 2025-03-07T09:43:34.691Z [coolify] [info] Initializing server... 33 | 2025-03-07T09:43:34.783Z [coolify] [info] Server started and connected successfully 34 | 2025-03-07T09:43:35.882Z [coolify] [info] Message from client: {"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-ai","version":"0.1.0"}},"jsonrpc":"2.0","id":0} 35 | node:internal/modules/cjs/loader:603 36 | throw e; 37 | ^ 38 | 39 | Error: Cannot find module '/Users/stumason/.npm/_npx/4e3adc8df0812880/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp' 40 | at createEsmNotFoundErr (node:internal/modules/cjs/loader:1186:15) 41 | at finalizeEsmResolution (node:internal/modules/cjs/loader:1174:15) 42 | at resolveExports (node:internal/modules/cjs/loader:596:14) 43 | at Module._findPath (node:internal/modules/cjs/loader:673:31) 44 | at Module._resolveFilename (node:internal/modules/cjs/loader:1135:27) 45 | at Module._load (node:internal/modules/cjs/loader:990:27) 46 | at Module.require (node:internal/modules/cjs/loader:1237:19) 47 | at require (node:internal/modules/helpers:176:18) 48 | at Object.<anonymous> (/Users/stumason/.npm/_npx/4e3adc8df0812880/node_modules/@masonator/coolify-mcp/dist/lib/mcp-server.js:4:15) 49 | at Module._compile (node:internal/modules/cjs/loader:1378:14) { 50 | code: 'MODULE_NOT_FOUND', 51 | path: '/Users/stumason/.npm/_npx/4e3adc8df0812880/node_modules/@modelcontextprotocol/sdk/package.json' 52 | } 53 | node:internal/modules/cjs/loader:603 54 | throw e; 55 | ^ 56 | 57 | Error: Cannot find module '/Users/stumason/.npm/_npx/4e3adc8df0812880/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp' 58 | at createEsmNotFoundErr (node:internal/modules/cjs/loader:1186:15) 59 | at finalizeEsmResolution (node:internal/modules/cjs/loader:1174:15) 60 | at resolveExports (node:internal/modules/cjs/loader:596:14) 61 | at Module._findPath (node:internal/modules/cjs/loader:673:31) 62 | at Module._resolveFilename (node:internal/modules/cjs/loader:1135:27) 63 | at Module._load (node:internal/modules/cjs/loader:990:27) 64 | at Module.require (node:internal/modules/cjs/loader:1237:19) 65 | at require (node:internal/modules/helpers:176:18) 66 | at Object.<anonymous> (/Users/stumason/.npm/_npx/4e3adc8df0812880/node_modules/@masonator/coolify-mcp/dist/lib/mcp-server.js:4:15) 67 | at Module._compile (node:internal/modules/cjs/loader:1378:14) { 68 | code: 'MODULE_NOT_FOUND', 69 | path: '/Users/stumason/.npm/_npx/4e3adc8df0812880/node_modules/@modelcontextprotocol/sdk/package.json' 70 | } 71 | 72 | Node.js v21.6.0 73 | 74 | Node.js v21.6.0 75 | 2025-03-07T09:43:36.909Z [coolify] [info] Server transport closed 76 | 2025-03-07T09:43:36.910Z [coolify] [info] Client transport closed 77 | 2025-03-07T09:43:36.910Z [coolify] [info] Server transport closed unexpectedly, this is likely due to the process exiting early. If you are developing this MCP server you can add output to stderr (i.e. `console.error('...')` in JavaScript, `print('...', file=sys.stderr)` in python) and it will appear in this log. 78 | 2025-03-07T09:43:36.910Z [coolify] [error] Server disconnected. For troubleshooting guidance, please visit our [debugging documentation](https://modelcontextprotocol.io/docs/tools/debugging) {"context":"connection"} 79 | 2025-03-07T09:43:36.911Z [coolify] [info] Client transport closed 80 | 2025-03-07T09:43:36.912Z [coolify] [info] Server transport closed 81 | 2025-03-07T09:43:36.912Z [coolify] [info] Client transport closed 82 | 2025-03-07T09:43:36.912Z [coolify] [info] Server transport closed unexpectedly, this is likely due to the process exiting early. If you are developing this MCP server you can add output to stderr (i.e. `console.error('...')` in JavaScript, `print('...', file=sys.stderr)` in python) and it will appear in this log. 83 | 2025-03-07T09:43:36.912Z [coolify] [error] Server disconnected. For troubleshooting guidance, please visit our [debugging documentation](https://modelcontextprotocol.io/docs/tools/debugging) {"context":"connection"} 84 | 2025-03-07T09:43:36.913Z [coolify] [info] Client transport closed 85 | 2025-03-07T09:51:22.595Z [coolify] [info] Initializing server... 86 | 2025-03-07T09:51:22.618Z [coolify] [info] Server started and connected successfully 87 | 2025-03-07T09:51:22.621Z [coolify] [info] Message from client: {"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-ai","version":"0.1.0"}},"jsonrpc":"2.0","id":0} 88 | 2025-03-07T09:51:23.837Z [coolify] [info] Initializing server... 89 | 2025-03-07T09:51:23.853Z [coolify] [info] Server started and connected successfully 90 | 2025-03-07T09:51:23.948Z [coolify] [info] Message from client: {"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-ai","version":"0.1.0"}},"jsonrpc":"2.0","id":0} 91 | node:internal/modules/cjs/loader:603 92 | throw e; 93 | ^ 94 | 95 | Error: Cannot find module '/Users/stumason/.npm/_npx/4e3adc8df0812880/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp' 96 | at createEsmNotFoundErr (node:internal/modules/cjs/loader:1186:15) 97 | at finalizeEsmResolution (node:internal/modules/cjs/loader:1174:15) 98 | at resolveExports (node:internal/modules/cjs/loader:596:14) 99 | at Module._findPath (node:internal/modules/cjs/loader:673:31) 100 | at Module._resolveFilename (node:internal/modules/cjs/loader:1135:27) 101 | at Module._load (node:internal/modules/cjs/loader:990:27) 102 | at Module.require (node:internal/modules/cjs/loader:1237:19) 103 | at require (node:internal/modules/helpers:176:18) 104 | at Object.<anonymous> (/Users/stumason/.npm/_npx/4e3adc8df0812880/node_modules/@masonator/coolify-mcp/dist/lib/mcp-server.js:4:15) 105 | at Module._compile (node:internal/modules/cjs/loader:1378:14) { 106 | code: 'MODULE_NOT_FOUND', 107 | path: '/Users/stumason/.npm/_npx/4e3adc8df0812880/node_modules/@modelcontextprotocol/sdk/package.json' 108 | } 109 | 110 | Node.js v21.6.0 111 | 2025-03-07T09:51:25.767Z [coolify] [info] Server transport closed 112 | 2025-03-07T09:51:25.768Z [coolify] [info] Client transport closed 113 | 2025-03-07T09:51:25.768Z [coolify] [info] Server transport closed unexpectedly, this is likely due to the process exiting early. If you are developing this MCP server you can add output to stderr (i.e. `console.error('...')` in JavaScript, `print('...', file=sys.stderr)` in python) and it will appear in this log. 114 | 2025-03-07T09:51:25.768Z [coolify] [error] Server disconnected. For troubleshooting guidance, please visit our [debugging documentation](https://modelcontextprotocol.io/docs/tools/debugging) {"context":"connection"} 115 | 2025-03-07T09:51:25.769Z [coolify] [info] Client transport closed 116 | node:internal/modules/cjs/loader:603 117 | throw e; 118 | ^ 119 | 120 | Error: Cannot find module '/Users/stumason/.npm/_npx/4e3adc8df0812880/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp' 121 | at createEsmNotFoundErr (node:internal/modules/cjs/loader:1186:15) 122 | at finalizeEsmResolution (node:internal/modules/cjs/loader:1174:15) 123 | at resolveExports (node:internal/modules/cjs/loader:596:14) 124 | at Module._findPath (node:internal/modules/cjs/loader:673:31) 125 | at Module._resolveFilename (node:internal/modules/cjs/loader:1135:27) 126 | at Module._load (node:internal/modules/cjs/loader:990:27) 127 | at Module.require (node:internal/modules/cjs/loader:1237:19) 128 | at require (node:internal/modules/helpers:176:18) 129 | at Object.<anonymous> (/Users/stumason/.npm/_npx/4e3adc8df0812880/node_modules/@masonator/coolify-mcp/dist/lib/mcp-server.js:4:15) 130 | at Module._compile (node:internal/modules/cjs/loader:1378:14) { 131 | code: 'MODULE_NOT_FOUND', 132 | path: '/Users/stumason/.npm/_npx/4e3adc8df0812880/node_modules/@modelcontextprotocol/sdk/package.json' 133 | } 134 | 135 | Node.js v21.6.0 136 | 2025-03-07T09:51:25.851Z [coolify] [info] Server transport closed 137 | 2025-03-07T09:51:25.851Z [coolify] [info] Client transport closed 138 | 2025-03-07T09:51:25.852Z [coolify] [info] Server transport closed unexpectedly, this is likely due to the process exiting early. If you are developing this MCP server you can add output to stderr (i.e. `console.error('...')` in JavaScript, `print('...', file=sys.stderr)` in python) and it will appear in this log. 139 | 2025-03-07T09:51:25.852Z [coolify] [error] Server disconnected. For troubleshooting guidance, please visit our [debugging documentation](https://modelcontextprotocol.io/docs/tools/debugging) {"context":"connection"} 140 | 2025-03-07T09:51:25.852Z [coolify] [info] Client transport closed 141 | ``` 142 | 143 | For reference, the github package.json looks like this: 144 | 145 | ```json 146 | { 147 | "name": "@modelcontextprotocol/server-github", 148 | "version": "0.6.2", 149 | "description": "MCP server for using the GitHub API", 150 | "license": "MIT", 151 | "author": "Anthropic, PBC (https://anthropic.com)", 152 | "homepage": "https://modelcontextprotocol.io", 153 | "bugs": "https://github.com/modelcontextprotocol/servers/issues", 154 | "type": "module", 155 | "bin": { 156 | "mcp-server-github": "dist/index.js" 157 | }, 158 | "files": ["dist"], 159 | "scripts": { 160 | "build": "tsc && shx chmod +x dist/*.js", 161 | "prepare": "npm run build", 162 | "watch": "tsc --watch" 163 | }, 164 | "dependencies": { 165 | "@modelcontextprotocol/sdk": "1.0.1", 166 | "@types/node": "^22", 167 | "@types/node-fetch": "^2.6.12", 168 | "node-fetch": "^3.3.2", 169 | "universal-user-agent": "^7.0.2", 170 | "zod": "^3.22.4", 171 | "zod-to-json-schema": "^3.23.5" 172 | }, 173 | "devDependencies": { 174 | "shx": "^0.3.4", 175 | "typescript": "^5.6.2" 176 | } 177 | } 178 | ``` 179 | ``` -------------------------------------------------------------------------------- /docs/mcp-js-readme.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP TypeScript SDK   2 | 3 | ## Table of Contents 4 | 5 | - [Overview](#overview) 6 | - [Installation](#installation) 7 | - [Quickstart](#quickstart) 8 | - [What is MCP?](#what-is-mcp) 9 | - [Core Concepts](#core-concepts) 10 | - [Server](#server) 11 | - [Resources](#resources) 12 | - [Tools](#tools) 13 | - [Prompts](#prompts) 14 | - [Running Your Server](#running-your-server) 15 | - [stdio](#stdio) 16 | - [HTTP with SSE](#http-with-sse) 17 | - [Testing and Debugging](#testing-and-debugging) 18 | - [Examples](#examples) 19 | - [Echo Server](#echo-server) 20 | - [SQLite Explorer](#sqlite-explorer) 21 | - [Advanced Usage](#advanced-usage) 22 | - [Low-Level Server](#low-level-server) 23 | - [Writing MCP Clients](#writing-mcp-clients) 24 | - [Server Capabilities](#server-capabilities) 25 | 26 | ## Overview 27 | 28 | The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements the full MCP specification, making it easy to: 29 | 30 | - Build MCP clients that can connect to any MCP server 31 | - Create MCP servers that expose resources, prompts and tools 32 | - Use standard transports like stdio and SSE 33 | - Handle all MCP protocol messages and lifecycle events 34 | 35 | ## Installation 36 | 37 | ```bash 38 | npm install @modelcontextprotocol/sdk 39 | ``` 40 | 41 | ## Quick Start 42 | 43 | Let's create a simple MCP server that exposes a calculator tool and some data: 44 | 45 | ```typescript 46 | import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; 47 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 48 | import { z } from 'zod'; 49 | 50 | // Create an MCP server 51 | const server = new McpServer({ 52 | name: 'Demo', 53 | version: '1.0.0', 54 | }); 55 | 56 | // Add an addition tool 57 | server.tool('add', { a: z.number(), b: z.number() }, async ({ a, b }) => ({ 58 | content: [{ type: 'text', text: String(a + b) }], 59 | })); 60 | 61 | // Add a dynamic greeting resource 62 | server.resource( 63 | 'greeting', 64 | new ResourceTemplate('greeting://{name}', { list: undefined }), 65 | async (uri, { name }) => ({ 66 | contents: [ 67 | { 68 | uri: uri.href, 69 | text: `Hello, ${name}!`, 70 | }, 71 | ], 72 | }), 73 | ); 74 | 75 | // Start receiving messages on stdin and sending messages on stdout 76 | const transport = new StdioServerTransport(); 77 | await server.connect(transport); 78 | ``` 79 | 80 | ## What is MCP? 81 | 82 | The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can: 83 | 84 | - Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context) 85 | - Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect) 86 | - Define interaction patterns through **Prompts** (reusable templates for LLM interactions) 87 | - And more! 88 | 89 | ## Core Concepts 90 | 91 | ### Server 92 | 93 | The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing: 94 | 95 | ```typescript 96 | const server = new McpServer({ 97 | name: 'My App', 98 | version: '1.0.0', 99 | }); 100 | ``` 101 | 102 | ### Resources 103 | 104 | Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects: 105 | 106 | ```typescript 107 | // Static resource 108 | server.resource('config', 'config://app', async (uri) => ({ 109 | contents: [ 110 | { 111 | uri: uri.href, 112 | text: 'App configuration here', 113 | }, 114 | ], 115 | })); 116 | 117 | // Dynamic resource with parameters 118 | server.resource( 119 | 'user-profile', 120 | new ResourceTemplate('users://{userId}/profile', { list: undefined }), 121 | async (uri, { userId }) => ({ 122 | contents: [ 123 | { 124 | uri: uri.href, 125 | text: `Profile data for user ${userId}`, 126 | }, 127 | ], 128 | }), 129 | ); 130 | ``` 131 | 132 | ### Tools 133 | 134 | Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects: 135 | 136 | ```typescript 137 | // Simple tool with parameters 138 | server.tool( 139 | 'calculate-bmi', 140 | { 141 | weightKg: z.number(), 142 | heightM: z.number(), 143 | }, 144 | async ({ weightKg, heightM }) => ({ 145 | content: [ 146 | { 147 | type: 'text', 148 | text: String(weightKg / (heightM * heightM)), 149 | }, 150 | ], 151 | }), 152 | ); 153 | 154 | // Async tool with external API call 155 | server.tool('fetch-weather', { city: z.string() }, async ({ city }) => { 156 | const response = await fetch(`https://api.weather.com/${city}`); 157 | const data = await response.text(); 158 | return { 159 | content: [{ type: 'text', text: data }], 160 | }; 161 | }); 162 | ``` 163 | 164 | ### Prompts 165 | 166 | Prompts are reusable templates that help LLMs interact with your server effectively: 167 | 168 | ```typescript 169 | server.prompt('review-code', { code: z.string() }, ({ code }) => ({ 170 | messages: [ 171 | { 172 | role: 'user', 173 | content: { 174 | type: 'text', 175 | text: `Please review this code:\n\n${code}`, 176 | }, 177 | }, 178 | ], 179 | })); 180 | ``` 181 | 182 | ## Running Your Server 183 | 184 | MCP servers in TypeScript need to be connected to a transport to communicate with clients. How you start the server depends on the choice of transport: 185 | 186 | ### stdio 187 | 188 | For command-line tools and direct integrations: 189 | 190 | ```typescript 191 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 192 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 193 | 194 | const server = new McpServer({ 195 | name: 'example-server', 196 | version: '1.0.0', 197 | }); 198 | 199 | // ... set up server resources, tools, and prompts ... 200 | 201 | const transport = new StdioServerTransport(); 202 | await server.connect(transport); 203 | ``` 204 | 205 | ### HTTP with SSE 206 | 207 | For remote servers, start a web server with a Server-Sent Events (SSE) endpoint, and a separate endpoint for the client to send its messages to: 208 | 209 | ```typescript 210 | import express from 'express'; 211 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 212 | import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; 213 | 214 | const server = new McpServer({ 215 | name: 'example-server', 216 | version: '1.0.0', 217 | }); 218 | 219 | // ... set up server resources, tools, and prompts ... 220 | 221 | const app = express(); 222 | 223 | app.get('/sse', async (req, res) => { 224 | const transport = new SSEServerTransport('/messages', res); 225 | await server.connect(transport); 226 | }); 227 | 228 | app.post('/messages', async (req, res) => { 229 | // Note: to support multiple simultaneous connections, these messages will 230 | // need to be routed to a specific matching transport. (This logic isn't 231 | // implemented here, for simplicity.) 232 | await transport.handlePostMessage(req, res); 233 | }); 234 | 235 | app.listen(3001); 236 | ``` 237 | 238 | ### Testing and Debugging 239 | 240 | To test your server, you can use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). See its README for more information. 241 | 242 | ## Examples 243 | 244 | ### Echo Server 245 | 246 | A simple server demonstrating resources, tools, and prompts: 247 | 248 | ```typescript 249 | import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; 250 | import { z } from 'zod'; 251 | 252 | const server = new McpServer({ 253 | name: 'Echo', 254 | version: '1.0.0', 255 | }); 256 | 257 | server.resource( 258 | 'echo', 259 | new ResourceTemplate('echo://{message}', { list: undefined }), 260 | async (uri, { message }) => ({ 261 | contents: [ 262 | { 263 | uri: uri.href, 264 | text: `Resource echo: ${message}`, 265 | }, 266 | ], 267 | }), 268 | ); 269 | 270 | server.tool('echo', { message: z.string() }, async ({ message }) => ({ 271 | content: [{ type: 'text', text: `Tool echo: ${message}` }], 272 | })); 273 | 274 | server.prompt('echo', { message: z.string() }, ({ message }) => ({ 275 | messages: [ 276 | { 277 | role: 'user', 278 | content: { 279 | type: 'text', 280 | text: `Please process this message: ${message}`, 281 | }, 282 | }, 283 | ], 284 | })); 285 | ``` 286 | 287 | ### SQLite Explorer 288 | 289 | A more complex example showing database integration: 290 | 291 | ```typescript 292 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 293 | import sqlite3 from 'sqlite3'; 294 | import { promisify } from 'util'; 295 | import { z } from 'zod'; 296 | 297 | const server = new McpServer({ 298 | name: 'SQLite Explorer', 299 | version: '1.0.0', 300 | }); 301 | 302 | // Helper to create DB connection 303 | const getDb = () => { 304 | const db = new sqlite3.Database('database.db'); 305 | return { 306 | all: promisify<string, any[]>(db.all.bind(db)), 307 | close: promisify(db.close.bind(db)), 308 | }; 309 | }; 310 | 311 | server.resource('schema', 'schema://main', async (uri) => { 312 | const db = getDb(); 313 | try { 314 | const tables = await db.all("SELECT sql FROM sqlite_master WHERE type='table'"); 315 | return { 316 | contents: [ 317 | { 318 | uri: uri.href, 319 | text: tables.map((t: { sql: string }) => t.sql).join('\n'), 320 | }, 321 | ], 322 | }; 323 | } finally { 324 | await db.close(); 325 | } 326 | }); 327 | 328 | server.tool('query', { sql: z.string() }, async ({ sql }) => { 329 | const db = getDb(); 330 | try { 331 | const results = await db.all(sql); 332 | return { 333 | content: [ 334 | { 335 | type: 'text', 336 | text: JSON.stringify(results, null, 2), 337 | }, 338 | ], 339 | }; 340 | } catch (err: unknown) { 341 | const error = err as Error; 342 | return { 343 | content: [ 344 | { 345 | type: 'text', 346 | text: `Error: ${error.message}`, 347 | }, 348 | ], 349 | isError: true, 350 | }; 351 | } finally { 352 | await db.close(); 353 | } 354 | }); 355 | ``` 356 | 357 | ## Advanced Usage 358 | 359 | ### Low-Level Server 360 | 361 | For more control, you can use the low-level Server class directly: 362 | 363 | ```typescript 364 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 365 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 366 | import { 367 | ListPromptsRequestSchema, 368 | GetPromptRequestSchema, 369 | } from '@modelcontextprotocol/sdk/types.js'; 370 | 371 | const server = new Server( 372 | { 373 | name: 'example-server', 374 | version: '1.0.0', 375 | }, 376 | { 377 | capabilities: { 378 | prompts: {}, 379 | }, 380 | }, 381 | ); 382 | 383 | server.setRequestHandler(ListPromptsRequestSchema, async () => { 384 | return { 385 | prompts: [ 386 | { 387 | name: 'example-prompt', 388 | description: 'An example prompt template', 389 | arguments: [ 390 | { 391 | name: 'arg1', 392 | description: 'Example argument', 393 | required: true, 394 | }, 395 | ], 396 | }, 397 | ], 398 | }; 399 | }); 400 | 401 | server.setRequestHandler(GetPromptRequestSchema, async (request) => { 402 | if (request.params.name !== 'example-prompt') { 403 | throw new Error('Unknown prompt'); 404 | } 405 | return { 406 | description: 'Example prompt', 407 | messages: [ 408 | { 409 | role: 'user', 410 | content: { 411 | type: 'text', 412 | text: 'Example prompt text', 413 | }, 414 | }, 415 | ], 416 | }; 417 | }); 418 | 419 | const transport = new StdioServerTransport(); 420 | await server.connect(transport); 421 | ``` 422 | 423 | ### Writing MCP Clients 424 | 425 | The SDK provides a high-level client interface: 426 | 427 | ```typescript 428 | import { Client } from '@modelcontextprotocol/sdk/client/index.js'; 429 | import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; 430 | 431 | const transport = new StdioClientTransport({ 432 | command: 'node', 433 | args: ['server.js'], 434 | }); 435 | 436 | const client = new Client( 437 | { 438 | name: 'example-client', 439 | version: '1.0.0', 440 | }, 441 | { 442 | capabilities: { 443 | prompts: {}, 444 | resources: {}, 445 | tools: {}, 446 | }, 447 | }, 448 | ); 449 | 450 | await client.connect(transport); 451 | 452 | // List prompts 453 | const prompts = await client.listPrompts(); 454 | 455 | // Get a prompt 456 | const prompt = await client.getPrompt('example-prompt', { 457 | arg1: 'value', 458 | }); 459 | 460 | // List resources 461 | const resources = await client.listResources(); 462 | 463 | // Read a resource 464 | const resource = await client.readResource('file:///example.txt'); 465 | 466 | // Call a tool 467 | const result = await client.callTool({ 468 | name: 'example-tool', 469 | arguments: { 470 | arg1: 'value', 471 | }, 472 | }); 473 | ``` 474 | 475 | ## Documentation 476 | 477 | - [Model Context Protocol documentation](https://modelcontextprotocol.io) 478 | - [MCP Specification](https://spec.modelcontextprotocol.io) 479 | - [Example Servers](https://github.com/modelcontextprotocol/servers) 480 | 481 | ## Contributing 482 | 483 | Issues and pull requests are welcome on GitHub at https://github.com/modelcontextprotocol/typescript-sdk. 484 | 485 | ## License 486 | 487 | This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details. 488 | ``` -------------------------------------------------------------------------------- /src/lib/mcp-server.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 2 | import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; 3 | import { CoolifyClient } from './coolify-client.js'; 4 | import debug from 'debug'; 5 | import { z } from 'zod'; 6 | import type { 7 | ServerInfo, 8 | ServerResources, 9 | ServerDomain, 10 | ValidationResponse, 11 | Project, 12 | CreateProjectRequest, 13 | UpdateProjectRequest, 14 | Environment, 15 | Deployment, 16 | Database, 17 | DatabaseUpdateRequest, 18 | Service, 19 | CreateServiceRequest, 20 | DeleteServiceOptions, 21 | } from '../types/coolify.js'; 22 | 23 | const log = debug('coolify:mcp'); 24 | 25 | // Define valid service types 26 | const serviceTypes = [ 27 | 'activepieces', 28 | 'appsmith', 29 | 'appwrite', 30 | 'authentik', 31 | 'babybuddy', 32 | 'budge', 33 | 'changedetection', 34 | 'chatwoot', 35 | 'classicpress-with-mariadb', 36 | 'classicpress-with-mysql', 37 | 'classicpress-without-database', 38 | 'cloudflared', 39 | 'code-server', 40 | 'dashboard', 41 | 'directus', 42 | 'directus-with-postgresql', 43 | 'docker-registry', 44 | 'docuseal', 45 | 'docuseal-with-postgres', 46 | 'dokuwiki', 47 | 'duplicati', 48 | 'emby', 49 | 'embystat', 50 | 'fider', 51 | 'filebrowser', 52 | 'firefly', 53 | 'formbricks', 54 | 'ghost', 55 | 'gitea', 56 | 'gitea-with-mariadb', 57 | 'gitea-with-mysql', 58 | 'gitea-with-postgresql', 59 | 'glance', 60 | 'glances', 61 | 'glitchtip', 62 | 'grafana', 63 | 'grafana-with-postgresql', 64 | 'grocy', 65 | 'heimdall', 66 | 'homepage', 67 | 'jellyfin', 68 | 'kuzzle', 69 | 'listmonk', 70 | 'logto', 71 | 'mediawiki', 72 | 'meilisearch', 73 | 'metabase', 74 | 'metube', 75 | 'minio', 76 | 'moodle', 77 | 'n8n', 78 | 'n8n-with-postgresql', 79 | 'next-image-transformation', 80 | 'nextcloud', 81 | 'nocodb', 82 | 'odoo', 83 | 'openblocks', 84 | 'pairdrop', 85 | 'penpot', 86 | 'phpmyadmin', 87 | 'pocketbase', 88 | 'posthog', 89 | 'reactive-resume', 90 | 'rocketchat', 91 | 'shlink', 92 | 'slash', 93 | 'snapdrop', 94 | 'statusnook', 95 | 'stirling-pdf', 96 | 'supabase', 97 | 'syncthing', 98 | 'tolgee', 99 | 'trigger', 100 | 'trigger-with-external-database', 101 | 'twenty', 102 | 'umami', 103 | 'unleash-with-postgresql', 104 | 'unleash-without-database', 105 | 'uptime-kuma', 106 | 'vaultwarden', 107 | 'vikunja', 108 | 'weblate', 109 | 'whoogle', 110 | 'wordpress-with-mariadb', 111 | 'wordpress-with-mysql', 112 | 'wordpress-without-database' 113 | ] as const; 114 | 115 | export class CoolifyMcpServer extends McpServer { 116 | private client: CoolifyClient; 117 | 118 | constructor(config: { baseUrl: string; accessToken: string }) { 119 | super({ 120 | name: 'coolify', 121 | version: '0.1.18' 122 | }); 123 | 124 | log('Initializing server with config: %o', config); 125 | this.client = new CoolifyClient(config); 126 | } 127 | 128 | async initialize(): Promise<void> { 129 | // Register capabilities first 130 | await this.server.registerCapabilities({ 131 | tools: {} 132 | }); 133 | 134 | // Then register all tools 135 | this.tool('list_servers', 'List all Coolify servers', {}, async () => { 136 | const servers = await this.client.listServers(); 137 | return { 138 | content: [{ type: 'text', text: JSON.stringify(servers, null, 2) }] 139 | }; 140 | }); 141 | 142 | this.tool('get_server', 'Get details about a specific Coolify server', { 143 | uuid: z.string().describe('UUID of the server to get details for') 144 | }, async (args) => { 145 | const server = await this.client.getServer(args.uuid); 146 | return { 147 | content: [{ type: 'text', text: JSON.stringify(server, null, 2) }] 148 | }; 149 | }); 150 | 151 | this.tool('get_server_resources', 'Get the current resources running on a specific Coolify server', { 152 | uuid: z.string() 153 | }, async (args, _extra) => { 154 | const resources = await this.client.getServerResources(args.uuid); 155 | return { 156 | content: [{ type: 'text', text: JSON.stringify(resources, null, 2) }] 157 | }; 158 | }); 159 | 160 | this.tool('get_server_domains', 'Get domains for a specific Coolify server', { 161 | uuid: z.string() 162 | }, async (args, _extra) => { 163 | const domains = await this.client.getServerDomains(args.uuid); 164 | return { 165 | content: [{ type: 'text', text: JSON.stringify(domains, null, 2) }] 166 | }; 167 | }); 168 | 169 | this.tool('validate_server', 'Validate a specific Coolify server', { 170 | uuid: z.string() 171 | }, async (args, _extra) => { 172 | const validation = await this.client.validateServer(args.uuid); 173 | return { 174 | content: [{ type: 'text', text: JSON.stringify(validation, null, 2) }] 175 | }; 176 | }); 177 | 178 | this.tool('list_projects', 'List all Coolify projects', {}, async (_args, _extra) => { 179 | const projects = await this.client.listProjects(); 180 | return { 181 | content: [{ type: 'text', text: JSON.stringify(projects, null, 2) }] 182 | }; 183 | }); 184 | 185 | this.tool('get_project', 'Get details about a specific Coolify project', { 186 | uuid: z.string() 187 | }, async (args, _extra) => { 188 | const project = await this.client.getProject(args.uuid); 189 | return { 190 | content: [{ type: 'text', text: JSON.stringify(project, null, 2) }] 191 | }; 192 | }); 193 | 194 | this.tool('create_project', 'Create a new Coolify project', { 195 | name: z.string(), 196 | description: z.string().optional() 197 | }, async (args, _extra) => { 198 | const result = await this.client.createProject({ 199 | name: args.name, 200 | description: args.description 201 | }); 202 | return { 203 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] 204 | }; 205 | }); 206 | 207 | this.tool('update_project', 'Update an existing Coolify project', { 208 | uuid: z.string(), 209 | name: z.string(), 210 | description: z.string().optional() 211 | }, async (args, _extra) => { 212 | const { uuid, ...updateData } = args; 213 | const result = await this.client.updateProject(uuid, updateData); 214 | return { 215 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] 216 | }; 217 | }); 218 | 219 | this.tool('delete_project', 'Delete a Coolify project', { 220 | uuid: z.string() 221 | }, async (args, _extra) => { 222 | const result = await this.client.deleteProject(args.uuid); 223 | return { 224 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] 225 | }; 226 | }); 227 | 228 | this.tool('get_project_environment', 'Get environment details for a Coolify project', { 229 | project_uuid: z.string(), 230 | environment_name_or_uuid: z.string() 231 | }, async (args, _extra) => { 232 | const environment = await this.client.getProjectEnvironment(args.project_uuid, args.environment_name_or_uuid); 233 | return { 234 | content: [{ type: 'text', text: JSON.stringify(environment, null, 2) }] 235 | }; 236 | }); 237 | 238 | this.tool('list_databases', 'List all Coolify databases', {}, async (_args, _extra) => { 239 | const databases = await this.client.listDatabases(); 240 | return { 241 | content: [{ type: 'text', text: JSON.stringify(databases, null, 2) }] 242 | }; 243 | }); 244 | 245 | this.tool('get_database', 'Get details about a specific Coolify database', { 246 | uuid: z.string() 247 | }, async (args, _extra) => { 248 | const database = await this.client.getDatabase(args.uuid); 249 | return { 250 | content: [{ type: 'text', text: JSON.stringify(database, null, 2) }] 251 | }; 252 | }); 253 | 254 | this.tool('update_database', 'Update a Coolify database', { 255 | uuid: z.string(), 256 | data: z.record(z.unknown()) 257 | }, async (args, _extra) => { 258 | const result = await this.client.updateDatabase(args.uuid, args.data); 259 | return { 260 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] 261 | }; 262 | }); 263 | 264 | const deleteOptionsSchema = { 265 | deleteConfigurations: z.boolean().optional(), 266 | deleteVolumes: z.boolean().optional(), 267 | dockerCleanup: z.boolean().optional(), 268 | deleteConnectedNetworks: z.boolean().optional() 269 | }; 270 | 271 | this.tool('delete_database', 'Delete a Coolify database', { 272 | uuid: z.string(), 273 | options: z.object(deleteOptionsSchema).optional() 274 | }, async (args, _extra) => { 275 | const result = await this.client.deleteDatabase(args.uuid, args.options); 276 | return { 277 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] 278 | }; 279 | }); 280 | 281 | this.tool('deploy_application', 'Deploy a Coolify application', { 282 | uuid: z.string() 283 | }, async (args, _extra) => { 284 | const result = await this.client.deployApplication(args.uuid); 285 | return { 286 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] 287 | }; 288 | }); 289 | 290 | this.tool('list_services', 'List all Coolify services', {}, async (_args, _extra) => { 291 | const services = await this.client.listServices(); 292 | return { 293 | content: [{ type: 'text', text: JSON.stringify(services, null, 2) }] 294 | }; 295 | }); 296 | 297 | this.tool('get_service', 'Get details about a specific Coolify service', { 298 | uuid: z.string() 299 | }, async (args, _extra) => { 300 | const service = await this.client.getService(args.uuid); 301 | return { 302 | content: [{ type: 'text', text: JSON.stringify(service, null, 2) }] 303 | }; 304 | }); 305 | 306 | this.tool('create_service', 'Create a new Coolify service', { 307 | type: z.enum(serviceTypes), 308 | project_uuid: z.string(), 309 | server_uuid: z.string(), 310 | name: z.string().optional(), 311 | description: z.string().optional(), 312 | environment_name: z.string().optional(), 313 | environment_uuid: z.string().optional(), 314 | destination_uuid: z.string().optional(), 315 | instant_deploy: z.boolean().optional() 316 | }, async (args, _extra) => { 317 | const result = await this.client.createService(args); 318 | return { 319 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] 320 | }; 321 | }); 322 | 323 | this.tool('delete_service', 'Delete a Coolify service', { 324 | uuid: z.string(), 325 | options: z.object(deleteOptionsSchema).optional() 326 | }, async (args, _extra) => { 327 | const result = await this.client.deleteService(args.uuid, args.options); 328 | return { 329 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] 330 | }; 331 | }); 332 | } 333 | 334 | async connect(transport: Transport): Promise<void> { 335 | log('Starting server...'); 336 | log('Validating connection...'); 337 | await this.client.validateConnection(); 338 | await this.initialize(); 339 | await super.connect(transport); 340 | log('Server started successfully'); 341 | } 342 | 343 | async list_servers(): Promise<ServerInfo[]> { 344 | return this.client.listServers(); 345 | } 346 | 347 | async get_server(uuid: string): Promise<ServerInfo> { 348 | return this.client.getServer(uuid); 349 | } 350 | 351 | async get_server_resources(uuid: string): Promise<ServerResources> { 352 | return this.client.getServerResources(uuid); 353 | } 354 | 355 | async get_server_domains(uuid: string): Promise<ServerDomain[]> { 356 | return this.client.getServerDomains(uuid); 357 | } 358 | 359 | async validate_server(uuid: string): Promise<ValidationResponse> { 360 | return this.client.validateServer(uuid); 361 | } 362 | 363 | async list_projects(): Promise<Project[]> { 364 | return this.client.listProjects(); 365 | } 366 | 367 | async get_project(uuid: string): Promise<Project> { 368 | return this.client.getProject(uuid); 369 | } 370 | 371 | async create_project(project: CreateProjectRequest): Promise<{ uuid: string }> { 372 | return this.client.createProject(project); 373 | } 374 | 375 | async update_project(uuid: string, project: UpdateProjectRequest): Promise<Project> { 376 | return this.client.updateProject(uuid, project); 377 | } 378 | 379 | async delete_project(uuid: string): Promise<{ message: string }> { 380 | return this.client.deleteProject(uuid); 381 | } 382 | 383 | async get_project_environment( 384 | projectUuid: string, 385 | environmentNameOrUuid: string, 386 | ): Promise<Environment> { 387 | return this.client.getProjectEnvironment(projectUuid, environmentNameOrUuid); 388 | } 389 | 390 | async deploy_application(params: { uuid: string }): Promise<Deployment> { 391 | return this.client.deployApplication(params.uuid); 392 | } 393 | 394 | async list_databases(): Promise<Database[]> { 395 | return this.client.listDatabases(); 396 | } 397 | 398 | async get_database(uuid: string): Promise<Database> { 399 | return this.client.getDatabase(uuid); 400 | } 401 | 402 | async update_database(uuid: string, data: DatabaseUpdateRequest): Promise<Database> { 403 | return this.client.updateDatabase(uuid, data); 404 | } 405 | 406 | async delete_database( 407 | uuid: string, 408 | options?: { 409 | deleteConfigurations?: boolean; 410 | deleteVolumes?: boolean; 411 | dockerCleanup?: boolean; 412 | deleteConnectedNetworks?: boolean; 413 | }, 414 | ): Promise<{ message: string }> { 415 | return this.client.deleteDatabase(uuid, options); 416 | } 417 | 418 | async list_services(): Promise<Service[]> { 419 | return this.client.listServices(); 420 | } 421 | 422 | async get_service(uuid: string): Promise<Service> { 423 | return this.client.getService(uuid); 424 | } 425 | 426 | async create_service(data: CreateServiceRequest): Promise<{ uuid: string; domains: string[] }> { 427 | return this.client.createService(data); 428 | } 429 | 430 | async delete_service(uuid: string, options?: DeleteServiceOptions): Promise<{ message: string }> { 431 | return this.client.deleteService(uuid, options); 432 | } 433 | } 434 | ```