Skip to content

Commit 6f3e458

Browse files
committed
Merge pull request #4 from StuMason/feature/project-management
Feature/project management
2 parents c6f36ff + bdd550b commit 6f3e458

File tree

7 files changed

+400
-43
lines changed

7 files changed

+400
-43
lines changed
Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
description: FOLLOW feature development workflow WHEN implementing new features TO ensure consistent, tested, and documented changes
33
globs: src/**/*.{ts,tsx}
4-
alwaysApply: false
54
---
65

76
# Feature Development Workflow
@@ -15,41 +14,71 @@ alwaysApply: false
1514
- Start from up-to-date main branch
1615
- Create feature branch with descriptive name (feature/feature-name)
1716
- Locate and review feature documentation in docs/features directory
18-
- Follow documentation requirements systematically
19-
- Commit frequently to trigger pre-commit hooks (linting, formatting, tests)
20-
- Add comprehensive tests for new functionality
21-
- Push completed work to remote for PR review
17+
- Follow implementation in small, focused chunks:
18+
1. Add types and interfaces first
19+
2. Add client methods next
20+
3. Add MCP server tools
21+
4. Add tests for each component
22+
5. Update documentation for completed items
23+
- Commit after EACH of these changes:
24+
- New types/interfaces
25+
- New client methods
26+
- New MCP tools
27+
- New tests
28+
- Documentation updates
29+
- Linting/formatting fixes
30+
- Push to remote after each significant commit to:
31+
- Back up your work
32+
- Enable early feedback
33+
- Make PRs easier to review
34+
- Use conventional commit messages:
35+
- feat: new feature
36+
- fix: bug fix
37+
- docs: documentation changes
38+
- test: adding tests
39+
- refactor: code changes that neither fix bugs nor add features
40+
- style: formatting, missing semi colons, etc
41+
- chore: updating build tasks, package manager configs, etc
2242

2343
## Examples
2444
<example>
2545
# Good workflow
2646
1. git checkout main && git pull
27-
2. git checkout -b feature/server-info
28-
3. Review docs/features/002-server-info-resource.md
29-
4. Implement ServerInfo interface
30-
5. Add getServerInfo method with tests
31-
6. Commit to run hooks: "feat: add server info interface"
32-
7. Implement ServerStatus interface
33-
8. Add getServerStatus method with tests
34-
9. Commit to run hooks: "feat: add server status resource"
35-
10. Fix any linting issues
36-
11. Push to remote when complete
47+
2. git checkout -b feature/project-management
48+
3. Review docs/features/003-project-management.md
49+
4. Add Project and Environment types
50+
5. Commit: "feat: add Project and Environment types"
51+
6. Push: git push -u origin feature/project-management
52+
7. Add client methods for projects
53+
8. Commit: "feat: add project-related methods to CoolifyClient"
54+
9. Push: git push
55+
10. Add MCP server tools
56+
11. Commit: "feat: add project management tools to MCP server"
57+
12. Push: git push
58+
13. Add tests
59+
14. Commit: "test: add project management tests"
60+
15. Push: git push
61+
16. Update feature doc for completed items
62+
17. Commit: "docs: update feature doc for project management"
63+
18. Push: git push
3764
</example>
3865

3966
<example type="invalid">
4067
# Poor workflow
41-
1. Start coding without checking docs
42-
2. Make all changes in one big commit
43-
3. Skip tests or add them later
68+
1. Make all changes at once (types, client, server, tests)
69+
2. Single large commit with all changes
70+
3. No documentation updates until the end
4471
4. Push directly to main
45-
5. Fix linting issues after PR
72+
5. No intermediate pushes to remote
73+
6. Vague commit messages like "project management work"
4674
</example>
4775

4876
## Critical Points
4977
<critical>
50-
- ALWAYS work from feature documentation
51-
-
52-
- NEVER skip tests for new functionality
53-
- Commit OFTEN to utilize pre-commit hooks
54-
- Keep commits focused and well-described
78+
- NEVER make large, multi-component changes in a single commit
79+
- ALWAYS commit after each logical component change
80+
- ALWAYS push to remote after significant commits
81+
- ALWAYS update documentation AS YOU GO
82+
- ALWAYS use conventional commit messages
83+
- NEVER wait until the end to write tests
5584
</critical>

docs/features/003-project-management.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,27 @@ Basic project management functionality - first interactive feature allowing user
3939

4040
## Implementation Checklist
4141

42-
- [ ] Project List Resource
42+
- [x] Project List Resource
4343

44-
- [ ] resources://coolify/projects/list implementation
45-
- [ ] Pagination support
46-
- [ ] Basic filtering
44+
- [x] resources://coolify/projects/list implementation
45+
- [x] Pagination support
46+
- [x] Basic filtering
4747

48-
- [ ] Project Management Tools
48+
- [x] Project Management Tools
4949

50-
- [ ] createProject tool
51-
- [ ] deleteProject tool
52-
- [ ] updateProject tool
50+
- [x] createProject tool
51+
- [x] deleteProject tool
52+
- [x] updateProject tool
5353

54-
- [ ] Project Detail Resource
54+
- [x] Project Detail Resource
5555

56-
- [ ] resources://coolify/projects/{id} implementation
57-
- [ ] Project status and configuration
56+
- [x] resources://coolify/projects/{id} implementation
57+
- [x] Project status and configuration
5858

59-
- [ ] Testing
60-
- [ ] CRUD operation tests
61-
- [ ] Error handling tests
62-
- [ ] Resource format tests
59+
- [x] Testing
60+
- [x] CRUD operation tests
61+
- [x] Error handling tests
62+
- [x] Resource format tests
6363

6464
## Dependencies
6565

docs/features/801-feature-workflow.md

Whitespace-only changes.

src/__tests__/mcp-server.test.ts

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { CoolifyMcpServer } from '../lib/mcp-server.js';
22
import { jest } from '@jest/globals';
3-
import type { ServerResources, ValidationResponse } from '../types/coolify.js';
3+
import type {
4+
ServerResources,
5+
ValidationResponse,
6+
Project,
7+
Environment,
8+
} from '../types/coolify.js';
49

510
describe('CoolifyMcpServer', () => {
611
const mockConfig = {
@@ -53,4 +58,105 @@ describe('CoolifyMcpServer', () => {
5358
expect(spy).toHaveBeenCalledWith('test-uuid');
5459
});
5560
});
61+
62+
describe('list_projects', () => {
63+
it('should call client listProjects', async () => {
64+
const mockProjects: Project[] = [
65+
{
66+
id: 1,
67+
uuid: 'test-project-uuid',
68+
name: 'test-project',
69+
description: 'Test project description',
70+
environments: [],
71+
},
72+
];
73+
74+
const spy = jest.spyOn(server['client'], 'listProjects').mockResolvedValue(mockProjects);
75+
76+
await server.list_projects();
77+
expect(spy).toHaveBeenCalled();
78+
});
79+
});
80+
81+
describe('get_project', () => {
82+
it('should call client getProject', async () => {
83+
const mockProject: Project = {
84+
id: 1,
85+
uuid: 'test-project-uuid',
86+
name: 'test-project',
87+
description: 'Test project description',
88+
environments: [],
89+
};
90+
91+
const spy = jest.spyOn(server['client'], 'getProject').mockResolvedValue(mockProject);
92+
93+
await server.get_project('test-project-uuid');
94+
expect(spy).toHaveBeenCalledWith('test-project-uuid');
95+
});
96+
});
97+
98+
describe('create_project', () => {
99+
it('should call client createProject', async () => {
100+
const mockResponse = { uuid: 'new-project-uuid' };
101+
const createRequest = {
102+
name: 'New Project',
103+
description: 'New project description',
104+
};
105+
106+
const spy = jest.spyOn(server['client'], 'createProject').mockResolvedValue(mockResponse);
107+
108+
await server.create_project(createRequest);
109+
expect(spy).toHaveBeenCalledWith(createRequest);
110+
});
111+
});
112+
113+
describe('update_project', () => {
114+
it('should call client updateProject', async () => {
115+
const mockProject: Project = {
116+
id: 1,
117+
uuid: 'test-project-uuid',
118+
name: 'Updated Project',
119+
description: 'Updated description',
120+
environments: [],
121+
};
122+
123+
const updateRequest = {
124+
name: 'Updated Project',
125+
description: 'Updated description',
126+
};
127+
128+
const spy = jest.spyOn(server['client'], 'updateProject').mockResolvedValue(mockProject);
129+
130+
await server.update_project('test-project-uuid', updateRequest);
131+
expect(spy).toHaveBeenCalledWith('test-project-uuid', updateRequest);
132+
});
133+
});
134+
135+
describe('delete_project', () => {
136+
it('should call client deleteProject', async () => {
137+
const mockResponse = { message: 'Project deleted successfully' };
138+
139+
const spy = jest.spyOn(server['client'], 'deleteProject').mockResolvedValue(mockResponse);
140+
141+
await server.delete_project('test-project-uuid');
142+
expect(spy).toHaveBeenCalledWith('test-project-uuid');
143+
});
144+
});
145+
146+
describe('get_project_environment', () => {
147+
it('should call client getProjectEnvironment', async () => {
148+
const mockEnvironment: Environment = {
149+
id: 1,
150+
uuid: 'test-env-uuid',
151+
name: 'test-environment',
152+
};
153+
154+
const spy = jest
155+
.spyOn(server['client'], 'getProjectEnvironment')
156+
.mockResolvedValue(mockEnvironment);
157+
158+
await server.get_project_environment('test-project-uuid', 'test-env-uuid');
159+
expect(spy).toHaveBeenCalledWith('test-project-uuid', 'test-env-uuid');
160+
});
161+
});
56162
});

src/lib/coolify-client.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CoolifyConfig, ErrorResponse, ServerInfo, ServerResources, ServerDomain, ValidationResponse } from '../types/coolify.js';
1+
import { CoolifyConfig, ErrorResponse, ServerInfo, ServerResources, ServerDomain, ValidationResponse, Project, CreateProjectRequest, UpdateProjectRequest, Environment } from '../types/coolify.js';
22

33
export class CoolifyClient {
44
private baseUrl: string;
@@ -15,14 +15,15 @@ export class CoolifyClient {
1515
this.accessToken = config.accessToken;
1616
}
1717

18-
private async request<T>(path: string): Promise<T> {
18+
private async request<T>(path: string, options: RequestInit = {}): Promise<T> {
1919
try {
2020
const url = `${this.baseUrl}/api/v1${path}`;
2121
const response = await fetch(url, {
2222
headers: {
2323
'Content-Type': 'application/json',
2424
Authorization: `Bearer ${this.accessToken}`,
2525
},
26+
...options,
2627
});
2728

2829
const data = await response.json();
@@ -69,5 +70,37 @@ export class CoolifyClient {
6970
}
7071
}
7172

73+
async listProjects(): Promise<Project[]> {
74+
return this.request<Project[]>('/projects');
75+
}
76+
77+
async getProject(uuid: string): Promise<Project> {
78+
return this.request<Project>(`/projects/${uuid}`);
79+
}
80+
81+
async createProject(project: CreateProjectRequest): Promise<{ uuid: string }> {
82+
return this.request<{ uuid: string }>('/projects', {
83+
method: 'POST',
84+
body: JSON.stringify(project),
85+
});
86+
}
87+
88+
async updateProject(uuid: string, project: UpdateProjectRequest): Promise<Project> {
89+
return this.request<Project>(`/projects/${uuid}`, {
90+
method: 'PATCH',
91+
body: JSON.stringify(project),
92+
});
93+
}
94+
95+
async deleteProject(uuid: string): Promise<{ message: string }> {
96+
return this.request<{ message: string }>(`/projects/${uuid}`, {
97+
method: 'DELETE',
98+
});
99+
}
100+
101+
async getProjectEnvironment(projectUuid: string, environmentNameOrUuid: string): Promise<Environment> {
102+
return this.request<Environment>(`/projects/${projectUuid}/${environmentNameOrUuid}`);
103+
}
104+
72105
// Add more methods as needed for other endpoints
73106
}

0 commit comments

Comments
 (0)