codens-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,93 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ /lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+
24
+ # Virtual environments
25
+ venv/
26
+ env/
27
+ ENV/
28
+ .venv
29
+
30
+ # IDEs
31
+ .vscode/
32
+ .idea/
33
+ *.swp
34
+ *.swo
35
+ *~
36
+
37
+ # OS
38
+ .DS_Store
39
+ Thumbs.db
40
+
41
+ # Testing
42
+ .pytest_cache/
43
+ .coverage
44
+ htmlcov/
45
+ .tox/
46
+ .hypothesis/
47
+
48
+ # Environment variables
49
+ .env
50
+ .env.local
51
+ .env.*.local
52
+
53
+ # Logs
54
+ *.log
55
+ logs/
56
+
57
+ # Database
58
+ *.db
59
+ *.sqlite
60
+ *.sqlite3
61
+
62
+ # Node.js
63
+ node_modules/
64
+ npm-debug.log*
65
+ yarn-debug.log*
66
+ yarn-error.log*
67
+ .pnpm-debug.log*
68
+
69
+ # Next.js
70
+ .next/
71
+ out/
72
+
73
+ # Production
74
+ /build
75
+ /dist
76
+
77
+ # Temporary files
78
+ *.tmp
79
+ *.temp
80
+ .cache/
81
+
82
+ # Terraform
83
+ *.tfstate
84
+ *.tfstate.*
85
+ .terraform/
86
+ .terraform.lock.hcl
87
+ tfplan
88
+
89
+ # AWS
90
+ .aws-sam/
91
+
92
+ # serena memories (keep but optional)
93
+ # .serena/
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to `codens-mcp` are documented here.
4
+
5
+ ## [0.1.0] - 2026-05-06
6
+
7
+ ### Added
8
+
9
+ - Initial release of `codens-mcp` — the unified MCP server for the Codens family.
10
+ - **Purple tools (16)**: All tools re-exported from `purple-codens-mcp>=0.2.0`.
11
+ - Auth: `purple_login`, `purple_whoami`
12
+ - Project: `purple_analyze_repo`, `purple_list_projects`, `purple_init_project`
13
+ - Repository: `purple_add_repository`, `purple_list_repositories`
14
+ - Instructions: `purple_import_instructions`, `purple_list_instructions`, `purple_sync_instructions`
15
+ - Workflow: `purple_create_workflow`, `purple_get_run_status`, `purple_list_runs`, `purple_cancel_run`, `purple_inject_message`
16
+ - SSE: `purple_subscribe_run_events`
17
+ - **Red tools (4)**: Red Codens bug-report and auto-fix integration.
18
+ - `red_create_bug_report` — POST `/api/v1/agent/create-bug-report`
19
+ - `red_get_bug_report` — GET `/api/v1/organizations/{org}/bug-reports/{id}`
20
+ - `red_analyze_bug_report` — POST `/api/v1/organizations/{org}/bug-reports/{id}/analyze`
21
+ - `red_submit_bug_fix_plan_to_purple` — POST `/api/v1/organizations/{org}/bug-fix-plans/{id}/submit`
22
+ - **Blue tools (4)**: Blue Codens QA/E2E automation.
23
+ - `blue_list_e2e_tests`
24
+ - `blue_generate_e2e_test`
25
+ - `blue_run_e2e_test`
26
+ - `blue_get_e2e_test_results`
27
+ - **Green tools (4)**: Green Codens PRD consultation and kickoff.
28
+ - `green_create_consultation_with_message`
29
+ - `green_send_consultation_message`
30
+ - `green_convert_consultation_to_prd`
31
+ - `green_create_kickoff`
32
+ - **Auth tools (2)**: Auth Codens agent signup and public pricing.
33
+ - `auth_agent_signup`
34
+ - `auth_get_pricing`
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright 2026 Corevice Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: codens-mcp
3
+ Version: 0.1.0
4
+ Summary: Unified MCP server for Codens family — Red (auto-fix), Blue (QA), Green (PRD), Auth, plus all Purple tools via purple-codens-mcp.
5
+ Project-URL: Homepage, https://github.com/Corevice/purple-codens/tree/main/codens-mcp
6
+ Project-URL: Repository, https://github.com/Corevice/purple-codens
7
+ Project-URL: Issues, https://github.com/Corevice/purple-codens/issues
8
+ Project-URL: Changelog, https://github.com/Corevice/purple-codens/blob/main/codens-mcp/CHANGELOG.md
9
+ Author-email: Corevice <engineering@corevice.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,auto-fix,automation,claude,codens,mcp,prd,qa,workflow
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: anyio>=4.0.0
23
+ Requires-Dist: httpx>=0.27.0
24
+ Requires-Dist: mcp>=1.0.0
25
+ Requires-Dist: purple-codens-mcp>=0.2.0
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: build>=1.0.0; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
30
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
31
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # codens-mcp
35
+
36
+ [![PyPI version](https://img.shields.io/pypi/v/codens-mcp)](https://pypi.org/project/codens-mcp/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/codens-mcp)](https://pypi.org/project/codens-mcp/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
39
+
40
+ Unified MCP server for the **Codens family** — lets Claude Code and other AI agents operate all Codens services through a single package.
41
+
42
+ Includes all 16 **Purple** tools (re-exported from `purple-codens-mcp`) plus new tools for **Red** (auto-fix), **Blue** (QA), **Green** (PRD), and **Auth Codens**.
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ pip install codens-mcp
48
+ ```
49
+
50
+ ## Quick Start (Claude Code)
51
+
52
+ Add to your `.claude/settings.json`:
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "codens": {
58
+ "command": "codens-mcp",
59
+ "args": []
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ Then log in once via `purple_login` — the JWT issued by Auth Codens is accepted by all family backends.
66
+
67
+ ## Available Tools (30 total)
68
+
69
+ ### Auth / Session (Purple)
70
+ | Tool | Description |
71
+ |------|-------------|
72
+ | `purple_login` | Log in via browser OAuth, device code, or email+password |
73
+ | `purple_whoami` | Show current authenticated user and organization |
74
+
75
+ ### Project Setup (Purple)
76
+ | Tool | Description |
77
+ |------|-------------|
78
+ | `purple_analyze_repo` | Scan local repo and return structured analysis |
79
+ | `purple_list_projects` | List all projects in the organization |
80
+ | `purple_init_project` | Full project setup: create → link repo → import instructions |
81
+
82
+ ### Repository (Purple)
83
+ | Tool | Description |
84
+ |------|-------------|
85
+ | `purple_add_repository` | Link a GitHub repository to a project |
86
+ | `purple_list_repositories` | List repositories linked to a project |
87
+
88
+ ### Instruction Files (Purple)
89
+ | Tool | Description |
90
+ |------|-------------|
91
+ | `purple_import_instructions` | Import CLAUDE.md + .claude/rules/ from GitHub |
92
+ | `purple_list_instructions` | List instruction files for a project |
93
+ | `purple_sync_instructions` | Diff local vs remote, update changed files |
94
+
95
+ ### Workflow & Runs (Purple)
96
+ | Tool | Description |
97
+ |------|-------------|
98
+ | `purple_create_workflow` | Create a new workflow |
99
+ | `purple_get_run_status` | Get current status of a workflow run |
100
+ | `purple_list_runs` | List workflow runs (filter by project/status) |
101
+ | `purple_cancel_run` | Cancel a running workflow |
102
+ | `purple_inject_message` | Inject a message into a heartbeat run |
103
+ | `purple_subscribe_run_events` | Stream SSE events for a workflow run |
104
+
105
+ ### Red Codens — Auto-Fix
106
+ | Tool | Description |
107
+ |------|-------------|
108
+ | `red_create_bug_report` | Create a bug report (agent endpoint) |
109
+ | `red_get_bug_report` | Get a bug report by ID |
110
+ | `red_analyze_bug_report` | Trigger AI analysis for a bug report |
111
+ | `red_submit_bug_fix_plan_to_purple` | Submit a fix plan to Purple for execution |
112
+
113
+ ### Blue Codens — QA Automation
114
+ | Tool | Description |
115
+ |------|-------------|
116
+ | `blue_list_e2e_tests` | List E2E tests |
117
+ | `blue_generate_e2e_test` | Generate an E2E test from a natural-language requirement |
118
+ | `blue_run_e2e_test` | Trigger a run for an existing E2E test |
119
+ | `blue_get_e2e_test_results` | Get results for an E2E test (latest run) |
120
+
121
+ ### Green Codens — PRD Management
122
+ | Tool | Description |
123
+ |------|-------------|
124
+ | `green_create_consultation_with_message` | Start a consultation and send the first message |
125
+ | `green_send_consultation_message` | Send a message in an existing consultation |
126
+ | `green_convert_consultation_to_prd` | Convert a consultation into a PRD |
127
+ | `green_create_kickoff` | Create a Kickoff in Green Codens |
128
+
129
+ ### Auth Codens
130
+ | Tool | Description |
131
+ |------|-------------|
132
+ | `auth_agent_signup` | Issue capability_token via existing user's API key |
133
+ | `auth_get_pricing` | Public pricing.json (no auth required) |
134
+
135
+ ## Changelog
136
+
137
+ See [CHANGELOG.md](CHANGELOG.md).
138
+
139
+ ## License
140
+
141
+ MIT — Copyright 2026 Corevice Inc.
@@ -0,0 +1,108 @@
1
+ # codens-mcp
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/codens-mcp)](https://pypi.org/project/codens-mcp/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/codens-mcp)](https://pypi.org/project/codens-mcp/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ Unified MCP server for the **Codens family** — lets Claude Code and other AI agents operate all Codens services through a single package.
8
+
9
+ Includes all 16 **Purple** tools (re-exported from `purple-codens-mcp`) plus new tools for **Red** (auto-fix), **Blue** (QA), **Green** (PRD), and **Auth Codens**.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install codens-mcp
15
+ ```
16
+
17
+ ## Quick Start (Claude Code)
18
+
19
+ Add to your `.claude/settings.json`:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "codens": {
25
+ "command": "codens-mcp",
26
+ "args": []
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ Then log in once via `purple_login` — the JWT issued by Auth Codens is accepted by all family backends.
33
+
34
+ ## Available Tools (30 total)
35
+
36
+ ### Auth / Session (Purple)
37
+ | Tool | Description |
38
+ |------|-------------|
39
+ | `purple_login` | Log in via browser OAuth, device code, or email+password |
40
+ | `purple_whoami` | Show current authenticated user and organization |
41
+
42
+ ### Project Setup (Purple)
43
+ | Tool | Description |
44
+ |------|-------------|
45
+ | `purple_analyze_repo` | Scan local repo and return structured analysis |
46
+ | `purple_list_projects` | List all projects in the organization |
47
+ | `purple_init_project` | Full project setup: create → link repo → import instructions |
48
+
49
+ ### Repository (Purple)
50
+ | Tool | Description |
51
+ |------|-------------|
52
+ | `purple_add_repository` | Link a GitHub repository to a project |
53
+ | `purple_list_repositories` | List repositories linked to a project |
54
+
55
+ ### Instruction Files (Purple)
56
+ | Tool | Description |
57
+ |------|-------------|
58
+ | `purple_import_instructions` | Import CLAUDE.md + .claude/rules/ from GitHub |
59
+ | `purple_list_instructions` | List instruction files for a project |
60
+ | `purple_sync_instructions` | Diff local vs remote, update changed files |
61
+
62
+ ### Workflow & Runs (Purple)
63
+ | Tool | Description |
64
+ |------|-------------|
65
+ | `purple_create_workflow` | Create a new workflow |
66
+ | `purple_get_run_status` | Get current status of a workflow run |
67
+ | `purple_list_runs` | List workflow runs (filter by project/status) |
68
+ | `purple_cancel_run` | Cancel a running workflow |
69
+ | `purple_inject_message` | Inject a message into a heartbeat run |
70
+ | `purple_subscribe_run_events` | Stream SSE events for a workflow run |
71
+
72
+ ### Red Codens — Auto-Fix
73
+ | Tool | Description |
74
+ |------|-------------|
75
+ | `red_create_bug_report` | Create a bug report (agent endpoint) |
76
+ | `red_get_bug_report` | Get a bug report by ID |
77
+ | `red_analyze_bug_report` | Trigger AI analysis for a bug report |
78
+ | `red_submit_bug_fix_plan_to_purple` | Submit a fix plan to Purple for execution |
79
+
80
+ ### Blue Codens — QA Automation
81
+ | Tool | Description |
82
+ |------|-------------|
83
+ | `blue_list_e2e_tests` | List E2E tests |
84
+ | `blue_generate_e2e_test` | Generate an E2E test from a natural-language requirement |
85
+ | `blue_run_e2e_test` | Trigger a run for an existing E2E test |
86
+ | `blue_get_e2e_test_results` | Get results for an E2E test (latest run) |
87
+
88
+ ### Green Codens — PRD Management
89
+ | Tool | Description |
90
+ |------|-------------|
91
+ | `green_create_consultation_with_message` | Start a consultation and send the first message |
92
+ | `green_send_consultation_message` | Send a message in an existing consultation |
93
+ | `green_convert_consultation_to_prd` | Convert a consultation into a PRD |
94
+ | `green_create_kickoff` | Create a Kickoff in Green Codens |
95
+
96
+ ### Auth Codens
97
+ | Tool | Description |
98
+ |------|-------------|
99
+ | `auth_agent_signup` | Issue capability_token via existing user's API key |
100
+ | `auth_get_pricing` | Public pricing.json (no auth required) |
101
+
102
+ ## Changelog
103
+
104
+ See [CHANGELOG.md](CHANGELOG.md).
105
+
106
+ ## License
107
+
108
+ MIT — Copyright 2026 Corevice Inc.
@@ -0,0 +1,63 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "codens-mcp"
7
+ version = "0.1.0"
8
+ description = "Unified MCP server for Codens family — Red (auto-fix), Blue (QA), Green (PRD), Auth, plus all Purple tools via purple-codens-mcp."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Corevice", email = "engineering@corevice.com" }]
13
+ keywords = ["mcp", "claude", "agent", "codens", "workflow", "automation", "qa", "prd", "auto-fix"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Software Development :: Libraries :: Python Modules",
23
+ ]
24
+ dependencies = [
25
+ "mcp>=1.0.0",
26
+ "httpx>=0.27.0",
27
+ "pydantic>=2.0.0",
28
+ "anyio>=4.0.0",
29
+ "purple-codens-mcp>=0.2.0",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/Corevice/purple-codens/tree/main/codens-mcp"
34
+ Repository = "https://github.com/Corevice/purple-codens"
35
+ Issues = "https://github.com/Corevice/purple-codens/issues"
36
+ Changelog = "https://github.com/Corevice/purple-codens/blob/main/codens-mcp/CHANGELOG.md"
37
+
38
+ [project.scripts]
39
+ codens-mcp = "codens_mcp.server:main"
40
+
41
+ [project.optional-dependencies]
42
+ dev = [
43
+ "pytest>=8.0.0",
44
+ "pytest-asyncio>=0.23.0",
45
+ "pytest-cov>=4.1.0",
46
+ "build>=1.0.0",
47
+ ]
48
+
49
+ [tool.hatch.build.targets.sdist]
50
+ include = ["src/codens_mcp", "README.md", "CHANGELOG.md", "LICENSE"]
51
+
52
+ [tool.hatch.build.targets.wheel]
53
+ packages = ["src/codens_mcp"]
54
+
55
+ [tool.pytest.ini_options]
56
+ asyncio_mode = "auto"
57
+
58
+ [tool.ruff]
59
+ line-length = 100
60
+
61
+ [tool.mypy]
62
+ python_version = "3.11"
63
+ strict = false
@@ -0,0 +1,2 @@
1
+ __version__ = "0.1.0"
2
+ __all__ = ["__version__"]
@@ -0,0 +1,3 @@
1
+ from .server import main
2
+
3
+ main()
@@ -0,0 +1,15 @@
1
+ from .auth_helper import AuthenticationError, Credentials, load_codens_credentials
2
+ from .red import RedClient
3
+ from .blue import BlueClient
4
+ from .green import GreenClient
5
+ from .auth import AuthClient
6
+
7
+ __all__ = [
8
+ "AuthenticationError",
9
+ "Credentials",
10
+ "load_codens_credentials",
11
+ "RedClient",
12
+ "BlueClient",
13
+ "GreenClient",
14
+ "AuthClient",
15
+ ]
@@ -0,0 +1,23 @@
1
+ """HTTP client for Auth Codens (agent-signup and public endpoints)."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+
7
+
8
+ class AuthClient:
9
+ def __init__(self, api_url: str, token: Optional[str] = None) -> None:
10
+ self.api_url = api_url.rstrip("/")
11
+ self.token = token
12
+
13
+ def get_no_auth(self, path: str) -> dict:
14
+ with httpx.Client(timeout=10.0) as c:
15
+ r = c.get(f"{self.api_url}{path}")
16
+ r.raise_for_status()
17
+ return r.json()
18
+
19
+ def post(self, path: str, json: dict) -> dict:
20
+ with httpx.Client(timeout=10.0) as c:
21
+ r = c.post(f"{self.api_url}{path}", json=json)
22
+ r.raise_for_status()
23
+ return r.json()
@@ -0,0 +1,33 @@
1
+ """Shared credential helper for Codens family clients.
2
+
3
+ Reuses the credential store written by purple_login (~/.purple-codens/credentials.json).
4
+ Auth Codens is the single OIDC issuer for the family, so the same JWT is accepted
5
+ by Red, Blue, and Green backends.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+
10
+ from purple_codens_mcp.auth import get_credentials
11
+
12
+
13
+ class AuthenticationError(Exception):
14
+ """Raised when credentials are missing or invalid."""
15
+
16
+
17
+ @dataclass
18
+ class Credentials:
19
+ token: str
20
+
21
+
22
+ def load_codens_credentials(api_url: str, service: str) -> Credentials:
23
+ """Load JWT from the purple-codens-mcp credential store.
24
+
25
+ For v0.1.0 all Codens backends share the JWT issued by Auth Codens via
26
+ purple_login. Future versions may use per-service capability_tokens.
27
+ """
28
+ creds = get_credentials(api_url)
29
+ if creds is None:
30
+ raise AuthenticationError(
31
+ f"Not logged in for {api_url}. Run purple_login first."
32
+ )
33
+ return Credentials(token=creds["token"])
@@ -0,0 +1,32 @@
1
+ """HTTP client for Blue Codens (QA automation service)."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+
7
+ from .auth_helper import AuthenticationError
8
+
9
+
10
+ class BlueClient:
11
+ def __init__(self, api_url: str, token: str) -> None:
12
+ self.api_url = api_url.rstrip("/")
13
+ self.token = token
14
+
15
+ def _headers(self) -> dict:
16
+ return {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"}
17
+
18
+ def get(self, path: str, params: Optional[dict] = None) -> dict:
19
+ with httpx.Client(timeout=30.0) as c:
20
+ r = c.get(f"{self.api_url}{path}", headers=self._headers(), params=params)
21
+ if r.status_code == 401:
22
+ raise AuthenticationError("401 from Blue Codens — token may be expired")
23
+ r.raise_for_status()
24
+ return r.json()
25
+
26
+ def post(self, path: str, json: Optional[dict] = None) -> dict:
27
+ with httpx.Client(timeout=30.0) as c:
28
+ r = c.post(f"{self.api_url}{path}", headers=self._headers(), json=json or {})
29
+ if r.status_code == 401:
30
+ raise AuthenticationError("401 from Blue Codens — token may be expired")
31
+ r.raise_for_status()
32
+ return r.json()
@@ -0,0 +1,32 @@
1
+ """HTTP client for Green Codens (PRD management service)."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+
7
+ from .auth_helper import AuthenticationError
8
+
9
+
10
+ class GreenClient:
11
+ def __init__(self, api_url: str, token: str) -> None:
12
+ self.api_url = api_url.rstrip("/")
13
+ self.token = token
14
+
15
+ def _headers(self) -> dict:
16
+ return {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"}
17
+
18
+ def get(self, path: str, params: Optional[dict] = None) -> dict:
19
+ with httpx.Client(timeout=30.0) as c:
20
+ r = c.get(f"{self.api_url}{path}", headers=self._headers(), params=params)
21
+ if r.status_code == 401:
22
+ raise AuthenticationError("401 from Green Codens — token may be expired")
23
+ r.raise_for_status()
24
+ return r.json()
25
+
26
+ def post(self, path: str, json: Optional[dict] = None) -> dict:
27
+ with httpx.Client(timeout=30.0) as c:
28
+ r = c.post(f"{self.api_url}{path}", headers=self._headers(), json=json or {})
29
+ if r.status_code == 401:
30
+ raise AuthenticationError("401 from Green Codens — token may be expired")
31
+ r.raise_for_status()
32
+ return r.json()
@@ -0,0 +1,32 @@
1
+ """HTTP client for Red Codens (auto-fix service)."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+
7
+ from .auth_helper import AuthenticationError
8
+
9
+
10
+ class RedClient:
11
+ def __init__(self, api_url: str, token: str) -> None:
12
+ self.api_url = api_url.rstrip("/")
13
+ self.token = token
14
+
15
+ def _headers(self) -> dict:
16
+ return {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"}
17
+
18
+ def get(self, path: str, params: Optional[dict] = None) -> dict:
19
+ with httpx.Client(timeout=30.0) as c:
20
+ r = c.get(f"{self.api_url}{path}", headers=self._headers(), params=params)
21
+ if r.status_code == 401:
22
+ raise AuthenticationError("401 from Red Codens — token may be expired")
23
+ r.raise_for_status()
24
+ return r.json()
25
+
26
+ def post(self, path: str, json: Optional[dict] = None) -> dict:
27
+ with httpx.Client(timeout=30.0) as c:
28
+ r = c.post(f"{self.api_url}{path}", headers=self._headers(), json=json or {})
29
+ if r.status_code == 401:
30
+ raise AuthenticationError("401 from Red Codens — token may be expired")
31
+ r.raise_for_status()
32
+ return r.json()
@@ -0,0 +1,30 @@
1
+ """Unified MCP server entry point for the Codens family."""
2
+
3
+ from mcp.server.fastmcp import FastMCP
4
+
5
+ from .tools.auth_tools import register_auth_tools
6
+ from .tools.blue_tools import register_blue_tools
7
+ from .tools.green_tools import register_green_tools
8
+ from .tools.purple_tools import register_purple_tools
9
+ from .tools.red_tools import register_red_tools
10
+
11
+
12
+ def main() -> None:
13
+ mcp = FastMCP(
14
+ "codens",
15
+ instructions=(
16
+ "Codens unified MCP server. "
17
+ "Use purple_login first, then use Red/Blue/Green/Auth tools as needed. "
18
+ "All tools that call Codens APIs require the api_url parameter."
19
+ ),
20
+ )
21
+ register_purple_tools(mcp)
22
+ register_red_tools(mcp)
23
+ register_blue_tools(mcp)
24
+ register_green_tools(mcp)
25
+ register_auth_tools(mcp)
26
+ mcp.run()
27
+
28
+
29
+ if __name__ == "__main__":
30
+ main()
File without changes
@@ -0,0 +1,47 @@
1
+ """Auth Codens MCP tools: agent signup and public pricing."""
2
+
3
+ from mcp.server.fastmcp import FastMCP
4
+
5
+ from ..client.auth import AuthClient
6
+
7
+
8
+ def register_auth_tools(mcp: FastMCP) -> None:
9
+
10
+ @mcp.tool()
11
+ async def auth_agent_signup(
12
+ api_url: str,
13
+ existing_api_key: str,
14
+ agent_identity_token: str,
15
+ agent_identity_provider: str = "anthropic",
16
+ ) -> dict:
17
+ """
18
+ Issue a capability_token and register an agent member via the existing user's API key.
19
+
20
+ Path: POST /api/v1/auth/agent-signup
21
+
22
+ Args:
23
+ api_url: Auth Codens API base URL
24
+ existing_api_key: The user's existing API key (used to authorize agent registration)
25
+ agent_identity_token: Identity token issued by the agent provider
26
+ agent_identity_provider: Provider name (default: anthropic)
27
+ """
28
+ return AuthClient(api_url).post(
29
+ "/api/v1/auth/agent-signup",
30
+ json={
31
+ "existing_api_key": existing_api_key,
32
+ "agent_identity_token": agent_identity_token,
33
+ "agent_identity_provider": agent_identity_provider,
34
+ },
35
+ )
36
+
37
+ @mcp.tool()
38
+ async def auth_get_pricing(api_url: str) -> dict:
39
+ """
40
+ Return the public pricing.json (proxied from BCP, 5-min cache + fallback). No auth required.
41
+
42
+ Path: GET /.well-known/pricing.json
43
+
44
+ Args:
45
+ api_url: Auth Codens API base URL
46
+ """
47
+ return AuthClient(api_url).get_no_auth("/.well-known/pricing.json")
@@ -0,0 +1,94 @@
1
+ """Blue Codens MCP tools: E2E test generation and execution."""
2
+
3
+ from mcp.server.fastmcp import FastMCP
4
+
5
+ from ..client.auth_helper import load_codens_credentials
6
+ from ..client.blue import BlueClient
7
+
8
+
9
+ def _client(api_url: str) -> BlueClient:
10
+ creds = load_codens_credentials(api_url, service="blue")
11
+ return BlueClient(api_url, creds.token)
12
+
13
+
14
+ def register_blue_tools(mcp: FastMCP) -> None:
15
+
16
+ @mcp.tool()
17
+ async def blue_list_e2e_tests(
18
+ api_url: str,
19
+ page: int = 1,
20
+ page_size: int = 20,
21
+ ) -> dict:
22
+ """
23
+ List E2E tests.
24
+
25
+ Path: GET /api/v1/e2e-tests
26
+
27
+ Args:
28
+ api_url: Blue Codens API base URL
29
+ page: Page number (default: 1)
30
+ page_size: Results per page (default: 20)
31
+ """
32
+ return _client(api_url).get(
33
+ "/api/v1/e2e-tests",
34
+ params={"page": page, "page_size": page_size},
35
+ )
36
+
37
+ @mcp.tool()
38
+ async def blue_generate_e2e_test(
39
+ api_url: str,
40
+ project_id: str,
41
+ requirement: str,
42
+ language: str = "playwright",
43
+ ) -> dict:
44
+ """
45
+ Generate an E2E test from a natural-language requirement.
46
+
47
+ Path: POST /api/v1/ai/generate/e2e-test
48
+
49
+ Args:
50
+ api_url: Blue Codens API base URL
51
+ project_id: Project ID
52
+ requirement: Natural-language description of the test scenario
53
+ language: Test framework (default: playwright)
54
+ """
55
+ return _client(api_url).post(
56
+ "/api/v1/ai/generate/e2e-test",
57
+ json={
58
+ "project_id": project_id,
59
+ "requirement": requirement,
60
+ "language": language,
61
+ },
62
+ )
63
+
64
+ @mcp.tool()
65
+ async def blue_run_e2e_test(
66
+ api_url: str,
67
+ e2e_test_id: str,
68
+ ) -> dict:
69
+ """
70
+ Trigger a run for an existing E2E test.
71
+
72
+ Path: POST /api/v1/e2e-tests/{e2e_test_id}/run
73
+
74
+ Args:
75
+ api_url: Blue Codens API base URL
76
+ e2e_test_id: E2E test ID
77
+ """
78
+ return _client(api_url).post(f"/api/v1/e2e-tests/{e2e_test_id}/run", json={})
79
+
80
+ @mcp.tool()
81
+ async def blue_get_e2e_test_results(
82
+ api_url: str,
83
+ e2e_test_id: str,
84
+ ) -> dict:
85
+ """
86
+ Get results for an E2E test (latest run).
87
+
88
+ Path: GET /api/v1/e2e-tests/{e2e_test_id}/results
89
+
90
+ Args:
91
+ api_url: Blue Codens API base URL
92
+ e2e_test_id: E2E test ID
93
+ """
94
+ return _client(api_url).get(f"/api/v1/e2e-tests/{e2e_test_id}/results")
@@ -0,0 +1,115 @@
1
+ """Green Codens MCP tools: PRD consultations and kickoffs."""
2
+
3
+ from typing import Optional
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+
7
+ from ..client.auth_helper import load_codens_credentials
8
+ from ..client.green import GreenClient
9
+
10
+
11
+ def _client(api_url: str) -> GreenClient:
12
+ creds = load_codens_credentials(api_url, service="green")
13
+ return GreenClient(api_url, creds.token)
14
+
15
+
16
+ def register_green_tools(mcp: FastMCP) -> None:
17
+
18
+ @mcp.tool()
19
+ async def green_create_consultation_with_message(
20
+ api_url: str,
21
+ organization_id: str,
22
+ project_id: str,
23
+ message: str,
24
+ ) -> dict:
25
+ """
26
+ Create a new consultation session and send the first message in one call (lazy creation).
27
+
28
+ Path: POST /api/v1/organizations/{organization_id}/consultations/with-message
29
+
30
+ Args:
31
+ api_url: Green Codens API base URL
32
+ organization_id: Organization ID
33
+ project_id: Project ID
34
+ message: First message to send in the consultation
35
+ """
36
+ return _client(api_url).post(
37
+ f"/api/v1/organizations/{organization_id}/consultations/with-message",
38
+ json={"project_id": project_id, "message": message},
39
+ )
40
+
41
+ @mcp.tool()
42
+ async def green_send_consultation_message(
43
+ api_url: str,
44
+ organization_id: str,
45
+ consultation_id: str,
46
+ message: str,
47
+ ) -> dict:
48
+ """
49
+ Send a message in an existing consultation and get the AI reply.
50
+
51
+ Path: POST /api/v1/organizations/{organization_id}/consultations/{consultation_id}/messages
52
+
53
+ Args:
54
+ api_url: Green Codens API base URL
55
+ organization_id: Organization ID
56
+ consultation_id: Consultation session ID
57
+ message: Message to send
58
+ """
59
+ return _client(api_url).post(
60
+ f"/api/v1/organizations/{organization_id}/consultations/{consultation_id}/messages",
61
+ json={"message": message},
62
+ )
63
+
64
+ @mcp.tool()
65
+ async def green_convert_consultation_to_prd(
66
+ api_url: str,
67
+ organization_id: str,
68
+ consultation_id: str,
69
+ title: Optional[str] = None,
70
+ ) -> dict:
71
+ """
72
+ Convert a consultation session into a PRD.
73
+
74
+ Path: POST /api/v1/organizations/{organization_id}/consultations/{consultation_id}/convert-to-prd
75
+
76
+ Args:
77
+ api_url: Green Codens API base URL
78
+ organization_id: Organization ID
79
+ consultation_id: Consultation session ID
80
+ title: Optional PRD title (auto-generated if omitted)
81
+ """
82
+ body = {"title": title} if title else {}
83
+ return _client(api_url).post(
84
+ f"/api/v1/organizations/{organization_id}/consultations/{consultation_id}/convert-to-prd",
85
+ json=body,
86
+ )
87
+
88
+ @mcp.tool()
89
+ async def green_create_kickoff(
90
+ api_url: str,
91
+ organization_id: str,
92
+ project_id: str,
93
+ title: str,
94
+ description: str,
95
+ ) -> dict:
96
+ """
97
+ Create a Kickoff in Green Codens.
98
+
99
+ Path: POST /api/v1/organizations/{organization_id}/kickoffs
100
+
101
+ Args:
102
+ api_url: Green Codens API base URL
103
+ organization_id: Organization ID
104
+ project_id: Project ID
105
+ title: Kickoff title
106
+ description: Kickoff description
107
+ """
108
+ return _client(api_url).post(
109
+ f"/api/v1/organizations/{organization_id}/kickoffs",
110
+ json={
111
+ "project_id": project_id,
112
+ "title": title,
113
+ "description": description,
114
+ },
115
+ )
@@ -0,0 +1,28 @@
1
+ """Re-export all 16 Purple tools from purple-codens-mcp dependency."""
2
+
3
+ from mcp.server.fastmcp import FastMCP
4
+ from purple_codens_mcp.client import PurpleCodensClient
5
+ from purple_codens_mcp.tools.auth_tools import register_auth_tools as _register_purple_auth
6
+ from purple_codens_mcp.tools.instruction_tools import register_instruction_tools as _register_instructions
7
+ from purple_codens_mcp.tools.project_tools import register_project_tools as _register_projects
8
+ from purple_codens_mcp.tools.repo_tools import register_repo_tools as _register_repos
9
+ from purple_codens_mcp.tools.sse_tools import register_sse_tools as _register_sse
10
+ from purple_codens_mcp.tools.workflow_tools import register_workflow_tools as _register_workflows
11
+
12
+ _purple_clients: dict[str, PurpleCodensClient] = {}
13
+
14
+
15
+ def _purple_get_client(api_url: str, force_reload: bool = False) -> PurpleCodensClient:
16
+ if force_reload or api_url not in _purple_clients:
17
+ _purple_clients[api_url] = PurpleCodensClient(api_url)
18
+ return _purple_clients[api_url]
19
+
20
+
21
+ def register_purple_tools(mcp: FastMCP) -> None:
22
+ """Register all 16 Purple tools (auth/project/repo/instruction/workflow/sse)."""
23
+ _register_purple_auth(mcp, _purple_get_client)
24
+ _register_projects(mcp, _purple_get_client)
25
+ _register_repos(mcp, _purple_get_client)
26
+ _register_instructions(mcp, _purple_get_client)
27
+ _register_workflows(mcp, _purple_get_client)
28
+ _register_sse(mcp, _purple_get_client)
@@ -0,0 +1,110 @@
1
+ """Red Codens MCP tools: bug reports and auto-fix plan submission."""
2
+
3
+ from typing import Optional
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+
7
+ from ..client.auth_helper import load_codens_credentials
8
+ from ..client.red import RedClient
9
+
10
+
11
+ def _client(api_url: str) -> RedClient:
12
+ creds = load_codens_credentials(api_url, service="red")
13
+ return RedClient(api_url, creds.token)
14
+
15
+
16
+ def register_red_tools(mcp: FastMCP) -> None:
17
+
18
+ @mcp.tool()
19
+ async def red_create_bug_report(
20
+ api_url: str,
21
+ organization_id: str,
22
+ project_id: str,
23
+ title: str,
24
+ description: str,
25
+ attachment_urls: Optional[list[str]] = None,
26
+ ) -> dict:
27
+ """
28
+ Create a bug report via Red Codens agent endpoint.
29
+
30
+ Path: POST /api/v1/agent/create-bug-report
31
+
32
+ Args:
33
+ api_url: Red Codens API base URL
34
+ organization_id: Organization ID
35
+ project_id: Project ID
36
+ title: Bug report title
37
+ description: Bug description
38
+ attachment_urls: Optional list of attachment URLs (screenshots, logs)
39
+ """
40
+ body: dict = {
41
+ "organization_id": organization_id,
42
+ "project_id": project_id,
43
+ "title": title,
44
+ "description": description,
45
+ }
46
+ if attachment_urls:
47
+ body["attachment_urls"] = attachment_urls
48
+ return _client(api_url).post("/api/v1/agent/create-bug-report", json=body)
49
+
50
+ @mcp.tool()
51
+ async def red_get_bug_report(
52
+ api_url: str,
53
+ organization_id: str,
54
+ bug_id: str,
55
+ ) -> dict:
56
+ """
57
+ Get a bug report by ID.
58
+
59
+ Path: GET /api/v1/organizations/{organization_id}/bug-reports/{bug_id}
60
+
61
+ Args:
62
+ api_url: Red Codens API base URL
63
+ organization_id: Organization ID
64
+ bug_id: Bug report ID
65
+ """
66
+ return _client(api_url).get(
67
+ f"/api/v1/organizations/{organization_id}/bug-reports/{bug_id}"
68
+ )
69
+
70
+ @mcp.tool()
71
+ async def red_analyze_bug_report(
72
+ api_url: str,
73
+ organization_id: str,
74
+ bug_id: str,
75
+ ) -> dict:
76
+ """
77
+ Trigger AI analysis for a bug report.
78
+
79
+ Path: POST /api/v1/organizations/{organization_id}/bug-reports/{bug_id}/analyze
80
+
81
+ Args:
82
+ api_url: Red Codens API base URL
83
+ organization_id: Organization ID
84
+ bug_id: Bug report ID
85
+ """
86
+ return _client(api_url).post(
87
+ f"/api/v1/organizations/{organization_id}/bug-reports/{bug_id}/analyze",
88
+ json={},
89
+ )
90
+
91
+ @mcp.tool()
92
+ async def red_submit_bug_fix_plan_to_purple(
93
+ api_url: str,
94
+ organization_id: str,
95
+ plan_id: str,
96
+ ) -> dict:
97
+ """
98
+ Submit DRAFT BugFixTasks of a plan to Purple Codens for execution.
99
+
100
+ Path: POST /api/v1/organizations/{organization_id}/bug-fix-plans/{plan_id}/submit
101
+
102
+ Args:
103
+ api_url: Red Codens API base URL
104
+ organization_id: Organization ID
105
+ plan_id: Bug fix plan ID
106
+ """
107
+ return _client(api_url).post(
108
+ f"/api/v1/organizations/{organization_id}/bug-fix-plans/{plan_id}/submit",
109
+ json={},
110
+ )