hanzo-tools-platform 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.
- hanzo_tools_platform-0.1.0/.gitignore +63 -0
- hanzo_tools_platform-0.1.0/PKG-INFO +22 -0
- hanzo_tools_platform-0.1.0/README.md +3 -0
- hanzo_tools_platform-0.1.0/hanzo_tools/__init__.py +0 -0
- hanzo_tools_platform-0.1.0/hanzo_tools/platform/__init__.py +7 -0
- hanzo_tools_platform-0.1.0/hanzo_tools/platform/platform_tool.py +314 -0
- hanzo_tools_platform-0.1.0/pyproject.toml +33 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# IDE and editor
|
|
2
|
+
.vscode
|
|
3
|
+
.idea
|
|
4
|
+
|
|
5
|
+
# Python
|
|
6
|
+
*.egg-info
|
|
7
|
+
__pycache__
|
|
8
|
+
.mypy_cache
|
|
9
|
+
.pytest_cache
|
|
10
|
+
*.pyc
|
|
11
|
+
.venv
|
|
12
|
+
|
|
13
|
+
# Build
|
|
14
|
+
dist
|
|
15
|
+
_dev
|
|
16
|
+
|
|
17
|
+
# Environment
|
|
18
|
+
.env
|
|
19
|
+
.envrc
|
|
20
|
+
|
|
21
|
+
# Logs
|
|
22
|
+
.prism.log
|
|
23
|
+
codegen.log
|
|
24
|
+
*.log
|
|
25
|
+
|
|
26
|
+
# Package manager
|
|
27
|
+
Brewfile.lock.json
|
|
28
|
+
|
|
29
|
+
# Agent config files (symlinked from user home)
|
|
30
|
+
LLM.md
|
|
31
|
+
AGENTS.md
|
|
32
|
+
CLAUDE.md
|
|
33
|
+
GEMINI.md
|
|
34
|
+
GROK.md
|
|
35
|
+
QWEN.md
|
|
36
|
+
|
|
37
|
+
# Local databases and state
|
|
38
|
+
.hanzo/
|
|
39
|
+
.grok/
|
|
40
|
+
|
|
41
|
+
# Documentation build
|
|
42
|
+
docs/.next/
|
|
43
|
+
docs/out/
|
|
44
|
+
docs/node_modules/
|
|
45
|
+
|
|
46
|
+
# Training data and scripts (DO NOT COMMIT)
|
|
47
|
+
training_dataset.jsonl
|
|
48
|
+
scripts/full_extractor.py
|
|
49
|
+
scripts/mega_extractor.py
|
|
50
|
+
scripts/mega_full_extractor.py
|
|
51
|
+
scripts/streaming_extractor.py
|
|
52
|
+
scripts/supplement_extractor.py
|
|
53
|
+
|
|
54
|
+
# Test files at root (experimental)
|
|
55
|
+
test_post_quantum_*.py
|
|
56
|
+
|
|
57
|
+
# Analysis documents (internal)
|
|
58
|
+
HANZO_INNOVATION_OPPORTUNITIES.md
|
|
59
|
+
POST_QUANTUM_CRYPTOGRAPHY_IMPLEMENTATION.md
|
|
60
|
+
|
|
61
|
+
# Experimental cryptography (WIP)
|
|
62
|
+
pkg/hanzo/src/hanzo/cryptography/
|
|
63
|
+
site/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hanzo-tools-platform
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Hanzo platform MCP tool — PaaS deployments, IAM users/orgs, and cloud services
|
|
5
|
+
Author-email: Hanzo AI <dev@hanzo.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: hanzo,iam,mcp,paas,platform,tools
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Requires-Dist: hanzo-iam>=0.1.0
|
|
15
|
+
Requires-Dist: hanzo-tools-auth>=0.1.0
|
|
16
|
+
Requires-Dist: hanzo-tools-core>=0.1.0
|
|
17
|
+
Requires-Dist: httpx>=0.27.0
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# hanzo-tools-platform
|
|
21
|
+
|
|
22
|
+
Hanzo platform MCP tool — PaaS deployments, IAM, and cloud services.
|
|
File without changes
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""MCP tool for Hanzo platform services — PaaS, IAM, and cloud.
|
|
2
|
+
|
|
3
|
+
Combines PaaS deployments, IAM user/org management, and cloud services
|
|
4
|
+
into a single MCP tool to avoid tool proliferation.
|
|
5
|
+
|
|
6
|
+
Auth: Uses HanzoSession from hanzo-tools-auth to exchange IAM tokens
|
|
7
|
+
for PaaS sessions and access IAM APIs.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
from typing import Annotated, Any, final
|
|
15
|
+
|
|
16
|
+
from mcp.server import FastMCP
|
|
17
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
18
|
+
from pydantic import Field
|
|
19
|
+
|
|
20
|
+
from hanzo_tools.core.base import BaseTool
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
DESCRIPTION = """Hanzo platform management — PaaS deployments, IAM, and cloud services.
|
|
25
|
+
|
|
26
|
+
Requires authentication via `hanzo login` (stored at ~/.hanzo/auth/token.json).
|
|
27
|
+
|
|
28
|
+
PaaS actions:
|
|
29
|
+
- deployments: List containers/deployments in a project environment
|
|
30
|
+
- deploy: Get details of a specific deployment
|
|
31
|
+
- logs: View container logs
|
|
32
|
+
- redeploy: Trigger a rolling restart of a container
|
|
33
|
+
- env: List environments for a project
|
|
34
|
+
|
|
35
|
+
IAM actions:
|
|
36
|
+
- whoami: Current user info
|
|
37
|
+
- users: List users in the organization
|
|
38
|
+
- orgs: List organizations
|
|
39
|
+
- roles: List roles
|
|
40
|
+
|
|
41
|
+
Cloud actions:
|
|
42
|
+
- services: List managed services (cluster info)
|
|
43
|
+
- projects: List projects in an organization
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_session():
|
|
48
|
+
"""Get HanzoSession singleton."""
|
|
49
|
+
from hanzo_tools.auth.session import HanzoSession
|
|
50
|
+
return HanzoSession.get()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@final
|
|
54
|
+
class PlatformTool(BaseTool):
|
|
55
|
+
"""MCP tool for Hanzo platform operations."""
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def name(self) -> str:
|
|
59
|
+
return "platform"
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def description(self) -> str:
|
|
63
|
+
return DESCRIPTION
|
|
64
|
+
|
|
65
|
+
async def call(
|
|
66
|
+
self,
|
|
67
|
+
ctx: MCPContext,
|
|
68
|
+
action: str = "whoami",
|
|
69
|
+
org: str | None = None,
|
|
70
|
+
project: str | None = None,
|
|
71
|
+
environment: str | None = None,
|
|
72
|
+
container: str | None = None,
|
|
73
|
+
**kwargs: Any,
|
|
74
|
+
) -> str:
|
|
75
|
+
try:
|
|
76
|
+
# IAM actions
|
|
77
|
+
if action == "whoami":
|
|
78
|
+
return await self._whoami()
|
|
79
|
+
elif action == "users":
|
|
80
|
+
return await self._users()
|
|
81
|
+
elif action == "orgs":
|
|
82
|
+
return await self._orgs()
|
|
83
|
+
elif action == "roles":
|
|
84
|
+
return await self._roles()
|
|
85
|
+
# PaaS actions
|
|
86
|
+
elif action == "projects":
|
|
87
|
+
return await self._projects(org)
|
|
88
|
+
elif action == "env":
|
|
89
|
+
return await self._envs(org, project)
|
|
90
|
+
elif action == "deployments":
|
|
91
|
+
return await self._deployments(org, project, environment)
|
|
92
|
+
elif action == "deploy":
|
|
93
|
+
return await self._deploy_detail(org, project, environment, container)
|
|
94
|
+
elif action == "logs":
|
|
95
|
+
return await self._logs(org, project, environment, container)
|
|
96
|
+
elif action == "redeploy":
|
|
97
|
+
return await self._redeploy(org, project, environment, container)
|
|
98
|
+
# Cloud actions
|
|
99
|
+
elif action == "services":
|
|
100
|
+
return await self._services()
|
|
101
|
+
else:
|
|
102
|
+
return json.dumps({
|
|
103
|
+
"error": f"Unknown action: {action}",
|
|
104
|
+
"available": [
|
|
105
|
+
"whoami", "users", "orgs", "roles",
|
|
106
|
+
"projects", "env", "deployments", "deploy", "logs", "redeploy",
|
|
107
|
+
"services",
|
|
108
|
+
],
|
|
109
|
+
})
|
|
110
|
+
except RuntimeError as e:
|
|
111
|
+
return json.dumps({"error": str(e)})
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.exception(f"Platform tool error: {e}")
|
|
114
|
+
return json.dumps({"error": f"Platform error: {e}"})
|
|
115
|
+
|
|
116
|
+
# -- IAM actions ---------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
async def _whoami(self) -> str:
|
|
119
|
+
session = _get_session()
|
|
120
|
+
if not session.is_authenticated():
|
|
121
|
+
return json.dumps({"error": "Not authenticated. Run 'hanzo login' first."})
|
|
122
|
+
|
|
123
|
+
token = session.get_iam_token()
|
|
124
|
+
if token:
|
|
125
|
+
try:
|
|
126
|
+
import jwt
|
|
127
|
+
|
|
128
|
+
claims = jwt.decode(token, options={"verify_signature": False})
|
|
129
|
+
return json.dumps({
|
|
130
|
+
"sub": claims.get("sub"),
|
|
131
|
+
"name": claims.get("name"),
|
|
132
|
+
"email": claims.get("email"),
|
|
133
|
+
"organization": claims.get("owner"),
|
|
134
|
+
"iss": claims.get("iss"),
|
|
135
|
+
}, indent=2)
|
|
136
|
+
except Exception:
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
info = session.get_token_info()
|
|
140
|
+
return json.dumps(info, indent=2)
|
|
141
|
+
|
|
142
|
+
async def _users(self) -> str:
|
|
143
|
+
session = _get_session()
|
|
144
|
+
iam = session.get_iam_client()
|
|
145
|
+
users = iam.get_users()
|
|
146
|
+
result = []
|
|
147
|
+
for u in users:
|
|
148
|
+
result.append({
|
|
149
|
+
"id": getattr(u, "id", None),
|
|
150
|
+
"name": getattr(u, "name", None),
|
|
151
|
+
"email": getattr(u, "email", None),
|
|
152
|
+
"display_name": getattr(u, "display_name", None),
|
|
153
|
+
})
|
|
154
|
+
return json.dumps({"count": len(result), "users": result}, indent=2)
|
|
155
|
+
|
|
156
|
+
async def _orgs(self) -> str:
|
|
157
|
+
session = _get_session()
|
|
158
|
+
iam = session.get_iam_client()
|
|
159
|
+
orgs = iam.get_organizations()
|
|
160
|
+
return json.dumps({"count": len(orgs), "organizations": orgs}, indent=2)
|
|
161
|
+
|
|
162
|
+
async def _roles(self) -> str:
|
|
163
|
+
session = _get_session()
|
|
164
|
+
iam = session.get_iam_client()
|
|
165
|
+
roles = iam.get_roles()
|
|
166
|
+
return json.dumps({"count": len(roles), "roles": roles}, indent=2)
|
|
167
|
+
|
|
168
|
+
# -- PaaS actions --------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
async def _projects(self, org: str | None) -> str:
|
|
171
|
+
if not org:
|
|
172
|
+
return json.dumps({"error": "Required: org (organization ID)"})
|
|
173
|
+
|
|
174
|
+
session = _get_session()
|
|
175
|
+
paas = session.get_paas_client()
|
|
176
|
+
projects = paas.get(f"/v1/org/{org}/project")
|
|
177
|
+
return json.dumps({"org": org, "count": len(projects), "projects": projects}, indent=2)
|
|
178
|
+
|
|
179
|
+
async def _envs(self, org: str | None, project: str | None) -> str:
|
|
180
|
+
if not org or not project:
|
|
181
|
+
return json.dumps({"error": "Required: org and project"})
|
|
182
|
+
|
|
183
|
+
session = _get_session()
|
|
184
|
+
paas = session.get_paas_client()
|
|
185
|
+
envs = paas.get(f"/v1/org/{org}/project/{project}/env")
|
|
186
|
+
return json.dumps({"org": org, "project": project, "environments": envs}, indent=2)
|
|
187
|
+
|
|
188
|
+
async def _deployments(self, org: str | None, project: str | None, env: str | None) -> str:
|
|
189
|
+
if not org or not project or not env:
|
|
190
|
+
return json.dumps({"error": "Required: org, project, and environment"})
|
|
191
|
+
|
|
192
|
+
session = _get_session()
|
|
193
|
+
paas = session.get_paas_client()
|
|
194
|
+
containers = paas.get(f"/v1/org/{org}/project/{project}/env/{env}/container")
|
|
195
|
+
|
|
196
|
+
result = []
|
|
197
|
+
for c in containers if isinstance(containers, list) else []:
|
|
198
|
+
result.append({
|
|
199
|
+
"id": c.get("id"),
|
|
200
|
+
"name": c.get("name"),
|
|
201
|
+
"image": c.get("image"),
|
|
202
|
+
"status": c.get("status"),
|
|
203
|
+
"replicas": c.get("replicas"),
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return json.dumps({
|
|
207
|
+
"org": org,
|
|
208
|
+
"project": project,
|
|
209
|
+
"environment": env,
|
|
210
|
+
"count": len(result),
|
|
211
|
+
"containers": result,
|
|
212
|
+
}, indent=2)
|
|
213
|
+
|
|
214
|
+
async def _deploy_detail(
|
|
215
|
+
self, org: str | None, project: str | None, env: str | None, container: str | None
|
|
216
|
+
) -> str:
|
|
217
|
+
if not org or not project or not env or not container:
|
|
218
|
+
return json.dumps({"error": "Required: org, project, environment, and container"})
|
|
219
|
+
|
|
220
|
+
session = _get_session()
|
|
221
|
+
paas = session.get_paas_client()
|
|
222
|
+
data = paas.get(f"/v1/org/{org}/project/{project}/env/{env}/container/{container}")
|
|
223
|
+
return json.dumps(data, indent=2)
|
|
224
|
+
|
|
225
|
+
async def _logs(
|
|
226
|
+
self, org: str | None, project: str | None, env: str | None, container: str | None
|
|
227
|
+
) -> str:
|
|
228
|
+
if not org or not project or not env or not container:
|
|
229
|
+
return json.dumps({"error": "Required: org, project, environment, and container"})
|
|
230
|
+
|
|
231
|
+
session = _get_session()
|
|
232
|
+
paas = session.get_paas_client()
|
|
233
|
+
logs = paas.get(f"/v1/org/{org}/project/{project}/env/{env}/container/{container}/logs")
|
|
234
|
+
if isinstance(logs, dict):
|
|
235
|
+
return json.dumps(logs, indent=2)
|
|
236
|
+
return str(logs)
|
|
237
|
+
|
|
238
|
+
async def _redeploy(
|
|
239
|
+
self, org: str | None, project: str | None, env: str | None, container: str | None
|
|
240
|
+
) -> str:
|
|
241
|
+
if not org or not project or not env or not container:
|
|
242
|
+
return json.dumps({"error": "Required: org, project, environment, and container"})
|
|
243
|
+
|
|
244
|
+
session = _get_session()
|
|
245
|
+
paas = session.get_paas_client()
|
|
246
|
+
|
|
247
|
+
# Fetch current config and PUT it back to trigger rolling restart
|
|
248
|
+
current = paas.get(f"/v1/org/{org}/project/{project}/env/{env}/container/{container}")
|
|
249
|
+
result = paas.put(
|
|
250
|
+
f"/v1/org/{org}/project/{project}/env/{env}/container/{container}",
|
|
251
|
+
json=current,
|
|
252
|
+
)
|
|
253
|
+
return json.dumps({"action": "redeployed", "container": container, "result": result}, indent=2)
|
|
254
|
+
|
|
255
|
+
# -- Cloud actions -------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
async def _services(self) -> str:
|
|
258
|
+
session = _get_session()
|
|
259
|
+
paas = session.get_paas_client()
|
|
260
|
+
info = paas.get("/v1/cluster/info")
|
|
261
|
+
templates = paas.get("/v1/cluster/templates")
|
|
262
|
+
return json.dumps({
|
|
263
|
+
"cluster": info,
|
|
264
|
+
"templates": templates,
|
|
265
|
+
}, indent=2)
|
|
266
|
+
|
|
267
|
+
# -- Registration --------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
def register(self, mcp_server: FastMCP) -> None:
|
|
270
|
+
"""Register platform tool with explicit parameters."""
|
|
271
|
+
tool_instance = self
|
|
272
|
+
|
|
273
|
+
@mcp_server.tool(
|
|
274
|
+
name="platform",
|
|
275
|
+
description=DESCRIPTION,
|
|
276
|
+
)
|
|
277
|
+
async def platform(
|
|
278
|
+
action: Annotated[
|
|
279
|
+
str,
|
|
280
|
+
Field(
|
|
281
|
+
description=(
|
|
282
|
+
"Action to perform. "
|
|
283
|
+
"IAM: whoami, users, orgs, roles. "
|
|
284
|
+
"PaaS: projects, env, deployments, deploy, logs, redeploy. "
|
|
285
|
+
"Cloud: services."
|
|
286
|
+
),
|
|
287
|
+
),
|
|
288
|
+
] = "whoami",
|
|
289
|
+
org: Annotated[
|
|
290
|
+
str | None,
|
|
291
|
+
Field(description="Organization ID (for PaaS actions)"),
|
|
292
|
+
] = None,
|
|
293
|
+
project: Annotated[
|
|
294
|
+
str | None,
|
|
295
|
+
Field(description="Project ID (for PaaS actions)"),
|
|
296
|
+
] = None,
|
|
297
|
+
environment: Annotated[
|
|
298
|
+
str | None,
|
|
299
|
+
Field(description="Environment ID (for PaaS actions)"),
|
|
300
|
+
] = None,
|
|
301
|
+
container: Annotated[
|
|
302
|
+
str | None,
|
|
303
|
+
Field(description="Container/deployment ID (for deploy, logs, redeploy)"),
|
|
304
|
+
] = None,
|
|
305
|
+
ctx: MCPContext = None,
|
|
306
|
+
) -> str:
|
|
307
|
+
return await tool_instance.call(
|
|
308
|
+
ctx,
|
|
309
|
+
action=action,
|
|
310
|
+
org=org,
|
|
311
|
+
project=project,
|
|
312
|
+
environment=environment,
|
|
313
|
+
container=container,
|
|
314
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hanzo-tools-platform"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Hanzo platform MCP tool — PaaS deployments, IAM users/orgs, and cloud services"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "Hanzo AI", email = "dev@hanzo.ai" }]
|
|
9
|
+
keywords = ["hanzo", "mcp", "paas", "iam", "platform", "tools"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
dependencies = [
|
|
19
|
+
"hanzo-tools-core>=0.1.0",
|
|
20
|
+
"hanzo-tools-auth>=0.1.0",
|
|
21
|
+
"hanzo-iam>=0.1.0",
|
|
22
|
+
"httpx>=0.27.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.entry-points."hanzo.tools"]
|
|
26
|
+
platform = "hanzo_tools.platform:TOOLS"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["hatchling"]
|
|
30
|
+
build-backend = "hatchling.build"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["hanzo_tools"]
|