@unraid-toolkit/mcp 0.2.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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/approval.d.ts +72 -0
  4. package/dist/approval.d.ts.map +1 -0
  5. package/dist/approval.js +106 -0
  6. package/dist/approval.js.map +1 -0
  7. package/dist/audit.d.ts +41 -0
  8. package/dist/audit.d.ts.map +1 -0
  9. package/dist/audit.js +38 -0
  10. package/dist/audit.js.map +1 -0
  11. package/dist/config.d.ts +38 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +88 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/format.d.ts +14 -0
  16. package/dist/format.d.ts.map +1 -0
  17. package/dist/format.js +15 -0
  18. package/dist/format.js.map +1 -0
  19. package/dist/index.d.ts +12 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +72 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/log.d.ts +17 -0
  24. package/dist/log.d.ts.map +1 -0
  25. package/dist/log.js +36 -0
  26. package/dist/log.js.map +1 -0
  27. package/dist/server.d.ts +25 -0
  28. package/dist/server.d.ts.map +1 -0
  29. package/dist/server.js +16 -0
  30. package/dist/server.js.map +1 -0
  31. package/dist/tools/annotations.d.ts +38 -0
  32. package/dist/tools/annotations.d.ts.map +1 -0
  33. package/dist/tools/annotations.js +38 -0
  34. package/dist/tools/annotations.js.map +1 -0
  35. package/dist/tools/array.d.ts +10 -0
  36. package/dist/tools/array.d.ts.map +1 -0
  37. package/dist/tools/array.js +145 -0
  38. package/dist/tools/array.js.map +1 -0
  39. package/dist/tools/confirm.d.ts +14 -0
  40. package/dist/tools/confirm.d.ts.map +1 -0
  41. package/dist/tools/confirm.js +17 -0
  42. package/dist/tools/confirm.js.map +1 -0
  43. package/dist/tools/destructive.d.ts +34 -0
  44. package/dist/tools/destructive.d.ts.map +1 -0
  45. package/dist/tools/destructive.js +84 -0
  46. package/dist/tools/destructive.js.map +1 -0
  47. package/dist/tools/disks.d.ts +8 -0
  48. package/dist/tools/disks.d.ts.map +1 -0
  49. package/dist/tools/disks.js +17 -0
  50. package/dist/tools/disks.js.map +1 -0
  51. package/dist/tools/docker.d.ts +8 -0
  52. package/dist/tools/docker.d.ts.map +1 -0
  53. package/dist/tools/docker.js +105 -0
  54. package/dist/tools/docker.js.map +1 -0
  55. package/dist/tools/index.d.ts +9 -0
  56. package/dist/tools/index.d.ts.map +1 -0
  57. package/dist/tools/index.js +24 -0
  58. package/dist/tools/index.js.map +1 -0
  59. package/dist/tools/notifications.d.ts +8 -0
  60. package/dist/tools/notifications.d.ts.map +1 -0
  61. package/dist/tools/notifications.js +60 -0
  62. package/dist/tools/notifications.js.map +1 -0
  63. package/dist/tools/pagination.d.ts +14 -0
  64. package/dist/tools/pagination.d.ts.map +1 -0
  65. package/dist/tools/pagination.js +14 -0
  66. package/dist/tools/pagination.js.map +1 -0
  67. package/dist/tools/policy.d.ts +39 -0
  68. package/dist/tools/policy.d.ts.map +1 -0
  69. package/dist/tools/policy.js +72 -0
  70. package/dist/tools/policy.js.map +1 -0
  71. package/dist/tools/shares.d.ts +8 -0
  72. package/dist/tools/shares.d.ts.map +1 -0
  73. package/dist/tools/shares.js +17 -0
  74. package/dist/tools/shares.js.map +1 -0
  75. package/dist/tools/system.d.ts +9 -0
  76. package/dist/tools/system.d.ts.map +1 -0
  77. package/dist/tools/system.js +37 -0
  78. package/dist/tools/system.js.map +1 -0
  79. package/dist/tools/ups.d.ts +8 -0
  80. package/dist/tools/ups.d.ts.map +1 -0
  81. package/dist/tools/ups.js +17 -0
  82. package/dist/tools/ups.js.map +1 -0
  83. package/dist/tools/vm.d.ts +10 -0
  84. package/dist/tools/vm.d.ts.map +1 -0
  85. package/dist/tools/vm.js +64 -0
  86. package/dist/tools/vm.js.map +1 -0
  87. package/dist/transports/http.d.ts +18 -0
  88. package/dist/transports/http.d.ts.map +1 -0
  89. package/dist/transports/http.js +92 -0
  90. package/dist/transports/http.js.map +1 -0
  91. package/dist/transports/stdio.d.ts +10 -0
  92. package/dist/transports/stdio.d.ts.map +1 -0
  93. package/dist/transports/stdio.js +13 -0
  94. package/dist/transports/stdio.js.map +1 -0
  95. package/package.json +34 -0
package/dist/config.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Runtime configuration for the MCP server — the wrapper-level concerns that
3
+ * are NOT the SDK's job: which transport to expose, network/auth settings, and
4
+ * the safety floor for (future) control tools.
5
+ *
6
+ * The Unraid *connection* (URL/key/TLS) is resolved separately via the SDK's
7
+ * `resolveConnectionConfig`.
8
+ */
9
+ import { z } from 'zod';
10
+ import { LOG_LEVELS } from './log.js';
11
+ const DEFAULT_HTTP_PORT = 3000;
12
+ const DEFAULT_MAX_BATCH = 10;
13
+ const TRANSPORTS = ['stdio', 'http', 'both'];
14
+ const TRUTHY = new Set(['true', '1', 'yes', 'on']);
15
+ const FALSY = new Set(['false', '0', 'no', 'off', '']);
16
+ function boolFromEnv(def) {
17
+ return z.preprocess((value) => {
18
+ if (value === undefined)
19
+ return def;
20
+ if (typeof value !== 'string')
21
+ return value;
22
+ const normalized = value.trim().toLowerCase();
23
+ if (normalized === '')
24
+ return def;
25
+ if (TRUTHY.has(normalized))
26
+ return true;
27
+ if (FALSY.has(normalized))
28
+ return false;
29
+ return value;
30
+ }, z.boolean());
31
+ }
32
+ function intFromEnv(def, min, max) {
33
+ return z.preprocess((value) => {
34
+ if (value === undefined || value === '')
35
+ return def;
36
+ const n = Number(value);
37
+ return Number.isInteger(n) ? n : value;
38
+ }, z.number().int().min(min).max(max));
39
+ }
40
+ const optionalEnvString = z.preprocess((value) => (value === '' || value === undefined ? undefined : value), z.string().min(1).optional());
41
+ const csvList = z.preprocess((value) => {
42
+ if (typeof value !== 'string' || value === '')
43
+ return [];
44
+ return [
45
+ ...new Set(value
46
+ .split(',')
47
+ .map((s) => s.trim())
48
+ .filter((s) => s.length > 0)),
49
+ ];
50
+ }, z.array(z.string()));
51
+ const mcpConfigSchema = z.object({
52
+ transport: z.enum(TRANSPORTS).default('stdio'),
53
+ httpPort: intFromEnv(DEFAULT_HTTP_PORT, 1, 65535),
54
+ httpAuthToken: optionalEnvString,
55
+ readOnly: boolFromEnv(false),
56
+ maxBatch: intFromEnv(DEFAULT_MAX_BATCH, 1, 10_000),
57
+ denyTools: csvList,
58
+ logLevel: z.enum(LOG_LEVELS).default('info'),
59
+ auditLogPath: optionalEnvString,
60
+ });
61
+ function readEnv(env) {
62
+ return {
63
+ transport: env['MCP_TRANSPORT'],
64
+ httpPort: env['MCP_HTTP_PORT'],
65
+ httpAuthToken: env['MCP_AUTH_TOKEN'],
66
+ readOnly: env['MCP_READ_ONLY'],
67
+ maxBatch: env['MCP_MAX_BATCH'],
68
+ denyTools: env['MCP_DENY_TOOLS'],
69
+ logLevel: env['LOG_LEVEL'],
70
+ auditLogPath: env['MCP_AUDIT_LOG'],
71
+ };
72
+ }
73
+ /**
74
+ * Validate and normalize MCP runtime configuration from the environment.
75
+ *
76
+ * @throws Error with a readable, multi-line summary if validation fails.
77
+ */
78
+ export function loadMcpConfig(env = process.env) {
79
+ const result = mcpConfigSchema.safeParse(readEnv(env));
80
+ if (!result.success) {
81
+ const issues = result.error.issues
82
+ .map((issue) => ` - ${issue.path.join('.') || '(root)'}: ${issue.message}`)
83
+ .join('\n');
84
+ throw new Error(`Invalid Unraid MCP configuration:\n${issues}`);
85
+ }
86
+ return result.data;
87
+ }
88
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The seam between the SDK and MCP: adapt the SDK's `UnraidResult<T>` envelope
3
+ * into the MCP `CallToolResult` shape. This is the only place that conversion
4
+ * happens — tools never build `CallToolResult` by hand.
5
+ *
6
+ * The full `{ success, data, error }` envelope is preserved in the text content
7
+ * (the same shape the CLI emits in JSON mode, so clients handle results
8
+ * uniformly), and failures additionally set the protocol-native `isError` flag.
9
+ */
10
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
11
+ import type { UnraidResult } from '@unraid-toolkit/sdk';
12
+ /** Convert an SDK result envelope into an MCP tool result. */
13
+ export declare function formatResult<T>(result: UnraidResult<T>): CallToolResult;
14
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,8DAA8D;AAC9D,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,cAAc,CAGvE"}
package/dist/format.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The seam between the SDK and MCP: adapt the SDK's `UnraidResult<T>` envelope
3
+ * into the MCP `CallToolResult` shape. This is the only place that conversion
4
+ * happens — tools never build `CallToolResult` by hand.
5
+ *
6
+ * The full `{ success, data, error }` envelope is preserved in the text content
7
+ * (the same shape the CLI emits in JSON mode, so clients handle results
8
+ * uniformly), and failures additionally set the protocol-native `isError` flag.
9
+ */
10
+ /** Convert an SDK result envelope into an MCP tool result. */
11
+ export function formatResult(result) {
12
+ const content = [{ type: 'text', text: JSON.stringify(result, null, 2) }];
13
+ return result.success ? { content } : { content, isError: true };
14
+ }
15
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,8DAA8D;AAC9D,MAAM,UAAU,YAAY,CAAI,MAAuB;IACrD,MAAM,OAAO,GAAG,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IACnF,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACnE,CAAC"}
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `@unraid-toolkit/mcp` — MCP server for the Unraid GraphQL API.
4
+ *
5
+ * Running this file as a binary starts the server. Importing it as a library
6
+ * exposes {@link buildServer} and config helpers without side effects.
7
+ */
8
+ export { buildServer, type ServerContext, SERVER_NAME, SERVER_VERSION } from './server.js';
9
+ export { loadMcpConfig, type McpConfig } from './config.js';
10
+ /** Boot the server with the configured transport(s). */
11
+ export declare function main(): Promise<void>;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAaH,OAAO,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAW5D,wDAAwD;AACxD,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAqC1C"}
package/dist/index.js ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `@unraid-toolkit/mcp` — MCP server for the Unraid GraphQL API.
4
+ *
5
+ * Running this file as a binary starts the server. Importing it as a library
6
+ * exposes {@link buildServer} and config helpers without side effects.
7
+ */
8
+ import { resolve } from 'node:path';
9
+ import { pathToFileURL } from 'node:url';
10
+ import { createClient, resolveConnectionConfig } from '@unraid-toolkit/sdk';
11
+ import { loadMcpConfig } from './config.js';
12
+ import { createLogger } from './log.js';
13
+ import { createAuditLog } from './audit.js';
14
+ import { TokenStore } from './approval.js';
15
+ import { buildServer } from './server.js';
16
+ import { startStdio } from './transports/stdio.js';
17
+ import { startHttp } from './transports/http.js';
18
+ export { buildServer, SERVER_NAME, SERVER_VERSION } from './server.js';
19
+ export { loadMcpConfig } from './config.js';
20
+ function loadConfigOrExit() {
21
+ try {
22
+ return { runtime: loadMcpConfig(), connection: resolveConnectionConfig() };
23
+ }
24
+ catch (error) {
25
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
26
+ process.exit(1);
27
+ }
28
+ }
29
+ /** Boot the server with the configured transport(s). */
30
+ export async function main() {
31
+ const { runtime, connection } = loadConfigOrExit();
32
+ const logger = createLogger(runtime.logLevel);
33
+ const client = createClient(connection);
34
+ const audit = createAuditLog(runtime.auditLogPath, logger);
35
+ const ctx = { client, config: runtime, logger, audit, tokens: new TokenStore() };
36
+ logger.info('Starting Unraid MCP server', {
37
+ transport: runtime.transport,
38
+ endpoint: client.endpoint,
39
+ readOnly: runtime.readOnly,
40
+ });
41
+ const shutdownHandlers = [];
42
+ if (runtime.transport === 'http' || runtime.transport === 'both') {
43
+ const closeHttp = await startHttp(() => buildServer(ctx), runtime, logger);
44
+ shutdownHandlers.push(closeHttp);
45
+ }
46
+ if (runtime.transport === 'stdio' || runtime.transport === 'both') {
47
+ await startStdio(buildServer(ctx), logger);
48
+ }
49
+ const shutdown = (signal) => {
50
+ logger.info(`Received ${signal}, shutting down`);
51
+ void Promise.allSettled(shutdownHandlers.map((h) => h())).finally(() => {
52
+ process.exit(0);
53
+ });
54
+ };
55
+ process.on('SIGINT', () => {
56
+ shutdown('SIGINT');
57
+ });
58
+ process.on('SIGTERM', () => {
59
+ shutdown('SIGTERM');
60
+ });
61
+ }
62
+ const entryPath = process.argv[1];
63
+ // Resolve to an absolute path first so the comparison holds when the script is
64
+ // launched via a relative path (e.g. `node packages/mcp/dist/index.js`).
65
+ const isMainModule = entryPath !== undefined && import.meta.url === pathToFileURL(resolve(entryPath)).href;
66
+ if (isMainModule) {
67
+ main().catch((error) => {
68
+ process.stderr.write(`Fatal: ${error instanceof Error ? error.message : String(error)}\n`);
69
+ process.exit(1);
70
+ });
71
+ }
72
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/log.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Minimal leveled logger that writes exclusively to **stderr**.
3
+ *
4
+ * Critical for the stdio transport: stdout carries the MCP JSON-RPC stream, so
5
+ * all diagnostic logging must go to stderr to avoid corrupting it.
6
+ */
7
+ export declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error"];
8
+ export type LogLevel = (typeof LOG_LEVELS)[number];
9
+ export interface Logger {
10
+ debug(message: string, meta?: Record<string, unknown>): void;
11
+ info(message: string, meta?: Record<string, unknown>): void;
12
+ warn(message: string, meta?: Record<string, unknown>): void;
13
+ error(message: string, meta?: Record<string, unknown>): void;
14
+ }
15
+ /** Create a logger that emits messages at or above `minLevel` to stderr. */
16
+ export declare function createLogger(minLevel?: LogLevel): Logger;
17
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,UAAU,6CAA8C,CAAC;AACtE,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAInD,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9D;AAOD,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,QAAQ,GAAE,QAAiB,GAAG,MAAM,CAoBhE"}
package/dist/log.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Minimal leveled logger that writes exclusively to **stderr**.
3
+ *
4
+ * Critical for the stdio transport: stdout carries the MCP JSON-RPC stream, so
5
+ * all diagnostic logging must go to stderr to avoid corrupting it.
6
+ */
7
+ export const LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
8
+ const LEVEL_RANK = { debug: 10, info: 20, warn: 30, error: 40 };
9
+ function formatLine(level, message, meta) {
10
+ const base = `[unraid-mcp] ${level.toUpperCase()} ${message}`;
11
+ return meta && Object.keys(meta).length > 0 ? `${base} ${JSON.stringify(meta)}` : base;
12
+ }
13
+ /** Create a logger that emits messages at or above `minLevel` to stderr. */
14
+ export function createLogger(minLevel = 'info') {
15
+ const threshold = LEVEL_RANK[minLevel];
16
+ function emit(level, message, meta) {
17
+ if (LEVEL_RANK[level] < threshold)
18
+ return;
19
+ process.stderr.write(`${formatLine(level, message, meta)}\n`);
20
+ }
21
+ return {
22
+ debug: (message, meta) => {
23
+ emit('debug', message, meta);
24
+ },
25
+ info: (message, meta) => {
26
+ emit('info', message, meta);
27
+ },
28
+ warn: (message, meta) => {
29
+ emit('warn', message, meta);
30
+ },
31
+ error: (message, meta) => {
32
+ emit('error', message, meta);
33
+ },
34
+ };
35
+ }
36
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAU,CAAC;AAGtE,MAAM,UAAU,GAA6B,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAS1F,SAAS,UAAU,CAAC,KAAe,EAAE,OAAe,EAAE,IAA8B;IAClF,MAAM,IAAI,GAAG,gBAAgB,KAAK,CAAC,WAAW,EAAE,IAAI,OAAO,EAAE,CAAC;IAC9D,OAAO,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACzF,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,WAAqB,MAAM;IACtD,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACvC,SAAS,IAAI,CAAC,KAAe,EAAE,OAAe,EAAE,IAA8B;QAC5E,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,SAAS;YAAE,OAAO;QAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAChE,CAAC;IACD,OAAO;QACL,KAAK,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE;YACvB,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAC/B,CAAC;QACD,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE;YACtB,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE;YACtB,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAC9B,CAAC;QACD,KAAK,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE;YACvB,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAC/B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Assembles the Unraid {@link McpServer} and registers every tool group.
3
+ * Transports connect to the server this builds.
4
+ */
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import type { UnraidClient } from '@unraid-toolkit/sdk';
7
+ import type { McpConfig } from './config.js';
8
+ import type { Logger } from './log.js';
9
+ import type { AuditLog } from './audit.js';
10
+ import { TokenStore } from './approval.js';
11
+ /** Shared dependencies passed to every tool group. */
12
+ export interface ServerContext {
13
+ client: UnraidClient;
14
+ config: McpConfig;
15
+ logger: Logger;
16
+ /** Layer-1 audit sink for mutations. */
17
+ audit: AuditLog;
18
+ /** Layer-2 confirmation-token store (token-gate fallback). */
19
+ tokens: TokenStore;
20
+ }
21
+ export declare const SERVER_NAME = "unraid-mcp";
22
+ export declare const SERVER_VERSION = "0.1.0";
23
+ /** Build and configure the MCP server with all tool groups registered. */
24
+ export declare function buildServer(ctx: ServerContext): McpServer;
25
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C,sDAAsD;AACtD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,KAAK,EAAE,QAAQ,CAAC;IAChB,8DAA8D;IAC9D,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,eAAe,CAAC;AACxC,eAAO,MAAM,cAAc,UAAU,CAAC;AAEtC,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,CAIzD"}
package/dist/server.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Assembles the Unraid {@link McpServer} and registers every tool group.
3
+ * Transports connect to the server this builds.
4
+ */
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { TokenStore } from './approval.js';
7
+ import { registerAllTools } from './tools/index.js';
8
+ export const SERVER_NAME = 'unraid-mcp';
9
+ export const SERVER_VERSION = '0.1.0';
10
+ /** Build and configure the MCP server with all tool groups registered. */
11
+ export function buildServer(ctx) {
12
+ const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
13
+ registerAllTools(server, ctx);
14
+ return server;
15
+ }
16
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAapD,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC;AACxC,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AAEtC,0EAA0E;AAC1E,MAAM,UAAU,WAAW,CAAC,GAAkB;IAC5C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;IAC7E,gBAAgB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9B,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Shared MCP tool annotations.
3
+ *
4
+ * Every Phase 1 tool is a read-only observation: it never mutates server state,
5
+ * is safe to retry, and reaches an external system (the Unraid API).
6
+ */
7
+ /** Annotations for a read-only, idempotent tool that calls the Unraid API. */
8
+ export declare const READ_ONLY_ANNOTATIONS: {
9
+ readonly readOnlyHint: true;
10
+ readonly destructiveHint: false;
11
+ readonly idempotentHint: true;
12
+ readonly openWorldHint: true;
13
+ };
14
+ /**
15
+ * Annotations for a Phase 2 "safe" control tool — a state-changing write that is
16
+ * reversible and low-risk (start/stop/pause/update). Not read-only, not
17
+ * idempotent, and (per MCP semantics) not flagged destructive — destructive,
18
+ * gated operations are Phase 3 and carry `destructiveHint: true`.
19
+ */
20
+ export declare const SAFE_WRITE_ANNOTATIONS: {
21
+ readonly readOnlyHint: false;
22
+ readonly destructiveHint: false;
23
+ readonly idempotentHint: false;
24
+ readonly openWorldHint: true;
25
+ };
26
+ /**
27
+ * Annotations for a Phase 3 destructive control tool — an irreversible or
28
+ * high-risk write (array stop, disk remove, container remove, force-stop/reset).
29
+ * Carries `destructiveHint: true`; the runtime additionally gates these behind
30
+ * the Layer-1 policy floor and Layer-2 human approval.
31
+ */
32
+ export declare const DESTRUCTIVE_ANNOTATIONS: {
33
+ readonly readOnlyHint: false;
34
+ readonly destructiveHint: true;
35
+ readonly idempotentHint: false;
36
+ readonly openWorldHint: true;
37
+ };
38
+ //# sourceMappingURL=annotations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/tools/annotations.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB;;;;;CAKxB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB;;;;;CAKzB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB;;;;;CAK1B,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Shared MCP tool annotations.
3
+ *
4
+ * Every Phase 1 tool is a read-only observation: it never mutates server state,
5
+ * is safe to retry, and reaches an external system (the Unraid API).
6
+ */
7
+ /** Annotations for a read-only, idempotent tool that calls the Unraid API. */
8
+ export const READ_ONLY_ANNOTATIONS = {
9
+ readOnlyHint: true,
10
+ destructiveHint: false,
11
+ idempotentHint: true,
12
+ openWorldHint: true,
13
+ };
14
+ /**
15
+ * Annotations for a Phase 2 "safe" control tool — a state-changing write that is
16
+ * reversible and low-risk (start/stop/pause/update). Not read-only, not
17
+ * idempotent, and (per MCP semantics) not flagged destructive — destructive,
18
+ * gated operations are Phase 3 and carry `destructiveHint: true`.
19
+ */
20
+ export const SAFE_WRITE_ANNOTATIONS = {
21
+ readOnlyHint: false,
22
+ destructiveHint: false,
23
+ idempotentHint: false,
24
+ openWorldHint: true,
25
+ };
26
+ /**
27
+ * Annotations for a Phase 3 destructive control tool — an irreversible or
28
+ * high-risk write (array stop, disk remove, container remove, force-stop/reset).
29
+ * Carries `destructiveHint: true`; the runtime additionally gates these behind
30
+ * the Layer-1 policy floor and Layer-2 human approval.
31
+ */
32
+ export const DESTRUCTIVE_ANNOTATIONS = {
33
+ readOnlyHint: false,
34
+ destructiveHint: true,
35
+ idempotentHint: false,
36
+ openWorldHint: true,
37
+ };
38
+ //# sourceMappingURL=annotations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations.js","sourceRoot":"","sources":["../../src/tools/annotations.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,YAAY,EAAE,IAAI;IAClB,eAAe,EAAE,KAAK;IACtB,cAAc,EAAE,IAAI;IACpB,aAAa,EAAE,IAAI;CACX,CAAC;AAEX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,YAAY,EAAE,KAAK;IACnB,eAAe,EAAE,KAAK;IACtB,cAAc,EAAE,KAAK;IACrB,aAAa,EAAE,IAAI;CACX,CAAC;AAEX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,YAAY,EAAE,KAAK;IACnB,eAAe,EAAE,IAAI;IACrB,cAAc,EAAE,KAAK;IACrB,aAAa,EAAE,IAAI;CACX,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Array & parity tools. Read adapters plus the Phase 3 destructive controls
3
+ * (array state/disk membership, mount/unmount, parity-check lifecycle), each
4
+ * routed through the Layer-1 floor + Layer-2 approval via {@link runDestructive}.
5
+ */
6
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ import type { ServerContext } from '../server.js';
8
+ /** Register array/parity tools on the given server. */
9
+ export declare function registerArrayTools(server: McpServer, ctx: ServerContext): void;
10
+ //# sourceMappingURL=array.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"array.d.ts","sourceRoot":"","sources":["../../src/tools/array.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAwBzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CAiM9E"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Array & parity tools. Read adapters plus the Phase 3 destructive controls
3
+ * (array state/disk membership, mount/unmount, parity-check lifecycle), each
4
+ * routed through the Layer-1 floor + Layer-2 approval via {@link runDestructive}.
5
+ */
6
+ import { z } from 'zod';
7
+ import { getArrayStatus, getParityHistory, setArrayState, addDiskToArray, removeDiskFromArray, mountArrayDisk, unmountArrayDisk, startParityCheck, pauseParityCheck, resumeParityCheck, cancelParityCheck, } from '@unraid-toolkit/sdk';
8
+ import { formatResult } from '../format.js';
9
+ import { READ_ONLY_ANNOTATIONS, SAFE_WRITE_ANNOTATIONS, DESTRUCTIVE_ANNOTATIONS, } from './annotations.js';
10
+ import { PAGINATION_INPUT } from './pagination.js';
11
+ import { CONFIRM_TOKEN_INPUT } from './confirm.js';
12
+ import { readOnlyBlock } from './policy.js';
13
+ import { runDestructive } from './destructive.js';
14
+ /** Register array/parity tools on the given server. */
15
+ export function registerArrayTools(server, ctx) {
16
+ server.registerTool('unraid_array_status', {
17
+ title: 'Get Unraid Array Status',
18
+ description: `Get the storage array's current state (STARTED/STOPPED/...), total/used/free capacity, parity-check status, and every member disk (parity, data, cache, boot) with size, filesystem usage, temperature, and health color.`,
19
+ inputSchema: {},
20
+ annotations: READ_ONLY_ANNOTATIONS,
21
+ }, async () => formatResult(await getArrayStatus(ctx.client)));
22
+ server.registerTool('unraid_parity_history', {
23
+ title: 'Get Unraid Parity-Check History',
24
+ description: `List past parity checks (date, duration, speed, error count, status). Newest entries first as returned by the server. Use limit/offset to page through a long history.`,
25
+ inputSchema: { ...PAGINATION_INPUT },
26
+ annotations: READ_ONLY_ANNOTATIONS,
27
+ }, async ({ limit, offset }) => formatResult(await getParityHistory(ctx.client, { limit, offset })));
28
+ // --- Phase 3: destructive array control ---
29
+ server.registerTool('unraid_array_set_state', {
30
+ title: 'Start/Stop Unraid Array',
31
+ description: `Start or stop the storage array. Stopping the array takes all shares and Docker/VM services offline. Destructive — requires human approval.`,
32
+ inputSchema: {
33
+ desiredState: z.enum(['START', 'STOP']).describe('Target array state'),
34
+ ...CONFIRM_TOKEN_INPUT,
35
+ },
36
+ annotations: DESTRUCTIVE_ANNOTATIONS,
37
+ }, async ({ desiredState, confirm_token }) => runDestructive(server, ctx, {
38
+ tool: 'unraid_array_set_state',
39
+ summary: `${desiredState === 'STOP' ? 'Stop' : 'Start'} the array`,
40
+ targets: ['array'],
41
+ token: confirm_token,
42
+ run: () => setArrayState(ctx.client, desiredState),
43
+ }));
44
+ server.registerTool('unraid_array_add_disk', {
45
+ title: 'Add Disk to Unraid Array',
46
+ description: `Add a disk to the array at an optional slot. Destructive — requires human approval.`,
47
+ inputSchema: {
48
+ id: z.string().describe('The disk id (PrefixedID)'),
49
+ slot: z.number().int().optional().describe('Target array slot'),
50
+ ...CONFIRM_TOKEN_INPUT,
51
+ },
52
+ annotations: DESTRUCTIVE_ANNOTATIONS,
53
+ }, async ({ id, slot, confirm_token }) => runDestructive(server, ctx, {
54
+ tool: 'unraid_array_add_disk',
55
+ summary: `Add disk ${id} to the array`,
56
+ targets: [id],
57
+ token: confirm_token,
58
+ run: () => addDiskToArray(ctx.client, id, slot),
59
+ }));
60
+ server.registerTool('unraid_array_remove_disk', {
61
+ title: 'Remove Disk from Unraid Array',
62
+ description: `Remove a disk from the array. The array must be stopped first. Destructive — requires human approval.`,
63
+ inputSchema: {
64
+ id: z.string().describe('The disk id (PrefixedID)'),
65
+ slot: z.number().int().optional().describe('The disk slot'),
66
+ ...CONFIRM_TOKEN_INPUT,
67
+ },
68
+ annotations: DESTRUCTIVE_ANNOTATIONS,
69
+ }, async ({ id, slot, confirm_token }) => runDestructive(server, ctx, {
70
+ tool: 'unraid_array_remove_disk',
71
+ summary: `Remove disk ${id} from the array`,
72
+ targets: [id],
73
+ token: confirm_token,
74
+ run: () => removeDiskFromArray(ctx.client, id, slot),
75
+ }));
76
+ server.registerTool('unraid_array_mount_disk', {
77
+ title: 'Mount Unraid Array Disk',
78
+ description: `Mount a single array disk. Destructive — requires human approval.`,
79
+ inputSchema: { id: z.string().describe('The disk id (PrefixedID)'), ...CONFIRM_TOKEN_INPUT },
80
+ annotations: DESTRUCTIVE_ANNOTATIONS,
81
+ }, async ({ id, confirm_token }) => runDestructive(server, ctx, {
82
+ tool: 'unraid_array_mount_disk',
83
+ summary: `Mount array disk ${id}`,
84
+ targets: [id],
85
+ token: confirm_token,
86
+ run: () => mountArrayDisk(ctx.client, id),
87
+ }));
88
+ server.registerTool('unraid_array_unmount_disk', {
89
+ title: 'Unmount Unraid Array Disk',
90
+ description: `Unmount a single array disk. Destructive — requires human approval.`,
91
+ inputSchema: { id: z.string().describe('The disk id (PrefixedID)'), ...CONFIRM_TOKEN_INPUT },
92
+ annotations: DESTRUCTIVE_ANNOTATIONS,
93
+ }, async ({ id, confirm_token }) => runDestructive(server, ctx, {
94
+ tool: 'unraid_array_unmount_disk',
95
+ summary: `Unmount array disk ${id}`,
96
+ targets: [id],
97
+ token: confirm_token,
98
+ run: () => unmountArrayDisk(ctx.client, id),
99
+ }));
100
+ // --- Phase 3: destructive parity control ---
101
+ server.registerTool('unraid_parity_start', {
102
+ title: 'Start Unraid Parity Check',
103
+ description: `Start a parity check. With correct=true it writes corrections to parity (a correcting check); correct=false runs a read-only check. Destructive (a correcting check writes to parity) and gated: requires human approval (elicitation or a confirmation token).`,
104
+ inputSchema: {
105
+ correct: z
106
+ .boolean()
107
+ .describe('Write corrections to parity (true) or read-only check (false)'),
108
+ ...CONFIRM_TOKEN_INPUT,
109
+ },
110
+ annotations: DESTRUCTIVE_ANNOTATIONS,
111
+ }, async ({ correct, confirm_token }) => runDestructive(server, ctx, {
112
+ tool: 'unraid_parity_start',
113
+ summary: `Start a ${correct ? 'correcting' : 'read-only'} parity check`,
114
+ targets: ['parity'],
115
+ token: confirm_token,
116
+ run: () => startParityCheck(ctx.client, correct),
117
+ }));
118
+ server.registerTool('unraid_parity_cancel', {
119
+ title: 'Cancel Unraid Parity Check',
120
+ description: `Cancel a running parity check (discards its progress). Destructive and gated: requires human approval (elicitation or a confirmation token).`,
121
+ inputSchema: { ...CONFIRM_TOKEN_INPUT },
122
+ annotations: DESTRUCTIVE_ANNOTATIONS,
123
+ }, async ({ confirm_token }) => runDestructive(server, ctx, {
124
+ tool: 'unraid_parity_cancel',
125
+ summary: 'Cancel the running parity check',
126
+ targets: ['parity'],
127
+ token: confirm_token,
128
+ run: () => cancelParityCheck(ctx.client),
129
+ }));
130
+ // Pause/resume are reversible, low-risk lifecycle toggles — safe writes, not
131
+ // gated. They still honor the read-only policy floor.
132
+ server.registerTool('unraid_parity_pause', {
133
+ title: 'Pause Unraid Parity Check',
134
+ description: `Pause a running parity check. Reversible — resume it with unraid_parity_resume.`,
135
+ inputSchema: {},
136
+ annotations: SAFE_WRITE_ANNOTATIONS,
137
+ }, async () => readOnlyBlock(ctx) ?? formatResult(await pauseParityCheck(ctx.client)));
138
+ server.registerTool('unraid_parity_resume', {
139
+ title: 'Resume Unraid Parity Check',
140
+ description: `Resume a paused parity check.`,
141
+ inputSchema: {},
142
+ annotations: SAFE_WRITE_ANNOTATIONS,
143
+ }, async () => readOnlyBlock(ctx) ?? formatResult(await resumeParityCheck(ctx.client)));
144
+ }
145
+ //# sourceMappingURL=array.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"array.js","sourceRoot":"","sources":["../../src/tools/array.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD,uDAAuD;AACvD,MAAM,UAAU,kBAAkB,CAAC,MAAiB,EAAE,GAAkB;IACtE,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,KAAK,EAAE,yBAAyB;QAChC,WAAW,EAAE,2NAA2N;QACxO,WAAW,EAAE,EAAE;QACf,WAAW,EAAE,qBAAqB;KACnC,EACD,KAAK,IAAI,EAAE,CAAC,YAAY,CAAC,MAAM,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAC3D,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;QACE,KAAK,EAAE,iCAAiC;QACxC,WAAW,EAAE,wKAAwK;QACrL,WAAW,EAAE,EAAE,GAAG,gBAAgB,EAAE;QACpC,WAAW,EAAE,qBAAqB;KACnC,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAC1B,YAAY,CAAC,MAAM,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CACtE,CAAC;IAEF,6CAA6C;IAE7C,MAAM,CAAC,YAAY,CACjB,wBAAwB,EACxB;QACE,KAAK,EAAE,yBAAyB;QAChC,WAAW,EAAE,6IAA6I;QAC1J,WAAW,EAAE;YACX,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YACtE,GAAG,mBAAmB;SACvB;QACD,WAAW,EAAE,uBAAuB;KACrC,EACD,KAAK,EAAE,EAAE,YAAY,EAAE,aAAa,EAAE,EAAE,EAAE,CACxC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;QAC1B,IAAI,EAAE,wBAAwB;QAC9B,OAAO,EAAE,GAAG,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,YAAY;QAClE,OAAO,EAAE,CAAC,OAAO,CAAC;QAClB,KAAK,EAAE,aAAa;QACpB,GAAG,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC;KACnD,CAAC,CACL,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;QACE,KAAK,EAAE,0BAA0B;QACjC,WAAW,EAAE,qFAAqF;QAClG,WAAW,EAAE;YACX,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;YACnD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;YAC/D,GAAG,mBAAmB;SACvB;QACD,WAAW,EAAE,uBAAuB;KACrC,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,CACpC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;QAC1B,IAAI,EAAE,uBAAuB;QAC7B,OAAO,EAAE,YAAY,EAAE,eAAe;QACtC,OAAO,EAAE,CAAC,EAAE,CAAC;QACb,KAAK,EAAE,aAAa;QACpB,GAAG,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC;KAChD,CAAC,CACL,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,0BAA0B,EAC1B;QACE,KAAK,EAAE,+BAA+B;QACtC,WAAW,EAAE,uGAAuG;QACpH,WAAW,EAAE;YACX,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;YACnD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC;YAC3D,GAAG,mBAAmB;SACvB;QACD,WAAW,EAAE,uBAAuB;KACrC,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,CACpC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;QAC1B,IAAI,EAAE,0BAA0B;QAChC,OAAO,EAAE,eAAe,EAAE,iBAAiB;QAC3C,OAAO,EAAE,CAAC,EAAE,CAAC;QACb,KAAK,EAAE,aAAa;QACpB,GAAG,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC;KACrD,CAAC,CACL,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,yBAAyB,EACzB;QACE,KAAK,EAAE,yBAAyB;QAChC,WAAW,EAAE,mEAAmE;QAChF,WAAW,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC,EAAE,GAAG,mBAAmB,EAAE;QAC5F,WAAW,EAAE,uBAAuB;KACrC,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,CAC9B,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;QAC1B,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,oBAAoB,EAAE,EAAE;QACjC,OAAO,EAAE,CAAC,EAAE,CAAC;QACb,KAAK,EAAE,aAAa;QACpB,GAAG,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;KAC1C,CAAC,CACL,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,2BAA2B,EAC3B;QACE,KAAK,EAAE,2BAA2B;QAClC,WAAW,EAAE,qEAAqE;QAClF,WAAW,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC,EAAE,GAAG,mBAAmB,EAAE;QAC5F,WAAW,EAAE,uBAAuB;KACrC,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,CAC9B,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;QAC1B,IAAI,EAAE,2BAA2B;QACjC,OAAO,EAAE,sBAAsB,EAAE,EAAE;QACnC,OAAO,EAAE,CAAC,EAAE,CAAC;QACb,KAAK,EAAE,aAAa;QACpB,GAAG,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;KAC5C,CAAC,CACL,CAAC;IAEF,8CAA8C;IAE9C,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,KAAK,EAAE,2BAA2B;QAClC,WAAW,EAAE,iQAAiQ;QAC9Q,WAAW,EAAE;YACX,OAAO,EAAE,CAAC;iBACP,OAAO,EAAE;iBACT,QAAQ,CAAC,+DAA+D,CAAC;YAC5E,GAAG,mBAAmB;SACvB;QACD,WAAW,EAAE,uBAAuB;KACrC,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,CACnC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;QAC1B,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,WAAW,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,eAAe;QACvE,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,KAAK,EAAE,aAAa;QACpB,GAAG,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;KACjD,CAAC,CACL,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,4BAA4B;QACnC,WAAW,EAAE,8IAA8I;QAC3J,WAAW,EAAE,EAAE,GAAG,mBAAmB,EAAE;QACvC,WAAW,EAAE,uBAAuB;KACrC,EACD,KAAK,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,CAC1B,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;QAC1B,IAAI,EAAE,sBAAsB;QAC5B,OAAO,EAAE,iCAAiC;QAC1C,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,KAAK,EAAE,aAAa;QACpB,GAAG,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC;KACzC,CAAC,CACL,CAAC;IAEF,6EAA6E;IAC7E,sDAAsD;IACtD,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,KAAK,EAAE,2BAA2B;QAClC,WAAW,EAAE,iFAAiF;QAC9F,WAAW,EAAE,EAAE;QACf,WAAW,EAAE,sBAAsB;KACpC,EACD,KAAK,IAAI,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,MAAM,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CACnF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,4BAA4B;QACnC,WAAW,EAAE,+BAA+B;QAC5C,WAAW,EAAE,EAAE;QACf,WAAW,EAAE,sBAAsB;KACpC,EACD,KAAK,IAAI,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,MAAM,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CACpF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared MCP input fragment for the confirmation-token gate.
3
+ *
4
+ * Destructive tools accept an optional `confirm_token`. On a client without
5
+ * elicitation, the first call returns a token; the caller re-invokes with it to
6
+ * proceed. Elicitation-capable clients ignore this field (approval happens
7
+ * interactively).
8
+ */
9
+ import { z } from 'zod';
10
+ /** Adds the optional `confirm_token` field to a destructive tool's input. */
11
+ export declare const CONFIRM_TOKEN_INPUT: {
12
+ confirm_token: z.ZodOptional<z.ZodString>;
13
+ };
14
+ //# sourceMappingURL=confirm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm.d.ts","sourceRoot":"","sources":["../../src/tools/confirm.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB;;CAO/B,CAAC"}