Skip to main content
Glama

onboard

Guide users through SecureCode setup from Claude Code, handling signup, API key creation, MCP configuration, and SDK installation in multiple steps.

Instructions

Start or continue the SecureCode onboarding. Guides the user through signup, .env import, API key creation, MCP configuration, and optional SDK setup — all from Claude Code. Call this tool multiple times to progress through the steps.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionNoAction to perform. "start" (default) progresses through signup/import steps. "configure-auto" lets the agent write the API key to the MCP config file automatically. "configure-manual" shows the API key so the user can configure it themselves. "setup-sdk" returns SDK installation instructions for the agent to execute. "add-environment" guides through adding secrets for a new environment (e.g., production) without repeating the full onboarding. "select-secrets" lists existing secrets in the vault by tags so the user can select which ones to use (recovery/skip import).
configPathNoPath to the MCP config file (.mcp.json or claude.json). Only needed for configure-auto if auto-detection fails.
selectedProjectNoProject tag selected by user (from select-secrets action). Used to configure .securecoderc.
selectedEnvNoEnvironment tag selected by user (from select-secrets action). Used to configure .securecoderc.

Implementation Reference

  • Tool definition and implementation for "onboard".
      'onboard',
      'Start or continue the SecureCode onboarding. Guides the user through signup, .env import, API key creation, MCP configuration, and optional SDK setup — all from Claude Code. Call this tool multiple times to progress through the steps.',
      {
        action: z.enum(['start', 'configure-auto', 'configure-manual', 'setup-sdk', 'add-environment', 'select-secrets']).optional()
          .describe('Action to perform. "start" (default) progresses through signup/import steps. "configure-auto" lets the agent write the API key to the MCP config file automatically. "configure-manual" shows the API key so the user can configure it themselves. "setup-sdk" returns SDK installation instructions for the agent to execute. "add-environment" guides through adding secrets for a new environment (e.g., production) without repeating the full onboarding. "select-secrets" lists existing secrets in the vault by tags so the user can select which ones to use (recovery/skip import).'),
        configPath: z.string().optional()
          .describe('Path to the MCP config file (.mcp.json or claude.json). Only needed for configure-auto if auto-detection fails.'),
        selectedProject: z.string().optional()
          .describe('Project tag selected by user (from select-secrets action). Used to configure .securecoderc.'),
        selectedEnv: z.string().optional()
          .describe('Environment tag selected by user (from select-secrets action). Used to configure .securecoderc.'),
      },
      async ({ action, configPath, selectedProject, selectedEnv }) => {
        try {
          const effectiveAction = action || 'start';
    
          // ── Select secrets from vault (tag-based recovery) ──
    
          if (effectiveAction === 'select-secrets') {
            if (!hasApiKey) {
              return wrapResponse([{
                type: 'text',
                text: 'The MCP server does not have an API key configured yet. Complete the onboarding first (run onboard).',
              }], true);
            }
    
            // If project/env were selected, save them to .securecoderc and confirm
            if (selectedProject || selectedEnv) {
              const proj = selectedProject && selectedProject !== '(untagged)' ? selectedProject : null;
              const env = selectedEnv && selectedEnv !== '(untagged)' ? selectedEnv : null;
              if (proj) onboardingProject = proj;
              if (env) onboardingEnv = env;
    
              // Update .securecoderc with selected project/env (keep existing API key)
              const rcPath = join(process.cwd(), '.securecoderc');
              if (existsSync(rcPath)) {
                let content = readFileSync(rcPath, 'utf-8');
                // Remove old project/env lines
                const lines = content.split('\n').filter(l => {
                  const t = l.trim();
                  return !t.startsWith('SECURECODE_PROJECT=') && !t.startsWith('SECURECODE_ENV=');
                });
                if (proj) lines.push(`SECURECODE_PROJECT=${proj}`);
                if (env) lines.push(`SECURECODE_ENV=${env}`);
                writeFileSync(rcPath, lines.filter(l => l !== '').join('\n') + '\n', { mode: 0o600 });
              }
    
              // List actual secrets for confirmation
              const client = getClient();
              const secrets = await client.listSecrets();
              const filtered = secrets.filter(s => {
                if (proj && s.tags?.project !== proj) return false;
                if (env && s.tags?.env !== env) return false;
                return true;
              });
    
              return wrapResponse([{
                type: 'text',
                text: [
                  `✓ Configuration updated: project=${proj || '(all)'}, env=${env || '(all)'}`,
                  '',
                  `Found ${filtered.length} secrets matching this selection:`,
                  ...filtered.map(s => `  - ${s.name}`),
                  '',
                  'These secrets are already in your vault and will be loaded by the SDK.',
                  '.securecoderc has been updated with the project/env configuration.',
                  '',
                  'Next: Set up the SDK to load these secrets at runtime.',
                  'AGENT INSTRUCTION: Call onboard with action "setup-sdk" to configure the SDK.',
                ].join('\n'),
              }]);
            }
    
            // No selection yet — list secrets grouped by tags
            let allSecrets: Array<{ name: string; tags: Record<string, string> }> = [];
            try {
              const client = getClient();
              const secrets = await client.listSecrets();
              allSecrets = secrets.map(s => ({ name: s.name, tags: s.tags || {} }));
            } catch {
              return wrapResponse([{
                type: 'text',
                text: 'Could not fetch secrets from the vault. Try again or run onboard(action: "start") to re-import.',
              }], true);
            }
    
            if (allSecrets.length === 0) {
              return wrapResponse([{
                type: 'text',
                text: [
                  'No secrets found in your vault.',
                  '',
                  'You need to import secrets first. Run onboard (action: "start") to begin the import flow.',
                ].join('\n'),
              }]);
            }
    
            // Group secrets by project/env
            const grouped = new Map<string, string[]>();
            for (const s of allSecrets) {
              const proj = s.tags.project || '(untagged)';
              const env = s.tags.env || '(untagged)';
              const key = `${proj}::${env}`;
              if (!grouped.has(key)) grouped.set(key, []);
              grouped.get(key)!.push(s.name);
            }
    
            const sections: string[] = [];
            for (const [key, names] of grouped) {
              const [proj, env] = key.split('::');
              sections.push(`### Project: ${proj}, Env: ${env} (${names.length} secrets)`);
              sections.push(...names.map(n => `  - [x] ${n}`));
              sections.push('');
            }
    
            return wrapResponse([{
              type: 'text',
              text: [
                '## Your Secrets in SecureCode',
                '',
                ...sections,
                'AGENT INSTRUCTION:',
                'Present this list to the user with ALL secrets selected by default (shown as checkboxes above).',
                'The user can deselect any they don\'t want for this project.',
                'After confirmation, call onboard with action: "select-secrets", selectedProject: "<project>", selectedEnv: "<env>"',
                'using the project/env from the group the user confirmed.',
                '',
                'If there are multiple groups, let the user pick which group(s) to use.',
                'If the user wants to import NEW secrets instead, call onboard with action: "start".',
              ].join('\n'),
            }]);
          }
    
          // ── Configure actions (after import is completed) ──
    
          if (effectiveAction === 'configure-auto' || effectiveAction === 'configure-manual') {
            if (!onboardingToken) {
              return wrapResponse([{
                type: 'text',
                text: 'No active onboarding session. Call onboard first to start the process.',
              }], true);
            }
    
            // Claim the API key (single-use)
            const claimRes = await onboardingFetch<Record<string, string>>(
              `/onboarding/${onboardingToken}/claim-key`,
              { method: 'POST' },
            );
    
            if ((claimRes as Record<string, unknown>).error) {
              const errMsg = (claimRes as Record<string, unknown>).error as string;
              // Key already claimed — check if it was auto-configured
              if (errMsg.includes('already claimed')) {
                onboardingToken = null; clearOnboardingTokenFromRc();
                return wrapResponse([{
                  type: 'text',
                  text: [
                    '✓ API key was already created for this onboarding session.',
                    '',
                    'If you need to reconfigure, create a new API key from the SecureCode dashboard:',
                    `${baseUrl}/en/settings`,
                  ].join('\n'),
                }]);
              }
              return wrapResponse([{ type: 'text', text: `Error: ${errMsg}` }], true);
            }
    
            const { apiKey: newKey, keyName } = claimRes;
    
            if (effectiveAction === 'configure-manual') {
              // User wants to see the key and do it themselves
              onboardingToken = null; clearOnboardingTokenFromRc();
              return wrapResponse([{
                type: 'text',
                text: [
                  `✓ API key "${keyName}" created.`,
                  '',
                  `API Key: ${newKey}`,
                  '',
                  'Add it to your MCP config file (.mcp.json or ~/.claude.json):',
                  '```json',
                  '{',
                  '  "mcpServers": {',
                  '    "securecode": {',
                  '      "command": "npx",',
                  '      "args": ["-y", "@securecode/mcp-server"]',
                  '    }',
                  '  }',
                  '}',
                  '```',
                  '',
                  'Create a .securecoderc file in your project root with:',
                  '```',
                  `SECURECODE_API_KEY=${newKey}`,
                  '```',
                  'Add .securecoderc to your .gitignore.',
                  'The MCP server reads the API key from .securecoderc automatically.',
                  '',
                  'After saving, close and reopen Claude Code to activate SecureCode.',
                  '',
                  '--- What\'s next? ---',
                  '',
                  'Right now your app still reads secrets from .env files.',
                  'With the SecureCode SDK, your app can load secrets directly from SecureCode at runtime.',
                  '',
                  'What you get:',
                  '  - Every access audited (who, when, from where) — visible in the dashboard',
                  '  - Security rules and email notifications on sensitive access',
                  '  - Secrets encrypted and under your control',
                  '  - No more .env files scattered across machines',
                  '',
                  'TELL THE USER: After reopening Claude Code, say "configure the SecureCode SDK in this project"',
                  'and I\'ll show you the options and set it up for you.',
                  '',
                  'AGENT INSTRUCTION: When the user asks to configure the SDK, call the onboard tool',
                  'with action "setup-sdk". This will list the user\'s secrets and return the setup options.',
                ].join('\n'),
              }]);
            }
    
            // Auto-configure: write the key to the config file
            const detected = configPath
              ? { primary: { path: configPath, isProject: configPath.includes('.mcp.json') }, conflicts: [] }
              : findMcpConfigPaths();
    
            if (!detected) {
              // No config file found — return key for manual setup
              onboardingToken = null; clearOnboardingTokenFromRc();
              return wrapResponse([{
                type: 'text',
                text: [
                  `✓ API key "${keyName}" created.`,
                  '',
                  'Could not find an MCP config file (.mcp.json or ~/.claude.json).',
                  `The API key is: ${newKey}`,
                  '',
                  'Create a .mcp.json file in your project root or ~/.claude.json with:',
                  '```json',
                  '{',
                  '  "mcpServers": {',
                  '    "securecode": {',
                  '      "command": "npx",',
                  '      "args": ["-y", "@securecode/mcp-server"]',
                  '    }',
                  '  }',
                  '}',
                  '```',
                  '',
                  `Then create a .securecoderc file in your project root with:`,
                  `SECURECODE_API_KEY=${newKey}`,
                  '',
                  'The MCP server reads the API key from .securecoderc automatically.',
                ].join('\n'),
              }]);
            }
    
            // Write the config to primary location (no API key in config — it lives in .securecoderc)
            writeMcpConfig(detected.primary.path);
            const configLabel = detected.primary.isProject ? 'project (.mcp.json)' : 'global (~/.claude.json)';
    
            // Write .securecoderc and update .gitignore
            writeSecurecodeRc(newKey, onboardingProject, onboardingEnv);
            updateGitignore();
    
            // Resolve conflicts: remove API key from env in other config files
            // (key now lives only in .securecoderc)
            const conflictsFixed: string[] = [];
            for (const conflict of detected.conflicts) {
              if (cleanMcpConfigEnv(conflict.path)) {
                conflictsFixed.push(conflict.path);
              }
            }
    
            // Also clean ~/.claude.json global config directly.
            // When user runs `claude mcp add securecode` before onboarding, it creates an entry
            // with env:{} — remove API key from there (it lives in .securecoderc only).
            try {
              const globalConfig = join(os.homedir(), '.claude.json');
              if (existsSync(globalConfig)) {
                const raw = readFileSync(globalConfig, 'utf-8');
                const parsed = JSON.parse(raw);
                let modified = false;
    
                // Clean top-level mcpServers entry
                if (parsed?.mcpServers?.securecode?.env) {
                  delete parsed.mcpServers.securecode.env;
                  modified = true;
                }
    
                // Clean per-project entries (path format varies on Windows)
                if (parsed?.projects && typeof parsed.projects === 'object') {
                  for (const [, proj] of Object.entries(parsed.projects)) {
                    const p = proj as Record<string, unknown>;
                    const mcpServers = p?.mcpServers as Record<string, Record<string, unknown>> | undefined;
                    if (mcpServers?.securecode?.env) {
                      delete mcpServers.securecode.env;
                      modified = true;
                    }
                  }
                }
    
                if (modified) {
                  writeFileSync(globalConfig, JSON.stringify(parsed, null, 2) + '\n');
                  if (!conflictsFixed.includes(globalConfig)) conflictsFixed.push(globalConfig);
                }
              }
            } catch { /* best-effort */ }
            onboardingToken = null; clearOnboardingTokenFromRc();
    
            return wrapResponse([{
              type: 'text',
              text: [
                '✓ Onboarding complete!',
                '',
                `API key "${keyName}" created and configured automatically.`,
                `Config written to: ${detected.primary.path} (${configLabel})`,
                'Created .securecoderc with API key and project config (added to .gitignore)',
                ...(conflictsFixed.length > 0
                  ? [`Also cleaned API key from env in: ${conflictsFixed.join(', ')} (key is now only in .securecoderc)`]
                  : []),
                '',
                '⚡ NEXT STEP: Close and reopen Claude Code to activate SecureCode.',
                '',
                'After reopening, your secrets are ready to use from Claude Code.',
                '',
                '--- What\'s next? ---',
                '',
                'Right now your app still reads secrets from .env files.',
                'With the SecureCode SDK, your app can load secrets directly from SecureCode at runtime.',
                '',
                'What you get:',
                '  - Every access audited (who, when, from where) — visible in the dashboard',
                '  - Security rules and email notifications on sensitive access',
                '  - Secrets encrypted and under your control',
                '  - No more .env files scattered across machines',
                '',
                'TELL THE USER: After reopening Claude Code, say "configure the SecureCode SDK in this project"',
                'and I\'ll show you the options and set it up for you.',
                '',
                'AGENT INSTRUCTION: When the user asks to configure the SDK, call the onboard tool',
                'with action "setup-sdk". This will list the user\'s secrets and return the setup options.',
                'Present the options to the user, let them choose, then execute the steps.',
                '',
                'IMPORTANT — MCP CONFIG TROUBLESHOOTING:',
                'The API key is stored ONLY in .securecoderc (never in MCP config env).',
                'If after restart the MCP gives "NO_API_KEY" error:',
                '  1. Check .securecoderc exists in the project root with SECURECODE_API_KEY=sc_...',
                '  2. Make sure .securecoderc is in the same directory where Claude Code is opened',
                '  3. If .securecoderc is missing, the user must run the onboarding again',
              ].join('\n'),
            }]);
          }
    
          // ── Add environment action (after onboarding is complete) ──
    
          if (effectiveAction === 'add-environment') {
            if (!hasApiKey) {
              return wrapResponse([{
                type: 'text',
                text: 'The MCP server does not have an API key configured yet. Complete the onboarding first (run onboard), then close and reopen Claude Code.',
              }], true);
            }
    
            // Get the base URL for the dashboard
            const baseUrl = (process.env.SECURECODE_API_URL || 'https://securecodehq.com').replace(/\/api$/, '').replace(/\/$/, '');
    
            return wrapResponse([{
              type: 'text',
              text: [
                '## Add New Environment',
                '',
                'To add secrets for a new environment (e.g., production, staging, testing):',
                '',
                `1. Open the SecureCode import page: ${baseUrl}/import`,
                '',
                '2. Select your project and choose the new environment',
                '   - Use the "Other..." option if your environment is not listed',
                '',
                '3. Import your .env file for that environment',
                '',
                '4. Update your local .securecoderc if needed:',
                '   ```',
                '   SECURECODE_ENV=production',
                '   ```',
                '',
                '5. Or use different SECURECODE_ENV values per deployment in your hosting platform',
                '',
                'Your existing secrets remain untouched. The new environment\'s secrets are tagged separately.',
                '',
                'AGENT INSTRUCTIONS:',
                '- Open the import URL in the browser for the user',
                '- After they confirm the import is done, ask if they want to update .securecoderc',
                '- If yes, update the SECURECODE_ENV value in .securecoderc',
                '- Tell the user that loadEnv() will automatically filter by the configured environment',
              ].join('\n'),
            }]);
          }
    
          // ── Setup SDK action (after MCP is configured and reconnected) ──
    
          if (effectiveAction === 'setup-sdk') {
            if (!hasApiKey) {
              return wrapResponse([{
                type: 'text',
                text: 'The MCP server does not have an API key configured yet. Complete the onboarding first (run onboard), then close and reopen Claude Code before setting up the SDK.',
              }], true);
            }
    
            // Fetch user's secrets to provide context
            let secretNames: string[] = [];
            let secretTags: Record<string, string>[] = [];
            try {
              const client = getClient();
              const secrets = await client.listSecrets();
              secretNames = secrets.map(s => s.name);
              secretTags = secrets.map(s => s.tags);
            } catch {
              // Best-effort — continue without secret list
            }
    
            // Detect project/env from secret tags
            const allTags = secretTags.reduce((acc, t) => ({ ...acc, ...t }), {} as Record<string, string>);
            const projectName = allTags.project || 'unknown';
            const envName = allTags.env || 'unknown';
    
            const secretListStr = secretNames.length > 0
              ? secretNames.map(n => `  - ${n}`).join('\n')
              : '  (no secrets found — import some first)';
    
            return wrapResponse([{
              type: 'text',
              text: [
                '## SecureCode SDK Setup',
                '',
                `The user has ${secretNames.length} secrets in SecureCode (project: ${projectName}, env: ${envName}):`,
                secretListStr,
                '',
                'Present these options to the user and let them choose:',
                '',
                '---',
                '',
                '### Option 1: loadEnv() — Load all secrets at startup (recommended)',
                '',
                'One line at your app\'s entry point loads ALL secrets into process.env.',
                'Your existing code doesn\'t change at all — process.env.DATABASE_URL etc. keep working.',
                '',
                'Best for: Most projects. Zero code changes beyond the setup.',
                '',
                '### Option 2: getSecret() — Access individual secrets on demand',
                '',
                'Fetch specific secrets when you need them, with full control over timing.',
                'Requires changing each process.env.X reference to await getSecret("X").',
                '',
                'Best for: Projects that only need 1-2 secrets, or when you want lazy loading.',
                '',
                '---',
                '',
                'AGENT INSTRUCTIONS — You MUST follow these steps exactly. Do NOT skip any step.',
                'Do NOT tell the user "it\'s already configured" without verifying the actual code.',
                '',
                '## For Option 1 (loadEnv):',
                '',
                'Step 1: Install the SDK if not already installed:',
                '```bash',
                'npm install @securecode/sdk',
                '```',
                '',
                'Step 2: You MUST write code in the app\'s entry point. Find the right file:',
                '- Next.js: src/instrumentation.ts (create if it doesn\'t exist)',
                '- Express/Fastify: Top of server.ts/app.ts, before database connections',
                '- Plain Node.js: Top of index.ts/main.ts',
                '',
                'You MUST add this code. Read the file first, then edit it:',
                '',
                'For Next.js (instrumentation.ts):',
                '```typescript',
                'export async function register() {',
                '  if (process.env.NEXT_RUNTIME === "nodejs") {',
                '    const { loadEnv } = await import("@securecode/sdk");',
                '    await loadEnv({ override: true });',
                '  }',
                '}',
                '```',
                '',
                'IMPORTANT: The NEXT_RUNTIME guard and dynamic import() are required because',
                'instrumentation.ts is compiled for both Node.js and Edge runtimes.',
                'The SDK uses Node.js built-ins (fs, os, path) that break the Edge compilation.',
                'A top-level static import will cause webpack errors.',
                '',
                'NOTE: In Next.js 14, instrumentation requires `experimental: { instrumentationHook: true }`',
                'in next.config.js. Next.js 15+ enables it by default. If the project uses Next.js 14,',
                'check next.config and add the flag if missing.',
                '',
                'For other frameworks (top of entry file):',
                '```typescript',
                'import { loadEnv } from "@securecode/sdk";',
                'await loadEnv({ override: true });',
                '```',
                '',
                'IMPORTANT: Use override: true so SecureCode values take precedence over .env files.',
                'This ensures the app reads from the vault, not from stale local .env values.',
                '',
                'Step 3: Verify your changes by reading back the file you just modified.',
                'Confirm the import and loadEnv() call are present.',
                '',
                'Step 4: The API key is already in .securecoderc (created during onboarding).',
                'Do NOT add SECURECODE_API_KEY to .env — the SDK reads it from .securecoderc automatically.',
                '',
                'Step 5: Run the project (npm run dev or equivalent) and verify it starts without errors.',
                '',
                'Step 6: Tell the user to check the Audit Log in the SecureCode dashboard',
                '(https://securecodehq.com/audit). They will see each secret access logged.',
                '',
                'Step 7: Once confirmed working, tell the user they can optionally remove',
                'the migrated secrets from their .env file. The .env file is no longer needed',
                'for those secrets — SecureCode loads them at runtime.',
                '',
                'Step 8: For production deployment, tell the user to set these env vars in',
                'their hosting platform (Vercel, Railway, etc.):',
                '  - SECURECODE_API_KEY=sc_...',
                '  - SECURECODE_PROJECT=<project-name> (optional, filters by project)',
                '  - SECURECODE_ENV=production (optional, filters by environment)',
                '',
                '## For Option 2 (getSecret):',
                '',
                'Step 1: Install the SDK if not already installed:',
                '```bash',
                'npm install @securecode/sdk',
                '```',
                '',
                'Step 2: The API key is already in .securecoderc (created during onboarding).',
                'Do NOT add anything to .env.',
                '',
                'Step 3: You MUST replace each process.env.SECRET_NAME usage in the codebase.',
                'Search for all process.env references and replace them:',
                '```typescript',
                'import { getSecret } from "@securecode/sdk";',
                'const value = await getSecret("SECRET_NAME");',
                '```',
                '',
                'Step 4: Verify your changes by reading back the modified files.',
                '',
                'Step 5: Run the project and verify it starts without errors.',
                '',
                'Step 6: Tell the user to check the Audit Log in the SecureCode dashboard.',
                '',
                'Step 7: For production, set SECURECODE_API_KEY + SECURECODE_PROJECT +',
                'SECURECODE_ENV as env vars in the hosting platform.',
                '',
                'IMPORTANT:',
                '- Do NOT hardcode the API key in source code — always use process.env or .securecoderc',
                '- The same API key used by the MCP server works for the SDK',
                '- After setup, tell the user to check the Audit Log in the dashboard',
                '  to see SDK reads being tracked',
                '- NEVER tell the user the setup is done without actually writing the code',
                '',
                '## FINAL STEP (mandatory):',
                'After the SDK is configured and verified, you MUST call the "security-check" tool.',
                'This tool analyzes the project and returns security actions you must execute.',
                'Do NOT skip this step — it ensures .securecoderc is protected from AI access,',
                'configures Claude denylist, and sets up project instructions for SecureCode usage.',
              ].join('\n'),
            }]);
          }
    
          // ── Standard flow: start / progress through steps ──
    
          // If we have a token, check its status first
          if (onboardingToken) {
            const status = await onboardingFetch<Record<string, unknown>>(`/onboarding/${onboardingToken}/status`);
    
            if (!status.found || status.status === 'expired') {
              onboardingToken = null; clearOnboardingTokenFromRc();
            } else if (status.status === 'completed') {
              // Import done — now guide to MCP configuration
              const instructions = status.migrationInstructions as string || '';
              const keyClaimed = status.keyClaimed as boolean;
    
              // Extract project/env from importResult.tags if available
              const importResult = status.importResult as Record<string, unknown> | undefined;
              if (importResult?.tags && typeof importResult.tags === 'object') {
                const tags = importResult.tags as Record<string, string>;
                if (tags.project) onboardingProject = tags.project;
                if (tags.env) onboardingEnv = tags.env;
              }
    
              if (keyClaimed) {
                onboardingToken = null; clearOnboardingTokenFromRc();
                return wrapResponse([{
                  type: 'text',
                  text: [
                    '✓ Onboarding already completed! API key was configured.',
                    '',
                    'Close and reopen Claude Code to activate SecureCode.',
                    'Run the help tool for more information.',
                  ].join('\n'),
                }]);
              }
    
              // Key not yet claimed — offer auto vs manual configuration
              return wrapResponse([{
                type: 'text',
                text: [
                  instructions,
                  '',
                  '--- MCP Configuration ---',
                  '',
                  'Now I need to configure the API key so SecureCode works in this project.',
                  '',
                  'You have two options:',
                  '',
                  '1. Let me do it automatically (recommended)',
                  '   I\'ll create an API key, find your MCP config file, and write it for you.',
                  '   You just close and reopen Claude Code and everything works.',
                  '   Quick, simple, and the API key never appears in this conversation.',
                  '',
                  '2. Do it yourself',
                  '   I\'ll show you the API key and the config snippet.',
                  '   You copy-paste it into your MCP config file manually.',
                  '',
                  'Tell me which option you prefer, and I\'ll call this tool with the right action.',
                ].join('\n'),
              }]);
            } else if (status.status === 'pending_import') {
              const importUrl = `${baseUrl}/onboarding/${onboardingToken}/import`;
              return wrapResponse([{
                type: 'text',
                text: [
                  '✓ Step 1 complete — account ready.',
                  '',
                  '📂 Step 2: Import your .env file.',
                  `Link: ${importUrl}`,
                  '',
                  'Drop your .env file or paste its content. Choose a project name and environment.',
                  '',
                  'Tell me when you\'re done, and I\'ll run this tool again to continue.',
                  '',
                  `AGENT INSTRUCTION: Open this URL automatically in the user's browser by running a shell command:`,
                  `  Windows: start "" "${importUrl}"`,
                  `  macOS: open "${importUrl}"`,
                  `  Linux: xdg-open "${importUrl}"`,
                  'Also show the link as fallback in case it doesn\'t open.',
                ].join('\n'),
              }]);
            } else if (status.status === 'pending_signup') {
              const signupUrl = `${baseUrl}/onboarding/${onboardingToken}/signup`;
              return wrapResponse([{
                type: 'text',
                text: [
                  '⏳ Waiting for you to sign up.',
                  `Link: ${signupUrl}`,
                  '',
                  'Tell me when you\'re done, and I\'ll run this tool again to continue.',
                  '',
                  `AGENT INSTRUCTION: Open this URL automatically in the user's browser by running a shell command:`,
                  `  Windows: start "" "${signupUrl}"`,
                  `  macOS: open "${signupUrl}"`,
                  `  Linux: xdg-open "${signupUrl}"`,
                  'Also show the link as fallback in case it doesn\'t open.',
                ].join('\n'),
              }]);
            }
          }
    
          // No token or expired — check if user already has secrets in vault
          // If they have an API key and existing secrets, offer to use them directly
          if (hasApiKey) {
            let vaultSecrets: Array<{ name: string; tags: Record<string, string> }> = [];
            try {
              const client = getClient();
              const secrets = await client.listSecrets();
              vaultSecrets = secrets.map(s => ({ name: s.name, tags: s.tags || {} }));
            } catch { /* ignore */ }
    
            if (vaultSecrets.length > 0) {
              // Group secrets by project/env
              const grouped = new Map<string, string[]>();
              for (const s of vaultSecrets) {
                const proj = s.tags.project || '(untagged)';
                const env = s.tags.env || '(untagged)';
                const key = `${proj}::${env}`;
                if (!grouped.has(key)) grouped.set(key, []);
                grouped.get(key)!.push(s.name);
              }
    
              const sections: string[] = [];
              for (const [key, names] of grouped) {
                const [proj, env] = key.split('::');
                sections.push(`**Project: ${proj}, Env: ${env}** (${names.length} secrets)`);
                sections.push(...names.map(n => `  - ${n}`));
                sections.push('');
              }
    
              return wrapResponse([{
                type: 'text',
                text: [
                  `✓ You already have ${vaultSecrets.length} secrets in SecureCode:`,
                  '',
                  ...sections,
                  'What would you like to do?',
                  '',
                  '1. **Use existing secrets** (recommended if you already imported)',
                  '   I\'ll configure the SDK to use your vault secrets.',
                  '',
                  '2. **Import new secrets** from a .env file',
                  '   Opens the import page to add more secrets.',
                  '',
                  'AGENT INSTRUCTION:',
                  'Present these options to the user with ALL secrets listed. Let the user choose.',
                  'If the user wants to use existing secrets, call onboard with action: "select-secrets"',
                  'to let them confirm which project/env to use.',
                  'If the user wants to import new secrets, call onboard with action: "add-environment".',
                ].join('\n'),
              }]);
            }
          }
    
          // No existing secrets or no API key — start fresh onboarding
          let agentName = 'unknown';
          try {
            const clientInfo = server.server.getClientVersion();
            if (clientInfo?.name) agentName = clientInfo.name;
          } catch { /* noop */ }
    
          const result = await onboardingFetch<Record<string, string>>('/onboarding/start', {
            method: 'POST',
            body: JSON.stringify({ source: 'mcp', agentName }),
          });
          onboardingToken = result.token;
          writeOnboardingTokenToRc(result.token);
    
          // Check if user is already authenticated (API key means they have an account)
          const status = await onboardingFetch<Record<string, unknown>>(`/onboarding/${result.token}/status`);
    
          if (status.status === 'pending_import') {
            return wrapResponse([{
              type: 'text',
              text: [
                '✓ You\'re already signed in.',
                '',
                '📂 Step 2: Import your .env file.',
                `Link: ${result.importUrl}`,
                '',
                'Drop your .env file or paste its content. Choose a project name and environment.',
                '',
                'Tell me when you\'re done, and I\'ll run this tool again to continue.',
                '',
                `AGENT INSTRUCTION: Open this URL automatically in the user's browser by running a shell command:`,
                `  Windows: start "" "${result.importUrl}"`,
                `  macOS: open "${result.importUrl}"`,
                `  Linux: xdg-open "${result.importUrl}"`,
                'Also show the link as fallback in case it doesn\'t open.',
              ].join('\n'),
            }]);
          }
    
          // New user — start from step 1
          return wrapResponse([{
            type: 'text',
            text: [
              'Welcome to SecureCode! Let\'s set you up in 3 steps.',
              '',
              '👤 Step 1: Create your account.',
              `Link: ${result.signupUrl}`,
              '',
              'Sign up with Google or email — it takes 30 seconds.',
              '',
              'Tell me when you\'re done, and I\'ll run this tool again to continue.',
              '',
              `AGENT INSTRUCTION: Open this URL automatically in the user's browser by running a shell command:`,
              `  Windows: start "" "${result.signupUrl}"`,
              `  macOS: open "${result.signupUrl}"`,
              `  Linux: xdg-open "${result.signupUrl}"`,
              'Also show the link as fallback in case it doesn\'t open.',
            ].join('\n'),
          }]);
        } catch (error) {
          return errorResult(error);
        }
      },
    );

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/juanisidoro/securecode-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server