@moor-sh/mcp 0.26.0 → 0.27.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/package.json +2 -2
- package/src/index.ts +204 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moor-sh/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.1",
|
|
4
4
|
"description": "MCP server for moor - lets AI agents (Claude Code, Cursor, etc.) manage your moor projects via the moor HTTP API.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@cfworker/json-schema": "^4.1.1",
|
|
36
|
-
"@modelcontextprotocol/server": "
|
|
36
|
+
"@modelcontextprotocol/server": "2.0.0-alpha.2",
|
|
37
37
|
"zod": "^4.3.6"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/src/index.ts
CHANGED
|
@@ -1283,6 +1283,20 @@ server.registerTool(
|
|
|
1283
1283
|
.describe(
|
|
1284
1284
|
"For github_url projects: pin the source credential row (from moor_source_credential_add) the build path should use. Build synthesizes the credentialed clone URL in memory; the secret is never stored on the project. Ignored when docker_image is set; save-time validation is structural only (id exists).",
|
|
1285
1285
|
),
|
|
1286
|
+
command: z
|
|
1287
|
+
.array(z.string())
|
|
1288
|
+
.nullable()
|
|
1289
|
+
.optional()
|
|
1290
|
+
.describe(
|
|
1291
|
+
'Override the image\'s default command (Docker Cmd) as an argv array, e.g. ["tunnel","run"]. Lets a stock image run a custom command with no throwaway Dockerfile. Omit to keep the image default; pass [] or null to clear a previously-set override. Applies on container recreate.',
|
|
1292
|
+
),
|
|
1293
|
+
entrypoint: z
|
|
1294
|
+
.array(z.string())
|
|
1295
|
+
.nullable()
|
|
1296
|
+
.optional()
|
|
1297
|
+
.describe(
|
|
1298
|
+
"Override the image's ENTRYPOINT as an argv array. Omit to keep the image default; pass [] or null to clear. Applies on container recreate.",
|
|
1299
|
+
),
|
|
1286
1300
|
}),
|
|
1287
1301
|
},
|
|
1288
1302
|
async (input) => {
|
|
@@ -1372,6 +1386,20 @@ server.registerTool(
|
|
|
1372
1386
|
.describe(
|
|
1373
1387
|
"Pin (or unlink, by passing null) the source credential the build path should use for this github_url project. Switching to docker_image force-clears the id regardless of input. Save-time validation is structural only; host-mismatch / not-active is enforced at build time.",
|
|
1374
1388
|
),
|
|
1389
|
+
command: z
|
|
1390
|
+
.array(z.string())
|
|
1391
|
+
.nullable()
|
|
1392
|
+
.optional()
|
|
1393
|
+
.describe(
|
|
1394
|
+
'Override the image\'s default command (Docker Cmd) as an argv array, e.g. ["tunnel","run"]. Pass [] or null to clear the override and return to the image default. Takes effect on container recreate.',
|
|
1395
|
+
),
|
|
1396
|
+
entrypoint: z
|
|
1397
|
+
.array(z.string())
|
|
1398
|
+
.nullable()
|
|
1399
|
+
.optional()
|
|
1400
|
+
.describe(
|
|
1401
|
+
"Override the image's ENTRYPOINT as an argv array. Pass [] or null to clear. Takes effect on container recreate.",
|
|
1402
|
+
),
|
|
1375
1403
|
}),
|
|
1376
1404
|
},
|
|
1377
1405
|
async (input) => {
|
|
@@ -1729,6 +1757,124 @@ server.registerTool(
|
|
|
1729
1757
|
},
|
|
1730
1758
|
);
|
|
1731
1759
|
|
|
1760
|
+
// --- Declarative file injection ---
|
|
1761
|
+
|
|
1762
|
+
server.registerTool(
|
|
1763
|
+
"moor_file_set",
|
|
1764
|
+
{
|
|
1765
|
+
title: "Set Project File",
|
|
1766
|
+
description:
|
|
1767
|
+
"Declare a file to inject into a project's container. moor writes it via a tar archive PUT right before the container starts, on every recreate, honoring the octal mode (e.g. 0600 for a TLS key). Identified by path — setting the same path again updates its content/mode rather than duplicating. Provide exactly one of content (inline) or env_ref (the name of a project env var to source content from at create time, so a secret stays in the env store instead of plaintext here). Takes effect on next container recreate (moor_rebuild / moor_restart / moor_deploy / moor_project run).",
|
|
1768
|
+
inputSchema: z.object({
|
|
1769
|
+
project: z.string().describe("Project name or ID"),
|
|
1770
|
+
path: z
|
|
1771
|
+
.string()
|
|
1772
|
+
.min(1)
|
|
1773
|
+
.describe("Absolute in-container destination path, e.g. /etc/ssl/cert.pem"),
|
|
1774
|
+
content: z
|
|
1775
|
+
.string()
|
|
1776
|
+
.optional()
|
|
1777
|
+
.describe("Inline file contents. Provide exactly one of content or env_ref."),
|
|
1778
|
+
env_ref: z
|
|
1779
|
+
.string()
|
|
1780
|
+
.optional()
|
|
1781
|
+
.describe(
|
|
1782
|
+
"Name of a project env var to source the contents from at create time. Keeps secrets (keys, certs) in the env store instead of plaintext here. Provide exactly one of content or env_ref.",
|
|
1783
|
+
),
|
|
1784
|
+
mode: z
|
|
1785
|
+
.string()
|
|
1786
|
+
.optional()
|
|
1787
|
+
.describe(
|
|
1788
|
+
"Octal permission string applied in the tar header, e.g. '0600'. Default '0644'.",
|
|
1789
|
+
),
|
|
1790
|
+
}),
|
|
1791
|
+
},
|
|
1792
|
+
async ({ project, path, content, env_ref, mode }) => {
|
|
1793
|
+
const p = await resolveProject(project);
|
|
1794
|
+
const body: Record<string, unknown> = { path };
|
|
1795
|
+
if (content !== undefined) body.content = content;
|
|
1796
|
+
if (env_ref !== undefined) body.env_ref = env_ref;
|
|
1797
|
+
if (mode !== undefined) body.mode = mode;
|
|
1798
|
+
const res = await apiPost(`/api/projects/${p.id}/files`, body);
|
|
1799
|
+
if (!res.ok) throw new Error(`Failed: ${await res.text()}`);
|
|
1800
|
+
const saved = (await res.json()) as {
|
|
1801
|
+
id: number;
|
|
1802
|
+
path: string;
|
|
1803
|
+
mode: string;
|
|
1804
|
+
source: string;
|
|
1805
|
+
env_ref: string | null;
|
|
1806
|
+
};
|
|
1807
|
+
const verb = res.status === 201 ? "Added" : "Updated";
|
|
1808
|
+
return {
|
|
1809
|
+
content: [
|
|
1810
|
+
{
|
|
1811
|
+
type: "text",
|
|
1812
|
+
text: `${verb} file on ${p.name}: id=${saved.id}, path=${saved.path}, mode=${saved.mode}, source=${saved.source}${saved.env_ref ? ` (env_ref=${saved.env_ref})` : ""}. Written into the container on next recreate.`,
|
|
1813
|
+
},
|
|
1814
|
+
],
|
|
1815
|
+
};
|
|
1816
|
+
},
|
|
1817
|
+
);
|
|
1818
|
+
|
|
1819
|
+
server.registerTool(
|
|
1820
|
+
"moor_file_list",
|
|
1821
|
+
{
|
|
1822
|
+
title: "List Project Files",
|
|
1823
|
+
description:
|
|
1824
|
+
"List the declarative files configured for a project. Each entry shows the in-container path, octal mode, and how content is sourced (inline or env). Raw inline content is never returned (it may be large, and env-sourced content lives in the env store).",
|
|
1825
|
+
inputSchema: z.object({
|
|
1826
|
+
project: z.string().describe("Project name or ID"),
|
|
1827
|
+
}),
|
|
1828
|
+
},
|
|
1829
|
+
async ({ project }) => {
|
|
1830
|
+
const p = await resolveProject(project);
|
|
1831
|
+
const res = await apiGet(`/api/projects/${p.id}/files`);
|
|
1832
|
+
if (!res.ok) throw new Error(`Failed: ${await res.text()}`);
|
|
1833
|
+
const rows = (await res.json()) as Array<{
|
|
1834
|
+
id: number;
|
|
1835
|
+
path: string;
|
|
1836
|
+
mode: string;
|
|
1837
|
+
source: string;
|
|
1838
|
+
env_ref: string | null;
|
|
1839
|
+
}>;
|
|
1840
|
+
if (rows.length === 0) {
|
|
1841
|
+
return { content: [{ type: "text", text: `No files configured for ${p.name}.` }] };
|
|
1842
|
+
}
|
|
1843
|
+
const lines = rows.map(
|
|
1844
|
+
(f) =>
|
|
1845
|
+
`id=${f.id} path=${f.path} mode=${f.mode} source=${f.source}${f.env_ref ? ` env_ref=${f.env_ref}` : ""}`,
|
|
1846
|
+
);
|
|
1847
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1848
|
+
},
|
|
1849
|
+
);
|
|
1850
|
+
|
|
1851
|
+
server.registerTool(
|
|
1852
|
+
"moor_file_remove",
|
|
1853
|
+
{
|
|
1854
|
+
title: "Remove Project File",
|
|
1855
|
+
description:
|
|
1856
|
+
"Remove a declared file from a project's injection set. The file stops being written on future container recreates; a copy already present in a running container is not deleted until the next recreate. Takes effect on next container recreate.",
|
|
1857
|
+
inputSchema: z.object({
|
|
1858
|
+
project: z.string().describe("Project name or ID"),
|
|
1859
|
+
file_id: z.number().int().positive().describe("File ID from moor_file_list"),
|
|
1860
|
+
}),
|
|
1861
|
+
},
|
|
1862
|
+
async ({ project, file_id }) => {
|
|
1863
|
+
const p = await resolveProject(project);
|
|
1864
|
+
const res = await apiDelete(`/api/projects/${p.id}/files/${file_id}`);
|
|
1865
|
+
if (res.status === 404) throw new Error(`File ${file_id} not found on project ${p.name}`);
|
|
1866
|
+
if (!res.ok) throw new Error(`Failed: ${await res.text()}`);
|
|
1867
|
+
return {
|
|
1868
|
+
content: [
|
|
1869
|
+
{
|
|
1870
|
+
type: "text",
|
|
1871
|
+
text: `Removed file ${file_id} from ${p.name}. Applies on next container recreate.`,
|
|
1872
|
+
},
|
|
1873
|
+
],
|
|
1874
|
+
};
|
|
1875
|
+
},
|
|
1876
|
+
);
|
|
1877
|
+
|
|
1732
1878
|
// --- Runs history (#37) ---
|
|
1733
1879
|
|
|
1734
1880
|
// A runs row can be a cron run, a build/manual run, OR a cron run whose cron
|
|
@@ -2006,6 +2152,47 @@ server.registerTool(
|
|
|
2006
2152
|
.describe(
|
|
2007
2153
|
"For github_url projects: pin the source credential row (created via moor_source_credential_add). Build path synthesizes the credentialed clone URL in memory; secret never gets stored on the project row. Pass null to detach without switching source type. Ignored when docker_image is set. Save-time validation is structural only (id exists); host-mismatch / not-active is enforced at build time so configuration can survive transient credential outages.",
|
|
2008
2154
|
),
|
|
2155
|
+
command: z
|
|
2156
|
+
.array(z.string())
|
|
2157
|
+
.nullable()
|
|
2158
|
+
.optional()
|
|
2159
|
+
.describe(
|
|
2160
|
+
'Override the image default command (Docker Cmd) as an argv array, e.g. ["tunnel","run"]. Lets a stock image (e.g. cloudflare/cloudflared) run a custom command with no throwaway Dockerfile. Omit to keep the image default; pass [] or null to clear. Applies on the recreate the run step performs.',
|
|
2161
|
+
),
|
|
2162
|
+
entrypoint: z
|
|
2163
|
+
.array(z.string())
|
|
2164
|
+
.nullable()
|
|
2165
|
+
.optional()
|
|
2166
|
+
.describe(
|
|
2167
|
+
"Override the image ENTRYPOINT as an argv array. Omit to keep the image default; pass [] or null to clear. Applies on container recreate.",
|
|
2168
|
+
),
|
|
2169
|
+
files: z
|
|
2170
|
+
.array(
|
|
2171
|
+
z.object({
|
|
2172
|
+
path: z
|
|
2173
|
+
.string()
|
|
2174
|
+
.min(1)
|
|
2175
|
+
.describe("Absolute in-container destination path, e.g. /etc/ssl/cert.pem"),
|
|
2176
|
+
content: z
|
|
2177
|
+
.string()
|
|
2178
|
+
.optional()
|
|
2179
|
+
.describe("Inline file contents. Provide exactly one of content or env_ref."),
|
|
2180
|
+
env_ref: z
|
|
2181
|
+
.string()
|
|
2182
|
+
.optional()
|
|
2183
|
+
.describe(
|
|
2184
|
+
"Name of a project env var to source the contents from at create time, so a secret (TLS key, token) lives in the env store rather than in plaintext here. Provide exactly one of content or env_ref.",
|
|
2185
|
+
),
|
|
2186
|
+
mode: z
|
|
2187
|
+
.string()
|
|
2188
|
+
.optional()
|
|
2189
|
+
.describe("Octal permission string for the tar header, e.g. '0600'. Default '0644'."),
|
|
2190
|
+
}),
|
|
2191
|
+
)
|
|
2192
|
+
.optional()
|
|
2193
|
+
.describe(
|
|
2194
|
+
"Declarative files to inject into the container before it starts, written on every recreate (additions/updates only — moor_deploy never removes files; use moor_file_remove). Each file's path identifies it; re-deploying the same path updates its content. Honors the octal mode in the tar header (e.g. 0600 for a key).",
|
|
2195
|
+
),
|
|
2009
2196
|
run: z
|
|
2010
2197
|
.boolean()
|
|
2011
2198
|
.optional()
|
|
@@ -2106,6 +2293,8 @@ server.registerTool(
|
|
|
2106
2293
|
memory_limit_mb: input.memory_limit_mb,
|
|
2107
2294
|
cpus: input.cpus,
|
|
2108
2295
|
source_credential_id: input.source_credential_id,
|
|
2296
|
+
command: input.command,
|
|
2297
|
+
entrypoint: input.entrypoint,
|
|
2109
2298
|
};
|
|
2110
2299
|
const res = await apiPost("/api/projects", createBody);
|
|
2111
2300
|
if (!res.ok) throw new Error(`[create] ${await res.text()}`);
|
|
@@ -2127,6 +2316,8 @@ server.registerTool(
|
|
|
2127
2316
|
if (input.cpus !== undefined) updateBody.cpus = input.cpus;
|
|
2128
2317
|
if (input.source_credential_id !== undefined)
|
|
2129
2318
|
updateBody.source_credential_id = input.source_credential_id;
|
|
2319
|
+
if (input.command !== undefined) updateBody.command = input.command;
|
|
2320
|
+
if (input.entrypoint !== undefined) updateBody.entrypoint = input.entrypoint;
|
|
2130
2321
|
|
|
2131
2322
|
if (Object.keys(updateBody).length > 0) {
|
|
2132
2323
|
const res = await apiPut(`/api/projects/${existing.id}`, updateBody);
|
|
@@ -2181,6 +2372,19 @@ server.registerTool(
|
|
|
2181
2372
|
}
|
|
2182
2373
|
}
|
|
2183
2374
|
|
|
2375
|
+
// Step 1.6: inject declarative files (additions/updates only — deploy never
|
|
2376
|
+
// removes files; use moor_file_remove for that). The route upserts by path,
|
|
2377
|
+
// so re-deploying the same path updates its content. Files are written into
|
|
2378
|
+
// the container right before start on the recreate the run step triggers.
|
|
2379
|
+
if (input.files && input.files.length > 0) {
|
|
2380
|
+
for (const f of input.files) {
|
|
2381
|
+
const fRes = await apiPost(`/api/projects/${projectId}/files`, f);
|
|
2382
|
+
if (!fRes.ok) {
|
|
2383
|
+
throw new Error(`[files] failed to set ${f.path}: ${await fRes.text()}`);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2184
2388
|
// Step 2: merge envs. Omitted env leaves existing untouched; {} is a no-op.
|
|
2185
2389
|
const envEntries = input.env ? Object.entries(input.env) : [];
|
|
2186
2390
|
const envProvided = envEntries.length > 0;
|