@ductape/mcp 0.1.2 → 0.1.4

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 +134 -19
  2. package/package.json +1 -1
  3. package/src/index.ts +146 -20
package/dist/index.js CHANGED
@@ -9,6 +9,8 @@
9
9
  * publishable_key on every tool call. Per-call publishable_key still overrides
10
10
  * the env var when provided.
11
11
  */
12
+ import { createRequire } from 'module';
13
+ import { execSync } from 'child_process';
12
14
  import { z } from 'zod';
13
15
  import { executeViaProxy, generateExecutablePayload, getAssetSchemas, } from './proxy-client.js';
14
16
  const MODULES = [
@@ -23,18 +25,26 @@ const MODULES = [
23
25
  const METHOD_DOCS = `
24
26
  ━━━ TOOL SELECTION GUIDE ━━━
25
27
 
26
- 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:
27
29
 
28
- 1. ASSET CREATION / UPDATE (create, update, add, register…)
29
- Input shape is fixed by the SDK's Joi validators.
30
- Call ductape_schema first to discover required fields and enum values.
31
- Then call ductape_execute with the filled params.
32
- EXCEPTION — features.create / quotas.create / fallback.create have TWO distinct input fields:
33
- a) Top-level "input": a SCHEMA DECLARATION — defines what fields the asset accepts at runtime.
34
- Construct this yourself based on what the caller will provide. Do NOT use ductape_generate_payload for this.
35
- b) options[].input (quotas/fallbacks) or steps[].input (features): maps declared inputs the underlying action's fields.
36
- ← CALL ductape_generate_payload for the target action to discover what fields it accepts,
37
- 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("product create --name \\"My Product\\" --tag my-product")
35
+ ductape_cli("environments list my-product")
36
+ ductape_cli("cloud connections list")
37
+ If the CLI is not installed, ductape_cli will return install instructions automatically.
38
+
39
+ 1. ASSET CREATION / UPDATE (create, update, add, register… for ANY asset type)
40
+ ALL creation and update operations require an access key and CANNOT go through ductape_execute.
41
+ → Use ductape_cli for every create/update operation. Examples:
42
+ ductape_cli("app create --name \\"Email Service\\" --description \\"Transactional email\\"")
43
+ ductape_cli("apps list")
44
+ ductape_cli("actions list my-app-tag")
45
+ This applies to: products, apps, actions, auths, environments, databases, storage,
46
+ graphs, vectors, brokers, sessions, notifications, jobs, features, quotas, fallbacks,
47
+ healthchecks, caches, secrets, and webhooks.
38
48
 
39
49
  2. RUNTIME OPERATIONS (run, dispatch, execute, start, send, produce, query, insert, update, delete…)
40
50
  The "input" field shape is product- and operation-specific — it is NOT derivable from Joi validators.
@@ -776,6 +786,68 @@ function buildSnippet(language, payload, operationFamily, method) {
776
786
  ? buildPythonSnippet(payload, operationFamily, method)
777
787
  : buildTypeScriptSnippet(payload, operationFamily, method);
778
788
  }
789
+ // ─── CLI helpers ─────────────────────────────────────────────────────────────
790
+ const ADMIN_SUBCOMMANDS = [
791
+ 'products', 'product',
792
+ 'apps', 'app',
793
+ 'workspaces', 'workspace',
794
+ 'environments', 'environment',
795
+ 'cloud',
796
+ 'secrets',
797
+ 'databases',
798
+ 'storage',
799
+ 'graphs',
800
+ 'vectors',
801
+ 'brokers',
802
+ 'notifications',
803
+ 'sessions',
804
+ 'caches',
805
+ 'jobs',
806
+ 'generate',
807
+ 'resources',
808
+ 'init',
809
+ 'login',
810
+ 'logout',
811
+ 'whoami',
812
+ ];
813
+ function checkCli() {
814
+ try {
815
+ const out = execSync('ductape --version', { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
816
+ return { available: true, version: out || 'unknown' };
817
+ }
818
+ catch {
819
+ return { available: false };
820
+ }
821
+ }
822
+ function runCli(command) {
823
+ const first = command.trim().split(/\s+/)[0];
824
+ if (!ADMIN_SUBCOMMANDS.includes(first)) {
825
+ return {
826
+ success: false,
827
+ output: `Subcommand "${first}" is not in the allowed admin subcommand list. Allowed: ${ADMIN_SUBCOMMANDS.join(', ')}.`,
828
+ };
829
+ }
830
+ try {
831
+ const output = execSync(`ductape ${command}`, {
832
+ encoding: 'utf8',
833
+ timeout: 30000,
834
+ stdio: ['pipe', 'pipe', 'pipe'],
835
+ });
836
+ return { success: true, output: output.trim() };
837
+ }
838
+ catch (err) {
839
+ const msg = (err.stderr || err.stdout || err.message || String(err)).trim();
840
+ return { success: false, output: msg };
841
+ }
842
+ }
843
+ const cliInputSchema = z.object({
844
+ command: z.string().describe('The ductape CLI command to run, without the leading "ductape" word. ' +
845
+ 'Examples: "products list", "product create --name \\"My Product\\" --tag my-product", ' +
846
+ '"environments list my-product", "cloud connections list".\n\n' +
847
+ 'Use this tool for ALL administrative operations: creating or updating products, apps, ' +
848
+ 'environments, resources, cloud connections, and workspace configuration.\n\n' +
849
+ 'The CLI uses the user\'s local logged-in session (ductape login) — no key is required.'),
850
+ });
779
851
  async function loadMcpSdk() {
780
852
  try {
781
853
  const [{ McpServer }, { StdioServerTransport }] = await Promise.all([
@@ -805,8 +877,7 @@ async function loadMcpSdk() {
805
877
  function handleCliFlags() {
806
878
  const arg = process.argv[2];
807
879
  if (arg === '--version' || arg === '-v') {
808
- // eslint-disable-next-line @typescript-eslint/no-require-imports
809
- const { version } = require('../package.json');
880
+ const { version } = createRequire(import.meta.url)('../package.json');
810
881
  process.stdout.write(version + '\n');
811
882
  return true;
812
883
  }
@@ -831,6 +902,33 @@ async function main() {
831
902
  const { McpServer, StdioServerTransport } = await loadMcpSdk();
832
903
  const server = new McpServer({ name: 'ductape-mcp', version: '0.1.0' });
833
904
  const transport = new StdioServerTransport();
905
+ const cliHandler = async (args) => {
906
+ const cli = checkCli();
907
+ if (!cli.available) {
908
+ return {
909
+ content: [{
910
+ type: 'text',
911
+ text: [
912
+ 'The Ductape CLI is not installed or not in PATH.',
913
+ '',
914
+ 'Install it with:',
915
+ ' npm install --global @ductape/cli',
916
+ '',
917
+ 'Then log in:',
918
+ ' ductape login',
919
+ '',
920
+ 'After logging in, retry this operation.',
921
+ ].join('\n'),
922
+ }],
923
+ isError: true,
924
+ };
925
+ }
926
+ const result = runCli(args.command);
927
+ return {
928
+ content: [{ type: 'text', text: result.output || '(no output)' }],
929
+ ...(result.success ? {} : { isError: true }),
930
+ };
931
+ };
834
932
  const executeHandler = async (args) => {
835
933
  try {
836
934
  const key = args.publishable_key || process.env.DUCTAPE_PUBLISHABLE_KEY;
@@ -899,12 +997,14 @@ async function main() {
899
997
  if (typeof server.registerTool === 'function') {
900
998
  server.registerTool('ductape_execute', {
901
999
  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.',
1000
+ description: 'Execute a Ductape SDK RUNTIME operation via the backend proxy (publishable key).\n\n' +
1001
+ 'ONLY use this tool for runtime operations: run, dispatch, execute, start, send, produce, query, insert, update, delete.\n' +
1002
+ 'Do NOT use this for creating or updating any asset (product, app, action, environment, database, storage, etc.) — ' +
1003
+ 'those require an access key. Use ductape_cli for all creation and update operations.\n\n' +
1004
+ 'Two-step rule for runtime operations:\n' +
1005
+ ' 1. Call ductape_generate_payload first to get the canonical payload template.\n' +
1006
+ ' This reveals the exact "input" field keys — they are product/env/operation-specific.\n' +
1007
+ ' 2. Fill in the values from the template, then call ductape_execute.',
908
1008
  inputSchema: executeInputSchema,
909
1009
  }, executeHandler);
910
1010
  server.registerTool('ductape_generate_payload', {
@@ -931,12 +1031,27 @@ async function main() {
931
1031
  'Pass module="app" or module="product" to scope the result.',
932
1032
  inputSchema: schemaInputSchema,
933
1033
  }, schemaHandler);
1034
+ server.registerTool('ductape_cli', {
1035
+ title: 'Ductape CLI',
1036
+ description: 'Run a Ductape CLI command for administrative operations.\n\n' +
1037
+ 'USE THIS TOOL for any operation that creates or modifies platform configuration:\n' +
1038
+ ' - Creating or updating products, apps, environments\n' +
1039
+ ' - Managing cloud connections and resources\n' +
1040
+ ' - Listing workspaces, products, secrets\n' +
1041
+ ' - Any operation that would require an access key via the SDK\n\n' +
1042
+ 'DO NOT use ductape_execute for these — it uses a publishable key which only ' +
1043
+ 'covers runtime operations. Administrative operations will fail with "Authentication failed".\n\n' +
1044
+ 'The CLI uses the user\'s local logged-in session (ductape login). ' +
1045
+ 'If the CLI is not installed, this tool will return install instructions automatically.',
1046
+ inputSchema: cliInputSchema,
1047
+ }, cliHandler);
934
1048
  }
935
1049
  else if (typeof server.tool === 'function') {
936
1050
  server.tool('ductape_execute', executeInputSchema.shape, executeHandler);
937
1051
  server.tool('ductape_generate_payload', payloadGenerateInputSchema.shape, payloadGenerateHandler);
938
1052
  server.tool('ductape_generate_snippet', snippetGenerateInputSchema.shape, snippetGenerateHandler);
939
1053
  server.tool('ductape_schema', schemaInputSchema.shape, schemaHandler);
1054
+ server.tool('ductape_cli', cliInputSchema.shape, cliHandler);
940
1055
  }
941
1056
  else {
942
1057
  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.2",
3
+ "version": "0.1.4",
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
@@ -10,6 +10,8 @@
10
10
  * the env var when provided.
11
11
  */
12
12
 
13
+ import { createRequire } from 'module';
14
+ import { execSync } from 'child_process';
13
15
  import { z } from 'zod';
14
16
  import {
15
17
  executeViaProxy,
@@ -34,18 +36,26 @@ const MODULES: SDKModule[] = [
34
36
  const METHOD_DOCS = `
35
37
  ━━━ TOOL SELECTION GUIDE ━━━
36
38
 
37
- There are two categories of SDK operations. Use the right tool for each:
38
-
39
- 1. ASSET CREATION / UPDATE (create, update, add, register…)
40
- Input shape is fixed by the SDK's Joi validators.
41
- Call ductape_schema first to discover required fields and enum values.
42
- Then call ductape_execute with the filled params.
43
- EXCEPTION — features.create / quotas.create / fallback.create have TWO distinct input fields:
44
- a) Top-level "input": a SCHEMA DECLARATION — defines what fields the asset accepts at runtime.
45
- Construct this yourself based on what the caller will provide. Do NOT use ductape_generate_payload for this.
46
- b) options[].input (quotas/fallbacks) or steps[].input (features): maps declared inputs the underlying action's fields.
47
- ← CALL ductape_generate_payload for the target action to discover what fields it accepts,
48
- 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("product create --name \\"My Product\\" --tag my-product")
46
+ ductape_cli("environments list my-product")
47
+ ductape_cli("cloud connections list")
48
+ If the CLI is not installed, ductape_cli will return install instructions automatically.
49
+
50
+ 1. ASSET CREATION / UPDATE (create, update, add, register… for ANY asset type)
51
+ ALL creation and update operations require an access key and CANNOT go through ductape_execute.
52
+ → Use ductape_cli for every create/update operation. Examples:
53
+ ductape_cli("app create --name \\"Email Service\\" --description \\"Transactional email\\"")
54
+ ductape_cli("apps list")
55
+ ductape_cli("actions list my-app-tag")
56
+ This applies to: products, apps, actions, auths, environments, databases, storage,
57
+ graphs, vectors, brokers, sessions, notifications, jobs, features, quotas, fallbacks,
58
+ healthchecks, caches, secrets, and webhooks.
49
59
 
50
60
  2. RUNTIME OPERATIONS (run, dispatch, execute, start, send, produce, query, insert, update, delete…)
51
61
  The "input" field shape is product- and operation-specific — it is NOT derivable from Joi validators.
@@ -819,6 +829,73 @@ function buildSnippet(
819
829
  }
820
830
 
821
831
 
832
+ // ─── CLI helpers ─────────────────────────────────────────────────────────────
833
+
834
+ const ADMIN_SUBCOMMANDS = [
835
+ 'products', 'product',
836
+ 'apps', 'app',
837
+ 'workspaces', 'workspace',
838
+ 'environments', 'environment',
839
+ 'cloud',
840
+ 'secrets',
841
+ 'databases',
842
+ 'storage',
843
+ 'graphs',
844
+ 'vectors',
845
+ 'brokers',
846
+ 'notifications',
847
+ 'sessions',
848
+ 'caches',
849
+ 'jobs',
850
+ 'generate',
851
+ 'resources',
852
+ 'init',
853
+ 'login',
854
+ 'logout',
855
+ 'whoami',
856
+ ];
857
+
858
+ function checkCli(): { available: boolean; version?: string } {
859
+ try {
860
+ const out = execSync('ductape --version', { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
861
+ return { available: true, version: out || 'unknown' };
862
+ } catch {
863
+ return { available: false };
864
+ }
865
+ }
866
+
867
+ function runCli(command: string): { success: boolean; output: string } {
868
+ const first = command.trim().split(/\s+/)[0];
869
+ if (!ADMIN_SUBCOMMANDS.includes(first)) {
870
+ return {
871
+ success: false,
872
+ output: `Subcommand "${first}" is not in the allowed admin subcommand list. Allowed: ${ADMIN_SUBCOMMANDS.join(', ')}.`,
873
+ };
874
+ }
875
+ try {
876
+ const output = execSync(`ductape ${command}`, {
877
+ encoding: 'utf8',
878
+ timeout: 30000,
879
+ stdio: ['pipe', 'pipe', 'pipe'],
880
+ });
881
+ return { success: true, output: output.trim() };
882
+ } catch (err: any) {
883
+ const msg = (err.stderr || err.stdout || err.message || String(err)).trim();
884
+ return { success: false, output: msg };
885
+ }
886
+ }
887
+
888
+ const cliInputSchema = z.object({
889
+ command: z.string().describe(
890
+ 'The ductape CLI command to run, without the leading "ductape" word. ' +
891
+ 'Examples: "products list", "product create --name \\"My Product\\" --tag my-product", ' +
892
+ '"environments list my-product", "cloud connections list".\n\n' +
893
+ 'Use this tool for ALL administrative operations: creating or updating products, apps, ' +
894
+ 'environments, resources, cloud connections, and workspace configuration.\n\n' +
895
+ 'The CLI uses the user\'s local logged-in session (ductape login) — no key is required.',
896
+ ),
897
+ });
898
+
822
899
  async function loadMcpSdk(): Promise<{
823
900
  McpServer: new (info: { name: string; version: string }) => any;
824
901
  StdioServerTransport: new () => any;
@@ -854,8 +931,7 @@ async function loadMcpSdk(): Promise<{
854
931
  function handleCliFlags(): boolean {
855
932
  const arg = process.argv[2];
856
933
  if (arg === '--version' || arg === '-v') {
857
- // eslint-disable-next-line @typescript-eslint/no-require-imports
858
- const { version } = require('../package.json') as { version: string };
934
+ const { version } = createRequire(import.meta.url)('../package.json') as { version: string };
859
935
  process.stdout.write(version + '\n');
860
936
  return true;
861
937
  }
@@ -884,6 +960,34 @@ async function main() {
884
960
  const server = new McpServer({ name: 'ductape-mcp', version: '0.1.0' });
885
961
  const transport = new StdioServerTransport();
886
962
 
963
+ const cliHandler = async (args: { command: string }) => {
964
+ const cli = checkCli();
965
+ if (!cli.available) {
966
+ return {
967
+ content: [{
968
+ type: 'text',
969
+ text: [
970
+ 'The Ductape CLI is not installed or not in PATH.',
971
+ '',
972
+ 'Install it with:',
973
+ ' npm install --global @ductape/cli',
974
+ '',
975
+ 'Then log in:',
976
+ ' ductape login',
977
+ '',
978
+ 'After logging in, retry this operation.',
979
+ ].join('\n'),
980
+ }],
981
+ isError: true,
982
+ };
983
+ }
984
+ const result = runCli(args.command);
985
+ return {
986
+ content: [{ type: 'text', text: result.output || '(no output)' }],
987
+ ...(result.success ? {} : { isError: true }),
988
+ };
989
+ };
990
+
887
991
  const executeHandler = async (args: { publishable_key?: string; module: SDKModule; method: string; params: unknown[] }) => {
888
992
  try {
889
993
  const key = args.publishable_key || process.env.DUCTAPE_PUBLISHABLE_KEY;
@@ -963,12 +1067,14 @@ async function main() {
963
1067
  {
964
1068
  title: 'Ductape SDK Execute',
965
1069
  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.',
1070
+ 'Execute a Ductape SDK RUNTIME operation via the backend proxy (publishable key).\n\n' +
1071
+ 'ONLY use this tool for runtime operations: run, dispatch, execute, start, send, produce, query, insert, update, delete.\n' +
1072
+ 'Do NOT use this for creating or updating any asset (product, app, action, environment, database, storage, etc.) — ' +
1073
+ 'those require an access key. Use ductape_cli for all creation and update operations.\n\n' +
1074
+ 'Two-step rule for runtime operations:\n' +
1075
+ ' 1. Call ductape_generate_payload first to get the canonical payload template.\n' +
1076
+ ' This reveals the exact "input" field keys — they are product/env/operation-specific.\n' +
1077
+ ' 2. Fill in the values from the template, then call ductape_execute.',
972
1078
  inputSchema: executeInputSchema,
973
1079
  },
974
1080
  executeHandler,
@@ -1011,11 +1117,31 @@ async function main() {
1011
1117
  },
1012
1118
  schemaHandler,
1013
1119
  );
1120
+ server.registerTool(
1121
+ 'ductape_cli',
1122
+ {
1123
+ title: 'Ductape CLI',
1124
+ description:
1125
+ 'Run a Ductape CLI command for administrative operations.\n\n' +
1126
+ 'USE THIS TOOL for any operation that creates or modifies platform configuration:\n' +
1127
+ ' - Creating or updating products, apps, environments\n' +
1128
+ ' - Managing cloud connections and resources\n' +
1129
+ ' - Listing workspaces, products, secrets\n' +
1130
+ ' - Any operation that would require an access key via the SDK\n\n' +
1131
+ 'DO NOT use ductape_execute for these — it uses a publishable key which only ' +
1132
+ 'covers runtime operations. Administrative operations will fail with "Authentication failed".\n\n' +
1133
+ 'The CLI uses the user\'s local logged-in session (ductape login). ' +
1134
+ 'If the CLI is not installed, this tool will return install instructions automatically.',
1135
+ inputSchema: cliInputSchema,
1136
+ },
1137
+ cliHandler,
1138
+ );
1014
1139
  } else if (typeof server.tool === 'function') {
1015
1140
  server.tool('ductape_execute', executeInputSchema.shape, executeHandler);
1016
1141
  server.tool('ductape_generate_payload', payloadGenerateInputSchema.shape, payloadGenerateHandler);
1017
1142
  server.tool('ductape_generate_snippet', snippetGenerateInputSchema.shape, snippetGenerateHandler);
1018
1143
  server.tool('ductape_schema', schemaInputSchema.shape, schemaHandler);
1144
+ server.tool('ductape_cli', cliInputSchema.shape, cliHandler);
1019
1145
  } else {
1020
1146
  console.error('MCP server does not expose .registerTool() or .tool()');
1021
1147
  process.exit(1);