@studiometa/forge-mcp 0.1.0 → 0.2.1
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/README.md +72 -22
- package/dist/flags-LFbdErsZ.js +23 -0
- package/dist/flags-LFbdErsZ.js.map +1 -0
- package/dist/flags.d.ts +19 -0
- package/dist/flags.d.ts.map +1 -0
- package/dist/formatters.d.ts +11 -3
- package/dist/formatters.d.ts.map +1 -1
- package/dist/handlers/index.d.ts +5 -3
- package/dist/handlers/index.d.ts.map +1 -1
- package/dist/handlers/types.d.ts +5 -0
- package/dist/handlers/types.d.ts.map +1 -1
- package/dist/handlers/utils.d.ts +6 -3
- package/dist/handlers/utils.d.ts.map +1 -1
- package/dist/{http-BJUKoZdb.js → http-w0DliUHY.js} +33 -9
- package/dist/http-w0DliUHY.js.map +1 -0
- package/dist/http.d.ts +11 -3
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +2 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +90 -32
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +10 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +15 -6
- package/dist/server.js.map +1 -1
- package/dist/stdio.d.ts +17 -2
- package/dist/stdio.d.ts.map +1 -1
- package/dist/tools.d.ts +36 -11
- package/dist/tools.d.ts.map +1 -1
- package/dist/{version-Cw8OGt4r.js → version-BmEJceWJ.js} +350 -130
- package/dist/version-BmEJceWJ.js.map +1 -0
- package/package.json +1 -1
- package/skills/SKILL.md +68 -38
- package/dist/http-BJUKoZdb.js.map +0 -1
- package/dist/version-Cw8OGt4r.js.map +0 -1
|
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { HttpClient, isForgeApiError } from "@studiometa/forge-api";
|
|
5
|
-
import {
|
|
5
|
+
import { RESOURCES, activateCertificate, createAuditLogger, createBackupConfig, createCertificate, createCommand, createDaemon, createDatabase, createDatabaseUser, createFirewallRule, createMonitor, createNginxTemplate, createRecipe, createRedirectRule, createScheduledJob, createSecurityRule, createServer, createSite, createSshKey, deleteBackupConfig, deleteCertificate, deleteDaemon, deleteDatabase, deleteDatabaseUser, deleteFirewallRule, deleteMonitor, deleteNginxTemplate, deleteRecipe, deleteRedirectRule, deleteScheduledJob, deleteSecurityRule, deleteServer, deleteSite, deleteSshKey, deploySiteAndWait, getBackupConfig, getCertificate, getCommand, getDaemon, getDatabase, getDatabaseUser, getDeploymentOutput, getDeploymentScript, getEnv, getFirewallRule, getMonitor, getNginxConfig, getNginxTemplate, getRecipe, getRedirectRule, getScheduledJob, getSecurityRule, getServer, getSite, getSshKey, getUser, listBackupConfigs, listCertificates, listCommands, listDaemons, listDatabaseUsers, listDatabases, listDeployments, listFirewallRules, listMonitors, listNginxTemplates, listRecipes, listRedirectRules, listScheduledJobs, listSecurityRules, listServers, listSites, listSshKeys, rebootServer, restartDaemon, runRecipe, updateDeploymentScript, updateEnv, updateNginxConfig, updateNginxTemplate } from "@studiometa/forge-core";
|
|
6
6
|
/**
|
|
7
7
|
* MCP Server Instructions
|
|
8
8
|
*
|
|
@@ -114,10 +114,18 @@ function formatDeploymentList(deployments) {
|
|
|
114
114
|
return `${deployments.length} deployment(s):\n${lines.join("\n")}`;
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
|
-
* Format a deployment action result
|
|
117
|
+
* Format a deployment action result.
|
|
118
|
+
*
|
|
119
|
+
* When a `DeployResult` is provided the output includes status, elapsed time,
|
|
120
|
+
* and the deployment log. When called with just IDs (legacy) it falls back to
|
|
121
|
+
* the simple confirmation message so existing tests keep passing.
|
|
118
122
|
*/
|
|
119
|
-
function formatDeployAction(siteId, serverId) {
|
|
120
|
-
return `Deployment triggered for site ${siteId} on server ${serverId}.`;
|
|
123
|
+
function formatDeployAction(siteId, serverId, result) {
|
|
124
|
+
if (!result) return `Deployment triggered for site ${siteId} on server ${serverId}.`;
|
|
125
|
+
const elapsedSec = (result.elapsed_ms / 1e3).toFixed(1);
|
|
126
|
+
const lines = [`Deployment ${result.status === "success" ? "✓ succeeded" : "✗ failed"} for site ${siteId} on server ${serverId} (${elapsedSec}s).`];
|
|
127
|
+
if (result.log) lines.push("", "Deployment log:", result.log);
|
|
128
|
+
return lines.join("\n");
|
|
121
129
|
}
|
|
122
130
|
/**
|
|
123
131
|
* Format deployment script content.
|
|
@@ -384,14 +392,23 @@ function isUserInputError(error) {
|
|
|
384
392
|
return error instanceof UserInputError;
|
|
385
393
|
}
|
|
386
394
|
/**
|
|
387
|
-
* Create a successful
|
|
388
|
-
*
|
|
395
|
+
* Create a successful result with both human-readable text and structured content.
|
|
396
|
+
*
|
|
397
|
+
* - When `data` is a string, `structuredContent` wraps it as `{ result: data }`.
|
|
398
|
+
* - When `data` is an object/array, `structuredContent` is `{ result: data }` and
|
|
399
|
+
* the text representation is the JSON-serialized form.
|
|
389
400
|
*/
|
|
390
401
|
function jsonResult(data) {
|
|
391
|
-
return {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
402
|
+
return {
|
|
403
|
+
content: [{
|
|
404
|
+
type: "text",
|
|
405
|
+
text: typeof data === "string" ? data : JSON.stringify(data, null, 2)
|
|
406
|
+
}],
|
|
407
|
+
structuredContent: {
|
|
408
|
+
success: true,
|
|
409
|
+
result: data
|
|
410
|
+
}
|
|
411
|
+
};
|
|
395
412
|
}
|
|
396
413
|
/**
|
|
397
414
|
* Validate an ID-like value (must be alphanumeric/dashes only).
|
|
@@ -403,7 +420,7 @@ function sanitizeId(value) {
|
|
|
403
420
|
return /^[\w-]+$/.test(value);
|
|
404
421
|
}
|
|
405
422
|
/**
|
|
406
|
-
* Create an error result.
|
|
423
|
+
* Create an error result with structured error content.
|
|
407
424
|
*/
|
|
408
425
|
function errorResult(message) {
|
|
409
426
|
return {
|
|
@@ -411,6 +428,10 @@ function errorResult(message) {
|
|
|
411
428
|
type: "text",
|
|
412
429
|
text: `Error: ${message}`
|
|
413
430
|
}],
|
|
431
|
+
structuredContent: {
|
|
432
|
+
success: false,
|
|
433
|
+
error: message
|
|
434
|
+
},
|
|
414
435
|
isError: true
|
|
415
436
|
};
|
|
416
437
|
}
|
|
@@ -1099,9 +1120,10 @@ async function handleDeployments(action, args, ctx) {
|
|
|
1099
1120
|
if (ctx.compact) return jsonResult(formatDeploymentList(result.data));
|
|
1100
1121
|
return jsonResult(result.data);
|
|
1101
1122
|
}
|
|
1102
|
-
case "deploy":
|
|
1103
|
-
await
|
|
1104
|
-
return jsonResult(formatDeployAction(args.site_id, args.server_id));
|
|
1123
|
+
case "deploy": {
|
|
1124
|
+
const deployResult = await deploySiteAndWait(opts, ctx.executorContext);
|
|
1125
|
+
return jsonResult(formatDeployAction(args.site_id, args.server_id, deployResult.data));
|
|
1126
|
+
}
|
|
1105
1127
|
case "get":
|
|
1106
1128
|
if (args.id) {
|
|
1107
1129
|
const result = await getDeploymentOutput({
|
|
@@ -3017,93 +3039,147 @@ const handleUser = createResourceHandler({
|
|
|
3017
3039
|
return formatUser(data);
|
|
3018
3040
|
}
|
|
3019
3041
|
});
|
|
3020
|
-
/** Valid resources derived from core constants */
|
|
3021
|
-
var VALID_RESOURCES = [...RESOURCES];
|
|
3022
3042
|
/**
|
|
3023
|
-
*
|
|
3043
|
+
* Read-only actions — safe operations that don't modify server state.
|
|
3024
3044
|
*/
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
case "nginx": return handleNginxConfig(action, args, ctx);
|
|
3032
|
-
case "certificates": return handleCertificates(action, args, ctx);
|
|
3033
|
-
case "databases": return handleDatabases(action, args, ctx);
|
|
3034
|
-
case "database-users": return handleDatabaseUsers(action, args, ctx);
|
|
3035
|
-
case "daemons": return handleDaemons(action, args, ctx);
|
|
3036
|
-
case "firewall-rules": return handleFirewallRules(action, args, ctx);
|
|
3037
|
-
case "ssh-keys": return handleSshKeys(action, args, ctx);
|
|
3038
|
-
case "security-rules": return handleSecurityRules(action, args, ctx);
|
|
3039
|
-
case "redirect-rules": return handleRedirectRules(action, args, ctx);
|
|
3040
|
-
case "monitors": return handleMonitors(action, args, ctx);
|
|
3041
|
-
case "nginx-templates": return handleNginxTemplates(action, args, ctx);
|
|
3042
|
-
case "recipes": return handleRecipes(action, args, ctx);
|
|
3043
|
-
case "backups": return handleBackups(action, args, ctx);
|
|
3044
|
-
case "commands": return handleCommands(action, args, ctx);
|
|
3045
|
-
case "scheduled-jobs": return handleScheduledJobs(action, args, ctx);
|
|
3046
|
-
case "user": return handleUser(action, args, ctx);
|
|
3047
|
-
default: return Promise.resolve(errorResult(`Unknown resource "${resource}". Valid resources: ${VALID_RESOURCES.join(", ")}. Use action="help" for documentation.`));
|
|
3048
|
-
}
|
|
3049
|
-
}
|
|
3045
|
+
const READ_ACTIONS = [
|
|
3046
|
+
"list",
|
|
3047
|
+
"get",
|
|
3048
|
+
"help",
|
|
3049
|
+
"schema"
|
|
3050
|
+
];
|
|
3050
3051
|
/**
|
|
3051
|
-
*
|
|
3052
|
-
*
|
|
3053
|
-
* This is the main entry point shared between stdio and HTTP transports.
|
|
3052
|
+
* Write actions — operations that modify server state.
|
|
3053
|
+
* These are separated into the `forge_write` tool for safety.
|
|
3054
3054
|
*/
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
try {
|
|
3071
|
-
return await routeToHandler(resource, action, {
|
|
3072
|
-
resource,
|
|
3073
|
-
action,
|
|
3074
|
-
...rest
|
|
3075
|
-
}, handlerContext);
|
|
3076
|
-
} catch (error) {
|
|
3077
|
-
if (isUserInputError(error)) return errorResult(error.toFormattedMessage());
|
|
3078
|
-
if (isForgeApiError(error)) return errorResult(`Forge API error (${error.status}): ${error.message}`);
|
|
3079
|
-
/* v8 ignore stop */
|
|
3080
|
-
return errorResult(error instanceof Error ? error.message : String(error));
|
|
3081
|
-
}
|
|
3055
|
+
const WRITE_ACTIONS = [
|
|
3056
|
+
"create",
|
|
3057
|
+
"update",
|
|
3058
|
+
"delete",
|
|
3059
|
+
"deploy",
|
|
3060
|
+
"reboot",
|
|
3061
|
+
"restart",
|
|
3062
|
+
"activate",
|
|
3063
|
+
"run"
|
|
3064
|
+
];
|
|
3065
|
+
/**
|
|
3066
|
+
* Check if an action is a write action.
|
|
3067
|
+
*/
|
|
3068
|
+
function isWriteAction(action) {
|
|
3069
|
+
return WRITE_ACTIONS.includes(action);
|
|
3082
3070
|
}
|
|
3083
3071
|
/**
|
|
3084
|
-
*
|
|
3072
|
+
* Check if an action is a read action.
|
|
3085
3073
|
*/
|
|
3086
|
-
function
|
|
3087
|
-
return
|
|
3088
|
-
"Laravel Forge API.",
|
|
3089
|
-
`Resources: ${RESOURCES.join(", ")}.`,
|
|
3090
|
-
`Actions: ${ACTIONS.join(", ")} (varies by resource).`,
|
|
3091
|
-
"Discovery: action=help with any resource for filters and examples.",
|
|
3092
|
-
"Server operations require id. Site operations require server_id.",
|
|
3093
|
-
"Deployment operations require server_id and site_id."
|
|
3094
|
-
].join("\n");
|
|
3074
|
+
function isReadAction(action) {
|
|
3075
|
+
return READ_ACTIONS.includes(action);
|
|
3095
3076
|
}
|
|
3096
3077
|
/**
|
|
3097
|
-
*
|
|
3078
|
+
* Output schema shared by all forge tools.
|
|
3079
|
+
*
|
|
3080
|
+
* All tools return a consistent envelope:
|
|
3081
|
+
* - success: true/false
|
|
3082
|
+
* - result: the resource data (shape varies by resource and action)
|
|
3083
|
+
* - error: error message (only when success is false)
|
|
3084
|
+
*
|
|
3085
|
+
* The `result` field contains resource-specific data:
|
|
3086
|
+
* - list actions → array of resource objects
|
|
3087
|
+
* - get actions → single resource object (or string for env/nginx/scripts)
|
|
3088
|
+
* - help/schema → documentation text or schema object
|
|
3089
|
+
* - write actions → confirmation message or updated resource
|
|
3090
|
+
*/
|
|
3091
|
+
var OUTPUT_SCHEMA = {
|
|
3092
|
+
type: "object",
|
|
3093
|
+
properties: {
|
|
3094
|
+
success: {
|
|
3095
|
+
type: "boolean",
|
|
3096
|
+
description: "Whether the operation succeeded"
|
|
3097
|
+
},
|
|
3098
|
+
result: { description: "Operation result — shape varies by resource and action (array for list, object for get, string for text content)" },
|
|
3099
|
+
error: {
|
|
3100
|
+
type: "string",
|
|
3101
|
+
description: "Error message (only present on failure)"
|
|
3102
|
+
}
|
|
3103
|
+
},
|
|
3104
|
+
required: ["success"]
|
|
3105
|
+
};
|
|
3106
|
+
/**
|
|
3107
|
+
* Shared input schema properties used by both forge and forge_write tools.
|
|
3108
|
+
*/
|
|
3109
|
+
var SHARED_INPUT_PROPERTIES = {
|
|
3110
|
+
resource: {
|
|
3111
|
+
type: "string",
|
|
3112
|
+
enum: [...RESOURCES],
|
|
3113
|
+
description: "Forge resource to operate on"
|
|
3114
|
+
},
|
|
3115
|
+
id: {
|
|
3116
|
+
type: "string",
|
|
3117
|
+
description: "Resource ID (for get, delete, update actions)"
|
|
3118
|
+
},
|
|
3119
|
+
server_id: {
|
|
3120
|
+
type: "string",
|
|
3121
|
+
description: "Server ID (required for most resources)"
|
|
3122
|
+
},
|
|
3123
|
+
site_id: {
|
|
3124
|
+
type: "string",
|
|
3125
|
+
description: "Site ID (required for site-level resources: deployments, env, certificates, etc.)"
|
|
3126
|
+
},
|
|
3127
|
+
compact: {
|
|
3128
|
+
type: "boolean",
|
|
3129
|
+
description: "Compact output (default: true for list, false for get)"
|
|
3130
|
+
}
|
|
3131
|
+
};
|
|
3132
|
+
/**
|
|
3133
|
+
* Core tools available in both stdio and HTTP transports.
|
|
3098
3134
|
*
|
|
3099
|
-
*
|
|
3100
|
-
*
|
|
3135
|
+
* Split into two tools for safety:
|
|
3136
|
+
* - `forge` — read-only operations (auto-approvable by MCP clients)
|
|
3137
|
+
* - `forge_write` — write operations (always requires confirmation)
|
|
3101
3138
|
*/
|
|
3102
3139
|
const TOOLS = [{
|
|
3103
3140
|
name: "forge",
|
|
3104
|
-
|
|
3141
|
+
title: "Laravel Forge",
|
|
3142
|
+
description: [
|
|
3143
|
+
"Laravel Forge API — read operations.",
|
|
3144
|
+
`Resources: ${RESOURCES.join(", ")}.`,
|
|
3145
|
+
`Actions: ${[...READ_ACTIONS].join(", ")}.`,
|
|
3146
|
+
"Discovery: action=help with any resource for filters and examples.",
|
|
3147
|
+
"Server operations require id. Site operations require server_id.",
|
|
3148
|
+
"Deployment operations require server_id and site_id."
|
|
3149
|
+
].join("\n"),
|
|
3105
3150
|
annotations: {
|
|
3106
3151
|
title: "Laravel Forge",
|
|
3152
|
+
readOnlyHint: true,
|
|
3153
|
+
destructiveHint: false,
|
|
3154
|
+
idempotentHint: true,
|
|
3155
|
+
openWorldHint: true
|
|
3156
|
+
},
|
|
3157
|
+
inputSchema: {
|
|
3158
|
+
type: "object",
|
|
3159
|
+
properties: {
|
|
3160
|
+
...SHARED_INPUT_PROPERTIES,
|
|
3161
|
+
action: {
|
|
3162
|
+
type: "string",
|
|
3163
|
+
enum: [...READ_ACTIONS],
|
|
3164
|
+
description: "Read action to perform. Use \"help\" for resource documentation."
|
|
3165
|
+
}
|
|
3166
|
+
},
|
|
3167
|
+
required: ["resource", "action"]
|
|
3168
|
+
},
|
|
3169
|
+
outputSchema: OUTPUT_SCHEMA
|
|
3170
|
+
}, {
|
|
3171
|
+
name: "forge_write",
|
|
3172
|
+
title: "Laravel Forge (Write)",
|
|
3173
|
+
description: [
|
|
3174
|
+
"Laravel Forge API — write operations (create, update, delete, deploy, reboot, etc.).",
|
|
3175
|
+
`Resources: ${RESOURCES.join(", ")}.`,
|
|
3176
|
+
`Actions: ${[...WRITE_ACTIONS].join(", ")}.`,
|
|
3177
|
+
"Server operations require id. Site operations require server_id.",
|
|
3178
|
+
"Deployment operations require server_id and site_id.",
|
|
3179
|
+
"Use forge tool with action=help for resource documentation."
|
|
3180
|
+
].join("\n"),
|
|
3181
|
+
annotations: {
|
|
3182
|
+
title: "Laravel Forge (Write)",
|
|
3107
3183
|
readOnlyHint: false,
|
|
3108
3184
|
destructiveHint: true,
|
|
3109
3185
|
idempotentHint: false,
|
|
@@ -3112,108 +3188,132 @@ const TOOLS = [{
|
|
|
3112
3188
|
inputSchema: {
|
|
3113
3189
|
type: "object",
|
|
3114
3190
|
properties: {
|
|
3115
|
-
|
|
3191
|
+
...SHARED_INPUT_PROPERTIES,
|
|
3192
|
+
action: {
|
|
3116
3193
|
type: "string",
|
|
3117
|
-
enum: [...
|
|
3194
|
+
enum: [...WRITE_ACTIONS],
|
|
3195
|
+
description: "Write action to perform"
|
|
3118
3196
|
},
|
|
3119
|
-
|
|
3197
|
+
name: {
|
|
3120
3198
|
type: "string",
|
|
3121
|
-
|
|
3122
|
-
description: "Use \"help\" for resource documentation"
|
|
3199
|
+
description: "Resource name (servers, databases, daemons, etc.)"
|
|
3123
3200
|
},
|
|
3124
|
-
|
|
3201
|
+
provider: {
|
|
3125
3202
|
type: "string",
|
|
3126
|
-
description: "
|
|
3203
|
+
description: "Server provider (e.g. ocean2, linode, aws)"
|
|
3127
3204
|
},
|
|
3128
|
-
|
|
3205
|
+
region: {
|
|
3129
3206
|
type: "string",
|
|
3130
|
-
description: "Server
|
|
3207
|
+
description: "Server region (e.g. nyc3, us-east-1)"
|
|
3131
3208
|
},
|
|
3132
|
-
|
|
3209
|
+
size: {
|
|
3133
3210
|
type: "string",
|
|
3134
|
-
description: "
|
|
3211
|
+
description: "Server size (e.g. s-1vcpu-1gb)"
|
|
3212
|
+
},
|
|
3213
|
+
credential_id: {
|
|
3214
|
+
type: "string",
|
|
3215
|
+
description: "Provider credential ID for server creation"
|
|
3216
|
+
},
|
|
3217
|
+
type: {
|
|
3218
|
+
type: "string",
|
|
3219
|
+
description: "Resource type (e.g. app, web, worker for servers; mysql, postgres for databases; disk_usage, used_memory for monitors)"
|
|
3220
|
+
},
|
|
3221
|
+
domain: {
|
|
3222
|
+
type: "string",
|
|
3223
|
+
description: "Site domain name (e.g. example.com)"
|
|
3224
|
+
},
|
|
3225
|
+
project_type: {
|
|
3226
|
+
type: "string",
|
|
3227
|
+
description: "Site project type (e.g. php, html, symfony, laravel)"
|
|
3228
|
+
},
|
|
3229
|
+
directory: {
|
|
3230
|
+
type: "string",
|
|
3231
|
+
description: "Web directory relative to site root (e.g. /public)"
|
|
3135
3232
|
},
|
|
3136
|
-
compact: {
|
|
3137
|
-
type: "boolean",
|
|
3138
|
-
description: "Compact output (default: true for list, false for get)"
|
|
3139
|
-
},
|
|
3140
|
-
name: { type: "string" },
|
|
3141
|
-
provider: { type: "string" },
|
|
3142
|
-
region: { type: "string" },
|
|
3143
|
-
size: { type: "string" },
|
|
3144
|
-
credential_id: { type: "string" },
|
|
3145
|
-
type: { type: "string" },
|
|
3146
|
-
domain: { type: "string" },
|
|
3147
|
-
project_type: { type: "string" },
|
|
3148
|
-
directory: { type: "string" },
|
|
3149
3233
|
content: {
|
|
3150
3234
|
type: "string",
|
|
3151
|
-
description: "Content for env, nginx, or deployment script"
|
|
3235
|
+
description: "Content body for env variables, nginx config, or deployment script updates"
|
|
3152
3236
|
},
|
|
3153
3237
|
command: {
|
|
3154
3238
|
type: "string",
|
|
3155
|
-
description: "Shell command (daemons
|
|
3239
|
+
description: "Shell command to execute (daemons: background process command; recipes: bash script inline; commands: site command)"
|
|
3156
3240
|
},
|
|
3157
3241
|
user: {
|
|
3158
3242
|
type: "string",
|
|
3159
|
-
description: "
|
|
3243
|
+
description: "Unix user to run as (daemons, scheduled jobs; e.g. forge, root)"
|
|
3160
3244
|
},
|
|
3161
3245
|
port: {
|
|
3162
3246
|
type: ["string", "number"],
|
|
3163
|
-
description: "Port number or range (firewall rules)"
|
|
3247
|
+
description: "Port number or range (firewall rules, e.g. 80 or 8000-9000)"
|
|
3164
3248
|
},
|
|
3165
3249
|
ip_address: {
|
|
3166
3250
|
type: "string",
|
|
3167
|
-
description: "IP address (firewall rules)"
|
|
3251
|
+
description: "IP address to allow/block (firewall rules, e.g. 192.168.1.1)"
|
|
3168
3252
|
},
|
|
3169
3253
|
key: {
|
|
3170
3254
|
type: "string",
|
|
3171
|
-
description: "Public SSH key content"
|
|
3255
|
+
description: "Public SSH key content (ssh-rsa ... or ssh-ed25519 ...)"
|
|
3172
3256
|
},
|
|
3173
3257
|
from: {
|
|
3174
3258
|
type: "string",
|
|
3175
|
-
description: "Source path
|
|
3259
|
+
description: "Source path for redirect rules (e.g. /old-page)"
|
|
3176
3260
|
},
|
|
3177
3261
|
to: {
|
|
3178
3262
|
type: "string",
|
|
3179
|
-
description: "Destination URL
|
|
3263
|
+
description: "Destination URL for redirect rules (e.g. /new-page)"
|
|
3180
3264
|
},
|
|
3181
3265
|
credentials: {
|
|
3182
3266
|
type: "array",
|
|
3183
|
-
description: "
|
|
3267
|
+
description: "HTTP basic auth credentials for security rules [{username, password}]",
|
|
3184
3268
|
items: { type: "object" }
|
|
3185
3269
|
},
|
|
3186
3270
|
operator: {
|
|
3187
3271
|
type: "string",
|
|
3188
|
-
description: "Comparison operator (
|
|
3272
|
+
description: "Comparison operator for monitors (e.g. gte, lte)"
|
|
3189
3273
|
},
|
|
3190
3274
|
threshold: {
|
|
3191
3275
|
type: "number",
|
|
3192
|
-
description: "Threshold value
|
|
3276
|
+
description: "Threshold value that triggers the monitor alert"
|
|
3193
3277
|
},
|
|
3194
3278
|
minutes: {
|
|
3195
3279
|
type: "number",
|
|
3196
|
-
description: "Check interval in minutes (
|
|
3280
|
+
description: "Check interval in minutes for monitors (e.g. 5)"
|
|
3281
|
+
},
|
|
3282
|
+
frequency: {
|
|
3283
|
+
type: "string",
|
|
3284
|
+
description: "Cron frequency for scheduled jobs (e.g. minutely, hourly, nightly, custom)"
|
|
3197
3285
|
},
|
|
3198
3286
|
script: {
|
|
3199
3287
|
type: "string",
|
|
3200
|
-
description: "Bash script content
|
|
3288
|
+
description: "Bash script content for recipes"
|
|
3201
3289
|
},
|
|
3202
3290
|
servers: {
|
|
3203
3291
|
type: "array",
|
|
3204
|
-
description: "Server IDs to run recipe on",
|
|
3292
|
+
description: "Server IDs to run a recipe on (e.g. [123, 456])",
|
|
3205
3293
|
items: { type: "number" }
|
|
3206
3294
|
}
|
|
3207
3295
|
},
|
|
3208
3296
|
required: ["resource", "action"]
|
|
3209
|
-
}
|
|
3297
|
+
},
|
|
3298
|
+
outputSchema: OUTPUT_SCHEMA
|
|
3210
3299
|
}];
|
|
3211
3300
|
/**
|
|
3301
|
+
* Get the list of core tools, optionally filtered.
|
|
3302
|
+
*
|
|
3303
|
+
* In read-only mode, forge_write is excluded entirely — it won't appear
|
|
3304
|
+
* in the tool listing and cannot be called.
|
|
3305
|
+
*/
|
|
3306
|
+
function getTools(options) {
|
|
3307
|
+
if (options?.readOnly) return TOOLS.filter((t) => t.name !== "forge_write");
|
|
3308
|
+
return [...TOOLS];
|
|
3309
|
+
}
|
|
3310
|
+
/**
|
|
3212
3311
|
* Additional tools only available in stdio mode.
|
|
3213
3312
|
*/
|
|
3214
3313
|
const STDIO_ONLY_TOOLS = [{
|
|
3215
3314
|
name: "forge_configure",
|
|
3216
|
-
|
|
3315
|
+
title: "Configure Forge",
|
|
3316
|
+
description: "Configure Laravel Forge API token. The token is stored locally in the XDG config directory.",
|
|
3217
3317
|
annotations: {
|
|
3218
3318
|
title: "Configure Forge",
|
|
3219
3319
|
readOnlyHint: false,
|
|
@@ -3228,10 +3328,26 @@ const STDIO_ONLY_TOOLS = [{
|
|
|
3228
3328
|
description: "Your Laravel Forge API token"
|
|
3229
3329
|
} },
|
|
3230
3330
|
required: ["apiToken"]
|
|
3331
|
+
},
|
|
3332
|
+
outputSchema: {
|
|
3333
|
+
type: "object",
|
|
3334
|
+
properties: {
|
|
3335
|
+
success: { type: "boolean" },
|
|
3336
|
+
message: {
|
|
3337
|
+
type: "string",
|
|
3338
|
+
description: "Confirmation message"
|
|
3339
|
+
},
|
|
3340
|
+
apiToken: {
|
|
3341
|
+
type: "string",
|
|
3342
|
+
description: "Masked API token (last 4 chars)"
|
|
3343
|
+
}
|
|
3344
|
+
},
|
|
3345
|
+
required: ["success"]
|
|
3231
3346
|
}
|
|
3232
3347
|
}, {
|
|
3233
3348
|
name: "forge_get_config",
|
|
3234
|
-
|
|
3349
|
+
title: "Get Forge Config",
|
|
3350
|
+
description: "Get current Forge configuration (shows masked token and config status).",
|
|
3235
3351
|
annotations: {
|
|
3236
3352
|
title: "Get Forge Config",
|
|
3237
3353
|
readOnlyHint: true,
|
|
@@ -3242,9 +3358,113 @@ const STDIO_ONLY_TOOLS = [{
|
|
|
3242
3358
|
inputSchema: {
|
|
3243
3359
|
type: "object",
|
|
3244
3360
|
properties: {}
|
|
3361
|
+
},
|
|
3362
|
+
outputSchema: {
|
|
3363
|
+
type: "object",
|
|
3364
|
+
properties: {
|
|
3365
|
+
apiToken: {
|
|
3366
|
+
type: "string",
|
|
3367
|
+
description: "Masked API token or 'not configured'"
|
|
3368
|
+
},
|
|
3369
|
+
configured: {
|
|
3370
|
+
type: "boolean",
|
|
3371
|
+
description: "Whether a token is configured"
|
|
3372
|
+
}
|
|
3373
|
+
},
|
|
3374
|
+
required: ["configured"]
|
|
3245
3375
|
}
|
|
3246
3376
|
}];
|
|
3247
|
-
|
|
3248
|
-
|
|
3377
|
+
/** Valid resources derived from core constants */
|
|
3378
|
+
var VALID_RESOURCES = [...RESOURCES];
|
|
3379
|
+
var _auditLogger = null;
|
|
3380
|
+
function getAuditLogger() {
|
|
3381
|
+
if (!_auditLogger) _auditLogger = createAuditLogger("mcp");
|
|
3382
|
+
return _auditLogger;
|
|
3383
|
+
}
|
|
3384
|
+
/**
|
|
3385
|
+
* Route to the appropriate resource handler.
|
|
3386
|
+
*/
|
|
3387
|
+
function routeToHandler(resource, action, args, ctx) {
|
|
3388
|
+
switch (resource) {
|
|
3389
|
+
case "servers": return handleServers(action, args, ctx);
|
|
3390
|
+
case "sites": return handleSites(action, args, ctx);
|
|
3391
|
+
case "deployments": return handleDeployments(action, args, ctx);
|
|
3392
|
+
case "env": return handleEnv(action, args, ctx);
|
|
3393
|
+
case "nginx": return handleNginxConfig(action, args, ctx);
|
|
3394
|
+
case "certificates": return handleCertificates(action, args, ctx);
|
|
3395
|
+
case "databases": return handleDatabases(action, args, ctx);
|
|
3396
|
+
case "database-users": return handleDatabaseUsers(action, args, ctx);
|
|
3397
|
+
case "daemons": return handleDaemons(action, args, ctx);
|
|
3398
|
+
case "firewall-rules": return handleFirewallRules(action, args, ctx);
|
|
3399
|
+
case "ssh-keys": return handleSshKeys(action, args, ctx);
|
|
3400
|
+
case "security-rules": return handleSecurityRules(action, args, ctx);
|
|
3401
|
+
case "redirect-rules": return handleRedirectRules(action, args, ctx);
|
|
3402
|
+
case "monitors": return handleMonitors(action, args, ctx);
|
|
3403
|
+
case "nginx-templates": return handleNginxTemplates(action, args, ctx);
|
|
3404
|
+
case "recipes": return handleRecipes(action, args, ctx);
|
|
3405
|
+
case "backups": return handleBackups(action, args, ctx);
|
|
3406
|
+
case "commands": return handleCommands(action, args, ctx);
|
|
3407
|
+
case "scheduled-jobs": return handleScheduledJobs(action, args, ctx);
|
|
3408
|
+
case "user": return handleUser(action, args, ctx);
|
|
3409
|
+
default: return Promise.resolve(errorResult(`Unknown resource "${resource}". Valid resources: ${VALID_RESOURCES.join(", ")}. Use action="help" for documentation.`));
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
/**
|
|
3413
|
+
* Execute a tool call with provided credentials.
|
|
3414
|
+
*
|
|
3415
|
+
* This is the main entry point shared between stdio and HTTP transports.
|
|
3416
|
+
* Validates that the action matches the tool (read vs write).
|
|
3417
|
+
*/
|
|
3418
|
+
async function executeToolWithCredentials(name, args, credentials) {
|
|
3419
|
+
const { resource, action, compact, ...rest } = args;
|
|
3420
|
+
if (!resource || !action) return errorResult("Missing required fields: \"resource\" and \"action\".");
|
|
3421
|
+
if (name === "forge" && isWriteAction(action)) return errorResult(`Action "${action}" is a write operation. Use the "forge_write" tool instead.`);
|
|
3422
|
+
if (name === "forge_write" && isReadAction(action)) return errorResult(`Action "${action}" is a read operation. Use the "forge" tool instead.`);
|
|
3423
|
+
if (action === "help")
|
|
3424
|
+
/* v8 ignore start */
|
|
3425
|
+
return resource ? handleHelp(resource) : handleHelpOverview();
|
|
3426
|
+
if (action === "schema")
|
|
3427
|
+
/* v8 ignore start */
|
|
3428
|
+
return resource ? handleSchema(resource) : handleSchemaOverview();
|
|
3429
|
+
if (!VALID_RESOURCES.includes(resource)) return errorResult(`Unknown resource "${resource}". Valid resources: ${VALID_RESOURCES.join(", ")}. Use action="help" for documentation.`);
|
|
3430
|
+
const handlerContext = {
|
|
3431
|
+
executorContext: { client: new HttpClient({ token: credentials.apiToken }) },
|
|
3432
|
+
compact: compact ?? action !== "get",
|
|
3433
|
+
includeHints: action === "get"
|
|
3434
|
+
};
|
|
3435
|
+
try {
|
|
3436
|
+
const result = await routeToHandler(resource, action, {
|
|
3437
|
+
resource,
|
|
3438
|
+
action,
|
|
3439
|
+
...rest
|
|
3440
|
+
}, handlerContext);
|
|
3441
|
+
if (name === "forge_write") getAuditLogger().log({
|
|
3442
|
+
source: "mcp",
|
|
3443
|
+
resource,
|
|
3444
|
+
action,
|
|
3445
|
+
args: rest,
|
|
3446
|
+
status: result.isError ? "error" : "success"
|
|
3447
|
+
});
|
|
3448
|
+
return result;
|
|
3449
|
+
} catch (error) {
|
|
3450
|
+
let errorMessage;
|
|
3451
|
+
if (isUserInputError(error)) errorMessage = error.toFormattedMessage();
|
|
3452
|
+
else if (isForgeApiError(error)) errorMessage = `Forge API error (${error.status}): ${error.message}`;
|
|
3453
|
+
else
|
|
3454
|
+
/* v8 ignore start */
|
|
3455
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
3456
|
+
if (name === "forge_write") getAuditLogger().log({
|
|
3457
|
+
source: "mcp",
|
|
3458
|
+
resource,
|
|
3459
|
+
action,
|
|
3460
|
+
args: rest,
|
|
3461
|
+
status: "error",
|
|
3462
|
+
error: errorMessage
|
|
3463
|
+
});
|
|
3464
|
+
return errorResult(errorMessage);
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
const VERSION = "0.2.1";
|
|
3468
|
+
export { INSTRUCTIONS as a, getTools as i, executeToolWithCredentials as n, STDIO_ONLY_TOOLS as r, VERSION as t };
|
|
3249
3469
|
|
|
3250
|
-
//# sourceMappingURL=version-
|
|
3470
|
+
//# sourceMappingURL=version-BmEJceWJ.js.map
|