@softtor/coolify-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Softtor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,232 @@
1
+ # Coolify MCP Server
2
+
3
+ [![npm version](https://badge.fury.io/js/@softtor%2Fcoolify-mcp-server.svg)](https://www.npmjs.com/package/@softtor/coolify-mcp-server)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ MCP (Model Context Protocol) server for [Coolify](https://coolify.io) API integration. Manage your applications, databases, services, servers, and deployments directly from Claude Code or any MCP-compatible client.
7
+
8
+ ## Features
9
+
10
+ - Multi-team support with dynamic API key configuration
11
+ - 27 tools covering all major Coolify operations
12
+ - Full TypeScript support
13
+ - Compatible with Claude Code and other MCP clients
14
+
15
+ ## Installation
16
+
17
+ ### Using npx (Recommended)
18
+
19
+ No installation required. Configure directly in your MCP settings:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "coolify": {
25
+ "command": "npx",
26
+ "args": ["-y", "@softtor/coolify-mcp-server"],
27
+ "env": {
28
+ "COOLIFY_BASE_URL": "https://your-coolify-instance.com",
29
+ "COOLIFY_API_KEY": "your-api-key"
30
+ }
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ ### Global Installation
37
+
38
+ ```bash
39
+ npm install -g @softtor/coolify-mcp-server
40
+ ```
41
+
42
+ Then configure in your MCP settings:
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "coolify": {
48
+ "command": "coolify-mcp-server",
49
+ "env": {
50
+ "COOLIFY_BASE_URL": "https://your-coolify-instance.com",
51
+ "COOLIFY_API_KEY": "your-api-key"
52
+ }
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ ## Configuration
59
+
60
+ ### Environment Variables
61
+
62
+ | Variable | Required | Description |
63
+ |----------|----------|-------------|
64
+ | `COOLIFY_BASE_URL` | No | Coolify instance URL (default: `https://cloud.softtor.com.br`) |
65
+ | `COOLIFY_API_KEY` | No* | Default API key |
66
+ | `COOLIFY_DEFAULT_TEAM` | No | Default team name (default: `default`) |
67
+ | `COOLIFY_TEAM_<NAME>_API_KEY` | No* | Team-specific API key |
68
+
69
+ *At least one API key must be provided.
70
+
71
+ ### Multi-Team Configuration
72
+
73
+ For organizations with multiple Coolify teams, configure team-specific API keys:
74
+
75
+ ```json
76
+ {
77
+ "mcpServers": {
78
+ "coolify": {
79
+ "command": "npx",
80
+ "args": ["-y", "@softtor/coolify-mcp-server"],
81
+ "env": {
82
+ "COOLIFY_BASE_URL": "https://your-coolify-instance.com",
83
+ "COOLIFY_DEFAULT_TEAM": "production",
84
+ "COOLIFY_TEAM_PRODUCTION_API_KEY": "prod-api-key",
85
+ "COOLIFY_TEAM_STAGING_API_KEY": "staging-api-key",
86
+ "COOLIFY_TEAM_DEV_API_KEY": "dev-api-key"
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ Then use the `team` parameter in any tool:
94
+
95
+ ```
96
+ coolify_list_applications { team: "staging" }
97
+ ```
98
+
99
+ ### Getting Your API Key
100
+
101
+ 1. Log in to your Coolify instance
102
+ 2. Go to **Settings** > **API Tokens**
103
+ 3. Create a new token with appropriate permissions
104
+ 4. Copy the token and use it as your API key
105
+
106
+ ## Available Tools
107
+
108
+ ### Applications (6 tools)
109
+
110
+ | Tool | Description |
111
+ |------|-------------|
112
+ | `coolify_list_applications` | List all applications |
113
+ | `coolify_get_application` | Get application details by UUID |
114
+ | `coolify_start_application` | Start/deploy an application |
115
+ | `coolify_stop_application` | Stop a running application |
116
+ | `coolify_restart_application` | Restart an application |
117
+ | `coolify_get_application_logs` | Get container logs |
118
+
119
+ ### Databases (6 tools)
120
+
121
+ | Tool | Description |
122
+ |------|-------------|
123
+ | `coolify_list_databases` | List all databases |
124
+ | `coolify_get_database` | Get database details by UUID |
125
+ | `coolify_start_database` | Start a database |
126
+ | `coolify_stop_database` | Stop a running database |
127
+ | `coolify_restart_database` | Restart a database |
128
+ | `coolify_list_database_backups` | List database backups |
129
+
130
+ ### Services (5 tools)
131
+
132
+ | Tool | Description |
133
+ |------|-------------|
134
+ | `coolify_list_services` | List all services |
135
+ | `coolify_get_service` | Get service details by UUID |
136
+ | `coolify_start_service` | Start a service |
137
+ | `coolify_stop_service` | Stop a running service |
138
+ | `coolify_restart_service` | Restart a service |
139
+
140
+ ### Servers (4 tools)
141
+
142
+ | Tool | Description |
143
+ |------|-------------|
144
+ | `coolify_list_servers` | List all servers |
145
+ | `coolify_get_server` | Get server details by UUID |
146
+ | `coolify_get_server_resources` | Get all resources on a server |
147
+ | `coolify_get_server_domains` | Get all domains mapped on a server |
148
+
149
+ ### Deployments (2 tools)
150
+
151
+ | Tool | Description |
152
+ |------|-------------|
153
+ | `coolify_deploy` | Deploy by UUID or tag |
154
+ | `coolify_list_deployments` | List deployment history |
155
+
156
+ ### Projects & Teams (4 tools)
157
+
158
+ | Tool | Description |
159
+ |------|-------------|
160
+ | `coolify_list_projects` | List all projects |
161
+ | `coolify_get_project` | Get project details by UUID |
162
+ | `coolify_list_teams` | List all accessible teams |
163
+ | `coolify_get_team` | Get team details by ID |
164
+
165
+ ## Usage Examples
166
+
167
+ ### List all applications
168
+
169
+ ```
170
+ coolify_list_applications
171
+ ```
172
+
173
+ ### Deploy an application
174
+
175
+ ```
176
+ coolify_deploy { uuid: "app-uuid-here" }
177
+ ```
178
+
179
+ ### Deploy with force rebuild
180
+
181
+ ```
182
+ coolify_deploy { uuid: "app-uuid-here", force: true }
183
+ ```
184
+
185
+ ### Deploy all applications with a tag
186
+
187
+ ```
188
+ coolify_deploy { tag: "production" }
189
+ ```
190
+
191
+ ### Get application logs
192
+
193
+ ```
194
+ coolify_get_application_logs { uuid: "app-uuid-here", since: 3600 }
195
+ ```
196
+
197
+ ### Use a specific team
198
+
199
+ ```
200
+ coolify_list_applications { team: "staging" }
201
+ ```
202
+
203
+ ## Development
204
+
205
+ ```bash
206
+ # Clone the repository
207
+ git clone https://github.com/Softtor/coolify-mcp-server.git
208
+ cd coolify-mcp-server
209
+
210
+ # Install dependencies
211
+ npm install
212
+
213
+ # Build
214
+ npm run build
215
+
216
+ # Run locally
217
+ COOLIFY_API_KEY=your-key npm start
218
+ ```
219
+
220
+ ## Contributing
221
+
222
+ Contributions are welcome! Please feel free to submit a Pull Request.
223
+
224
+ ## License
225
+
226
+ MIT License - see [LICENSE](LICENSE) for details.
227
+
228
+ ## Related
229
+
230
+ - [Coolify](https://coolify.io) - Self-hostable Heroku/Netlify alternative
231
+ - [Model Context Protocol](https://modelcontextprotocol.io) - Protocol for AI tool integration
232
+ - [Claude Code](https://claude.ai/code) - Anthropic's CLI for Claude
@@ -0,0 +1,12 @@
1
+ export interface TeamConfig {
2
+ name: string;
3
+ apiKey: string;
4
+ }
5
+ export interface Config {
6
+ baseUrl: string;
7
+ defaultTeam: string;
8
+ teams: Map<string, TeamConfig>;
9
+ }
10
+ export declare function getConfig(): Config;
11
+ export declare function getTeamApiKey(teamName?: string): string;
12
+ export declare function listAvailableTeams(): string[];
package/dist/config.js ADDED
@@ -0,0 +1,55 @@
1
+ import { z } from "zod";
2
+ const ConfigSchema = z.object({
3
+ baseUrl: z.string().url(),
4
+ defaultTeam: z.string().min(1),
5
+ });
6
+ function loadTeams() {
7
+ const teams = new Map();
8
+ for (const [key, value] of Object.entries(process.env)) {
9
+ const match = key.match(/^COOLIFY_TEAM_(.+)_API_KEY$/);
10
+ if (match && value) {
11
+ const teamName = match[1].toLowerCase();
12
+ teams.set(teamName, { name: teamName, apiKey: value });
13
+ }
14
+ }
15
+ if (process.env.COOLIFY_API_KEY) {
16
+ teams.set("default", { name: "default", apiKey: process.env.COOLIFY_API_KEY });
17
+ }
18
+ return teams;
19
+ }
20
+ let configInstance = null;
21
+ export function getConfig() {
22
+ if (configInstance) {
23
+ return configInstance;
24
+ }
25
+ const teams = loadTeams();
26
+ if (teams.size === 0) {
27
+ throw new Error("No API keys configured. Set COOLIFY_API_KEY or COOLIFY_TEAM_<NAME>_API_KEY environment variables.");
28
+ }
29
+ const baseUrl = process.env.COOLIFY_BASE_URL || "https://cloud.softtor.com.br";
30
+ const envDefaultTeam = process.env.COOLIFY_DEFAULT_TEAM || "default";
31
+ const defaultTeam = teams.has(envDefaultTeam) ? envDefaultTeam : teams.keys().next().value;
32
+ const validatedConfig = ConfigSchema.parse({
33
+ baseUrl,
34
+ defaultTeam,
35
+ });
36
+ configInstance = {
37
+ ...validatedConfig,
38
+ teams,
39
+ };
40
+ return configInstance;
41
+ }
42
+ export function getTeamApiKey(teamName) {
43
+ const config = getConfig();
44
+ const team = teamName || config.defaultTeam;
45
+ const teamConfig = config.teams.get(team.toLowerCase());
46
+ if (!teamConfig) {
47
+ const availableTeams = Array.from(config.teams.keys()).join(", ");
48
+ throw new Error(`Team "${team}" not found. Available teams: ${availableTeams}`);
49
+ }
50
+ return teamConfig.apiKey;
51
+ }
52
+ export function listAvailableTeams() {
53
+ const config = getConfig();
54
+ return Array.from(config.teams.keys());
55
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { z } from "zod";
6
+ import { getConfig, listAvailableTeams } from "./config.js";
7
+ import { applicationTools } from "./tools/applications.js";
8
+ import { databaseTools } from "./tools/databases.js";
9
+ import { serviceTools } from "./tools/services.js";
10
+ import { serverTools } from "./tools/servers.js";
11
+ import { deploymentTools } from "./tools/deployments.js";
12
+ import { projectTools } from "./tools/projects.js";
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ const allTools = [
15
+ ...applicationTools,
16
+ ...databaseTools,
17
+ ...serviceTools,
18
+ ...serverTools,
19
+ ...deploymentTools,
20
+ ...projectTools,
21
+ ];
22
+ function zodToJsonSchema(schema) {
23
+ if (schema instanceof z.ZodObject) {
24
+ const shape = schema.shape;
25
+ const properties = {};
26
+ const required = [];
27
+ for (const [key, value] of Object.entries(shape)) {
28
+ const zodValue = value;
29
+ properties[key] = zodFieldToJsonSchema(zodValue);
30
+ if (!(zodValue instanceof z.ZodOptional)) {
31
+ required.push(key);
32
+ }
33
+ }
34
+ return {
35
+ type: "object",
36
+ properties,
37
+ required: required.length > 0 ? required : undefined,
38
+ };
39
+ }
40
+ if (schema instanceof z.ZodEffects) {
41
+ return zodToJsonSchema(schema._def.schema);
42
+ }
43
+ return { type: "object" };
44
+ }
45
+ function zodFieldToJsonSchema(field) {
46
+ if (field instanceof z.ZodString) {
47
+ return {
48
+ type: "string",
49
+ description: field.description,
50
+ };
51
+ }
52
+ if (field instanceof z.ZodNumber) {
53
+ return {
54
+ type: "number",
55
+ description: field.description,
56
+ };
57
+ }
58
+ if (field instanceof z.ZodBoolean) {
59
+ return {
60
+ type: "boolean",
61
+ description: field.description,
62
+ };
63
+ }
64
+ if (field instanceof z.ZodOptional) {
65
+ return zodFieldToJsonSchema(field._def.innerType);
66
+ }
67
+ if (field instanceof z.ZodDefault) {
68
+ return zodFieldToJsonSchema(field._def.innerType);
69
+ }
70
+ return { type: "string" };
71
+ }
72
+ const server = new Server({
73
+ name: "coolify-mcp-server",
74
+ version: "1.0.0",
75
+ }, {
76
+ capabilities: {
77
+ tools: {},
78
+ },
79
+ });
80
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
81
+ const config = getConfig();
82
+ const teams = listAvailableTeams();
83
+ const teamsInfo = `Available teams: ${teams.join(", ")}. Default: ${config.defaultTeam}`;
84
+ return {
85
+ tools: allTools.map((tool) => ({
86
+ name: tool.name,
87
+ description: `${tool.description}\n\n${teamsInfo}`,
88
+ inputSchema: zodToJsonSchema(tool.inputSchema),
89
+ })),
90
+ };
91
+ });
92
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
93
+ const { name, arguments: args } = request.params;
94
+ const tool = allTools.find((t) => t.name === name);
95
+ if (!tool) {
96
+ throw new Error(`Unknown tool: ${name}`);
97
+ }
98
+ try {
99
+ const validatedArgs = tool.inputSchema.parse(args);
100
+ const result = await tool.handler(validatedArgs);
101
+ return {
102
+ content: [{ type: "text", text: result }],
103
+ };
104
+ }
105
+ catch (error) {
106
+ const errorMessage = error instanceof Error ? error.message : String(error);
107
+ return {
108
+ content: [{ type: "text", text: `Error: ${errorMessage}` }],
109
+ isError: true,
110
+ };
111
+ }
112
+ });
113
+ async function main() {
114
+ try {
115
+ getConfig();
116
+ }
117
+ catch (error) {
118
+ console.error("Configuration error:", error instanceof Error ? error.message : error);
119
+ process.exit(1);
120
+ }
121
+ const transport = new StdioServerTransport();
122
+ await server.connect(transport);
123
+ }
124
+ main().catch((error) => {
125
+ console.error("Fatal error:", error);
126
+ process.exit(1);
127
+ });
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ export declare const TeamParamSchema: z.ZodObject<{
3
+ team: z.ZodOptional<z.ZodString>;
4
+ }, "strip", z.ZodTypeAny, {
5
+ team?: string | undefined;
6
+ }, {
7
+ team?: string | undefined;
8
+ }>;
9
+ export declare const UuidParamSchema: z.ZodObject<{
10
+ team: z.ZodOptional<z.ZodString>;
11
+ } & {
12
+ uuid: z.ZodString;
13
+ }, "strip", z.ZodTypeAny, {
14
+ uuid: string;
15
+ team?: string | undefined;
16
+ }, {
17
+ uuid: string;
18
+ team?: string | undefined;
19
+ }>;
20
+ export declare const DeployParamSchema: z.ZodObject<{
21
+ team: z.ZodOptional<z.ZodString>;
22
+ } & {
23
+ uuid: z.ZodOptional<z.ZodString>;
24
+ tag: z.ZodOptional<z.ZodString>;
25
+ force: z.ZodOptional<z.ZodBoolean>;
26
+ }, "strip", z.ZodTypeAny, {
27
+ team?: string | undefined;
28
+ uuid?: string | undefined;
29
+ tag?: string | undefined;
30
+ force?: boolean | undefined;
31
+ }, {
32
+ team?: string | undefined;
33
+ uuid?: string | undefined;
34
+ tag?: string | undefined;
35
+ force?: boolean | undefined;
36
+ }>;
37
+ export declare const LogsParamSchema: z.ZodObject<{
38
+ team: z.ZodOptional<z.ZodString>;
39
+ } & {
40
+ uuid: z.ZodString;
41
+ } & {
42
+ since: z.ZodOptional<z.ZodNumber>;
43
+ }, "strip", z.ZodTypeAny, {
44
+ uuid: string;
45
+ team?: string | undefined;
46
+ since?: number | undefined;
47
+ }, {
48
+ uuid: string;
49
+ team?: string | undefined;
50
+ since?: number | undefined;
51
+ }>;
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ export const TeamParamSchema = z.object({
3
+ team: z.string().optional().describe("Team name to use (default: configured default team)"),
4
+ });
5
+ export const UuidParamSchema = TeamParamSchema.extend({
6
+ uuid: z.string().describe("Resource UUID"),
7
+ });
8
+ export const DeployParamSchema = TeamParamSchema.extend({
9
+ uuid: z.string().optional().describe("Application UUID to deploy"),
10
+ tag: z.string().optional().describe("Tag to deploy (deploys all resources with this tag)"),
11
+ force: z.boolean().optional().describe("Force rebuild without cache"),
12
+ });
13
+ export const LogsParamSchema = UuidParamSchema.extend({
14
+ since: z.number().optional().describe("Get logs since N seconds ago (default: 3600)"),
15
+ });
@@ -0,0 +1,12 @@
1
+ type HttpMethod = "GET" | "POST" | "PATCH" | "DELETE";
2
+ export interface RequestOptions {
3
+ team?: string;
4
+ timeout?: number;
5
+ params?: Record<string, string | number | boolean | undefined>;
6
+ }
7
+ export declare function coolifyRequest<T>(endpoint: string, method?: HttpMethod, data?: unknown, options?: RequestOptions): Promise<T>;
8
+ export declare function coolifyGet<T>(endpoint: string, options?: RequestOptions): Promise<T>;
9
+ export declare function coolifyPost<T>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T>;
10
+ export declare function coolifyPatch<T>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T>;
11
+ export declare function coolifyDelete<T>(endpoint: string, options?: RequestOptions): Promise<T>;
12
+ export {};
@@ -0,0 +1,48 @@
1
+ import axios from "axios";
2
+ import { getConfig, getTeamApiKey } from "../config.js";
3
+ import { handleApiError, formatErrorResponse } from "./error-handler.js";
4
+ export async function coolifyRequest(endpoint, method = "GET", data, options = {}) {
5
+ const config = getConfig();
6
+ const apiKey = getTeamApiKey(options.team);
7
+ const axiosConfig = {
8
+ method,
9
+ url: `${config.baseUrl}/api/v1${endpoint}`,
10
+ timeout: options.timeout ?? 30000,
11
+ headers: {
12
+ "Content-Type": "application/json",
13
+ Authorization: `Bearer ${apiKey}`,
14
+ },
15
+ };
16
+ if (data) {
17
+ axiosConfig.data = data;
18
+ }
19
+ if (options.params) {
20
+ const filteredParams = {};
21
+ for (const [key, value] of Object.entries(options.params)) {
22
+ if (value !== undefined) {
23
+ filteredParams[key] = value;
24
+ }
25
+ }
26
+ axiosConfig.params = filteredParams;
27
+ }
28
+ try {
29
+ const response = await axios(axiosConfig);
30
+ return response.data;
31
+ }
32
+ catch (error) {
33
+ const apiError = handleApiError(error);
34
+ throw new Error(formatErrorResponse(apiError));
35
+ }
36
+ }
37
+ export async function coolifyGet(endpoint, options = {}) {
38
+ return coolifyRequest(endpoint, "GET", undefined, options);
39
+ }
40
+ export async function coolifyPost(endpoint, data, options = {}) {
41
+ return coolifyRequest(endpoint, "POST", data, options);
42
+ }
43
+ export async function coolifyPatch(endpoint, data, options = {}) {
44
+ return coolifyRequest(endpoint, "PATCH", data, options);
45
+ }
46
+ export async function coolifyDelete(endpoint, options = {}) {
47
+ return coolifyRequest(endpoint, "DELETE", undefined, options);
48
+ }
@@ -0,0 +1,7 @@
1
+ export interface CoolifyError {
2
+ message: string;
3
+ status?: number;
4
+ details?: unknown;
5
+ }
6
+ export declare function handleApiError(error: unknown): CoolifyError;
7
+ export declare function formatErrorResponse(error: CoolifyError): string;
@@ -0,0 +1,59 @@
1
+ import { AxiosError } from "axios";
2
+ export function handleApiError(error) {
3
+ if (error instanceof AxiosError) {
4
+ const status = error.response?.status;
5
+ const data = error.response?.data;
6
+ if (status === 401) {
7
+ return {
8
+ message: "Authentication failed. Check your API key.",
9
+ status,
10
+ details: data,
11
+ };
12
+ }
13
+ if (status === 403) {
14
+ return {
15
+ message: "Access denied. Your API key may not have permission for this operation.",
16
+ status,
17
+ details: data,
18
+ };
19
+ }
20
+ if (status === 404) {
21
+ return {
22
+ message: "Resource not found.",
23
+ status,
24
+ details: data,
25
+ };
26
+ }
27
+ if (status === 422) {
28
+ return {
29
+ message: "Validation error.",
30
+ status,
31
+ details: data,
32
+ };
33
+ }
34
+ return {
35
+ message: data?.message || error.message || "Unknown API error",
36
+ status,
37
+ details: data,
38
+ };
39
+ }
40
+ if (error instanceof Error) {
41
+ return {
42
+ message: error.message,
43
+ };
44
+ }
45
+ return {
46
+ message: "Unknown error occurred",
47
+ details: error,
48
+ };
49
+ }
50
+ export function formatErrorResponse(error) {
51
+ let response = `Error: ${error.message}`;
52
+ if (error.status) {
53
+ response += ` (HTTP ${error.status})`;
54
+ }
55
+ if (error.details) {
56
+ response += `\nDetails: ${JSON.stringify(error.details, null, 2)}`;
57
+ }
58
+ return response;
59
+ }