sciple-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.
- sciple_mcp-0.1.0/.env.example +3 -0
- sciple_mcp-0.1.0/.github/workflows/publish.yml +50 -0
- sciple_mcp-0.1.0/.gitignore +34 -0
- sciple_mcp-0.1.0/PKG-INFO +103 -0
- sciple_mcp-0.1.0/README.md +80 -0
- sciple_mcp-0.1.0/pyproject.toml +50 -0
- sciple_mcp-0.1.0/src/sciple_mcp/__init__.py +0 -0
- sciple_mcp-0.1.0/src/sciple_mcp/client.py +46 -0
- sciple_mcp-0.1.0/src/sciple_mcp/server.py +42 -0
- sciple_mcp-0.1.0/src/sciple_mcp/tools/__init__.py +0 -0
- sciple_mcp-0.1.0/src/sciple_mcp/tools/environments.py +138 -0
- sciple_mcp-0.1.0/src/sciple_mcp/tools/observability.py +162 -0
- sciple_mcp-0.1.0/src/sciple_mcp/tools/services.py +179 -0
- sciple_mcp-0.1.0/tests/__init__.py +0 -0
- sciple_mcp-0.1.0/tests/test_client.py +37 -0
- sciple_mcp-0.1.0/uv.lock +807 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Tag-driven release: bump `version` in pyproject.toml, commit, then
|
|
4
|
+
# git tag v0.1.0 && git push origin v0.1.0
|
|
5
|
+
# Publishing pauses for manual approval (the `pypi` environment) before
|
|
6
|
+
# the upload step runs.
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
tags:
|
|
11
|
+
- "v*"
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
name: Build sdist + wheel
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v3
|
|
22
|
+
|
|
23
|
+
- name: Build distributions
|
|
24
|
+
run: uv build
|
|
25
|
+
|
|
26
|
+
- name: Upload built distributions
|
|
27
|
+
uses: actions/upload-artifact@v4
|
|
28
|
+
with:
|
|
29
|
+
name: dist
|
|
30
|
+
path: dist/
|
|
31
|
+
|
|
32
|
+
publish:
|
|
33
|
+
name: Publish to PyPI via OIDC
|
|
34
|
+
needs: build
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment:
|
|
37
|
+
name: pypi
|
|
38
|
+
url: https://pypi.org/project/sciple-mcp/
|
|
39
|
+
permissions:
|
|
40
|
+
# Required for PyPI Trusted Publishing (OIDC). No PyPI API token needed.
|
|
41
|
+
id-token: write
|
|
42
|
+
steps:
|
|
43
|
+
- name: Download built distributions
|
|
44
|
+
uses: actions/download-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
name: dist
|
|
47
|
+
path: dist/
|
|
48
|
+
|
|
49
|
+
- name: Publish to PyPI
|
|
50
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
*.egg
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
.eggs/
|
|
10
|
+
|
|
11
|
+
# uv / virtualenvs
|
|
12
|
+
.venv/
|
|
13
|
+
.python-version
|
|
14
|
+
|
|
15
|
+
# Test / caches
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
.coverage.*
|
|
19
|
+
htmlcov/
|
|
20
|
+
.mypy_cache/
|
|
21
|
+
.ruff_cache/
|
|
22
|
+
|
|
23
|
+
# Env files (secrets — never commit)
|
|
24
|
+
.env
|
|
25
|
+
.env.local
|
|
26
|
+
|
|
27
|
+
# Editors
|
|
28
|
+
.vscode/
|
|
29
|
+
.idea/
|
|
30
|
+
*.swp
|
|
31
|
+
|
|
32
|
+
# OS
|
|
33
|
+
.DS_Store
|
|
34
|
+
Thumbs.db
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sciple-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for populating and managing Sciple platform content
|
|
5
|
+
Project-URL: Homepage, https://github.com/navaganeshr/sciple-mcp
|
|
6
|
+
Project-URL: Source, https://github.com/navaganeshr/sciple-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/navaganeshr/sciple-mcp/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/navaganeshr/sciple-mcp#readme
|
|
9
|
+
Author-email: Navaganesh Raju <navaganesh.raju@gmail.com>
|
|
10
|
+
Keywords: anthropic,claude,mcp,model-context-protocol,sciple
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Classifier: Topic :: System :: Systems Administration
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Requires-Dist: httpx>=0.27.0
|
|
20
|
+
Requires-Dist: mcp[cli]>=1.0.0
|
|
21
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# Sciple Platform MCP Server
|
|
25
|
+
|
|
26
|
+
MCP server that lets a local Claude populate and manage Sciple platform content — environments, environment groups, and services — via the Sciple REST API. Engineers use it to bootstrap tenant structure and maintain the service catalog without leaving their AI coding session.
|
|
27
|
+
|
|
28
|
+
## Setup
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
cd services/sciple-mcp
|
|
32
|
+
uv sync
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Environment variables** (`.env` or shell):
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
SCIPLE_API_URL=http://localhost:8000/api/v1
|
|
39
|
+
SCIPLE_API_TOKEN=sciple_pat_...
|
|
40
|
+
SCIPLE_TENANT_ID=<your tenant id>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`SCIPLE_API_TOKEN` is a personal access token minted under **Profile → Access tokens** in the dashboard, scoped to the permissions the server should have (e.g. `environments.view`, `environments.manage`, `services.view`, `services.manage`, `observability.view`, `observability.manage`). The PAT is single-tenant — its bound tenant must equal `SCIPLE_TENANT_ID`.
|
|
44
|
+
|
|
45
|
+
## Wire into Claude Desktop / Claude Code
|
|
46
|
+
|
|
47
|
+
Add to `~/.claude/claude_desktop_config.json` (or your Claude Code MCP config):
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"sciple-platform": {
|
|
53
|
+
"command": "uv",
|
|
54
|
+
"args": ["run", "--project", "/path/to/services/sciple-mcp", "sciple-mcp"],
|
|
55
|
+
"env": {
|
|
56
|
+
"SCIPLE_API_URL": "http://localhost:8000/api/v1",
|
|
57
|
+
"SCIPLE_API_TOKEN": "sciple_pat_...",
|
|
58
|
+
"SCIPLE_TENANT_ID": "..."
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then restart Claude Desktop or Claude Code. You should see 17 platform tools available.
|
|
66
|
+
|
|
67
|
+
## Tools
|
|
68
|
+
|
|
69
|
+
### Environments
|
|
70
|
+
|
|
71
|
+
| Tool | Description |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `list_environments` | List all environments in the tenant (id, name, slug, group, default flag) |
|
|
74
|
+
| `create_environment` | Create an environment with optional group assignment and default flag |
|
|
75
|
+
| `update_environment` | Update an environment's name, description, group, or sort order |
|
|
76
|
+
| `delete_environment` | Delete an environment by id (irreversible) |
|
|
77
|
+
| `list_environment_groups` | List environment groups (id, name, slug, AWS account binding) |
|
|
78
|
+
| `create_environment_group` | Create an environment group with optional AWS account binding |
|
|
79
|
+
|
|
80
|
+
### Observability
|
|
81
|
+
|
|
82
|
+
| Tool | Description |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `list_dashboards` | List all observability dashboards in the tenant (id, name, panel count) |
|
|
85
|
+
| `get_dashboard` | Get a dashboard's name, description, and panel list |
|
|
86
|
+
| `create_dashboard` | Create a new dashboard with optional description |
|
|
87
|
+
| `update_dashboard` | Replace a dashboard's name and description (full PUT; name required) |
|
|
88
|
+
| `delete_dashboard` | Delete a dashboard and all its panels (irreversible) |
|
|
89
|
+
| `add_panel` | Add a panel to a dashboard with optional PromQL query |
|
|
90
|
+
| `delete_panel` | Delete a panel from a dashboard (irreversible) |
|
|
91
|
+
|
|
92
|
+
### Services
|
|
93
|
+
|
|
94
|
+
| Tool | Description |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `list_services` | List all services in the tenant catalog (id, name, slug) |
|
|
97
|
+
| `create_service` | Create a service in the catalog with kind, language, SCM provider, and repository |
|
|
98
|
+
| `update_service` | Update a service's metadata, lifecycle, owner, tags, links, or environment associations |
|
|
99
|
+
| `delete_service` | Delete a service from the catalog by id (irreversible) |
|
|
100
|
+
|
|
101
|
+
## Security
|
|
102
|
+
|
|
103
|
+
The server can only do what the PAT's scope allows. Attempts to write without the relevant manage permission return a 403 from the API and are surfaced as an error in Claude's response. The PAT is revocable at any time from **Profile → Access tokens** in the Sciple dashboard — revoking it immediately cuts off the server's access without any config change.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Sciple Platform MCP Server
|
|
2
|
+
|
|
3
|
+
MCP server that lets a local Claude populate and manage Sciple platform content — environments, environment groups, and services — via the Sciple REST API. Engineers use it to bootstrap tenant structure and maintain the service catalog without leaving their AI coding session.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd services/sciple-mcp
|
|
9
|
+
uv sync
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**Environment variables** (`.env` or shell):
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
SCIPLE_API_URL=http://localhost:8000/api/v1
|
|
16
|
+
SCIPLE_API_TOKEN=sciple_pat_...
|
|
17
|
+
SCIPLE_TENANT_ID=<your tenant id>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`SCIPLE_API_TOKEN` is a personal access token minted under **Profile → Access tokens** in the dashboard, scoped to the permissions the server should have (e.g. `environments.view`, `environments.manage`, `services.view`, `services.manage`, `observability.view`, `observability.manage`). The PAT is single-tenant — its bound tenant must equal `SCIPLE_TENANT_ID`.
|
|
21
|
+
|
|
22
|
+
## Wire into Claude Desktop / Claude Code
|
|
23
|
+
|
|
24
|
+
Add to `~/.claude/claude_desktop_config.json` (or your Claude Code MCP config):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"sciple-platform": {
|
|
30
|
+
"command": "uv",
|
|
31
|
+
"args": ["run", "--project", "/path/to/services/sciple-mcp", "sciple-mcp"],
|
|
32
|
+
"env": {
|
|
33
|
+
"SCIPLE_API_URL": "http://localhost:8000/api/v1",
|
|
34
|
+
"SCIPLE_API_TOKEN": "sciple_pat_...",
|
|
35
|
+
"SCIPLE_TENANT_ID": "..."
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then restart Claude Desktop or Claude Code. You should see 17 platform tools available.
|
|
43
|
+
|
|
44
|
+
## Tools
|
|
45
|
+
|
|
46
|
+
### Environments
|
|
47
|
+
|
|
48
|
+
| Tool | Description |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `list_environments` | List all environments in the tenant (id, name, slug, group, default flag) |
|
|
51
|
+
| `create_environment` | Create an environment with optional group assignment and default flag |
|
|
52
|
+
| `update_environment` | Update an environment's name, description, group, or sort order |
|
|
53
|
+
| `delete_environment` | Delete an environment by id (irreversible) |
|
|
54
|
+
| `list_environment_groups` | List environment groups (id, name, slug, AWS account binding) |
|
|
55
|
+
| `create_environment_group` | Create an environment group with optional AWS account binding |
|
|
56
|
+
|
|
57
|
+
### Observability
|
|
58
|
+
|
|
59
|
+
| Tool | Description |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `list_dashboards` | List all observability dashboards in the tenant (id, name, panel count) |
|
|
62
|
+
| `get_dashboard` | Get a dashboard's name, description, and panel list |
|
|
63
|
+
| `create_dashboard` | Create a new dashboard with optional description |
|
|
64
|
+
| `update_dashboard` | Replace a dashboard's name and description (full PUT; name required) |
|
|
65
|
+
| `delete_dashboard` | Delete a dashboard and all its panels (irreversible) |
|
|
66
|
+
| `add_panel` | Add a panel to a dashboard with optional PromQL query |
|
|
67
|
+
| `delete_panel` | Delete a panel from a dashboard (irreversible) |
|
|
68
|
+
|
|
69
|
+
### Services
|
|
70
|
+
|
|
71
|
+
| Tool | Description |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `list_services` | List all services in the tenant catalog (id, name, slug) |
|
|
74
|
+
| `create_service` | Create a service in the catalog with kind, language, SCM provider, and repository |
|
|
75
|
+
| `update_service` | Update a service's metadata, lifecycle, owner, tags, links, or environment associations |
|
|
76
|
+
| `delete_service` | Delete a service from the catalog by id (irreversible) |
|
|
77
|
+
|
|
78
|
+
## Security
|
|
79
|
+
|
|
80
|
+
The server can only do what the PAT's scope allows. Attempts to write without the relevant manage permission return a 403 from the API and are surfaced as an error in Claude's response. The PAT is revocable at any time from **Profile → Access tokens** in the Sciple dashboard — revoking it immediately cuts off the server's access without any config change.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sciple-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP server for populating and managing Sciple platform content"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Navaganesh Raju", email = "navaganesh.raju@gmail.com" },
|
|
13
|
+
]
|
|
14
|
+
keywords = ["mcp", "sciple", "claude", "anthropic", "model-context-protocol"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: System Administrators",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Topic :: Software Development :: Libraries",
|
|
22
|
+
"Topic :: System :: Systems Administration",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"mcp[cli]>=1.0.0",
|
|
26
|
+
"httpx>=0.27.0",
|
|
27
|
+
"python-dotenv>=1.0.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/navaganeshr/sciple-mcp"
|
|
32
|
+
Source = "https://github.com/navaganeshr/sciple-mcp"
|
|
33
|
+
Issues = "https://github.com/navaganeshr/sciple-mcp/issues"
|
|
34
|
+
Documentation = "https://github.com/navaganeshr/sciple-mcp#readme"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
sciple-mcp = "sciple_mcp.server:main"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
packages = ["src/sciple_mcp"]
|
|
41
|
+
|
|
42
|
+
[dependency-groups]
|
|
43
|
+
dev = [
|
|
44
|
+
"pytest>=8.0.0",
|
|
45
|
+
"pytest-asyncio>=0.23.0",
|
|
46
|
+
"respx>=0.21.0",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.pytest.ini_options]
|
|
50
|
+
asyncio_mode = "auto"
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Thin async HTTP client wrapping the Sciple REST API.
|
|
2
|
+
|
|
3
|
+
Authenticates with a Sciple personal access token (PAT). The PAT is sent as a
|
|
4
|
+
Bearer token; the API's get_bearer resolves it on data routes, and X-Tenant-ID
|
|
5
|
+
selects the tenant (which must match the PAT's bound tenant).
|
|
6
|
+
"""
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ScipleClient:
|
|
11
|
+
def __init__(self, base_url: str, token: str, tenant_id: str) -> None:
|
|
12
|
+
self._base = base_url.rstrip("/")
|
|
13
|
+
self._headers = {
|
|
14
|
+
"Authorization": f"Bearer {token}",
|
|
15
|
+
"X-Tenant-ID": tenant_id,
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async def get(self, path: str) -> object:
|
|
20
|
+
async with httpx.AsyncClient() as http:
|
|
21
|
+
r = await http.get(f"{self._base}{path}", headers=self._headers)
|
|
22
|
+
r.raise_for_status()
|
|
23
|
+
return r.json()
|
|
24
|
+
|
|
25
|
+
async def post(self, path: str, body: dict | None = None) -> object:
|
|
26
|
+
async with httpx.AsyncClient() as http:
|
|
27
|
+
r = await http.post(f"{self._base}{path}", headers=self._headers, json=body or {})
|
|
28
|
+
r.raise_for_status()
|
|
29
|
+
return r.json()
|
|
30
|
+
|
|
31
|
+
async def patch(self, path: str, body: dict) -> object:
|
|
32
|
+
async with httpx.AsyncClient() as http:
|
|
33
|
+
r = await http.patch(f"{self._base}{path}", headers=self._headers, json=body)
|
|
34
|
+
r.raise_for_status()
|
|
35
|
+
return r.json()
|
|
36
|
+
|
|
37
|
+
async def put(self, path: str, body: dict) -> object:
|
|
38
|
+
async with httpx.AsyncClient() as http:
|
|
39
|
+
r = await http.put(f"{self._base}{path}", headers=self._headers, json=body)
|
|
40
|
+
r.raise_for_status()
|
|
41
|
+
return r.json()
|
|
42
|
+
|
|
43
|
+
async def delete(self, path: str) -> None:
|
|
44
|
+
async with httpx.AsyncClient() as http:
|
|
45
|
+
r = await http.delete(f"{self._base}{path}", headers=self._headers)
|
|
46
|
+
r.raise_for_status()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Sciple platform MCP server — exposes platform CRUD tools to a local Claude.
|
|
2
|
+
|
|
3
|
+
Auth: a Sciple personal access token (PAT) in SCIPLE_API_TOKEN. The server's
|
|
4
|
+
reach is whatever the PAT's scope grants; out-of-scope writes fail server-side.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
from mcp.server.fastmcp import FastMCP
|
|
10
|
+
|
|
11
|
+
from sciple_mcp.client import ScipleClient
|
|
12
|
+
from sciple_mcp.tools import environments, observability, services
|
|
13
|
+
|
|
14
|
+
load_dotenv()
|
|
15
|
+
|
|
16
|
+
mcp = FastMCP("Sciple Platform")
|
|
17
|
+
|
|
18
|
+
_client: ScipleClient | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_client() -> ScipleClient:
|
|
22
|
+
global _client
|
|
23
|
+
if _client is None:
|
|
24
|
+
_client = ScipleClient(
|
|
25
|
+
base_url=os.environ["SCIPLE_API_URL"],
|
|
26
|
+
token=os.environ["SCIPLE_API_TOKEN"],
|
|
27
|
+
tenant_id=os.environ["SCIPLE_TENANT_ID"],
|
|
28
|
+
)
|
|
29
|
+
return _client
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
environments.register(mcp, _get_client)
|
|
33
|
+
observability.register(mcp, _get_client)
|
|
34
|
+
services.register(mcp, _get_client)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main() -> None:
|
|
38
|
+
mcp.run(transport="stdio")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Environment + environment-group tools — populate the platform's env structure.
|
|
2
|
+
|
|
3
|
+
Wraps the REST routes:
|
|
4
|
+
GET/POST /environments GET/PATCH/DELETE /environments/{id}
|
|
5
|
+
GET/POST /envgroups GET/PATCH/DELETE /envgroups/{id}
|
|
6
|
+
Requires the PAT to hold environments.view (reads) / environments.manage (writes).
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
|
|
12
|
+
from sciple_mcp.client import ScipleClient
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def register(mcp, get_client: Callable[[], ScipleClient]) -> None:
|
|
16
|
+
|
|
17
|
+
@mcp.tool()
|
|
18
|
+
async def list_environments() -> str:
|
|
19
|
+
"""List all environments in the tenant (id, name, slug, group, default flag)."""
|
|
20
|
+
envs = await get_client().get("/environments")
|
|
21
|
+
if not envs:
|
|
22
|
+
return "No environments found."
|
|
23
|
+
lines = []
|
|
24
|
+
for e in envs:
|
|
25
|
+
default = " [default]" if e.get("is_default") else ""
|
|
26
|
+
grp = f" group={e['environment_group_id']}" if e.get("environment_group_id") else ""
|
|
27
|
+
lines.append(f"- {e['name']} (id={e['id']}, slug={e['slug']}){default}{grp}")
|
|
28
|
+
return "\n".join(lines)
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
async def create_environment(
|
|
32
|
+
name: str,
|
|
33
|
+
slug: str | None = None,
|
|
34
|
+
environment_group_id: str | None = None,
|
|
35
|
+
description: str | None = None,
|
|
36
|
+
is_default: bool = False,
|
|
37
|
+
display_order: int = 0,
|
|
38
|
+
) -> str:
|
|
39
|
+
"""Create an environment.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
name: Display name, e.g. "Production".
|
|
43
|
+
slug: URL-safe id; server generates one from name if omitted.
|
|
44
|
+
environment_group_id: Optional parent environment-group id.
|
|
45
|
+
description: Optional human description.
|
|
46
|
+
is_default: Mark this the tenant's default environment.
|
|
47
|
+
display_order: Sort order in the UI (lower = earlier).
|
|
48
|
+
"""
|
|
49
|
+
body: dict = {"name": name, "is_default": is_default, "display_order": display_order}
|
|
50
|
+
if slug is not None:
|
|
51
|
+
body["slug"] = slug
|
|
52
|
+
if environment_group_id is not None:
|
|
53
|
+
body["environment_group_id"] = environment_group_id
|
|
54
|
+
if description is not None:
|
|
55
|
+
body["description"] = description
|
|
56
|
+
e = await get_client().post("/environments", body)
|
|
57
|
+
return f"Created environment '{e['name']}' (id={e['id']}, slug={e['slug']})."
|
|
58
|
+
|
|
59
|
+
@mcp.tool()
|
|
60
|
+
async def update_environment(
|
|
61
|
+
env_id: str,
|
|
62
|
+
name: str | None = None,
|
|
63
|
+
description: str | None = None,
|
|
64
|
+
environment_group_id: str | None = None,
|
|
65
|
+
is_default: bool | None = None,
|
|
66
|
+
display_order: int | None = None,
|
|
67
|
+
) -> str:
|
|
68
|
+
"""Update an environment. Only provided fields change.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
env_id: The environment id to update.
|
|
72
|
+
name: New display name.
|
|
73
|
+
description: New description.
|
|
74
|
+
environment_group_id: Move to a different environment group.
|
|
75
|
+
is_default: Set/unset default.
|
|
76
|
+
display_order: New sort order.
|
|
77
|
+
"""
|
|
78
|
+
body: dict = {}
|
|
79
|
+
for key, val in (
|
|
80
|
+
("name", name), ("description", description),
|
|
81
|
+
("environment_group_id", environment_group_id),
|
|
82
|
+
("is_default", is_default), ("display_order", display_order),
|
|
83
|
+
):
|
|
84
|
+
if val is not None:
|
|
85
|
+
body[key] = val
|
|
86
|
+
if not body:
|
|
87
|
+
return "Nothing to update — provide at least one field."
|
|
88
|
+
e = await get_client().patch(f"/environments/{env_id}", body)
|
|
89
|
+
return f"Updated environment '{e['name']}' (id={e['id']})."
|
|
90
|
+
|
|
91
|
+
@mcp.tool()
|
|
92
|
+
async def delete_environment(env_id: str) -> str:
|
|
93
|
+
"""Delete an environment by id. This cannot be undone.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
env_id: The environment id to delete.
|
|
97
|
+
"""
|
|
98
|
+
await get_client().delete(f"/environments/{env_id}")
|
|
99
|
+
return f"Deleted environment {env_id}."
|
|
100
|
+
|
|
101
|
+
@mcp.tool()
|
|
102
|
+
async def list_environment_groups() -> str:
|
|
103
|
+
"""List environment groups (id, name, slug, aws account binding)."""
|
|
104
|
+
groups = await get_client().get("/envgroups")
|
|
105
|
+
if not groups:
|
|
106
|
+
return "No environment groups found."
|
|
107
|
+
lines = []
|
|
108
|
+
for g in groups:
|
|
109
|
+
acct = f" aws={g['aws_account_id']}" if g.get("aws_account_id") else ""
|
|
110
|
+
lines.append(f"- {g['name']} (id={g['id']}, slug={g['slug']}){acct}")
|
|
111
|
+
return "\n".join(lines)
|
|
112
|
+
|
|
113
|
+
@mcp.tool()
|
|
114
|
+
async def create_environment_group(
|
|
115
|
+
name: str,
|
|
116
|
+
slug: str | None = None,
|
|
117
|
+
description: str | None = None,
|
|
118
|
+
display_order: int = 0,
|
|
119
|
+
aws_account_id: str | None = None,
|
|
120
|
+
) -> str:
|
|
121
|
+
"""Create an environment group.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
name: Display name, e.g. "Production accounts".
|
|
125
|
+
slug: URL-safe id; generated from name if omitted.
|
|
126
|
+
description: Optional description.
|
|
127
|
+
display_order: Sort order in the UI.
|
|
128
|
+
aws_account_id: Optional AWS account id to bind the group to.
|
|
129
|
+
"""
|
|
130
|
+
body: dict = {"name": name, "display_order": display_order}
|
|
131
|
+
if slug is not None:
|
|
132
|
+
body["slug"] = slug
|
|
133
|
+
if description is not None:
|
|
134
|
+
body["description"] = description
|
|
135
|
+
if aws_account_id is not None:
|
|
136
|
+
body["aws_account_id"] = aws_account_id
|
|
137
|
+
g = await get_client().post("/envgroups", body)
|
|
138
|
+
return f"Created environment group '{g['name']}' (id={g['id']}, slug={g['slug']})."
|