mesh-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,39 @@
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
+ share/python-wheels/
20
+ *.egg-info/
21
+ .installed.cfg
22
+ *.egg
23
+
24
+ # Virtual Environment
25
+ .venv/
26
+ venv/
27
+ ENV/
28
+ env/
29
+
30
+ # IDE / Editor
31
+ .idea/
32
+ .vscode/
33
+ *.swp
34
+ *.swo
35
+
36
+ # Testing / Cache
37
+ .pytest_cache/
38
+ .coverage
39
+ htmlcov/
mesh_mcp-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nagachaitanya Inamdar
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,24 @@
1
+ Metadata-Version: 2.5
2
+ Name: mesh-mcp
3
+ Version: 0.1.0
4
+ Summary: Cross-boundary contract blast-radius engine for LLM coding agents
5
+ Project-URL: Homepage, https://github.com/Nagachaitanya007/mesh-mcp
6
+ Project-URL: Repository, https://github.com/Nagachaitanya007/mesh-mcp
7
+ Author-email: Nagachaitanya <nagachaitanyainamdar@gmail.com>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: mcp>=1.0.0
12
+ Requires-Dist: pyyaml>=6.0
13
+ Description-Content-Type: text/markdown
14
+
15
+ # mesh-mcp
16
+
17
+ Cross-boundary contract blast-radius engine for LLM coding agents.
18
+
19
+ Helps AI agents detect what breaks when you change an API contract or schema.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install mesh-mcp
@@ -0,0 +1,10 @@
1
+ # mesh-mcp
2
+
3
+ Cross-boundary contract blast-radius engine for LLM coding agents.
4
+
5
+ Helps AI agents detect what breaks when you change an API contract or schema.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install mesh-mcp
@@ -0,0 +1,7 @@
1
+ from pydantic import BaseModel
2
+
3
+ class Order(BaseModel):
4
+ id: str
5
+ total: float
6
+ currency: str
7
+ tax_amount: float | None = None
@@ -0,0 +1,72 @@
1
+ openapi: 3.0.3
2
+ info:
3
+ title: Orders API
4
+ version: 1.0.0
5
+ description: Sample API for testing mesh-mcp
6
+
7
+ paths:
8
+ /orders:
9
+ get:
10
+ summary: List orders
11
+ responses:
12
+ '200':
13
+ description: A list of orders
14
+ content:
15
+ application/json:
16
+ schema:
17
+ type: array
18
+ items:
19
+ $ref: '#/components/schemas/Order'
20
+ post:
21
+ summary: Create an order
22
+ requestBody:
23
+ required: true
24
+ content:
25
+ application/json:
26
+ schema:
27
+ $ref: '#/components/schemas/Order'
28
+ responses:
29
+ '201':
30
+ description: Order created
31
+
32
+ /orders/{id}:
33
+ get:
34
+ summary: Get an order by ID
35
+ parameters:
36
+ - name: id
37
+ in: path
38
+ required: true
39
+ schema:
40
+ type: string
41
+ responses:
42
+ '200':
43
+ description: An order
44
+ content:
45
+ application/json:
46
+ schema:
47
+ $ref: '#/components/schemas/Order'
48
+
49
+ components:
50
+ schemas:
51
+ Order:
52
+ type: object
53
+ required:
54
+ - id
55
+ - total
56
+ properties:
57
+ id:
58
+ type: string
59
+ customer_id:
60
+ type: string
61
+ total:
62
+ type: number
63
+ format: float
64
+ currency:
65
+ type: string
66
+ enum: [USD, EUR, GBP]
67
+ tax_amount:
68
+ type: number
69
+ format: float
70
+ status:
71
+ type: string
72
+ enum: [pending, paid, shipped, cancelled]
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "mesh-mcp"
3
+ version = "0.1.0"
4
+ description = "Cross-boundary contract blast-radius engine for LLM coding agents"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = {text = "MIT"}
8
+ authors = [
9
+ {name = "Nagachaitanya", email = "nagachaitanyainamdar@gmail.com"}
10
+ ]
11
+ dependencies = [
12
+ "mcp>=1.0.0",
13
+ "pyyaml>=6.0",
14
+ ]
15
+
16
+ [project.urls]
17
+ Homepage = "https://github.com/Nagachaitanya007/mesh-mcp"
18
+ Repository = "https://github.com/Nagachaitanya007/mesh-mcp"
19
+
20
+ [project.scripts]
21
+ mesh-mcp = "mesh_mcp.server:main"
22
+
23
+ [build-system]
24
+ requires = ["hatchling"]
25
+ build-backend = "hatchling.build"
26
+
27
+ [tool.hatch.build.targets.wheel]
28
+ packages = ["src/mesh_mcp"]
@@ -0,0 +1,3 @@
1
+ """mesh-mcp: Contract blast-radius MCP server."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,59 @@
1
+ """OpenAPI contract detector."""
2
+
3
+ # TODO: Parse OpenAPI / Swagger files
4
+ """OpenAPI contract detector."""
5
+
6
+ from pathlib import Path
7
+ import yaml
8
+ import json
9
+
10
+
11
+ def find_openapi_files(root: Path) -> list[Path]:
12
+ """Find OpenAPI / Swagger files in the workspace."""
13
+ patterns = ["**/openapi.yaml", "**/openapi.yml", "**/openapi.json",
14
+ "**/swagger.yaml", "**/swagger.yml", "**/swagger.json"]
15
+ files = []
16
+ for pattern in patterns:
17
+ files.extend(root.glob(pattern))
18
+ # Remove duplicates and ignore common junk
19
+ unique = []
20
+ seen = set()
21
+ for f in files:
22
+ if f.resolve() not in seen and ".venv" not in str(f) and "node_modules" not in str(f):
23
+ seen.add(f.resolve())
24
+ unique.append(f)
25
+ return unique
26
+
27
+
28
+ def parse_openapi(file_path: Path) -> dict | None:
29
+ """Parse an OpenAPI file and extract basic contract info."""
30
+ try:
31
+ text = file_path.read_text(encoding="utf-8")
32
+ if file_path.suffix in {".yaml", ".yml"}:
33
+ data = yaml.safe_load(text)
34
+ else:
35
+ data = json.loads(text)
36
+
37
+ if not isinstance(data, dict):
38
+ return None
39
+
40
+ info = {
41
+ "file": str(file_path),
42
+ "title": data.get("info", {}).get("title", file_path.stem),
43
+ "version": data.get("info", {}).get("version", "unknown"),
44
+ "schemas": list(data.get("components", {}).get("schemas", {}).keys()),
45
+ "paths": list(data.get("paths", {}).keys()),
46
+ }
47
+ return info
48
+ except Exception:
49
+ return None
50
+
51
+
52
+ def extract_contracts(root: Path) -> list[dict]:
53
+ """Find and parse all OpenAPI contracts in the workspace."""
54
+ contracts = []
55
+ for file_path in find_openapi_files(root):
56
+ parsed = parse_openapi(file_path)
57
+ if parsed:
58
+ contracts.append(parsed)
59
+ return contracts
@@ -0,0 +1,35 @@
1
+ """Simple Python consumer detector."""
2
+
3
+ from pathlib import Path
4
+ import re
5
+
6
+
7
+ def find_python_consumers(root: Path, schema_names: list[str]) -> list[dict]:
8
+ """
9
+ Find Python files that mention the given schema names.
10
+ This is a simple text-based detector for the MVP.
11
+ """
12
+ consumers = []
13
+ ignore_dirs = {".venv", "venv", "__pycache__", ".git", "node_modules", "src"}
14
+
15
+ for py_file in root.rglob("*.py"):
16
+ if any(part in ignore_dirs for part in py_file.parts):
17
+ continue
18
+
19
+ try:
20
+ text = py_file.read_text(encoding="utf-8", errors="ignore")
21
+ except Exception:
22
+ continue
23
+
24
+ for schema in schema_names:
25
+ # Simple check: class name or schema name appears
26
+ if re.search(rf"\b{schema}\b", text):
27
+ consumers.append({
28
+ "file": str(py_file),
29
+ "language": "python",
30
+ "schema": schema,
31
+ "reason": f"Mentions '{schema}'"
32
+ })
33
+ break # one match per file is enough for now
34
+
35
+ return consumers
@@ -0,0 +1,3 @@
1
+ """TypeScript consumer detector."""
2
+
3
+ # TODO: Find TypeScript interfaces that reference contracts
@@ -0,0 +1,3 @@
1
+ """Simple in-memory contract graph."""
2
+
3
+ # TODO: Store producers and consumers
@@ -0,0 +1,3 @@
1
+ """Workspace indexer for contracts and consumers."""
2
+
3
+ # TODO: Scan for OpenAPI files and consumer references
@@ -0,0 +1,138 @@
1
+ from mcp.server.mcpserver import MCPServer
2
+ from pathlib import Path
3
+ import sys
4
+
5
+ from mesh_mcp.detectors.openapi import extract_contracts
6
+ from mesh_mcp.detectors.python_detector import find_python_consumers
7
+
8
+ mcp = MCPServer("mesh-mcp")
9
+
10
+ # Workspace
11
+ if len(sys.argv) > 1:
12
+ WORKSPACE = Path(sys.argv[1]).resolve()
13
+ else:
14
+ WORKSPACE = Path.cwd().resolve()
15
+
16
+ @mcp.tool()
17
+ def list_contracts() -> str:
18
+ """
19
+ List all OpenAPI contracts found in the workspace.
20
+ Use this to see what APIs and schemas are available.
21
+ """
22
+ contracts = extract_contracts(WORKSPACE)
23
+ if not contracts:
24
+ return "No OpenAPI contracts found in the workspace."
25
+
26
+ lines = ["# Detected Contracts\n"]
27
+ for c in contracts:
28
+ lines.append(f"## {c['title']} (v{c['version']})")
29
+ lines.append(f"- File: {c['file']}")
30
+ if c["schemas"]:
31
+ lines.append(f"- Schemas: {', '.join(c['schemas'][:10])}")
32
+ if c["paths"]:
33
+ lines.append(f"- Paths: {', '.join(c['paths'][:8])}")
34
+ lines.append("")
35
+ return "\n".join(lines)
36
+
37
+
38
+ @mcp.tool()
39
+ def trace_contract(contract_name: str) -> str:
40
+ """
41
+ Trace a specific contract by name (schema or title).
42
+ Shows where it is defined and basic details.
43
+ Example: trace_contract("Order")
44
+ """
45
+ contracts = extract_contracts(WORKSPACE)
46
+ matches = []
47
+
48
+ for c in contracts:
49
+ if (contract_name.lower() in c["title"].lower() or
50
+ contract_name.lower() in [s.lower() for s in c["schemas"]] or
51
+ any(contract_name.lower() in p.lower() for p in c["paths"])):
52
+ matches.append(c)
53
+
54
+ if not matches:
55
+ return f"No contract found matching '{contract_name}'."
56
+
57
+ lines = [f"# Trace: {contract_name}\n"]
58
+ for c in matches:
59
+ lines.append(f"**Defined in:** {c['file']}")
60
+ lines.append(f"**Title:** {c['title']} (v{c['version']})")
61
+ if c["schemas"]:
62
+ lines.append(f"**Schemas:** {', '.join(c['schemas'])}")
63
+ if c["paths"]:
64
+ lines.append(f"**Paths:** {', '.join(c['paths'])}")
65
+ lines.append("")
66
+ return "\n".join(lines)
67
+
68
+
69
+ from mesh_mcp.detectors.python_detector import find_python_consumers
70
+
71
+ @mcp.tool()
72
+ def find_consumers(schema_name: str) -> str:
73
+ """
74
+ Find Python files that appear to consume a given schema.
75
+ Example: find_consumers("Order")
76
+ """
77
+ contracts = extract_contracts(WORKSPACE)
78
+ all_schemas = []
79
+ for c in contracts:
80
+ all_schemas.extend(c["schemas"])
81
+
82
+ if schema_name not in all_schemas and not any(schema_name.lower() in s.lower() for s in all_schemas):
83
+ return f"Schema '{schema_name}' not found in any OpenAPI contract."
84
+
85
+ consumers = find_python_consumers(WORKSPACE, [schema_name])
86
+
87
+ if not consumers:
88
+ return f"No Python consumers found for '{schema_name}'."
89
+
90
+ lines = [f"# Consumers of {schema_name}\n"]
91
+ for cons in consumers:
92
+ lines.append(f"- {cons['file']} ({cons['reason']})")
93
+ return "\n".join(lines)
94
+
95
+
96
+ @mcp.tool()
97
+ def check_blast_radius(schema_name: str, change_description: str = "") -> str:
98
+ """
99
+ Check the blast radius of changing a schema.
100
+ Shows which files may be affected.
101
+ Example: check_blast_radius("Order", "removed field tax_amount")
102
+ """
103
+ contracts = extract_contracts(WORKSPACE)
104
+ matching = [c for c in contracts if schema_name in c["schemas"] or schema_name.lower() in c["title"].lower()]
105
+
106
+ if not matching:
107
+ return f"Schema '{schema_name}' not found."
108
+
109
+ consumers = find_python_consumers(WORKSPACE, [schema_name])
110
+
111
+ lines = [f"# Blast Radius: {schema_name}\n"]
112
+ if change_description:
113
+ lines.append(f"**Proposed change:** {change_description}\n")
114
+
115
+ lines.append("**Defined in:**")
116
+ for c in matching:
117
+ lines.append(f"- {c['file']} ({c['title']})")
118
+
119
+ lines.append("\n**Potentially affected consumers:**")
120
+ if consumers:
121
+ for cons in consumers:
122
+ # Skip the server file itself for cleaner output
123
+ if "server.py" in cons["file"]:
124
+ continue
125
+ lines.append(f"- {cons['file']} — {cons['reason']}")
126
+ else:
127
+ lines.append("- No consumers detected")
128
+
129
+ lines.append("\n**Recommendation:** Review the listed files before making breaking changes.")
130
+ return "\n".join(lines)
131
+
132
+
133
+ def main():
134
+ mcp.run(transport="stdio")
135
+
136
+
137
+ if __name__ == "__main__":
138
+ main()
@@ -0,0 +1 @@
1
+ """Tests for mesh-mcp."""