cloudwright-ai-mcp 0.3.1__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.
- cloudwright_ai_mcp-0.3.1/.gitignore +20 -0
- cloudwright_ai_mcp-0.3.1/PKG-INFO +10 -0
- cloudwright_ai_mcp-0.3.1/cloudwright_mcp/__init__.py +1 -0
- cloudwright_ai_mcp-0.3.1/cloudwright_mcp/py.typed +0 -0
- cloudwright_ai_mcp-0.3.1/cloudwright_mcp/serializers.py +24 -0
- cloudwright_ai_mcp-0.3.1/cloudwright_mcp/server.py +32 -0
- cloudwright_ai_mcp-0.3.1/cloudwright_mcp/tools/__init__.py +0 -0
- cloudwright_ai_mcp-0.3.1/cloudwright_mcp/tools/analyze.py +40 -0
- cloudwright_ai_mcp-0.3.1/cloudwright_mcp/tools/cost.py +25 -0
- cloudwright_ai_mcp-0.3.1/cloudwright_mcp/tools/design.py +44 -0
- cloudwright_ai_mcp-0.3.1/cloudwright_mcp/tools/export.py +42 -0
- cloudwright_ai_mcp-0.3.1/cloudwright_mcp/tools/session.py +55 -0
- cloudwright_ai_mcp-0.3.1/cloudwright_mcp/tools/validate.py +53 -0
- cloudwright_ai_mcp-0.3.1/pyproject.toml +24 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
dist/
|
|
5
|
+
build/
|
|
6
|
+
.env
|
|
7
|
+
*.db
|
|
8
|
+
!packages/core/cloudwright/data/catalog.db
|
|
9
|
+
node_modules/
|
|
10
|
+
.vite/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
.mypy_cache/
|
|
14
|
+
|
|
15
|
+
# AI tooling
|
|
16
|
+
.claude/
|
|
17
|
+
.planning/
|
|
18
|
+
CLAUDE.md
|
|
19
|
+
packages/*/CLAUDE.md
|
|
20
|
+
docs/fix-plan.md
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cloudwright-ai-mcp
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: MCP server for Cloudwright architecture intelligence
|
|
5
|
+
Project-URL: Homepage, https://github.com/xmpuspus/cloudwright
|
|
6
|
+
Author: Xavier Puspus
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Requires-Dist: cloudwright-ai<1,>=0.3.0
|
|
10
|
+
Requires-Dist: mcp<2,>=1.12
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.1"
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def security_report_to_dict(report) -> dict:
|
|
7
|
+
return {
|
|
8
|
+
"passed": report.passed,
|
|
9
|
+
"critical_count": report.critical_count,
|
|
10
|
+
"high_count": report.high_count,
|
|
11
|
+
"findings": [dataclasses.asdict(f) for f in report.findings],
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def lint_warnings_to_dict(warnings) -> list[dict]:
|
|
16
|
+
return [dataclasses.asdict(w) for w in warnings]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def score_result_to_dict(result) -> dict:
|
|
20
|
+
return result.to_dict()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def analysis_result_to_dict(result) -> dict:
|
|
24
|
+
return result.to_dict()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""FastMCP server for Cloudwright architecture intelligence."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from mcp.server.fastmcp import FastMCP
|
|
6
|
+
|
|
7
|
+
from cloudwright_mcp.tools import analyze, cost, design, export, session, validate
|
|
8
|
+
|
|
9
|
+
_GROUPS = {
|
|
10
|
+
"design": design,
|
|
11
|
+
"cost": cost,
|
|
12
|
+
"validate": validate,
|
|
13
|
+
"analyze": analyze,
|
|
14
|
+
"export": export,
|
|
15
|
+
"session": session,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_server(tools: set[str] | None = None) -> FastMCP:
|
|
20
|
+
"""Create a FastMCP server with selected tool groups.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
tools: Set of group names to register. None = all groups.
|
|
24
|
+
Valid groups: design, cost, validate, analyze, export, session.
|
|
25
|
+
"""
|
|
26
|
+
mcp = FastMCP("cloudwright", description="Architecture intelligence for cloud engineers")
|
|
27
|
+
|
|
28
|
+
for name, module in _GROUPS.items():
|
|
29
|
+
if tools is None or name in tools:
|
|
30
|
+
module.register(mcp)
|
|
31
|
+
|
|
32
|
+
return mcp
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from mcp.server.fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def register(mcp: FastMCP) -> None:
|
|
7
|
+
@mcp.tool()
|
|
8
|
+
def analyze_blast_radius(spec_json: dict, component_id: str | None = None) -> dict:
|
|
9
|
+
"""Analyze blast radius and dependency structure of an architecture."""
|
|
10
|
+
from cloudwright.analyzer import Analyzer
|
|
11
|
+
from cloudwright.spec import ArchSpec
|
|
12
|
+
|
|
13
|
+
from cloudwright_mcp.serializers import analysis_result_to_dict
|
|
14
|
+
|
|
15
|
+
spec = ArchSpec.model_validate(spec_json)
|
|
16
|
+
result = Analyzer().analyze(spec, component_id=component_id)
|
|
17
|
+
return analysis_result_to_dict(result)
|
|
18
|
+
|
|
19
|
+
@mcp.tool()
|
|
20
|
+
def score_architecture(spec_json: dict) -> dict:
|
|
21
|
+
"""Score an architecture across reliability, security, cost, compliance, and complexity."""
|
|
22
|
+
from cloudwright.scorer import Scorer
|
|
23
|
+
from cloudwright.spec import ArchSpec
|
|
24
|
+
|
|
25
|
+
from cloudwright_mcp.serializers import score_result_to_dict
|
|
26
|
+
|
|
27
|
+
spec = ArchSpec.model_validate(spec_json)
|
|
28
|
+
result = Scorer().score(spec)
|
|
29
|
+
return score_result_to_dict(result)
|
|
30
|
+
|
|
31
|
+
@mcp.tool()
|
|
32
|
+
def diff_architectures(old_spec_json: dict, new_spec_json: dict) -> dict:
|
|
33
|
+
"""Diff two architecture specs and return a structured change report."""
|
|
34
|
+
from cloudwright.differ import Differ
|
|
35
|
+
from cloudwright.spec import ArchSpec
|
|
36
|
+
|
|
37
|
+
old = ArchSpec.model_validate(old_spec_json)
|
|
38
|
+
new = ArchSpec.model_validate(new_spec_json)
|
|
39
|
+
result = Differ().diff(old, new)
|
|
40
|
+
return result.model_dump(exclude_none=True)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from mcp.server.fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def register(mcp: FastMCP) -> None:
|
|
7
|
+
@mcp.tool()
|
|
8
|
+
def estimate_cost(spec_json: dict, pricing_tier: str = "on_demand") -> dict:
|
|
9
|
+
"""Estimate monthly cost for an architecture spec."""
|
|
10
|
+
from cloudwright.cost import CostEngine
|
|
11
|
+
from cloudwright.spec import ArchSpec
|
|
12
|
+
|
|
13
|
+
spec = ArchSpec.model_validate(spec_json)
|
|
14
|
+
estimate = CostEngine().estimate(spec, pricing_tier=pricing_tier)
|
|
15
|
+
return estimate.model_dump(exclude_none=True)
|
|
16
|
+
|
|
17
|
+
@mcp.tool()
|
|
18
|
+
def compare_provider_costs(spec_json: dict, providers: list[str]) -> list[dict]:
|
|
19
|
+
"""Compare costs for an architecture across cloud providers."""
|
|
20
|
+
from cloudwright.cost import CostEngine
|
|
21
|
+
from cloudwright.spec import ArchSpec
|
|
22
|
+
|
|
23
|
+
spec = ArchSpec.model_validate(spec_json)
|
|
24
|
+
comparisons = CostEngine().compare_providers(spec, providers)
|
|
25
|
+
return [c.model_dump(exclude_none=True) for c in comparisons]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from mcp.server.fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def register(mcp: FastMCP) -> None:
|
|
7
|
+
@mcp.tool()
|
|
8
|
+
def design_architecture(
|
|
9
|
+
description: str,
|
|
10
|
+
provider: str = "aws",
|
|
11
|
+
region: str = "us-east-1",
|
|
12
|
+
budget_monthly: float | None = None,
|
|
13
|
+
compliance: list[str] | None = None,
|
|
14
|
+
) -> dict:
|
|
15
|
+
"""Design a cloud architecture from a natural language description."""
|
|
16
|
+
from cloudwright.architect import Architect
|
|
17
|
+
from cloudwright.cost import CostEngine
|
|
18
|
+
from cloudwright.spec import Constraints
|
|
19
|
+
|
|
20
|
+
constraints = Constraints(regions=[region], budget_monthly=budget_monthly, compliance=compliance or [])
|
|
21
|
+
spec = Architect().design(description, constraints=constraints)
|
|
22
|
+
if not spec.cost_estimate:
|
|
23
|
+
spec = spec.model_copy(update={"cost_estimate": CostEngine().estimate(spec)})
|
|
24
|
+
return spec.model_dump(exclude_none=True)
|
|
25
|
+
|
|
26
|
+
@mcp.tool()
|
|
27
|
+
def modify_architecture(spec_json: dict, instruction: str) -> dict:
|
|
28
|
+
"""Modify an existing architecture with a natural language instruction."""
|
|
29
|
+
from cloudwright.architect import Architect
|
|
30
|
+
from cloudwright.spec import ArchSpec
|
|
31
|
+
|
|
32
|
+
spec = ArchSpec.model_validate(spec_json)
|
|
33
|
+
modified = Architect().modify(spec, instruction)
|
|
34
|
+
return modified.model_dump(exclude_none=True)
|
|
35
|
+
|
|
36
|
+
@mcp.tool()
|
|
37
|
+
def compare_providers(spec_json: dict, providers: list[str]) -> list[dict]:
|
|
38
|
+
"""Compare an architecture across cloud providers."""
|
|
39
|
+
from cloudwright.architect import Architect
|
|
40
|
+
from cloudwright.spec import ArchSpec
|
|
41
|
+
|
|
42
|
+
spec = ArchSpec.model_validate(spec_json)
|
|
43
|
+
alternatives = Architect().compare(spec, providers)
|
|
44
|
+
return [a.model_dump(exclude_none=True) for a in alternatives]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from mcp.server.fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def register(mcp: FastMCP) -> None:
|
|
7
|
+
@mcp.tool()
|
|
8
|
+
def export_architecture(spec_json: dict, format: str = "terraform") -> dict:
|
|
9
|
+
"""Export an architecture spec to Terraform, CloudFormation, Mermaid, D2, or other formats."""
|
|
10
|
+
from cloudwright.exporter import export_spec
|
|
11
|
+
from cloudwright.spec import ArchSpec
|
|
12
|
+
|
|
13
|
+
spec = ArchSpec.model_validate(spec_json)
|
|
14
|
+
content = export_spec(spec, fmt=format)
|
|
15
|
+
return {"format": format, "content": content}
|
|
16
|
+
|
|
17
|
+
@mcp.tool()
|
|
18
|
+
def list_services(provider: str = "aws") -> list[dict]:
|
|
19
|
+
"""List all cloud services for a provider from the service registry."""
|
|
20
|
+
from cloudwright.registry import ServiceRegistry
|
|
21
|
+
|
|
22
|
+
services = ServiceRegistry().list_services(provider)
|
|
23
|
+
return [s.to_dict() for s in services]
|
|
24
|
+
|
|
25
|
+
@mcp.tool()
|
|
26
|
+
def catalog_search(
|
|
27
|
+
provider: str = "aws",
|
|
28
|
+
query: str | None = None,
|
|
29
|
+
vcpus: int | None = None,
|
|
30
|
+
memory_gb: float | None = None,
|
|
31
|
+
max_price_per_hour: float | None = None,
|
|
32
|
+
) -> list[dict]:
|
|
33
|
+
"""Search the cloud instance catalog by provider, specs, or text query."""
|
|
34
|
+
from cloudwright.catalog import Catalog
|
|
35
|
+
|
|
36
|
+
return Catalog().search(
|
|
37
|
+
query=query,
|
|
38
|
+
provider=provider,
|
|
39
|
+
vcpus=vcpus,
|
|
40
|
+
memory_gb=memory_gb,
|
|
41
|
+
max_price_per_hour=max_price_per_hour,
|
|
42
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
from mcp.server.fastmcp import FastMCP
|
|
6
|
+
|
|
7
|
+
_sessions: dict[str, object] = {}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register(mcp: FastMCP) -> None:
|
|
11
|
+
@mcp.tool()
|
|
12
|
+
def chat_create_session(
|
|
13
|
+
provider: str = "aws",
|
|
14
|
+
budget_monthly: float | None = None,
|
|
15
|
+
compliance: list[str] | None = None,
|
|
16
|
+
) -> dict:
|
|
17
|
+
"""Create a new stateful architecture design conversation session."""
|
|
18
|
+
from cloudwright.architect import ConversationSession
|
|
19
|
+
from cloudwright.spec import Constraints
|
|
20
|
+
|
|
21
|
+
constraints = Constraints(budget_monthly=budget_monthly, compliance=compliance or [])
|
|
22
|
+
session = ConversationSession(constraints=constraints)
|
|
23
|
+
session_id = uuid.uuid4().hex[:12]
|
|
24
|
+
_sessions[session_id] = session
|
|
25
|
+
return {"session_id": session_id}
|
|
26
|
+
|
|
27
|
+
@mcp.tool()
|
|
28
|
+
def chat_send(session_id: str, message: str) -> dict:
|
|
29
|
+
"""Send a message to an existing conversation session and get a response."""
|
|
30
|
+
session = _sessions.get(session_id)
|
|
31
|
+
if session is None:
|
|
32
|
+
return {"error": f"Session {session_id!r} not found. Create one with chat_create_session."}
|
|
33
|
+
|
|
34
|
+
text, spec = session.send(message)
|
|
35
|
+
return {
|
|
36
|
+
"response": text,
|
|
37
|
+
"spec": spec.model_dump(exclude_none=True) if spec is not None else None,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@mcp.tool()
|
|
41
|
+
def chat_list_sessions() -> list[dict]:
|
|
42
|
+
"""List all active conversation sessions."""
|
|
43
|
+
from cloudwright.architect import ConversationSession
|
|
44
|
+
|
|
45
|
+
result = []
|
|
46
|
+
for sid, session in _sessions.items():
|
|
47
|
+
if isinstance(session, ConversationSession):
|
|
48
|
+
result.append(
|
|
49
|
+
{
|
|
50
|
+
"session_id": sid,
|
|
51
|
+
"message_count": len(session.history),
|
|
52
|
+
"has_spec": session.current_spec is not None,
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
return result
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from mcp.server.fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def register(mcp: FastMCP) -> None:
|
|
7
|
+
@mcp.tool()
|
|
8
|
+
def validate_compliance(
|
|
9
|
+
spec_json: dict,
|
|
10
|
+
frameworks: list[str],
|
|
11
|
+
well_architected: bool = False,
|
|
12
|
+
) -> list[dict]:
|
|
13
|
+
"""Validate an architecture against compliance frameworks."""
|
|
14
|
+
from cloudwright.spec import ArchSpec
|
|
15
|
+
from cloudwright.validator import Validator
|
|
16
|
+
|
|
17
|
+
spec = ArchSpec.model_validate(spec_json)
|
|
18
|
+
results = Validator().validate(spec, compliance=frameworks, well_architected=well_architected)
|
|
19
|
+
return [r.model_dump(exclude_none=True) for r in results]
|
|
20
|
+
|
|
21
|
+
@mcp.tool()
|
|
22
|
+
def security_scan(spec_json: dict) -> dict:
|
|
23
|
+
"""Scan an architecture for security anti-patterns and misconfigurations."""
|
|
24
|
+
from cloudwright.security import SecurityScanner
|
|
25
|
+
from cloudwright.spec import ArchSpec
|
|
26
|
+
|
|
27
|
+
from cloudwright_mcp.serializers import security_report_to_dict
|
|
28
|
+
|
|
29
|
+
spec = ArchSpec.model_validate(spec_json)
|
|
30
|
+
report = SecurityScanner().scan(spec)
|
|
31
|
+
return security_report_to_dict(report)
|
|
32
|
+
|
|
33
|
+
@mcp.tool()
|
|
34
|
+
def scan_terraform(hcl_content: str) -> dict:
|
|
35
|
+
"""Scan Terraform HCL for security misconfigurations."""
|
|
36
|
+
from cloudwright.security import scan_terraform as _scan_terraform
|
|
37
|
+
|
|
38
|
+
from cloudwright_mcp.serializers import security_report_to_dict
|
|
39
|
+
|
|
40
|
+
report = _scan_terraform(hcl_content)
|
|
41
|
+
return security_report_to_dict(report)
|
|
42
|
+
|
|
43
|
+
@mcp.tool()
|
|
44
|
+
def lint_architecture(spec_json: dict) -> list[dict]:
|
|
45
|
+
"""Lint an architecture for anti-patterns and best-practice violations."""
|
|
46
|
+
from cloudwright.linter import lint
|
|
47
|
+
from cloudwright.spec import ArchSpec
|
|
48
|
+
|
|
49
|
+
from cloudwright_mcp.serializers import lint_warnings_to_dict
|
|
50
|
+
|
|
51
|
+
spec = ArchSpec.model_validate(spec_json)
|
|
52
|
+
warnings = lint(spec)
|
|
53
|
+
return lint_warnings_to_dict(warnings)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cloudwright-ai-mcp"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "MCP server for Cloudwright architecture intelligence"
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{name = "Xavier Puspus"}]
|
|
12
|
+
dependencies = [
|
|
13
|
+
"cloudwright-ai>=0.3.0,<1",
|
|
14
|
+
"mcp>=1.12,<2",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/xmpuspus/cloudwright"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.version]
|
|
21
|
+
path = "cloudwright_mcp/__init__.py"
|
|
22
|
+
|
|
23
|
+
[tool.hatch.build.targets.wheel]
|
|
24
|
+
packages = ["cloudwright_mcp"]
|