Files
mcpctl/.taskmaster/tasks/task_033.md
Michal a869f77070 merge: AgentProject membership — the junction is now load-bearing
Merges feat/agent-project-membership (task 166/152). An agent can finally join
many projects: membership routes, per-membership role and optional brain
override, chat resolving context from the membership, and CLI attach-agent /
detach-agent / get memberships. The legacy Agent.projectId fallback is kept for
this release as the schema comment promises.

Conflict resolved in main.ts: that branch added its RBAC case to
mapUrlToPermission inline, while this branch had extracted the function to
middleware/url-permission.ts so it could be tested. Kept the extraction and
ported the case into it — joining a team is `edit:agents:<name>`, not
`create:agents`, which is the authority to mint a whole agent.

Also fixes something the extraction makes possible. security.test.ts contained a
hand-copied reproduction of mapUrlToPermission, because main.ts self-invokes
main() and nothing in it was importable. A security test asserting against a
copy stops testing the system the moment the real one changes, and this copy had
already drifted. It now imports the real function; all 18 assertions still pass,
against the code that actually runs.

Local: build clean, workspace 2550 passed, lint unchanged at 794 errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4wNHWf7xSwnZCWpJcyv9p
2026-08-09 23:34:18 +01:00

6.8 KiB

Task ID: 33

Title: Update mcpctl to use mcplocal as daemon

Status: done

Dependencies: 27 ✓

Priority: high

Description: Modify mcpctl CLI to connect to mcplocal instead of mcpd directly, update configuration options, add dual connectivity status checking, and implement authentication commands (login/logout) with secure credential storage.

Details:

  1. Update src/cli/src/config/schema.ts:
    export interface McpctlConfig {
      mcplocalUrl: string;  // NEW: default 'http://localhost:3200'
      mcpdUrl: string;      // Keep for reference/direct access if needed
      // ... other fields
    }
    
  2. Update src/cli/src/config/defaults.ts:
  3. Update src/cli/src/api-client.ts:
    • Default baseUrl now points to mcplocal
  4. Add new config commands in src/cli/src/commands/config.ts:
    .command('set-mcplocal-url <url>')
    .command('set-mcpd-url <url>')
    .command('get-mcplocal-url')
    .command('get-mcpd-url')
    
  5. Update src/cli/src/commands/status.ts to show both connections and auth status:
    $ mcpctl status
    mcplocal: connected (localhost:3200)
    mcpd: connected (nas.local:3100) via mcplocal
    Auth: logged in as user@example.com
    LLM Provider: ollama (llama3.2)
    Token savings: 45% (last 24h)
    
  6. Update CLI --daemon-url flag to point to mcplocal
  7. Add --direct flag to bypass mcplocal and talk to mcpd directly (for debugging)
  8. Create src/cli/src/commands/auth.ts with login/logout commands:
    • mcpctl login: Prompt for mcpd URL (if not configured) and credentials
    • Call POST /api/v1/auth/login with { email, password }
    • Store session token in ~/.mcpctl/credentials with 0600 permissions
    • mcpctl logout: Invalidate session and delete stored token
  9. Create src/cli/src/auth/credentials.ts for secure token storage:
    • Use fs.chmod to set 0600 permissions on credentials file
    • Token format: { token: string, mcpdUrl: string, user: string, expiresAt?: string }
  10. Update api-client.ts to include stored token in requests to mcplocal
    • mcplocal passes this token to mcpd for authentication

Test Strategy:

  1. Unit test: Default config points to mcplocal URL
  2. Unit test: Config commands update correct fields
  3. Integration test: CLI commands work through mcplocal proxy
  4. Test status command shows both mcplocal and mcpd status
  5. Test --direct flag bypasses mcplocal
  6. Test backward compatibility with existing config files
  7. Unit test: login command stores token with correct permissions (0600)
  8. Unit test: logout command removes credentials file
  9. Integration test: login flow with POST /api/v1/auth/login
  10. Test status command shows auth status (logged in as user)
  11. Test token is passed to mcplocal in API requests
  12. Test invalid credentials return appropriate error message
  13. Test expired token handling

Subtasks

33.1. Update config schema for mcplocal and mcpd URLs

Status: pending
Dependencies: None

Modify McpctlConfigSchema in src/cli/src/config/schema.ts to include separate mcplocalUrl and mcpdUrl fields with appropriate defaults.

Details:

Update the Zod schema to add mcplocalUrl (default: http://localhost:3200) and mcpdUrl (default: http://localhost:3100). Update DEFAULT_CONFIG and ensure backward compatibility with existing daemonUrl field by mapping it to mcplocalUrl.

33.2. Create auth credentials storage module

Status: pending
Dependencies: None

Create src/cli/src/auth/credentials.ts to handle secure storage and retrieval of session tokens in ~/.mcpctl/credentials.

Details:

Implement saveCredentials(token, mcpdUrl, user), loadCredentials(), and deleteCredentials() functions. Use fs.chmod to set 0600 permissions. Store JSON format: { token, mcpdUrl, user, expiresAt }. Handle file not found gracefully in loadCredentials.

33.3. Implement login command

Status: pending
Dependencies: 33.2

Create src/cli/src/commands/auth.ts with mcpctl login command that prompts for mcpd URL and credentials, calls POST /api/v1/auth/login, and stores the session token.

Details:

Use inquirer or prompts library for interactive credential input (email, password). If mcpdUrl not configured, prompt for it. Call POST /api/v1/auth/login with credentials. On success, save token using credentials module. Display 'Logged in as {user}' on success. Handle errors (invalid credentials, network errors) with clear messages.

33.4. Implement logout command

Status: pending
Dependencies: 33.2

Add mcpctl logout command to auth.ts that invalidates the session and removes stored credentials.

Details:

Load stored credentials, optionally call a logout endpoint on mcpd to invalidate server-side session, then delete the local credentials file. Display 'Logged out successfully' or 'Not logged in' as appropriate.

33.5. Update api-client to include auth token

Status: pending
Dependencies: 33.2

Modify src/cli/src/api-client.ts to load and include stored session token in Authorization header for requests to mcplocal.

Details:

Import loadCredentials from auth module. Add Authorization: Bearer {token} header to requests when credentials exist. Handle expired token by returning appropriate error suggesting re-login.

33.6. Update status command to show auth status

Status: pending
Dependencies: 33.2, 33.5

Modify src/cli/src/commands/status.ts to display authentication status (logged in as user X or not logged in) along with mcplocal and mcpd connectivity.

Details:

Load credentials and display auth status line: 'Auth: logged in as {user}' or 'Auth: not logged in'. Update status output format to show mcplocal and mcpd status separately with the auth info.

33.7. Add config commands for mcplocal and mcpd URLs

Status: pending
Dependencies: 33.1

Add set-mcplocal-url, set-mcpd-url, get-mcplocal-url, and get-mcpd-url commands to src/cli/src/commands/config.ts.

Details:

Add four new subcommands to the config command for setting and getting the mcplocal and mcpd URLs independently. Update the generic 'set' command to handle these new schema fields.

33.8. Add --direct flag for mcpd bypass

Status: pending
Dependencies: 33.1, 33.5

Add --direct flag to CLI commands that bypasses mcplocal and connects directly to mcpd for debugging purposes.

Details:

Add global --direct option to the main CLI. When set, api-client uses mcpdUrl instead of mcplocalUrl. Useful for debugging connectivity issues between mcplocal and mcpd.

33.9. Register auth commands in CLI entry point

Status: pending
Dependencies: 33.3, 33.4

Import and register the login and logout commands in src/cli/src/index.ts.

Details:

Import createAuthCommand from commands/auth.ts and add it to the main program with program.addCommand(createAuthCommand()).