@ductape/mcp 0.1.3 → 0.1.5

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 (3) hide show
  1. package/dist/index.js +152 -17
  2. package/package.json +1 -1
  3. package/src/index.ts +164 -18
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@
10
10
  * the env var when provided.
11
11
  */
12
12
  import { createRequire } from 'module';
13
+ import { execSync } from 'child_process';
13
14
  import { z } from 'zod';
14
15
  import { executeViaProxy, generateExecutablePayload, getAssetSchemas, } from './proxy-client.js';
15
16
  const MODULES = [
@@ -24,18 +25,45 @@ const MODULES = [
24
25
  const METHOD_DOCS = `
25
26
  ━━━ TOOL SELECTION GUIDE ━━━
26
27
 
27
- There are two categories of SDK operations. Use the right tool for each:
28
+ There are THREE categories of operations. Use the right tool for each:
28
29
 
29
- 1. ASSET CREATION / UPDATE (create, update, add, register…)
30
- Input shape is fixed by the SDK's Joi validators.
31
- Call ductape_schema first to discover required fields and enum values.
32
- Then call ductape_execute with the filled params.
33
- EXCEPTION — features.create / quotas.create / fallback.create have TWO distinct input fields:
34
- a) Top-level "input": a SCHEMA DECLARATION — defines what fields the asset accepts at runtime.
35
- Construct this yourself based on what the caller will provide. Do NOT use ductape_generate_payload for this.
36
- b) options[].input (quotas/fallbacks) or steps[].input (features): maps declared inputs the underlying action's fields.
37
- CALL ductape_generate_payload for the target action to discover what fields it accepts,
38
- then wire them using "$Input{fieldName}" or "$Step{stepTag}{field}" references.
30
+ 0. ADMINISTRATIVE OPERATIONS (create/update products, apps, environments, cloud connections…)
31
+ These require an access key and CANNOT be done via ductape_execute (publishable key only).
32
+ Use ductape_cli instead. Examples:
33
+ ductape_cli("products list")
34
+ ductape_cli("products create --name \\"My Product\\" --tag my-product")
35
+ ductape_cli("cloud connections list")
36
+ ductape_cli("link --product my-product --env dev")
37
+ If the CLI is not installed, ductape_cli will return install instructions automatically.
38
+ NOTE: Environments and app actions are configured in the Workbench UI there are no CLI commands for them.
39
+
40
+ DECLARATIVE SYNC (apply sessions, notifications, events from code; run DB migrations)
41
+ → Also use ductape_cli. The project must be linked first (ductape init --link).
42
+ ductape_cli("apply") # sync all: sessions + notifications + events
43
+ ductape_cli("apply sessions") # sessions only
44
+ ductape_cli("apply notifications") # notifications only
45
+ ductape_cli("apply events") # event brokers only
46
+ ductape_cli("apply --dry-run") # preview without changes
47
+ ductape_cli("db schema generate") # diff schema.json → write migration files
48
+ ductape_cli("db schema generate --destructive") # also generate drop operations
49
+ ductape_cli("db migrate") # apply pending migration files
50
+ ductape_cli("db migrate --dry-run") # preview without applying
51
+ ductape_cli("db migrate --env prd") # apply to a specific environment
52
+ ductape_cli("db migrate status") # show applied vs pending migrations
53
+ ductape_cli("db migrate status --json") # machine-readable status
54
+ ductape_cli("db migrate rollback") # roll back last migration
55
+ ductape_cli("db migrate rollback -n 3") # roll back last 3 migrations
56
+
57
+ 1. ASSET CREATION / UPDATE (create, update, add, register… for ANY asset type)
58
+ ALL creation and update operations require an access key and CANNOT go through ductape_execute.
59
+ → Use ductape_cli for every create/update operation. Examples:
60
+ ductape_cli("apps create --name \\"Email Service\\" --description \\"Transactional email\\"")
61
+ ductape_cli("apps list")
62
+ ductape_cli("resources storage list")
63
+ ductape_cli("resources database create -f db-config.json")
64
+ This applies to: products, apps, and resources (databases, storage, caches, etc.),
65
+ cloud connections, and secrets. Environments, app actions, auths, features, quotas,
66
+ fallbacks, jobs, and healthchecks are configured in the Workbench UI.
39
67
 
40
68
  2. RUNTIME OPERATIONS (run, dispatch, execute, start, send, produce, query, insert, update, delete…)
41
69
  The "input" field shape is product- and operation-specific — it is NOT derivable from Joi validators.
@@ -777,6 +805,63 @@ function buildSnippet(language, payload, operationFamily, method) {
777
805
  ? buildPythonSnippet(payload, operationFamily, method)
778
806
  : buildTypeScriptSnippet(payload, operationFamily, method);
779
807
  }
808
+ // ─── CLI helpers ─────────────────────────────────────────────────────────────
809
+ const ADMIN_SUBCOMMANDS = [
810
+ 'login', 'logout', 'whoami',
811
+ 'profiles',
812
+ 'workspaces',
813
+ 'link', 'unlink', 'init',
814
+ 'products', 'apps',
815
+ 'resources',
816
+ 'cloud',
817
+ 'secrets',
818
+ 'generate',
819
+ 'apply',
820
+ 'db',
821
+ 'graph',
822
+ ];
823
+ function checkCli() {
824
+ try {
825
+ const out = execSync('ductape --version', { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
826
+ return { available: true, version: out || 'unknown' };
827
+ }
828
+ catch {
829
+ return { available: false };
830
+ }
831
+ }
832
+ function runCli(command) {
833
+ const first = command.trim().split(/\s+/)[0];
834
+ if (!ADMIN_SUBCOMMANDS.includes(first)) {
835
+ return {
836
+ success: false,
837
+ output: `Subcommand "${first}" is not in the allowed admin subcommand list. Allowed: ${ADMIN_SUBCOMMANDS.join(', ')}.`,
838
+ };
839
+ }
840
+ try {
841
+ const output = execSync(`ductape ${command}`, {
842
+ encoding: 'utf8',
843
+ timeout: 30000,
844
+ stdio: ['pipe', 'pipe', 'pipe'],
845
+ });
846
+ return { success: true, output: output.trim() };
847
+ }
848
+ catch (err) {
849
+ const msg = (err.stderr || err.stdout || err.message || String(err)).trim();
850
+ return { success: false, output: msg };
851
+ }
852
+ }
853
+ const cliInputSchema = z.object({
854
+ command: z.string().describe('The ductape CLI command to run, without the leading "ductape" word. ' +
855
+ 'Examples: "products list", "products create --name \\"My Product\\" --tag my-product", ' +
856
+ '"apps list", "apps create -f app.json", "resources storage list", ' +
857
+ '"cloud connections list", "link --product my-product --env dev".\n\n' +
858
+ 'Use this tool for administrative operations: creating or updating products, apps, ' +
859
+ 'resources (databases, storage, caches…), cloud connections, secrets, ' +
860
+ 'and for apply/migrate workflows.\n\n' +
861
+ 'Note: environments, app actions, features, quotas, fallbacks, and jobs are configured ' +
862
+ 'in the Workbench UI — there are no CLI commands for them.\n\n' +
863
+ 'The CLI uses the user\'s local logged-in session (ductape login) — no key is required.'),
864
+ });
780
865
  async function loadMcpSdk() {
781
866
  try {
782
867
  const [{ McpServer }, { StdioServerTransport }] = await Promise.all([
@@ -831,6 +916,33 @@ async function main() {
831
916
  const { McpServer, StdioServerTransport } = await loadMcpSdk();
832
917
  const server = new McpServer({ name: 'ductape-mcp', version: '0.1.0' });
833
918
  const transport = new StdioServerTransport();
919
+ const cliHandler = async (args) => {
920
+ const cli = checkCli();
921
+ if (!cli.available) {
922
+ return {
923
+ content: [{
924
+ type: 'text',
925
+ text: [
926
+ 'The Ductape CLI is not installed or not in PATH.',
927
+ '',
928
+ 'Install it with:',
929
+ ' npm install --global @ductape/cli',
930
+ '',
931
+ 'Then log in:',
932
+ ' ductape login',
933
+ '',
934
+ 'After logging in, retry this operation.',
935
+ ].join('\n'),
936
+ }],
937
+ isError: true,
938
+ };
939
+ }
940
+ const result = runCli(args.command);
941
+ return {
942
+ content: [{ type: 'text', text: result.output || '(no output)' }],
943
+ ...(result.success ? {} : { isError: true }),
944
+ };
945
+ };
834
946
  const executeHandler = async (args) => {
835
947
  try {
836
948
  const key = args.publishable_key || process.env.DUCTAPE_PUBLISHABLE_KEY;
@@ -899,12 +1011,14 @@ async function main() {
899
1011
  if (typeof server.registerTool === 'function') {
900
1012
  server.registerTool('ductape_execute', {
901
1013
  title: 'Ductape SDK Execute',
902
- description: 'Execute a Ductape SDK operation via the backend proxy.\n\n' +
903
- 'IMPORTANT two-step rule for runtime operations (run, dispatch, execute, start, send, produce, query, insert, update, delete):\n' +
904
- ' 1. Call ductape_generate_payload first to get the canonical payload template for the specific operation.\n' +
905
- ' This reveals the exact "input" field keys and their types they are product/env/operation-specific.\n' +
906
- ' 2. Fill in the values from the template, then call ductape_execute.\n\n' +
907
- 'For asset creation/update operations (create, update, add), use ductape_schema to discover required fields instead.',
1014
+ description: 'Execute a Ductape SDK RUNTIME operation via the backend proxy (publishable key).\n\n' +
1015
+ 'ONLY use this tool for runtime operations: run, dispatch, execute, start, send, produce, query, insert, update, delete.\n' +
1016
+ 'Do NOT use this for creating or updating any asset (product, app, action, environment, database, storage, etc.) — ' +
1017
+ 'those require an access key. Use ductape_cli for all creation and update operations.\n\n' +
1018
+ 'Two-step rule for runtime operations:\n' +
1019
+ ' 1. Call ductape_generate_payload first to get the canonical payload template.\n' +
1020
+ ' This reveals the exact "input" field keys — they are product/env/operation-specific.\n' +
1021
+ ' 2. Fill in the values from the template, then call ductape_execute.',
908
1022
  inputSchema: executeInputSchema,
909
1023
  }, executeHandler);
910
1024
  server.registerTool('ductape_generate_payload', {
@@ -931,12 +1045,33 @@ async function main() {
931
1045
  'Pass module="app" or module="product" to scope the result.',
932
1046
  inputSchema: schemaInputSchema,
933
1047
  }, schemaHandler);
1048
+ server.registerTool('ductape_cli', {
1049
+ title: 'Ductape CLI',
1050
+ description: 'Run a Ductape CLI command for administrative operations.\n\n' +
1051
+ 'USE THIS TOOL for any operation that creates or modifies platform configuration:\n' +
1052
+ ' - Creating or updating products (products create/update) and apps (apps create/update)\n' +
1053
+ ' - Importing an app from a Postman v2.1 or OpenAPI 3.0 file: "apps import <file> -t postman|openapi"\n' +
1054
+ ' - Managing resources via "resources <type> <verb>" (databases, storage, caches…)\n' +
1055
+ ' - Managing cloud connections and cloud-linked resources\n' +
1056
+ ' - Listing workspaces, products, secrets\n' +
1057
+ ' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
1058
+ ' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
1059
+ ' - Running database migrations: "db migrate", "db schema generate"\n\n' +
1060
+ 'NOTE: Environments, app actions, auths, features, quotas, fallbacks, and jobs are ' +
1061
+ 'configured in the Workbench UI — the CLI does not have commands for them.\n\n' +
1062
+ 'DO NOT use ductape_execute for admin operations — it uses a publishable key which only ' +
1063
+ 'covers runtime operations. Administrative operations will fail with "Authentication failed".\n\n' +
1064
+ 'The CLI uses the user\'s local logged-in session (ductape login). ' +
1065
+ 'If the CLI is not installed, this tool will return install instructions automatically.',
1066
+ inputSchema: cliInputSchema,
1067
+ }, cliHandler);
934
1068
  }
935
1069
  else if (typeof server.tool === 'function') {
936
1070
  server.tool('ductape_execute', executeInputSchema.shape, executeHandler);
937
1071
  server.tool('ductape_generate_payload', payloadGenerateInputSchema.shape, payloadGenerateHandler);
938
1072
  server.tool('ductape_generate_snippet', snippetGenerateInputSchema.shape, snippetGenerateHandler);
939
1073
  server.tool('ductape_schema', schemaInputSchema.shape, schemaHandler);
1074
+ server.tool('ductape_cli', cliInputSchema.shape, cliHandler);
940
1075
  }
941
1076
  else {
942
1077
  console.error('MCP server does not expose .registerTool() or .tool()');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { createRequire } from 'module';
14
+ import { execSync } from 'child_process';
14
15
  import { z } from 'zod';
15
16
  import {
16
17
  executeViaProxy,
@@ -35,18 +36,45 @@ const MODULES: SDKModule[] = [
35
36
  const METHOD_DOCS = `
36
37
  ━━━ TOOL SELECTION GUIDE ━━━
37
38
 
38
- There are two categories of SDK operations. Use the right tool for each:
39
-
40
- 1. ASSET CREATION / UPDATE (create, update, add, register…)
41
- Input shape is fixed by the SDK's Joi validators.
42
- Call ductape_schema first to discover required fields and enum values.
43
- Then call ductape_execute with the filled params.
44
- EXCEPTION — features.create / quotas.create / fallback.create have TWO distinct input fields:
45
- a) Top-level "input": a SCHEMA DECLARATION — defines what fields the asset accepts at runtime.
46
- Construct this yourself based on what the caller will provide. Do NOT use ductape_generate_payload for this.
47
- b) options[].input (quotas/fallbacks) or steps[].input (features): maps declared inputs the underlying action's fields.
48
- CALL ductape_generate_payload for the target action to discover what fields it accepts,
49
- then wire them using "$Input{fieldName}" or "$Step{stepTag}{field}" references.
39
+ There are THREE categories of operations. Use the right tool for each:
40
+
41
+ 0. ADMINISTRATIVE OPERATIONS (create/update products, apps, environments, cloud connections…)
42
+ These require an access key and CANNOT be done via ductape_execute (publishable key only).
43
+ Use ductape_cli instead. Examples:
44
+ ductape_cli("products list")
45
+ ductape_cli("products create --name \\"My Product\\" --tag my-product")
46
+ ductape_cli("cloud connections list")
47
+ ductape_cli("link --product my-product --env dev")
48
+ If the CLI is not installed, ductape_cli will return install instructions automatically.
49
+ NOTE: Environments and app actions are configured in the Workbench UI there are no CLI commands for them.
50
+
51
+ DECLARATIVE SYNC (apply sessions, notifications, events from code; run DB migrations)
52
+ → Also use ductape_cli. The project must be linked first (ductape init --link).
53
+ ductape_cli("apply") # sync all: sessions + notifications + events
54
+ ductape_cli("apply sessions") # sessions only
55
+ ductape_cli("apply notifications") # notifications only
56
+ ductape_cli("apply events") # event brokers only
57
+ ductape_cli("apply --dry-run") # preview without changes
58
+ ductape_cli("db schema generate") # diff schema.json → write migration files
59
+ ductape_cli("db schema generate --destructive") # also generate drop operations
60
+ ductape_cli("db migrate") # apply pending migration files
61
+ ductape_cli("db migrate --dry-run") # preview without applying
62
+ ductape_cli("db migrate --env prd") # apply to a specific environment
63
+ ductape_cli("db migrate status") # show applied vs pending migrations
64
+ ductape_cli("db migrate status --json") # machine-readable status
65
+ ductape_cli("db migrate rollback") # roll back last migration
66
+ ductape_cli("db migrate rollback -n 3") # roll back last 3 migrations
67
+
68
+ 1. ASSET CREATION / UPDATE (create, update, add, register… for ANY asset type)
69
+ ALL creation and update operations require an access key and CANNOT go through ductape_execute.
70
+ → Use ductape_cli for every create/update operation. Examples:
71
+ ductape_cli("apps create --name \\"Email Service\\" --description \\"Transactional email\\"")
72
+ ductape_cli("apps list")
73
+ ductape_cli("resources storage list")
74
+ ductape_cli("resources database create -f db-config.json")
75
+ This applies to: products, apps, and resources (databases, storage, caches, etc.),
76
+ cloud connections, and secrets. Environments, app actions, auths, features, quotas,
77
+ fallbacks, jobs, and healthchecks are configured in the Workbench UI.
50
78
 
51
79
  2. RUNTIME OPERATIONS (run, dispatch, execute, start, send, produce, query, insert, update, delete…)
52
80
  The "input" field shape is product- and operation-specific — it is NOT derivable from Joi validators.
@@ -820,6 +848,68 @@ function buildSnippet(
820
848
  }
821
849
 
822
850
 
851
+ // ─── CLI helpers ─────────────────────────────────────────────────────────────
852
+
853
+ const ADMIN_SUBCOMMANDS = [
854
+ 'login', 'logout', 'whoami',
855
+ 'profiles',
856
+ 'workspaces',
857
+ 'link', 'unlink', 'init',
858
+ 'products', 'apps',
859
+ 'resources',
860
+ 'cloud',
861
+ 'secrets',
862
+ 'generate',
863
+ 'apply',
864
+ 'db',
865
+ 'graph',
866
+ ];
867
+
868
+ function checkCli(): { available: boolean; version?: string } {
869
+ try {
870
+ const out = execSync('ductape --version', { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
871
+ return { available: true, version: out || 'unknown' };
872
+ } catch {
873
+ return { available: false };
874
+ }
875
+ }
876
+
877
+ function runCli(command: string): { success: boolean; output: string } {
878
+ const first = command.trim().split(/\s+/)[0];
879
+ if (!ADMIN_SUBCOMMANDS.includes(first)) {
880
+ return {
881
+ success: false,
882
+ output: `Subcommand "${first}" is not in the allowed admin subcommand list. Allowed: ${ADMIN_SUBCOMMANDS.join(', ')}.`,
883
+ };
884
+ }
885
+ try {
886
+ const output = execSync(`ductape ${command}`, {
887
+ encoding: 'utf8',
888
+ timeout: 30000,
889
+ stdio: ['pipe', 'pipe', 'pipe'],
890
+ });
891
+ return { success: true, output: output.trim() };
892
+ } catch (err: any) {
893
+ const msg = (err.stderr || err.stdout || err.message || String(err)).trim();
894
+ return { success: false, output: msg };
895
+ }
896
+ }
897
+
898
+ const cliInputSchema = z.object({
899
+ command: z.string().describe(
900
+ 'The ductape CLI command to run, without the leading "ductape" word. ' +
901
+ 'Examples: "products list", "products create --name \\"My Product\\" --tag my-product", ' +
902
+ '"apps list", "apps create -f app.json", "resources storage list", ' +
903
+ '"cloud connections list", "link --product my-product --env dev".\n\n' +
904
+ 'Use this tool for administrative operations: creating or updating products, apps, ' +
905
+ 'resources (databases, storage, caches…), cloud connections, secrets, ' +
906
+ 'and for apply/migrate workflows.\n\n' +
907
+ 'Note: environments, app actions, features, quotas, fallbacks, and jobs are configured ' +
908
+ 'in the Workbench UI — there are no CLI commands for them.\n\n' +
909
+ 'The CLI uses the user\'s local logged-in session (ductape login) — no key is required.',
910
+ ),
911
+ });
912
+
823
913
  async function loadMcpSdk(): Promise<{
824
914
  McpServer: new (info: { name: string; version: string }) => any;
825
915
  StdioServerTransport: new () => any;
@@ -884,6 +974,34 @@ async function main() {
884
974
  const server = new McpServer({ name: 'ductape-mcp', version: '0.1.0' });
885
975
  const transport = new StdioServerTransport();
886
976
 
977
+ const cliHandler = async (args: { command: string }) => {
978
+ const cli = checkCli();
979
+ if (!cli.available) {
980
+ return {
981
+ content: [{
982
+ type: 'text',
983
+ text: [
984
+ 'The Ductape CLI is not installed or not in PATH.',
985
+ '',
986
+ 'Install it with:',
987
+ ' npm install --global @ductape/cli',
988
+ '',
989
+ 'Then log in:',
990
+ ' ductape login',
991
+ '',
992
+ 'After logging in, retry this operation.',
993
+ ].join('\n'),
994
+ }],
995
+ isError: true,
996
+ };
997
+ }
998
+ const result = runCli(args.command);
999
+ return {
1000
+ content: [{ type: 'text', text: result.output || '(no output)' }],
1001
+ ...(result.success ? {} : { isError: true }),
1002
+ };
1003
+ };
1004
+
887
1005
  const executeHandler = async (args: { publishable_key?: string; module: SDKModule; method: string; params: unknown[] }) => {
888
1006
  try {
889
1007
  const key = args.publishable_key || process.env.DUCTAPE_PUBLISHABLE_KEY;
@@ -963,12 +1081,14 @@ async function main() {
963
1081
  {
964
1082
  title: 'Ductape SDK Execute',
965
1083
  description:
966
- 'Execute a Ductape SDK operation via the backend proxy.\n\n' +
967
- 'IMPORTANT two-step rule for runtime operations (run, dispatch, execute, start, send, produce, query, insert, update, delete):\n' +
968
- ' 1. Call ductape_generate_payload first to get the canonical payload template for the specific operation.\n' +
969
- ' This reveals the exact "input" field keys and their types they are product/env/operation-specific.\n' +
970
- ' 2. Fill in the values from the template, then call ductape_execute.\n\n' +
971
- 'For asset creation/update operations (create, update, add), use ductape_schema to discover required fields instead.',
1084
+ 'Execute a Ductape SDK RUNTIME operation via the backend proxy (publishable key).\n\n' +
1085
+ 'ONLY use this tool for runtime operations: run, dispatch, execute, start, send, produce, query, insert, update, delete.\n' +
1086
+ 'Do NOT use this for creating or updating any asset (product, app, action, environment, database, storage, etc.) — ' +
1087
+ 'those require an access key. Use ductape_cli for all creation and update operations.\n\n' +
1088
+ 'Two-step rule for runtime operations:\n' +
1089
+ ' 1. Call ductape_generate_payload first to get the canonical payload template.\n' +
1090
+ ' This reveals the exact "input" field keys — they are product/env/operation-specific.\n' +
1091
+ ' 2. Fill in the values from the template, then call ductape_execute.',
972
1092
  inputSchema: executeInputSchema,
973
1093
  },
974
1094
  executeHandler,
@@ -1011,11 +1131,37 @@ async function main() {
1011
1131
  },
1012
1132
  schemaHandler,
1013
1133
  );
1134
+ server.registerTool(
1135
+ 'ductape_cli',
1136
+ {
1137
+ title: 'Ductape CLI',
1138
+ description:
1139
+ 'Run a Ductape CLI command for administrative operations.\n\n' +
1140
+ 'USE THIS TOOL for any operation that creates or modifies platform configuration:\n' +
1141
+ ' - Creating or updating products (products create/update) and apps (apps create/update)\n' +
1142
+ ' - Importing an app from a Postman v2.1 or OpenAPI 3.0 file: "apps import <file> -t postman|openapi"\n' +
1143
+ ' - Managing resources via "resources <type> <verb>" (databases, storage, caches…)\n' +
1144
+ ' - Managing cloud connections and cloud-linked resources\n' +
1145
+ ' - Listing workspaces, products, secrets\n' +
1146
+ ' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
1147
+ ' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
1148
+ ' - Running database migrations: "db migrate", "db schema generate"\n\n' +
1149
+ 'NOTE: Environments, app actions, auths, features, quotas, fallbacks, and jobs are ' +
1150
+ 'configured in the Workbench UI — the CLI does not have commands for them.\n\n' +
1151
+ 'DO NOT use ductape_execute for admin operations — it uses a publishable key which only ' +
1152
+ 'covers runtime operations. Administrative operations will fail with "Authentication failed".\n\n' +
1153
+ 'The CLI uses the user\'s local logged-in session (ductape login). ' +
1154
+ 'If the CLI is not installed, this tool will return install instructions automatically.',
1155
+ inputSchema: cliInputSchema,
1156
+ },
1157
+ cliHandler,
1158
+ );
1014
1159
  } else if (typeof server.tool === 'function') {
1015
1160
  server.tool('ductape_execute', executeInputSchema.shape, executeHandler);
1016
1161
  server.tool('ductape_generate_payload', payloadGenerateInputSchema.shape, payloadGenerateHandler);
1017
1162
  server.tool('ductape_generate_snippet', snippetGenerateInputSchema.shape, snippetGenerateHandler);
1018
1163
  server.tool('ductape_schema', schemaInputSchema.shape, schemaHandler);
1164
+ server.tool('ductape_cli', cliInputSchema.shape, cliHandler);
1019
1165
  } else {
1020
1166
  console.error('MCP server does not expose .registerTool() or .tool()');
1021
1167
  process.exit(1);