Files
mcpctl/.taskmaster/tasks/task_005.md

127 lines
8.9 KiB
Markdown
Raw Normal View History

2026-02-21 03:10:39 +00:00
# Task ID: 5
**Title:** Implement Project Management APIs
**Status:** pending
**Dependencies:** 4
**Priority:** high
**Description:** Create APIs for managing MCP projects that group multiple MCP profiles together for easy assignment to Claude sessions.
**Details:**
Create project management endpoints:
```typescript
// routes/projects.ts
app.post('/api/projects', async (req) => {
const { name, description, profileIds } = req.body;
const project = await prisma.project.create({
data: {
name,
description,
profiles: {
create: profileIds.map(profileId => ({ profileId }))
}
},
include: { profiles: { include: { profile: { include: { server: true } } } } }
});
return project;
});
app.get('/api/projects', async () => {
return prisma.project.findMany({
include: { profiles: { include: { profile: { include: { server: true } } } } }
});
});
app.get('/api/projects/:name', async (req) => {
return prisma.project.findUnique({
where: { name: req.params.name },
include: { profiles: { include: { profile: { include: { server: true } } } } }
});
});
app.put('/api/projects/:id/profiles', async (req) => {
const { profileIds } = req.body;
// Update project profiles
await prisma.projectMcpProfile.deleteMany({ where: { projectId: req.params.id } });
await prisma.projectMcpProfile.createMany({
data: profileIds.map(profileId => ({ projectId: req.params.id, profileId }))
});
});
// Generate .mcp.json format for Claude
app.get('/api/projects/:name/mcp-config', async (req) => {
const project = await prisma.project.findUnique({
where: { name: req.params.name },
include: { profiles: { include: { profile: { include: { server: true } } } } }
});
// Transform to .mcp.json format
return generateMcpConfig(project);
});
```
**Test Strategy:**
Test project CRUD operations. Verify profile associations work correctly. Test MCP config generation produces valid .mcp.json format.
## Subtasks
### 5.1. Write TDD tests for project Zod validation schemas and generateMcpConfig function
**Status:** pending
**Dependencies:** None
Create comprehensive Vitest test suites for project validation schemas and the critical generateMcpConfig function BEFORE implementing any code, following TDD red phase.
**Details:**
Create src/mcpd/tests/unit/validation/project.schema.test.ts with tests for: (1) CreateProjectSchema validates name (non-empty string, max 64 chars, alphanumeric-dash only), description (optional string, max 500 chars), profileIds (array of valid UUIDs, can be empty); (2) UpdateProjectSchema as partial; (3) UpdateProjectProfilesSchema validates profileIds array. Create src/mcpd/tests/unit/services/generate-mcp-config.test.ts with tests for generateMcpConfig function: (1) Returns valid .mcp.json structure with mcpServers object, (2) Each server entry has command, args, and env keys, (3) SECURITY: env values for secret fields are EXCLUDED or masked (critical requirement from context), (4) Server names are correctly derived from profile.server.name, (5) Empty project returns empty mcpServers object, (6) Multiple profiles from same server are handled correctly (no duplicates or merged appropriately). Security test: Verify generateMcpConfig strips SLACK_BOT_TOKEN, JIRA_API_TOKEN, GITHUB_TOKEN and any field marked secret:true in envTemplate.
### 5.2. Implement project repository and generateMcpConfig service with security filtering
**Status:** pending
**Dependencies:** 5.1
Create the project repository following the repository pattern from Task 4, plus the generateMcpConfig function that transforms project data to .mcp.json format while stripping sensitive credentials.
**Details:**
Create src/mcpd/src/repositories/project.repository.ts implementing IProjectRepository interface with methods: create(data: CreateProjectInput), findById(id: string, include?: { profiles?: { include?: { profile?: { include?: { server?: boolean } } } } }), findByName(name: string, include?: same), findAll(include?: same), update(id: string, data: UpdateProjectInput), delete(id: string), updateProfiles(projectId: string, profileIds: string[]) - handles delete-all-then-create pattern from task details. Create src/mcpd/src/services/mcp-config-generator.ts with generateMcpConfig(project: ProjectWithProfiles): McpJsonConfig function. Implementation: (1) Iterate project.profiles, (2) For each profile, get server.command, server.args, (3) Build env object from profile.config BUT filter out any key where server.envTemplate[key].secret === true, (4) Return { mcpServers: { [server.name]: { command, args, env } } }. SECURITY CRITICAL: The env object must NEVER include secret values - these are populated locally by the CLI (Task 9). Add JSDoc comment explaining this security design. Create TypeScript type McpJsonConfig matching .mcp.json schema structure.
### 5.3. Implement project service layer with authorization and profile validation
**Status:** pending
**Dependencies:** 5.2
Create ProjectService with business logic including authorization checks, profile existence validation, and orchestration of repository and mcp-config-generator.
**Details:**
Create src/mcpd/src/services/project.service.ts with constructor accepting IProjectRepository and IMcpProfileRepository (DI from Task 4). Methods: createProject(userId: string, data: CreateProjectInput) - validate with Zod schema, check 'project:create' permission, verify all profileIds exist via profile repository, call project repository; getProject(userId: string, nameOrId: string) - check read permission, return project with nested profiles; listProjects(userId: string) - filter based on permissions; updateProject(userId: string, id: string, data: UpdateProjectInput) - check 'project:update' permission; deleteProject(userId: string, id: string) - check 'project:delete' permission; updateProjectProfiles(userId: string, projectId: string, profileIds: string[]) - validate all profiles exist AND user has permission to use each profile (prevents adding profiles user cannot access); getMcpConfig(userId: string, projectName: string) - get project, verify read permission, call generateMcpConfig. Write TDD tests mocking repositories. Note: This service will be consumed by Task 9 (mcpctl claude add-mcp-project).
### 5.4. Implement REST API routes for project CRUD and mcp-config endpoint
**Status:** pending
**Dependencies:** 5.3
Create Fastify route handlers for all project management endpoints including the critical /api/projects/:name/mcp-config endpoint used by the CLI.
**Details:**
Create src/mcpd/src/routes/projects.ts with routes: POST /api/projects (create project with optional profileIds array), GET /api/projects (list all projects user can access), GET /api/projects/:name (get project by name with full profile/server hierarchy), PUT /api/projects/:id (update project name/description), DELETE /api/projects/:id (delete project), PUT /api/projects/:id/profiles (replace all profiles - uses delete-then-create pattern per task details), GET /api/projects/:name/mcp-config (generate .mcp.json format output - CRITICAL endpoint for Task 9 CLI integration). Each route: (1) Uses Zod schema validation middleware, (2) Calls ProjectService method, (3) Returns consistent response format from Task 4 pattern. Register routes in server.ts with /api prefix. The mcp-config endpoint response format must be stable as Task 9 depends on it: { mcpServers: { [name: string]: { command: string, args: string[], env: Record<string, string> } } }. Add OpenAPI/Swagger JSDoc annotations for mcp-config endpoint documenting the exact response format.
### 5.5. Create integration tests and security review for project APIs
**Status:** pending
**Dependencies:** 5.4
Write comprehensive integration tests simulating the full workflow from project creation through mcp-config generation, plus security review documenting credential handling.
**Details:**
Create src/mcpd/tests/integration/projects.test.ts with end-to-end scenarios: (1) Full workflow test: create MCP server (from Task 4 seed), create profile with credentials, create project referencing profile, call mcp-config endpoint, verify output is valid and EXCLUDES secrets; (2) Multi-profile project: create project with Slack + Jira profiles, verify mcp-config merges correctly; (3) Profile update atomicity: update project profiles, verify old profiles removed and new ones added in single transaction; (4) Authorization flow: verify user A cannot add user B's profiles to their project; (5) Concurrent access: simultaneous project updates don't corrupt data. Create src/mcpd/docs/SECURITY_REVIEW.md section for Task 5 documenting: (1) generateMcpConfig deliberately excludes secret env vars, (2) CLI (Task 9) is responsible for injecting secrets locally from user's credential store, (3) Profile permission checks prevent unauthorized profile usage, (4) Response format designed to be safe for transmission over network. Run 'pnpm test:coverage' targeting >85% coverage for project-related files.