aidlc.html-creator-mcp 1.0.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,26 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ *.egg-info/
7
+ build/
8
+ dist/
9
+ .aws-sam/
10
+ samconfig.local.toml
11
+ .DS_Store
12
+ .env
13
+ .env.*
14
+ !.env.example
15
+ *.pem
16
+ *.key
17
+
18
+ # Generated per environment by deploy/publish-admin.sh from the stack outputs, and uploaded
19
+ # straight to the site bucket. Committing it would mean a file that is wrong for every
20
+ # environment but the one it was written for. admin-ui/public/config.sample.json documents the
21
+ # shape. The script now writes it to a temp directory, so this only catches a hand-made copy.
22
+ admin-ui/public/config.json
23
+
24
+ # Scratch screenshots from driving the admin console in a browser during review. Evidence for
25
+ # one session, not part of the repo.
26
+ .tmp-shots/
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.5
2
+ Name: aidlc.html-creator-mcp
3
+ Version: 1.0.0
4
+ Summary: Connects an MCP client over stdio to the html-creator assessment site workflow, hosted by the MSC Labs CoE.
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+
8
+ # aidlc.html-creator-mcp
9
+
10
+ Connects an MCP client to the html-creator assessment site workflow, which runs in the MSC Labs CoE account. The workflow, its
11
+ guardrails and its policy live there; this package is only the stdio bridge.
12
+
13
+ ## Configure
14
+
15
+ Track the latest release:
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "aidlc.html-creator": {
21
+ "command": "uvx",
22
+ "args": ["aidlc.html-creator-mcp@latest"],
23
+ "env": { "MCP_API_TOKEN": "${MCP_API_TOKEN}" }
24
+ }
25
+ }
26
+ }
27
+ ```
28
+
29
+ Or pin one, so a new release reaches your machine only when you change the number:
30
+
31
+ ```json
32
+ "args": ["aidlc.html-creator-mcp==1.0.0"]
33
+ ```
34
+
35
+ Set `MCP_API_TOKEN` to the key the CoE issued you, and add `MCP_API_TOKEN` to your client's
36
+ approved environment variables. Without a key the registry answers 403 and nothing works.
37
+
38
+ ## What it does not do
39
+
40
+ It holds no copy of the workflow, caches nothing and decides nothing. Every step, reference and
41
+ policy check is answered by the hosted server at call time, so what you run is always what the
42
+ CoE published.
43
+
44
+ | Variable | Default | Purpose |
45
+ | --- | --- | --- |
46
+ | `MCP_API_TOKEN` | — | Your API key. Required. |
47
+ | `MSCLABS_MCP_BASE_URL` | the CoE endpoint | Point at a different deployment. |
48
+ | `MSCLABS_MCP_TIMEOUT` | `120` | Seconds to wait for one call. |
@@ -0,0 +1,41 @@
1
+ # aidlc.html-creator-mcp
2
+
3
+ Connects an MCP client to the html-creator assessment site workflow, which runs in the MSC Labs CoE account. The workflow, its
4
+ guardrails and its policy live there; this package is only the stdio bridge.
5
+
6
+ ## Configure
7
+
8
+ Track the latest release:
9
+
10
+ ```json
11
+ {
12
+ "mcpServers": {
13
+ "aidlc.html-creator": {
14
+ "command": "uvx",
15
+ "args": ["aidlc.html-creator-mcp@latest"],
16
+ "env": { "MCP_API_TOKEN": "${MCP_API_TOKEN}" }
17
+ }
18
+ }
19
+ }
20
+ ```
21
+
22
+ Or pin one, so a new release reaches your machine only when you change the number:
23
+
24
+ ```json
25
+ "args": ["aidlc.html-creator-mcp==1.0.0"]
26
+ ```
27
+
28
+ Set `MCP_API_TOKEN` to the key the CoE issued you, and add `MCP_API_TOKEN` to your client's
29
+ approved environment variables. Without a key the registry answers 403 and nothing works.
30
+
31
+ ## What it does not do
32
+
33
+ It holds no copy of the workflow, caches nothing and decides nothing. Every step, reference and
34
+ policy check is answered by the hosted server at call time, so what you run is always what the
35
+ CoE published.
36
+
37
+ | Variable | Default | Purpose |
38
+ | --- | --- | --- |
39
+ | `MCP_API_TOKEN` | — | Your API key. Required. |
40
+ | `MSCLABS_MCP_BASE_URL` | the CoE endpoint | Point at a different deployment. |
41
+ | `MSCLABS_MCP_TIMEOUT` | `120` | Seconds to wait for one call. |
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "aidlc.html-creator-mcp"
3
+ version = "1.0.0"
4
+ description = "Connects an MCP client over stdio to the html-creator assessment site workflow, hosted by the MSC Labs CoE."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ # The standard library only, on purpose: nothing third-party should ever see the API key, and
8
+ # `uvx` start-up stays the download of one small wheel.
9
+ dependencies = []
10
+
11
+ [project.scripts]
12
+ "aidlc.html-creator-mcp" = "aidlc_html_creator_mcp.bridge:main"
13
+
14
+ [build-system]
15
+ requires = ["hatchling"]
16
+ build-backend = "hatchling.build"
17
+
18
+ [tool.hatch.build.targets.wheel]
19
+ packages = ["src/aidlc_html_creator_mcp"]
@@ -0,0 +1,3 @@
1
+ """stdio bridge to aidlc.html-creator, hosted by the MSC Labs CoE."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,5 @@
1
+ """`python -m aidlc_html_creator_mcp`, for a client that would rather run a module than a script."""
2
+
3
+ from .bridge import main
4
+
5
+ raise SystemExit(main())
@@ -0,0 +1,130 @@
1
+ """stdio to the hosted registry, and nothing else.
2
+
3
+ This is the only piece of the registry that runs on a developer's machine, so it is kept as
4
+ close to empty as a working program can be: it reads a JSON-RPC line from stdin, posts it to
5
+ the hosted server with the developer's API key, and writes the reply back to stdout. There is
6
+ no procedure here, no policy, no cached content and no state — those stay server-side, which is
7
+ the whole reason the registry is hosted rather than installed. A developer can read every line
8
+ of this file and find nothing to tune.
9
+
10
+ It depends on the standard library alone. That keeps `uvx` start-up to the download of one
11
+ small wheel, and keeps the API key out of reach of any third-party package.
12
+
13
+ Posting a line and reading the reply is enough because the hosted servers run in stateless JSON
14
+ mode: one request, one response, no session to keep alive and no event stream to multiplex.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import os
21
+ import sys
22
+ import urllib.error
23
+ import urllib.request
24
+
25
+ #: Filled in per distribution by the generator in `build_clients.py`.
26
+ SERVER_ID = "html-creator"
27
+ SERVER_NAME = "aidlc.html-creator"
28
+ BASE_URL = "https://8a3gpe2ji0.execute-api.ap-southeast-7.amazonaws.com/v1"
29
+ VERSION = "1.0.0"
30
+
31
+ TOKEN_VARS = ("MCP_API_TOKEN", "MSCLABS_MCP_TOKEN")
32
+ TIMEOUT = float(os.environ.get("MSCLABS_MCP_TIMEOUT", "120"))
33
+
34
+
35
+ def _log(message: str) -> None:
36
+ """Diagnostics go to stderr. stdout carries the protocol and nothing else."""
37
+ print(f"[{SERVER_NAME}] {message}", file=sys.stderr, flush=True)
38
+
39
+
40
+ def _token() -> str:
41
+ for name in TOKEN_VARS:
42
+ value = os.environ.get(name, "").strip()
43
+ if value:
44
+ return value
45
+ _log(
46
+ "No API key. Set MCP_API_TOKEN to the key the CoE issued you, and add MCP_API_TOKEN to "
47
+ "your client's approved environment variables. Ask the CoE for a key if you have none."
48
+ )
49
+ raise SystemExit(2)
50
+
51
+
52
+ def _endpoint() -> str:
53
+ base = os.environ.get("MSCLABS_MCP_BASE_URL", BASE_URL).rstrip("/")
54
+ return f"{base}/{SERVER_ID}/mcp"
55
+
56
+
57
+ def _error(request_id: object, code: int, message: str) -> str:
58
+ return json.dumps({"jsonrpc": "2.0", "id": request_id, "error": {"code": code, "message": message}})
59
+
60
+
61
+ def _forward(url: str, token: str, line: str, request_id: object) -> str | None:
62
+ """Posts one message upstream and returns the reply to write to stdout, if any."""
63
+ request = urllib.request.Request(url, data=line.encode(), method="POST")
64
+ request.add_header("Content-Type", "application/json")
65
+ request.add_header("Accept", "application/json, text/event-stream")
66
+ request.add_header("x-api-key", token)
67
+ request.add_header("User-Agent", f"{SERVER_NAME}/{VERSION}")
68
+ try:
69
+ with urllib.request.urlopen(request, timeout=TIMEOUT) as response:
70
+ body = response.read().decode(errors="replace")
71
+ except urllib.error.HTTPError as exc:
72
+ detail = exc.read().decode(errors="replace")[:400]
73
+ if exc.code in (401, 403):
74
+ # The most common failure by far, and the one whose default message sends people
75
+ # looking in the wrong place: the client reports an OAuth problem when the real
76
+ # cause is a key that is missing, mistyped or revoked.
77
+ _log(
78
+ f"{exc.code} from the registry: the API key was rejected. Check MCP_API_TOKEN is "
79
+ "the whole key, that your client is allowed to pass it through, and that the key "
80
+ "has not been revoked."
81
+ )
82
+ else:
83
+ _log(f"{exc.code} from the registry: {detail}")
84
+ if request_id is None:
85
+ return None
86
+ return _error(request_id, -32000, f"registry returned HTTP {exc.code}")
87
+ except Exception as exc: # noqa: BLE001 - any transport failure becomes one JSON-RPC error
88
+ _log(f"could not reach the registry: {exc}")
89
+ if request_id is None:
90
+ return None
91
+ return _error(request_id, -32000, f"could not reach the registry: {exc}")
92
+
93
+ if request_id is None:
94
+ # A notification gets no reply, and writing one would desynchronise the client.
95
+ return None
96
+ # Stateless JSON mode answers with a JSON object; an SSE frame is still accepted in case a
97
+ # future deployment negotiates one.
98
+ for candidate in body.splitlines():
99
+ candidate = candidate.removeprefix("data:").strip()
100
+ if candidate.startswith("{"):
101
+ return candidate
102
+ return _error(request_id, -32000, "the registry returned an empty response")
103
+
104
+
105
+ def main() -> int:
106
+ token = _token()
107
+ url = _endpoint()
108
+ _log(f"bridging stdio to {url}")
109
+
110
+ for line in sys.stdin:
111
+ line = line.strip()
112
+ if not line:
113
+ continue
114
+ try:
115
+ message = json.loads(line)
116
+ except json.JSONDecodeError:
117
+ sys.stdout.write(_error(None, -32700, "parse error") + "\n")
118
+ sys.stdout.flush()
119
+ continue
120
+
121
+ request_id = message.get("id") if isinstance(message, dict) else None
122
+ reply = _forward(url, token, line, request_id)
123
+ if reply is not None:
124
+ sys.stdout.write(reply + "\n")
125
+ sys.stdout.flush()
126
+ return 0
127
+
128
+
129
+ if __name__ == "__main__":
130
+ raise SystemExit(main())