oscal-generator-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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MEOK AI Labs
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,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: oscal-generator-mcp
3
+ Version: 0.1.0
4
+ Summary: Generate machine-readable NIST OSCAL (SSP / component-definition) + FedRAMP RFC-0024 readiness — governed + signed. CSOAI Layer-0.
5
+ License: Apache-2.0
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: mcp>=1.28.0
10
+ Requires-Dist: pydantic>=2.0
11
+ Requires-Dist: cryptography>=41.0
12
+ Provides-Extra: validate
13
+ Requires-Dist: compliance-trestle>=4.0.0; extra == "validate"
14
+ Dynamic: license-file
15
+
16
+ # oscal-generator-mcp
17
+
18
+ Generate **machine-readable NIST OSCAL** packages (System Security Plan + Component Definition) and score **FedRAMP RFC-0024 readiness** — governed + SIGIL-signed. CSOAI Layer-0.
19
+
20
+ **Why:** RFC-0024 (13 Jan 2026) mandates machine-readable OSCAL packages, first deadline **30 Sep 2026** — yet ~0 of 100+ 2025 Rev5 authorizations actually produced OSCAL. System description in → valid OSCAL JSON out, signed.
21
+
22
+ ## Tools
23
+ - `generate_ssp(system_name, impact_level, controls, ts)` → OSCAL System Security Plan
24
+ - `generate_component_definition(component_name, control_ids, ts)` → OSCAL Component Definition
25
+ - `validate_oscal(document)` → structural validation
26
+ - `rfc0024_readiness(...)` → 0–100 readiness score + gaps vs the 30 Sep 2026 deadline
27
+
28
+ Deterministic (uuid5 + explicit ts) → reproducible packages. Apache-2.0.
@@ -0,0 +1,13 @@
1
+ # oscal-generator-mcp
2
+
3
+ Generate **machine-readable NIST OSCAL** packages (System Security Plan + Component Definition) and score **FedRAMP RFC-0024 readiness** — governed + SIGIL-signed. CSOAI Layer-0.
4
+
5
+ **Why:** RFC-0024 (13 Jan 2026) mandates machine-readable OSCAL packages, first deadline **30 Sep 2026** — yet ~0 of 100+ 2025 Rev5 authorizations actually produced OSCAL. System description in → valid OSCAL JSON out, signed.
6
+
7
+ ## Tools
8
+ - `generate_ssp(system_name, impact_level, controls, ts)` → OSCAL System Security Plan
9
+ - `generate_component_definition(component_name, control_ids, ts)` → OSCAL Component Definition
10
+ - `validate_oscal(document)` → structural validation
11
+ - `rfc0024_readiness(...)` → 0–100 readiness score + gaps vs the 30 Sep 2026 deadline
12
+
13
+ Deterministic (uuid5 + explicit ts) → reproducible packages. Apache-2.0.
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: oscal-generator-mcp
3
+ Version: 0.1.0
4
+ Summary: Generate machine-readable NIST OSCAL (SSP / component-definition) + FedRAMP RFC-0024 readiness — governed + signed. CSOAI Layer-0.
5
+ License: Apache-2.0
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: mcp>=1.28.0
10
+ Requires-Dist: pydantic>=2.0
11
+ Requires-Dist: cryptography>=41.0
12
+ Provides-Extra: validate
13
+ Requires-Dist: compliance-trestle>=4.0.0; extra == "validate"
14
+ Dynamic: license-file
15
+
16
+ # oscal-generator-mcp
17
+
18
+ Generate **machine-readable NIST OSCAL** packages (System Security Plan + Component Definition) and score **FedRAMP RFC-0024 readiness** — governed + SIGIL-signed. CSOAI Layer-0.
19
+
20
+ **Why:** RFC-0024 (13 Jan 2026) mandates machine-readable OSCAL packages, first deadline **30 Sep 2026** — yet ~0 of 100+ 2025 Rev5 authorizations actually produced OSCAL. System description in → valid OSCAL JSON out, signed.
21
+
22
+ ## Tools
23
+ - `generate_ssp(system_name, impact_level, controls, ts)` → OSCAL System Security Plan
24
+ - `generate_component_definition(component_name, control_ids, ts)` → OSCAL Component Definition
25
+ - `validate_oscal(document)` → structural validation
26
+ - `rfc0024_readiness(...)` → 0–100 readiness score + gaps vs the 30 Sep 2026 deadline
27
+
28
+ Deterministic (uuid5 + explicit ts) → reproducible packages. Apache-2.0.
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ server.py
5
+ oscal_generator_mcp.egg-info/PKG-INFO
6
+ oscal_generator_mcp.egg-info/SOURCES.txt
7
+ oscal_generator_mcp.egg-info/dependency_links.txt
8
+ oscal_generator_mcp.egg-info/entry_points.txt
9
+ oscal_generator_mcp.egg-info/requires.txt
10
+ oscal_generator_mcp.egg-info/top_level.txt
11
+ tests/test_oscal.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ oscal-generator-mcp = server:main
@@ -0,0 +1,6 @@
1
+ mcp>=1.28.0
2
+ pydantic>=2.0
3
+ cryptography>=41.0
4
+
5
+ [validate]
6
+ compliance-trestle>=4.0.0
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "oscal-generator-mcp"
7
+ version = "0.1.0"
8
+ description = "Generate machine-readable NIST OSCAL (SSP / component-definition) + FedRAMP RFC-0024 readiness — governed + signed. CSOAI Layer-0."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = {text = "Apache-2.0"}
12
+ dependencies = ["mcp>=1.28.0", "pydantic>=2.0", "cryptography>=41.0"]
13
+
14
+ [project.optional-dependencies]
15
+ # NIST-grade strict validation via the standard community OSCAL toolchain.
16
+ validate = ["compliance-trestle>=4.0.0"]
17
+
18
+ [project.scripts]
19
+ oscal-generator-mcp = "server:main"
20
+
21
+ [tool.setuptools]
22
+ py-modules = ["server"]
@@ -0,0 +1,362 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ OSCAL Generator MCP — CSOAI Layer-0.
4
+
5
+ Generates machine-readable NIST OSCAL packages (System Security Plan +
6
+ Component Definition) and runs an RFC-0024 readiness check. FedRAMP RFC-0024
7
+ (13 Jan 2026) mandates machine-readable OSCAL packages — first deadline
8
+ 30 Sep 2026 — yet ~0 of 100+ 2025 Rev5 authorizations produced OSCAL. This
9
+ closes that vacuum: a system description in → valid OSCAL JSON out, signed.
10
+
11
+ Tools: generate_ssp · generate_component_definition · validate_oscal ·
12
+ validate_oscal_strict (trestle/NIST-grade) · rfc0024_readiness
13
+ """
14
+ from mcp.server.fastmcp import FastMCP
15
+ from pydantic import BaseModel, Field
16
+ from typing import List, Dict, Any, Optional
17
+
18
+ mcp = FastMCP("OSCAL Generator", instructions="Generate machine-readable NIST OSCAL (SSP / component-definition) + RFC-0024 readiness, governed + signed.")
19
+
20
+ # ── SIGIL: every generated artifact → one signed hash-chained hop ──
21
+ import hashlib as _hl, time as _t, json as _j, os as _os, uuid as _uuid
22
+ _SIGIL_LOG = _os.environ.get("SIGIL_LOG", _os.path.join(_os.path.dirname(_os.path.abspath(__file__)), "oscal_sigil.log"))
23
+ def _sigil(op, body):
24
+ try:
25
+ prev = ""
26
+ if _os.path.exists(_SIGIL_LOG):
27
+ with open(_SIGIL_LOG) as f:
28
+ ls = f.readlines()
29
+ if ls: prev = _j.loads(ls[-1]).get("digest", "")
30
+ ts = int(_t.time()); dg = _hl.sha256(f"{op}|{ts}|{prev[:8]}|{body}".encode()).hexdigest()[:16]
31
+ _os.makedirs(_os.path.dirname(_SIGIL_LOG), exist_ok=True)
32
+ with open(_SIGIL_LOG, "a") as f: f.write(_j.dumps({"ts": ts, "op": op, "body": body, "prev_digest": prev, "digest": dg}) + "\n")
33
+ return dg
34
+ except Exception: return ""
35
+
36
+ OSCAL_VERSION = "1.1.2"
37
+ _NS = "uuid" # deterministic uuid5 namespace base
38
+
39
+ # ── Ed25519: OSCAL packages cryptographically signed (RFC-0024 "signed package" = real, offline-verifiable) ──
40
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
41
+ from cryptography.hazmat.primitives import serialization as _ser
42
+
43
+ _OSCAL_KEY_SEED = _os.environ.get("OSCAL_SIGNING_SEED", "csoai-oscal/signing-key-v1")
44
+ def _signing_key() -> Ed25519PrivateKey:
45
+ """Deterministic dev key from a seed. In production this calls the KMS/HSM."""
46
+ return Ed25519PrivateKey.from_private_bytes(_hl.sha256(_OSCAL_KEY_SEED.encode()).digest())
47
+ def _canon(doc) -> bytes:
48
+ return _j.dumps(doc, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
49
+
50
+
51
+ def _uuid5(*parts: str) -> str:
52
+ return str(_uuid.uuid5(_uuid.NAMESPACE_URL, "csoai-oscal/" + "/".join(str(p) for p in parts)))
53
+
54
+
55
+ def _now_iso(ts: Optional[int] = None) -> str:
56
+ # OSCAL wants RFC3339; deterministic from an int ts (passed in for reproducibility)
57
+ import datetime as _dt
58
+ t = ts if ts is not None else 0
59
+ return _dt.datetime.fromtimestamp(t, _dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
60
+
61
+
62
+ def _metadata(title: str, ts: int) -> Dict[str, Any]:
63
+ return {
64
+ "title": title,
65
+ "last-modified": _now_iso(ts),
66
+ "version": "1.0.0",
67
+ "oscal-version": OSCAL_VERSION,
68
+ "roles": [{"id": "system-owner", "title": "System Owner"},
69
+ {"id": "authorizing-official", "title": "Authorizing Official"}],
70
+ "parties": [{"uuid": _uuid5(title, "org"), "type": "organization", "name": "CSOAI-governed system owner"}],
71
+ }
72
+
73
+
74
+ class OscalDoc(BaseModel):
75
+ model: str
76
+ uuid: str
77
+ document: Dict[str, Any]
78
+ sigil: str = ""
79
+
80
+
81
+ class Validation(BaseModel):
82
+ valid: bool
83
+ model: Optional[str] = None
84
+ errors: List[str] = Field(default_factory=list)
85
+ warnings: List[str] = Field(default_factory=list)
86
+
87
+
88
+ class Readiness(BaseModel):
89
+ ready: bool
90
+ score: int
91
+ deadline: str = "2026-09-30 (RFC-0024 first machine-readable deadline)"
92
+ gaps: List[str] = Field(default_factory=list)
93
+ note: str = ""
94
+
95
+
96
+ @mcp.tool()
97
+ def generate_ssp(system_name: str, impact_level: str = "moderate", controls: Optional[List[str]] = None, ts: int = 0) -> OscalDoc:
98
+ """Generate a NIST OSCAL System Security Plan (SSP) skeleton for a system.
99
+ impact_level: low|moderate|high. controls: NIST 800-53 control ids (e.g. AC-2, AU-6); defaults to a baseline set."""
100
+ ctrls = controls or ["AC-2", "AC-3", "AU-2", "AU-6", "CA-2", "CM-2", "IA-2", "RA-5", "SC-7", "SI-4"]
101
+ uid = _uuid5(system_name, "ssp")
102
+ doc = {"system-security-plan": {
103
+ "uuid": uid,
104
+ "metadata": _metadata(f"{system_name} — System Security Plan", ts),
105
+ "import-profile": {"href": f"#baseline-{impact_level}"},
106
+ "system-characteristics": {
107
+ "system-name": system_name,
108
+ "security-sensitivity-level": impact_level,
109
+ "system-information": {"information-types": [{
110
+ "uuid": _uuid5(system_name, "info"), "title": "System information",
111
+ "confidentiality-impact": {"base": impact_level},
112
+ "integrity-impact": {"base": impact_level},
113
+ "availability-impact": {"base": impact_level}}]},
114
+ "status": {"state": "operational"},
115
+ },
116
+ "system-implementation": {"components": [{
117
+ "uuid": _uuid5(system_name, "comp"), "type": "this-system",
118
+ "title": system_name, "status": {"state": "operational"}}]},
119
+ "control-implementation": {
120
+ "description": "Control implementations for the named controls.",
121
+ "implemented-requirements": [
122
+ {"uuid": _uuid5(system_name, c), "control-id": c.lower(),
123
+ "statements": [{"statement-id": f"{c.lower()}_smt", "uuid": _uuid5(system_name, c, "smt"),
124
+ "by-components": [{"component-uuid": _uuid5(system_name, "comp"),
125
+ "uuid": _uuid5(system_name, c, "bc"),
126
+ "description": f"{c} implemented and governed (CSOAI-attested)."}]}]}
127
+ for c in ctrls]},
128
+ }}
129
+ return OscalDoc(model="ssp", uuid=uid, document=doc, sigil=_sigil("OSCAL", f"ssp|{system_name}|{impact_level}"))
130
+
131
+
132
+ @mcp.tool()
133
+ def generate_component_definition(component_name: str, control_ids: Optional[List[str]] = None, ts: int = 0) -> OscalDoc:
134
+ """Generate a NIST OSCAL Component Definition for a reusable component (e.g. an MCP server, a service)."""
135
+ ctrls = control_ids or ["AC-2", "AU-2", "SC-7"]
136
+ uid = _uuid5(component_name, "compdef")
137
+ cuid = _uuid5(component_name, "component")
138
+ doc = {"component-definition": {
139
+ "uuid": uid,
140
+ "metadata": _metadata(f"{component_name} — Component Definition", ts),
141
+ "components": [{
142
+ "uuid": cuid, "type": "software", "title": component_name,
143
+ "description": f"{component_name} — CSOAI-governed component.",
144
+ "control-implementations": [{
145
+ "uuid": _uuid5(component_name, "ci"), "source": "#nist-800-53",
146
+ "description": "Controls this component satisfies.",
147
+ "implemented-requirements": [
148
+ {"uuid": _uuid5(component_name, c), "control-id": c.lower(),
149
+ "description": f"{c} satisfied by {component_name}."} for c in ctrls]}]}],
150
+ }}
151
+ return OscalDoc(model="component-definition", uuid=uid, document=doc, sigil=_sigil("OSCAL", f"compdef|{component_name}"))
152
+
153
+
154
+ @mcp.tool()
155
+ def validate_oscal(document: Dict[str, Any]) -> Validation:
156
+ """Validate an OSCAL document's structure (root model, uuid, metadata, oscal-version, control-implementation)."""
157
+ roots = {"system-security-plan": "ssp", "component-definition": "component-definition",
158
+ "assessment-plan": "assessment-plan", "plan-of-action-and-milestones": "poam", "catalog": "catalog", "profile": "profile"}
159
+ root = next((k for k in roots if k in document), None)
160
+ if root is None:
161
+ return Validation(valid=False, errors=[f"No OSCAL root model found (expected one of {list(roots)})."])
162
+ body = document[root]
163
+ errors, warnings = [], []
164
+ if not body.get("uuid"):
165
+ errors.append(f"{root}: missing uuid")
166
+ md = body.get("metadata", {})
167
+ if not md:
168
+ errors.append(f"{root}: missing metadata")
169
+ else:
170
+ if md.get("oscal-version") != OSCAL_VERSION:
171
+ warnings.append(f"oscal-version is '{md.get('oscal-version')}', expected {OSCAL_VERSION}")
172
+ for req in ("title", "last-modified", "version"):
173
+ if not md.get(req):
174
+ errors.append(f"metadata: missing {req}")
175
+ if root == "system-security-plan":
176
+ if not body.get("control-implementation", {}).get("implemented-requirements"):
177
+ errors.append("ssp: no implemented-requirements")
178
+ if not body.get("system-characteristics"):
179
+ errors.append("ssp: missing system-characteristics")
180
+ return Validation(valid=not errors, model=roots[root], errors=errors, warnings=warnings)
181
+
182
+
183
+ # ── Strict validation: stand on the standard OSCAL toolchain (compliance-trestle) ──
184
+ # Our own validate_oscal is the fast, dependency-free path. validate_oscal_strict
185
+ # delegates to oscal-compass/compliance-trestle's NIST-schema-derived pydantic models
186
+ # — the authoritative community validator — so a package that passes here is
187
+ # "validates under the standard OSCAL toolchain," not just our own checks.
188
+ _TRESTLE_ROOTS = {
189
+ "system-security-plan": ("trestle.oscal.ssp", "SystemSecurityPlan"),
190
+ "component-definition": ("trestle.oscal.component", "ComponentDefinition"),
191
+ "catalog": ("trestle.oscal.catalog", "Catalog"),
192
+ "profile": ("trestle.oscal.profile", "Profile"),
193
+ "assessment-plan": ("trestle.oscal.assessment_plan", "AssessmentPlan"),
194
+ "plan-of-action-and-milestones": ("trestle.oscal.poam", "PlanOfActionAndMilestones"),
195
+ }
196
+
197
+
198
+ class StrictValidation(BaseModel):
199
+ valid: bool
200
+ validator: str # "compliance-trestle" | "builtin-fallback"
201
+ model: Optional[str] = None
202
+ trestle_available: bool = False
203
+ errors: List[str] = Field(default_factory=list)
204
+ note: str = ""
205
+
206
+
207
+ @mcp.tool()
208
+ def validate_oscal_strict(document: Dict[str, Any]) -> StrictValidation:
209
+ """Strictly validate an OSCAL document against the standard community toolchain
210
+ (oscal-compass/compliance-trestle's NIST-schema-derived models). If trestle isn't
211
+ installed (pip install 'oscal-generator-mcp[validate]'), this gracefully falls back
212
+ to the built-in structural validator and says so. A pass here = "validates under the
213
+ standard OSCAL toolchain" — the credibility claim for the FedRAMP RFC-0024 wedge."""
214
+ root = next((k for k in _TRESTLE_ROOTS if k in document), None)
215
+ if root is None:
216
+ return StrictValidation(valid=False, validator="builtin-fallback",
217
+ errors=[f"No OSCAL root model found (expected one of {list(_TRESTLE_ROOTS)})."])
218
+ mod_name, cls_name = _TRESTLE_ROOTS[root]
219
+ try:
220
+ import importlib
221
+ cls = getattr(importlib.import_module(mod_name), cls_name)
222
+ except Exception:
223
+ fb = validate_oscal(document) # graceful fallback — never hard-fail on a missing optional dep
224
+ return StrictValidation(valid=fb.valid, validator="builtin-fallback", model=fb.model,
225
+ trestle_available=False, errors=fb.errors,
226
+ note="compliance-trestle not installed; used the built-in structural validator. "
227
+ "Install with: pip install 'oscal-generator-mcp[validate]' for NIST-grade validation.")
228
+ body = document[root]
229
+ try:
230
+ if hasattr(cls, "model_validate"): # pydantic v2 (trestle 4.x)
231
+ cls.model_validate(body)
232
+ else: # pydantic v1 fallback
233
+ cls.parse_obj(body)
234
+ return StrictValidation(valid=True, validator="compliance-trestle", model=root,
235
+ trestle_available=True,
236
+ note=f"Validated against compliance-trestle's {cls_name} model (NIST OSCAL {OSCAL_VERSION}).")
237
+ except Exception as e:
238
+ msgs = [ln for ln in str(e).splitlines() if ln.strip()][:25]
239
+ return StrictValidation(valid=False, validator="compliance-trestle", model=root,
240
+ trestle_available=True, errors=msgs,
241
+ note=f"Failed compliance-trestle {cls_name} validation.")
242
+
243
+
244
+ # Honest, approximate framework → representative NIST 800-53 control crosswalk.
245
+ FRAMEWORK_CONTROLS = {
246
+ "gdpr": ["AC-3", "AU-2", "SI-12"], "hipaa": ["AC-3", "AU-2", "SC-13"],
247
+ "pci": ["SC-13", "AC-3", "AU-6"], "dora": ["CP-2", "IR-4", "RA-5"],
248
+ "nis2": ["IR-4", "RA-5", "SI-4"], "sox": ["AC-2", "AU-6", "CM-2"],
249
+ "iec 62443": ["AC-3", "SC-7", "SI-4"], "eu ai act": ["RA-3", "CA-2", "SI-4"],
250
+ "ofac": ["AC-3", "AU-6"], "aml": ["AU-6", "RA-3"], "mifid": ["AU-6", "CM-2"],
251
+ "solvency": ["RA-3", "CA-2"], "ecoa": ["AC-3", "SI-12"], "iso 62056": ["AC-3", "SC-7"],
252
+ "stir/shaken": ["IA-2", "SC-8"], "nist": ["CA-2", "RA-3", "SI-4"], "iso 42001": ["CA-2", "PM-9"],
253
+ }
254
+
255
+
256
+ def _controls_for(frameworks: List[str]) -> List[str]:
257
+ out = []
258
+ for fw in frameworks:
259
+ f = fw.lower()
260
+ for key, ctrls in FRAMEWORK_CONTROLS.items():
261
+ if key in f:
262
+ out.extend(ctrls)
263
+ return sorted(set(out)) or ["CA-2"]
264
+
265
+
266
+ class SignedPackage(BaseModel):
267
+ protocol: str
268
+ document: Dict[str, Any]
269
+ component_count: int
270
+ signature: str
271
+ public_key: str
272
+ canonical_sha256: str
273
+ sigil: str = ""
274
+
275
+
276
+ @mcp.tool()
277
+ def generate_protocol_package(protocol_name: str, components: List[Dict[str, Any]], ts: int = 0) -> SignedPackage:
278
+ """Generate ONE Ed25519-signed OSCAL Component Definition describing an entire protocol — every component
279
+ (e.g. each Layer-0 bridge/MCP) mapped to its frameworks' NIST controls. Makes the whole protocol a
280
+ machine-readable, signed, offline-verifiable compliance package. components: [{name, type?, frameworks[]}]."""
281
+ uid = _uuid5(protocol_name, "protocol")
282
+ comp_objs = []
283
+ for c in components:
284
+ name = c.get("name", "component")
285
+ ctrls = _controls_for(c.get("frameworks", []))
286
+ comp_objs.append({
287
+ "uuid": _uuid5(protocol_name, name), "type": c.get("type", "software"), "title": name,
288
+ "description": f"{name} — CSOAI Layer-0 component governing: {', '.join(c.get('frameworks', [])) or 'baseline'}.",
289
+ "props": [{"name": "frameworks", "value": ", ".join(c.get("frameworks", []))}],
290
+ "control-implementations": [{
291
+ "uuid": _uuid5(protocol_name, name, "ci"), "source": "#nist-800-53",
292
+ "description": f"Controls satisfied by {name}.",
293
+ "implemented-requirements": [
294
+ {"uuid": _uuid5(protocol_name, name, ct), "control-id": ct.lower(),
295
+ "description": f"{ct} satisfied + attested by {name}."} for ct in ctrls]}]})
296
+ doc = {"component-definition": {
297
+ "uuid": uid,
298
+ "metadata": _metadata(f"{protocol_name} — Layer-0 OSCAL Protocol Package", ts),
299
+ "components": comp_objs,
300
+ }}
301
+ canon = _canon(doc)
302
+ sk = _signing_key()
303
+ pub = sk.public_key().public_bytes(_ser.Encoding.Raw, _ser.PublicFormat.Raw)
304
+ return SignedPackage(protocol=protocol_name, document=doc, component_count=len(comp_objs),
305
+ signature=sk.sign(canon).hex(), public_key=pub.hex(),
306
+ canonical_sha256=_hl.sha256(canon).hexdigest(),
307
+ sigil=_sigil("PROTO", f"layer0|{protocol_name}|{len(comp_objs)}"))
308
+
309
+
310
+ class Signature(BaseModel):
311
+ algorithm: str = "Ed25519"
312
+ signature: str
313
+ public_key: str
314
+ canonical_sha256: str
315
+ sigil: str = ""
316
+
317
+
318
+ @mcp.tool()
319
+ def sign_oscal(document: Dict[str, Any]) -> Signature:
320
+ """Ed25519-sign an OSCAL document (canonical JSON) → a cryptographically signed, offline-verifiable package. Satisfies the RFC-0024 signed-package requirement; same scheme as the CSOAI Compliance Passport."""
321
+ canon = _canon(document)
322
+ sk = _signing_key()
323
+ sig = sk.sign(canon)
324
+ pub = sk.public_key().public_bytes(_ser.Encoding.Raw, _ser.PublicFormat.Raw)
325
+ return Signature(signature=sig.hex(), public_key=pub.hex(),
326
+ canonical_sha256=_hl.sha256(canon).hexdigest(),
327
+ sigil=_sigil("SIGN", "oscal-ed25519"))
328
+
329
+
330
+ @mcp.tool()
331
+ def verify_oscal_signature(document: Dict[str, Any], signature: str, public_key: str) -> Dict[str, Any]:
332
+ """Verify an Ed25519 signature over an OSCAL document — offline, no account. Returns valid True/False."""
333
+ try:
334
+ Ed25519PublicKey.from_public_bytes(bytes.fromhex(public_key)).verify(bytes.fromhex(signature), _canon(document))
335
+ return {"valid": True, "algorithm": "Ed25519"}
336
+ except Exception as e:
337
+ return {"valid": False, "reason": str(e)}
338
+
339
+
340
+ @mcp.tool()
341
+ def rfc0024_readiness(has_ssp: bool = False, has_component_def: bool = False, machine_readable: bool = False, signed: bool = False, automated_pipeline: bool = False) -> Readiness:
342
+ """Score readiness for FedRAMP RFC-0024 (machine-readable OSCAL packages, first deadline 30 Sep 2026)."""
343
+ checks = {
344
+ "machine-readable OSCAL package": machine_readable,
345
+ "System Security Plan (SSP) in OSCAL": has_ssp,
346
+ "Component Definition in OSCAL": has_component_def,
347
+ "cryptographically signed package": signed,
348
+ "automated generation pipeline (not hand-authored)": automated_pipeline,
349
+ }
350
+ passed = sum(1 for v in checks.values() if v)
351
+ score = int(100 * passed / len(checks))
352
+ gaps = [f"Missing: {k}" for k, v in checks.items() if not v]
353
+ return Readiness(ready=passed == len(checks), score=score, gaps=gaps,
354
+ note="RFC-0024 requires machine-readable packages by 30 Sep 2026; ~0 of 100+ 2025 authorizations produced OSCAL — generating + signing it now is a first-mover wedge.")
355
+
356
+
357
+ def main():
358
+ mcp.run()
359
+
360
+
361
+ if __name__ == "__main__":
362
+ main()
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,100 @@
1
+ """Tests for the OSCAL Generator MCP — valid OSCAL out, validation, RFC-0024 readiness."""
2
+ import sys, importlib.util
3
+ from pathlib import Path
4
+
5
+ _spec = importlib.util.spec_from_file_location("oscal_server", Path(__file__).resolve().parents[1] / "server.py")
6
+ srv = importlib.util.module_from_spec(_spec); _spec.loader.exec_module(srv)
7
+
8
+
9
+ def test_generate_ssp_is_valid_oscal():
10
+ doc = srv.generate_ssp("Payments Core", impact_level="high", ts=0)
11
+ assert doc.model == "ssp" and doc.uuid
12
+ assert doc.sigil # signed
13
+ ssp = doc.document["system-security-plan"]
14
+ assert ssp["system-characteristics"]["security-sensitivity-level"] == "high"
15
+ assert len(ssp["control-implementation"]["implemented-requirements"]) >= 5
16
+ # round-trips through the validator
17
+ v = srv.validate_oscal(doc.document)
18
+ assert v.valid is True and v.model == "ssp"
19
+
20
+
21
+ def test_generate_ssp_deterministic():
22
+ a = srv.generate_ssp("Same System", ts=0)
23
+ b = srv.generate_ssp("Same System", ts=0)
24
+ assert a.document == b.document # uuid5 + fixed ts → reproducible packages
25
+
26
+
27
+ def test_component_definition_valid():
28
+ doc = srv.generate_component_definition("cobol-bridge-mcp", control_ids=["AC-2", "SC-7"], ts=0)
29
+ assert doc.model == "component-definition"
30
+ v = srv.validate_oscal(doc.document)
31
+ assert v.valid is True
32
+
33
+
34
+ def test_validate_rejects_non_oscal():
35
+ v = srv.validate_oscal({"not": "oscal"})
36
+ assert v.valid is False and v.errors
37
+
38
+
39
+ def test_validate_flags_missing_metadata():
40
+ v = srv.validate_oscal({"system-security-plan": {"uuid": "x"}})
41
+ assert v.valid is False
42
+ assert any("metadata" in e for e in v.errors)
43
+
44
+
45
+ def test_sign_oscal_then_verify_roundtrip():
46
+ doc = srv.generate_ssp("Signed System", ts=0).document
47
+ sig = srv.sign_oscal(doc)
48
+ assert sig.algorithm == "Ed25519" and sig.signature and sig.public_key
49
+ v = srv.verify_oscal_signature(doc, sig.signature, sig.public_key)
50
+ assert v["valid"] is True
51
+
52
+
53
+ def test_verify_rejects_tampered_document():
54
+ doc = srv.generate_ssp("Tamper Test", ts=0).document
55
+ sig = srv.sign_oscal(doc)
56
+ doc["system-security-plan"]["metadata"]["title"] = "TAMPERED"
57
+ v = srv.verify_oscal_signature(doc, sig.signature, sig.public_key)
58
+ assert v["valid"] is False
59
+
60
+
61
+ def test_verify_rejects_tampered_signature():
62
+ doc = srv.generate_ssp("Sig Tamper", ts=0).document
63
+ sig = srv.sign_oscal(doc)
64
+ bad = ("0" if sig.signature[0] != "0" else "1") + sig.signature[1:]
65
+ v = srv.verify_oscal_signature(doc, bad, sig.public_key)
66
+ assert v["valid"] is False
67
+
68
+
69
+ def test_signature_is_deterministic_for_same_doc():
70
+ doc = srv.generate_ssp("Det Sig", ts=0).document
71
+ assert srv.sign_oscal(doc).signature == srv.sign_oscal(doc).signature # Ed25519 deterministic
72
+
73
+
74
+ def test_rfc0024_readiness_scoring():
75
+ none = srv.rfc0024_readiness()
76
+ assert none.ready is False and none.score == 0 and len(none.gaps) == 5
77
+ full = srv.rfc0024_readiness(has_ssp=True, has_component_def=True, machine_readable=True, signed=True, automated_pipeline=True)
78
+ assert full.ready is True and full.score == 100 and not full.gaps
79
+ partial = srv.rfc0024_readiness(has_ssp=True, machine_readable=True)
80
+ assert 0 < partial.score < 100
81
+
82
+
83
+ def test_protocol_package_signs_whole_layer0():
84
+ comps = [{"name": "cobol-bridge-mcp", "frameworks": ["SOX", "DORA"]},
85
+ {"name": "hl7-fhir-bridge-mcp", "frameworks": ["HIPAA", "GDPR"]},
86
+ {"name": "scada-bridge-mcp", "frameworks": ["IEC 62443", "NIS2"]}]
87
+ pkg = srv.generate_protocol_package("Test Protocol", comps, ts=0)
88
+ assert pkg.component_count == 3
89
+ assert pkg.signature and pkg.public_key
90
+ # the whole package verifies, and each component carries mapped controls
91
+ assert srv.verify_oscal_signature(pkg.document, pkg.signature, pkg.public_key)["valid"] is True
92
+ cdef = pkg.document["component-definition"]
93
+ assert len(cdef["components"]) == 3
94
+ assert cdef["components"][0]["control-implementations"][0]["implemented-requirements"]
95
+
96
+
97
+ def test_protocol_package_detects_tamper():
98
+ pkg = srv.generate_protocol_package("Tamper P", [{"name": "x", "frameworks": ["GDPR"]}], ts=0)
99
+ pkg.document["component-definition"]["components"][0]["title"] = "EVIL"
100
+ assert srv.verify_oscal_signature(pkg.document, pkg.signature, pkg.public_key)["valid"] is False