@ductape/mcp 0.1.5 → 0.1.6

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/dist/index.js CHANGED
@@ -806,6 +806,9 @@ function buildSnippet(language, payload, operationFamily, method) {
806
806
  : buildTypeScriptSnippet(payload, operationFamily, method);
807
807
  }
808
808
  // ─── CLI helpers ─────────────────────────────────────────────────────────────
809
+ // Per-process cache: avoids re-running whoami / workspaces use on every call.
810
+ let authState = 'unknown';
811
+ let workspaceSynced = false;
809
812
  const ADMIN_SUBCOMMANDS = [
810
813
  'login', 'logout', 'whoami',
811
814
  'profiles',
@@ -829,6 +832,29 @@ function checkCli() {
829
832
  return { available: false };
830
833
  }
831
834
  }
835
+ function checkLoginState() {
836
+ try {
837
+ execSync('ductape whoami --json', { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] });
838
+ authState = 'ok';
839
+ return 'ok';
840
+ }
841
+ catch {
842
+ authState = 'none';
843
+ return 'none';
844
+ }
845
+ }
846
+ function syncWorkspace() {
847
+ const target = process.env.DUCTAPE_WORKSPACE;
848
+ workspaceSynced = true; // mark done regardless so we don't retry on every call
849
+ if (!target)
850
+ return;
851
+ try {
852
+ execSync(`ductape workspaces use "${target}"`, { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] });
853
+ }
854
+ catch {
855
+ // best-effort; if it fails the user will see workspace-mismatch errors on subsequent commands
856
+ }
857
+ }
832
858
  function runCli(command) {
833
859
  const first = command.trim().split(/\s+/)[0];
834
860
  if (!ADMIN_SUBCOMMANDS.includes(first)) {
@@ -837,8 +863,14 @@ function runCli(command) {
837
863
  output: `Subcommand "${first}" is not in the allowed admin subcommand list. Allowed: ${ADMIN_SUBCOMMANDS.join(', ')}.`,
838
864
  };
839
865
  }
866
+ // Auto-inject --workspace on login if DUCTAPE_WORKSPACE is set and caller hasn't specified one
867
+ let finalCommand = command;
868
+ const ws = process.env.DUCTAPE_WORKSPACE;
869
+ if (first === 'login' && ws && !command.includes('--workspace') && !command.includes('--skip-workspace-select')) {
870
+ finalCommand = `${command} --workspace "${ws}"`;
871
+ }
840
872
  try {
841
- const output = execSync(`ductape ${command}`, {
873
+ const output = execSync(`ductape ${finalCommand}`, {
842
874
  encoding: 'utf8',
843
875
  timeout: 30000,
844
876
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -937,7 +969,45 @@ async function main() {
937
969
  isError: true,
938
970
  };
939
971
  }
972
+ const firstWord = args.command.trim().split(/\s+/)[0];
973
+ const isAuthCommand = firstWord === 'login' || firstWord === 'logout';
974
+ if (!isAuthCommand) {
975
+ // Check login status once per process (cached after first call)
976
+ if (authState === 'unknown') {
977
+ checkLoginState();
978
+ }
979
+ if (authState === 'none') {
980
+ const wsFlag = process.env.DUCTAPE_WORKSPACE ? ` --workspace "${process.env.DUCTAPE_WORKSPACE}"` : '';
981
+ return {
982
+ content: [{
983
+ type: 'text',
984
+ text: [
985
+ 'Not logged in to the Ductape CLI.',
986
+ '',
987
+ 'Ask the user for their Ductape email and password, then call:',
988
+ ` ductape_cli("login --email <email> --password <password>${wsFlag}")`,
989
+ '',
990
+ 'Or the user can run `ductape login` in their terminal and then retry.',
991
+ ].join('\n'),
992
+ }],
993
+ isError: true,
994
+ };
995
+ }
996
+ // Sync to the configured workspace once per process (best-effort)
997
+ if (!workspaceSynced) {
998
+ syncWorkspace();
999
+ }
1000
+ }
940
1001
  const result = runCli(args.command);
1002
+ // Update cached state after auth commands
1003
+ if (firstWord === 'login' && result.success) {
1004
+ authState = 'ok';
1005
+ workspaceSynced = false; // re-sync workspace after fresh login
1006
+ }
1007
+ if (firstWord === 'logout' && result.success) {
1008
+ authState = 'none';
1009
+ workspaceSynced = false;
1010
+ }
941
1011
  return {
942
1012
  content: [{ type: 'text', text: result.output || '(no output)' }],
943
1013
  ...(result.success ? {} : { isError: true }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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
@@ -850,6 +850,10 @@ function buildSnippet(
850
850
 
851
851
  // ─── CLI helpers ─────────────────────────────────────────────────────────────
852
852
 
853
+ // Per-process cache: avoids re-running whoami / workspaces use on every call.
854
+ let authState: 'unknown' | 'ok' | 'none' = 'unknown';
855
+ let workspaceSynced = false;
856
+
853
857
  const ADMIN_SUBCOMMANDS = [
854
858
  'login', 'logout', 'whoami',
855
859
  'profiles',
@@ -874,6 +878,28 @@ function checkCli(): { available: boolean; version?: string } {
874
878
  }
875
879
  }
876
880
 
881
+ function checkLoginState(): 'ok' | 'none' {
882
+ try {
883
+ execSync('ductape whoami --json', { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] });
884
+ authState = 'ok';
885
+ return 'ok';
886
+ } catch {
887
+ authState = 'none';
888
+ return 'none';
889
+ }
890
+ }
891
+
892
+ function syncWorkspace(): void {
893
+ const target = process.env.DUCTAPE_WORKSPACE;
894
+ workspaceSynced = true; // mark done regardless so we don't retry on every call
895
+ if (!target) return;
896
+ try {
897
+ execSync(`ductape workspaces use "${target}"`, { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] });
898
+ } catch {
899
+ // best-effort; if it fails the user will see workspace-mismatch errors on subsequent commands
900
+ }
901
+ }
902
+
877
903
  function runCli(command: string): { success: boolean; output: string } {
878
904
  const first = command.trim().split(/\s+/)[0];
879
905
  if (!ADMIN_SUBCOMMANDS.includes(first)) {
@@ -882,8 +908,14 @@ function runCli(command: string): { success: boolean; output: string } {
882
908
  output: `Subcommand "${first}" is not in the allowed admin subcommand list. Allowed: ${ADMIN_SUBCOMMANDS.join(', ')}.`,
883
909
  };
884
910
  }
911
+ // Auto-inject --workspace on login if DUCTAPE_WORKSPACE is set and caller hasn't specified one
912
+ let finalCommand = command;
913
+ const ws = process.env.DUCTAPE_WORKSPACE;
914
+ if (first === 'login' && ws && !command.includes('--workspace') && !command.includes('--skip-workspace-select')) {
915
+ finalCommand = `${command} --workspace "${ws}"`;
916
+ }
885
917
  try {
886
- const output = execSync(`ductape ${command}`, {
918
+ const output = execSync(`ductape ${finalCommand}`, {
887
919
  encoding: 'utf8',
888
920
  timeout: 30000,
889
921
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -995,7 +1027,50 @@ async function main() {
995
1027
  isError: true,
996
1028
  };
997
1029
  }
1030
+
1031
+ const firstWord = args.command.trim().split(/\s+/)[0];
1032
+ const isAuthCommand = firstWord === 'login' || firstWord === 'logout';
1033
+
1034
+ if (!isAuthCommand) {
1035
+ // Check login status once per process (cached after first call)
1036
+ if (authState === 'unknown') {
1037
+ checkLoginState();
1038
+ }
1039
+ if (authState === 'none') {
1040
+ const wsFlag = process.env.DUCTAPE_WORKSPACE ? ` --workspace "${process.env.DUCTAPE_WORKSPACE}"` : '';
1041
+ return {
1042
+ content: [{
1043
+ type: 'text',
1044
+ text: [
1045
+ 'Not logged in to the Ductape CLI.',
1046
+ '',
1047
+ 'Ask the user for their Ductape email and password, then call:',
1048
+ ` ductape_cli("login --email <email> --password <password>${wsFlag}")`,
1049
+ '',
1050
+ 'Or the user can run `ductape login` in their terminal and then retry.',
1051
+ ].join('\n'),
1052
+ }],
1053
+ isError: true,
1054
+ };
1055
+ }
1056
+ // Sync to the configured workspace once per process (best-effort)
1057
+ if (!workspaceSynced) {
1058
+ syncWorkspace();
1059
+ }
1060
+ }
1061
+
998
1062
  const result = runCli(args.command);
1063
+
1064
+ // Update cached state after auth commands
1065
+ if (firstWord === 'login' && result.success) {
1066
+ authState = 'ok';
1067
+ workspaceSynced = false; // re-sync workspace after fresh login
1068
+ }
1069
+ if (firstWord === 'logout' && result.success) {
1070
+ authState = 'none';
1071
+ workspaceSynced = false;
1072
+ }
1073
+
999
1074
  return {
1000
1075
  content: [{ type: 'text', text: result.output || '(no output)' }],
1001
1076
  ...(result.success ? {} : { isError: true }),