first commit
This commit is contained in:
327
.taskmaster/tasks/task_010.md
Normal file
327
.taskmaster/tasks/task_010.md
Normal file
@@ -0,0 +1,327 @@
|
||||
# Task ID: 10
|
||||
|
||||
**Title:** Implement Interactive MCP Server Setup Wizard
|
||||
|
||||
**Status:** pending
|
||||
|
||||
**Dependencies:** 7, 4
|
||||
|
||||
**Priority:** medium
|
||||
|
||||
**Description:** Create an interactive setup wizard that guides users through MCP server configuration, including OAuth flows and API token generation.
|
||||
|
||||
**Details:**
|
||||
|
||||
Create interactive setup wizard:
|
||||
|
||||
```typescript
|
||||
// commands/setup.ts
|
||||
import inquirer from 'inquirer';
|
||||
import open from 'open';
|
||||
|
||||
program
|
||||
.command('setup')
|
||||
.argument('<server-type>', 'MCP server type (slack, jira, github, etc.)')
|
||||
.action(async (serverType) => {
|
||||
const client = getClient();
|
||||
const serverDef = await client.get(`/api/mcp-servers/types/${serverType}`);
|
||||
|
||||
console.log(`\n🚀 Setting up ${serverDef.name} MCP Server\n`);
|
||||
|
||||
// Show setup guide
|
||||
if (serverDef.setupGuide) {
|
||||
console.log(serverDef.setupGuide);
|
||||
}
|
||||
|
||||
// Collect required credentials
|
||||
const answers = {};
|
||||
for (const [key, info] of Object.entries(serverDef.envTemplate)) {
|
||||
if (info.oauth) {
|
||||
// Handle OAuth flow
|
||||
console.log(`\n📱 Opening browser for ${key} authentication...`);
|
||||
const authUrl = `${client.serverUrl}/auth/${serverType}/start`;
|
||||
await open(authUrl);
|
||||
|
||||
const { token } = await inquirer.prompt([{
|
||||
type: 'input',
|
||||
name: 'token',
|
||||
message: 'Paste the token from the browser:'
|
||||
}]);
|
||||
answers[key] = token;
|
||||
} else if (info.url) {
|
||||
// Guide user to token generation page
|
||||
console.log(`\n🔗 Opening ${info.description}...`);
|
||||
await open(info.url);
|
||||
console.log('Generate an API token with the following permissions:');
|
||||
console.log(info.permissions?.join(', '));
|
||||
|
||||
const { value } = await inquirer.prompt([{
|
||||
type: 'password',
|
||||
name: 'value',
|
||||
message: `Enter your ${key}:`
|
||||
}]);
|
||||
answers[key] = value;
|
||||
} else {
|
||||
const { value } = await inquirer.prompt([{
|
||||
type: info.secret ? 'password' : 'input',
|
||||
name: 'value',
|
||||
message: `Enter ${key}:`,
|
||||
default: info.default
|
||||
}]);
|
||||
answers[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Create profile with credentials
|
||||
const { profileName } = await inquirer.prompt([{
|
||||
type: 'input',
|
||||
name: 'profileName',
|
||||
message: 'Name for this profile:',
|
||||
default: `${serverType}-default`
|
||||
}]);
|
||||
|
||||
const profile = await client.post(`/api/mcp-servers/${serverDef.id}/profiles`, {
|
||||
name: profileName,
|
||||
config: answers
|
||||
});
|
||||
|
||||
console.log(`\n✅ Profile "${profileName}" created successfully!`);
|
||||
console.log(`Use: mcpctl project add-profile <project> ${profileName}`);
|
||||
});
|
||||
```
|
||||
|
||||
Server-side setup definitions:
|
||||
```typescript
|
||||
const slackSetup = {
|
||||
envTemplate: {
|
||||
SLACK_BOT_TOKEN: {
|
||||
description: 'Slack Bot Token',
|
||||
url: 'https://api.slack.com/apps',
|
||||
permissions: ['channels:read', 'chat:write', 'users:read'],
|
||||
secret: true
|
||||
},
|
||||
SLACK_TEAM_ID: {
|
||||
description: 'Slack Team ID',
|
||||
secret: false
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Test Strategy:**
|
||||
|
||||
Test wizard flow with mocked inquirer responses. Test OAuth URL generation. Test profile creation with collected credentials. Integration test with actual Slack/Jira setup.
|
||||
|
||||
## Subtasks
|
||||
|
||||
### 10.1. Write TDD tests for wizard step components and credential collection flow
|
||||
|
||||
**Status:** pending
|
||||
**Dependencies:** None
|
||||
|
||||
Create comprehensive Vitest test suites for all wizard step functions BEFORE implementation, including tests for OAuth flows, API token collection, service account JSON upload, and inquirer prompt mocking for deterministic testing.
|
||||
|
||||
**Details:**
|
||||
|
||||
Create src/cli/tests/unit/commands/setup/wizard-steps.test.ts with TDD tests using vi.mock('inquirer') for deterministic prompt testing. Test cases: (1) collectCredential() with OAuth type opens browser and waits for callback token, (2) collectCredential() with API token type shows URL guidance and accepts password input, (3) collectCredential() with service account type accepts file path and validates JSON structure (for BigQuery), (4) collectCredential() with connection string type validates format (for Snowflake), (5) showSetupGuide() renders markdown correctly to terminal, (6) validateCredential() calls mcpd API to verify token before storage, (7) createProfile() posts to /api/mcp-servers/:id/profiles endpoint. Create src/cli/tests/unit/commands/setup/index.test.ts testing full wizard flow: parse server type argument, fetch server definition, iterate envTemplate, collect all credentials, create profile. Write mock fixtures for server definitions (Slack OAuth, Jira API token, GitHub PAT, BigQuery service account, Snowflake OAuth + connection string, dbt Cloud API token). All tests should fail initially (TDD red phase).
|
||||
|
||||
### 10.2. Implement composable wizard step functions with auth strategy pattern
|
||||
|
||||
**Status:** pending
|
||||
**Dependencies:** 10.1
|
||||
|
||||
Create reusable, testable wizard step functions following the strategy pattern for different authentication types (OAuth, API token, service account JSON, connection string, multi-step flows) that can be composed for complex data platform MCP setups.
|
||||
|
||||
**Details:**
|
||||
|
||||
Create src/cli/src/commands/setup/auth-strategies.ts with authentication strategy interface and implementations:
|
||||
|
||||
```typescript
|
||||
interface AuthStrategy {
|
||||
name: string;
|
||||
collect(envKey: string, info: EnvTemplateInfo, options: CollectOptions): Promise<string>;
|
||||
validate?(value: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
class OAuthStrategy implements AuthStrategy // Opens browser, waits for callback
|
||||
class ApiTokenStrategy implements AuthStrategy // Shows URL, accepts password input
|
||||
class ServiceAccountStrategy implements AuthStrategy // File path input, JSON validation
|
||||
class ConnectionStringStrategy implements AuthStrategy // Format validation (user:pass@host:port/db)
|
||||
class MultiStepStrategy implements AuthStrategy // Composes multiple sub-strategies
|
||||
```
|
||||
|
||||
Create src/cli/src/commands/setup/wizard-steps.ts with composable functions:
|
||||
- showSetupGuide(guide: string): void - Render markdown to terminal with chalk
|
||||
- selectAuthStrategy(info: EnvTemplateInfo): AuthStrategy - Factory based on envTemplate metadata
|
||||
- collectCredentials(envTemplate: EnvTemplate, strategies: AuthStrategy[]): Promise<Record<string, string>>
|
||||
- validateAllCredentials(credentials: Record<string, string>, server: McpServer): Promise<ValidationResult>
|
||||
- createProfile(serverId: string, profileName: string, config: Record<string, string>): Promise<Profile>
|
||||
|
||||
Data Engineer MCP support:
|
||||
- BigQuery: ServiceAccountStrategy expecting JSON key file with 'type': 'service_account'
|
||||
- Snowflake: MultiStepStrategy combining ConnectionStringStrategy + OAuthStrategy
|
||||
- dbt Cloud: ApiTokenStrategy with project selection step
|
||||
|
||||
All functions must pass TDD tests from subtask 1.
|
||||
|
||||
### 10.3. Implement setup command with --non-interactive flag for CI/scripting
|
||||
|
||||
**Status:** pending
|
||||
**Dependencies:** 10.2
|
||||
|
||||
Create the main 'mcpctl setup <server-type>' command that orchestrates the wizard flow, with --non-interactive flag for CI/automation that accepts credentials via environment variables or stdin JSON.
|
||||
|
||||
**Details:**
|
||||
|
||||
Create src/cli/src/commands/setup/index.ts implementing CommandModule:
|
||||
|
||||
```typescript
|
||||
program
|
||||
.command('setup')
|
||||
.argument('<server-type>', 'MCP server type (slack, jira, github, bigquery, snowflake, dbt)')
|
||||
.option('--non-interactive', 'Run without prompts, use env vars or stdin')
|
||||
.option('--profile-name <name>', 'Name for the created profile')
|
||||
.option('--stdin', 'Read credentials JSON from stdin')
|
||||
.option('--dry-run', 'Validate without creating profile')
|
||||
.action(async (serverType, options) => { ... })
|
||||
```
|
||||
|
||||
Interactive flow:
|
||||
1. Fetch server definition from mcpd: GET /api/mcp-servers/types/:type
|
||||
2. Display setup guide with showSetupGuide()
|
||||
3. For each envTemplate entry, use selectAuthStrategy() and collect()
|
||||
4. Validate all credentials with validateAllCredentials()
|
||||
5. Prompt for profile name (default: ${serverType}-default)
|
||||
6. Create profile via mcpd API
|
||||
7. Print success message with 'mcpctl project add-profile' hint
|
||||
|
||||
Non-interactive flow:
|
||||
- --stdin: Read JSON from stdin with structure { "SLACK_BOT_TOKEN": "xoxb-...", ... }
|
||||
- Env vars: Check for each envTemplate key in process.env
|
||||
- Fail with clear error if required credential missing
|
||||
- Validate all credentials before creating profile
|
||||
- --dry-run: Skip profile creation, just validate
|
||||
|
||||
Offline/local dev support:
|
||||
- When mcpd unreachable, offer cached server definitions
|
||||
- Support --mcpd-url override for local development
|
||||
|
||||
Register via CommandRegistry. Write integration tests.
|
||||
|
||||
### 10.4. Implement OAuth browser flow with proxy and enterprise SSO support
|
||||
|
||||
**Status:** pending
|
||||
**Dependencies:** 10.2
|
||||
|
||||
Create secure OAuth flow handler that opens browser for authentication, handles callback tokens, supports HTTP/HTTPS proxies, custom CA certificates for enterprise SSO, and secure redirect URL handling.
|
||||
|
||||
**Details:**
|
||||
|
||||
Create src/cli/src/commands/setup/oauth-handler.ts:
|
||||
|
||||
```typescript
|
||||
export class OAuthHandler {
|
||||
constructor(private config: OAuthConfig) {}
|
||||
|
||||
async startOAuthFlow(serverType: string): Promise<string> {
|
||||
// 1. Generate state token for CSRF protection
|
||||
// 2. Build auth URL with state and redirect_uri
|
||||
// 3. Start local callback server on random port
|
||||
// 4. Open browser with 'open' package
|
||||
// 5. Wait for callback with token or timeout
|
||||
// 6. Validate state matches
|
||||
// 7. Return access token
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Enterprise networking support:
|
||||
- Load proxy settings from config (Task 7) and environment (HTTP_PROXY, HTTPS_PROXY, NO_PROXY)
|
||||
- Support custom CA certificates for enterprise SSO (config.tls.caFile)
|
||||
- Use https.Agent with proxy-agent for HTTPS requests through proxy
|
||||
- Handle proxy authentication (Proxy-Authorization header)
|
||||
|
||||
Callback server:
|
||||
- Start on localhost:0 (random available port)
|
||||
- Timeout after 5 minutes with clear error message
|
||||
- CSRF protection via state parameter
|
||||
- Redirect to success page after token received
|
||||
- Shutdown immediately after callback
|
||||
|
||||
Security considerations:
|
||||
- State token must be cryptographically random (crypto.randomBytes)
|
||||
- Validate redirect_uri matches expected pattern
|
||||
- Don't log access tokens
|
||||
- Clear token from memory after passing to credential store
|
||||
|
||||
Create src/cli/tests/unit/commands/setup/oauth-handler.test.ts with mocked browser and HTTP server.
|
||||
|
||||
### 10.5. Implement secure credential storage and comprehensive security review
|
||||
|
||||
**Status:** pending
|
||||
**Dependencies:** 10.2, 10.3, 10.4
|
||||
|
||||
Create secure credential storage for wizard-collected tokens using system keychain or encrypted file storage, validate tokens before storage, and conduct comprehensive security review of all OAuth handling, credential storage, and browser redirect safety.
|
||||
|
||||
**Details:**
|
||||
|
||||
Create src/cli/src/commands/setup/credential-store.ts:
|
||||
|
||||
```typescript
|
||||
export class WizardCredentialStore {
|
||||
// Store credentials securely for later profile creation
|
||||
async storeCredential(key: string, value: string, options: StoreOptions): Promise<void>
|
||||
|
||||
// Validate credential with mcpd before storing
|
||||
async validateAndStore(serverType: string, key: string, value: string): Promise<ValidationResult>
|
||||
|
||||
// Retrieve for profile creation (one-time use)
|
||||
async retrieveAndClear(key: string): Promise<string>
|
||||
}
|
||||
```
|
||||
|
||||
Secure storage implementation:
|
||||
- Primary: System keychain via 'keytar' package (macOS Keychain, Windows Credential Vault, Linux Secret Service)
|
||||
- Fallback: Encrypted file at ~/.mcpctl/wizard-credentials (AES-256-GCM)
|
||||
- Encryption key derived from machine-specific data + user password
|
||||
- Credentials cleared after profile creation (one-time use)
|
||||
|
||||
API token validation before storage:
|
||||
- POST /api/mcp-servers/:type/validate-credentials with credentials
|
||||
- Slack: Test token with auth.test API
|
||||
- Jira: Test with /rest/api/3/myself
|
||||
- GitHub: Test with /user API
|
||||
- BigQuery: Test service account with projects.list
|
||||
- Snowflake: Test connection with simple query
|
||||
- dbt: Test with /api/v2/accounts
|
||||
|
||||
SECURITY REVIEW - create src/cli/docs/SETUP_WIZARD_SECURITY_REVIEW.md:
|
||||
|
||||
1. OAuth Token Handling:
|
||||
- State parameter uses crypto.randomBytes(32)
|
||||
- Tokens never logged or written to non-encrypted storage
|
||||
- Browser redirect validates callback URL pattern
|
||||
- Local callback server binds to localhost only
|
||||
|
||||
2. Credential Storage Security:
|
||||
- Keychain used when available, encrypted file fallback
|
||||
- File permissions 600 on credential storage
|
||||
- Credentials cleared after single use
|
||||
- No credentials in CLI history (no --token=xxx args)
|
||||
|
||||
3. API Token Validation:
|
||||
- All tokens validated before storage
|
||||
- Validation errors don't leak token in error message
|
||||
- Failed validation clears token from memory
|
||||
|
||||
4. Network Security:
|
||||
- HTTPS required for OAuth (except localhost callback)
|
||||
- Proxy credentials handled securely
|
||||
- Custom CA for enterprise SSO supported
|
||||
|
||||
5. Browser Redirect Safety:
|
||||
- Only localhost:port/callback pattern accepted
|
||||
- State token prevents CSRF
|
||||
- Success page doesn't display token
|
||||
|
||||
Run 'pnpm audit --audit-level=high' and document findings.
|
||||
Reference in New Issue
Block a user