mrpd 0.1.0__py3-none-any.whl

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.
Files changed (51) hide show
  1. mrpd/__init__.py +1 -0
  2. mrpd/adapters/__init__.py +1 -0
  3. mrpd/api/app.py +8 -0
  4. mrpd/api/routes.py +200 -0
  5. mrpd/cli.py +182 -0
  6. mrpd/commands/__init__.py +1 -0
  7. mrpd/commands/bridge_mcp.py +189 -0
  8. mrpd/commands/bridge_openapi.py +279 -0
  9. mrpd/commands/init_provider.py +176 -0
  10. mrpd/commands/mrpify_mcp.py +80 -0
  11. mrpd/commands/mrpify_openapi.py +80 -0
  12. mrpd/commands/publish.py +102 -0
  13. mrpd/commands/route.py +126 -0
  14. mrpd/commands/run.py +169 -0
  15. mrpd/commands/serve.py +13 -0
  16. mrpd/commands/validate.py +71 -0
  17. mrpd/core/__init__.py +1 -0
  18. mrpd/core/artifacts.py +39 -0
  19. mrpd/core/config.py +26 -0
  20. mrpd/core/defaults.py +7 -0
  21. mrpd/core/envelopes.py +29 -0
  22. mrpd/core/errors.py +37 -0
  23. mrpd/core/evidence.py +17 -0
  24. mrpd/core/models.py +37 -0
  25. mrpd/core/provider.py +100 -0
  26. mrpd/core/registry.py +115 -0
  27. mrpd/core/schema.py +64 -0
  28. mrpd/core/scoring.py +92 -0
  29. mrpd/core/util.py +27 -0
  30. mrpd/spec/__init__.py +1 -0
  31. mrpd/spec/fixtures/README.md +8 -0
  32. mrpd/spec/fixtures/invalid/bad_msg_type.json +8 -0
  33. mrpd/spec/fixtures/invalid/missing_required_fields.json +7 -0
  34. mrpd/spec/fixtures/valid/discover.json +13 -0
  35. mrpd/spec/fixtures/valid/error.json +13 -0
  36. mrpd/spec/fixtures/valid/offer.json +20 -0
  37. mrpd/spec/schemas/envelope.schema.json +102 -0
  38. mrpd/spec/schemas/manifest.schema.json +52 -0
  39. mrpd/spec/schemas/payloads/discover.schema.json +23 -0
  40. mrpd/spec/schemas/payloads/error.schema.json +15 -0
  41. mrpd/spec/schemas/payloads/evidence.schema.json +17 -0
  42. mrpd/spec/schemas/payloads/execute.schema.json +14 -0
  43. mrpd/spec/schemas/payloads/negotiate.schema.json +15 -0
  44. mrpd/spec/schemas/payloads/offer.schema.json +30 -0
  45. mrpd/spec/schemas/types/artifact.schema.json +15 -0
  46. mrpd/spec/schemas/types/input.schema.json +20 -0
  47. mrpd-0.1.0.dist-info/METADATA +104 -0
  48. mrpd-0.1.0.dist-info/RECORD +51 -0
  49. mrpd-0.1.0.dist-info/WHEEL +5 -0
  50. mrpd-0.1.0.dist-info/entry_points.txt +2 -0
  51. mrpd-0.1.0.dist-info/top_level.txt +1 -0
mrpd/core/provider.py ADDED
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from mrpd.core.artifacts import store_bytes
8
+ from mrpd.core.util import approx_tokens, strip_html, utc_now_rfc3339
9
+
10
+
11
+ SERVICE_ID = "service:mrpd"
12
+
13
+
14
+ def provider_manifest(base_url: str) -> dict[str, Any]:
15
+ # base_url should be full origin, e.g. http://127.0.0.1:8787
16
+ return {
17
+ "capability_id": "capability:mrp/summarize_url",
18
+ "capability": "summarize_url",
19
+ "version": "0.1",
20
+ "tags": ["mrp", "summarize", "web"],
21
+ "inputs": [{"type": "url"}],
22
+ "outputs": [{"type": "markdown"}, {"type": "artifact"}],
23
+ "constraints": {"policy": ["no_pii"]},
24
+ "cost": {"unit": "usd", "estimate": 0.0},
25
+ "latency": {"p50": "200ms"},
26
+ "proofs_required": [],
27
+ "endpoints": {
28
+ "discover": f"{base_url}/mrp/discover",
29
+ "negotiate": f"{base_url}/mrp/negotiate",
30
+ "execute": f"{base_url}/mrp/execute",
31
+ },
32
+ }
33
+
34
+
35
+ def offers_for_discover(_discover_payload: dict[str, Any]) -> list[dict[str, Any]]:
36
+ return [
37
+ {
38
+ "route_id": "route:mrpd/summarize_url@0.1",
39
+ "capability": "summarize_url",
40
+ "confidence": 0.9,
41
+ "cost": {"unit": "usd", "estimate": 0.0},
42
+ "latency": {"p50": "200ms"},
43
+ "proofs": [],
44
+ "policy": ["no_pii"],
45
+ "risk": {"data_retention_days": 0, "training_use": "none", "subprocessors": []},
46
+ "endpoint": "/mrp/execute",
47
+ }
48
+ ]
49
+
50
+
51
+ async def execute_summarize_url(inputs: list[dict[str, Any]]) -> dict[str, Any]:
52
+ url = None
53
+ for item in inputs:
54
+ if item.get("type") == "url":
55
+ url = item.get("value")
56
+ break
57
+ if not url:
58
+ raise ValueError("missing url input")
59
+
60
+ async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
61
+ r = await client.get(url, headers={"User-Agent": "mrpd/0.1"})
62
+ r.raise_for_status()
63
+ ct = r.headers.get("content-type", "")
64
+ raw_bytes = r.content
65
+
66
+ text = ""
67
+ if "text/html" in ct:
68
+ text = strip_html(raw_bytes.decode("utf-8", "replace"))
69
+ else:
70
+ # best effort
71
+ text = raw_bytes.decode("utf-8", "replace")
72
+
73
+ # store artifact of extracted text
74
+ artifact = store_bytes(text.encode("utf-8"), mime="text/plain", suffix=".txt")
75
+
76
+ # crude summary: first ~1200 chars
77
+ summary = text[:1200]
78
+ if len(text) > 1200:
79
+ summary += "\n\n…"
80
+
81
+ tokens_in = approx_tokens(text)
82
+ tokens_out = approx_tokens(summary)
83
+
84
+ return {
85
+ "outputs": [
86
+ {"type": "markdown", "value": f"## Summary\n\n{summary}\n"},
87
+ artifact,
88
+ ],
89
+ "provenance": {
90
+ "citations": [url],
91
+ "timestamp": utc_now_rfc3339(),
92
+ "source_hashes": [artifact["hash"]],
93
+ },
94
+ "usage": {
95
+ "tokens_in_est": tokens_in,
96
+ "tokens_out_est": tokens_out,
97
+ "bytes_in": len(raw_bytes),
98
+ "bytes_text": len(text.encode("utf-8")),
99
+ },
100
+ }
mrpd/core/registry.py ADDED
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from typing import Optional
6
+ from urllib.parse import urlparse
7
+
8
+ import httpx
9
+
10
+ from mrpd.core.defaults import MRP_BOOTSTRAP_REGISTRY_RAW, MRP_DEFAULT_REGISTRY_BASE
11
+ from mrpd.core.models import RegistryEntry, RegistryQueryResponse
12
+
13
+
14
+ def normalize_manifest_endpoints(manifest: dict, manifest_url: str) -> dict:
15
+ """Ensure manifest.endpoints contain absolute URLs.
16
+
17
+ If endpoints are relative ("/mrp/execute"), prefix with the origin of manifest_url.
18
+ """
19
+
20
+ out = dict(manifest)
21
+ endpoints = dict(out.get("endpoints") or {})
22
+
23
+ parsed = urlparse(manifest_url)
24
+ origin = f"{parsed.scheme}://{parsed.netloc}" if parsed.scheme and parsed.netloc else ""
25
+
26
+ for k, v in list(endpoints.items()):
27
+ if isinstance(v, str) and v.startswith("/") and origin:
28
+ endpoints[k] = origin + v
29
+
30
+ out["endpoints"] = endpoints
31
+ return out
32
+
33
+
34
+ class RegistryClient:
35
+ def __init__(self, base_url: str = MRP_DEFAULT_REGISTRY_BASE, timeout: float = 10.0) -> None:
36
+ self.base_url = base_url.rstrip("/")
37
+ self.timeout = timeout
38
+
39
+ async def query(
40
+ self,
41
+ *,
42
+ capability: Optional[str] = None,
43
+ policy: Optional[str] = None,
44
+ limit: int = 50,
45
+ cursor: Optional[str] = None,
46
+ ) -> RegistryQueryResponse:
47
+ """Query the MRP registry API. Falls back to raw GitHub JSON if API is unavailable."""
48
+
49
+ url = f"{self.base_url}/mrp/registry/query"
50
+ params = {"limit": str(limit)}
51
+ if capability:
52
+ params["capability"] = capability
53
+ if policy:
54
+ params["policy"] = policy
55
+ if cursor:
56
+ params["cursor"] = cursor
57
+
58
+ try:
59
+ async with httpx.AsyncClient(timeout=self.timeout, follow_redirects=False) as client:
60
+ r = await client.get(url, params=params, headers={"Accept": "application/json"})
61
+ r.raise_for_status()
62
+ return RegistryQueryResponse.model_validate(r.json())
63
+ except Exception:
64
+ entries = await self._fetch_raw_entries()
65
+ if capability:
66
+ entries = [e for e in entries if capability in e.capabilities]
67
+ if policy:
68
+ entries = [e for e in entries if policy in e.policies]
69
+ return RegistryQueryResponse(results=entries)
70
+
71
+ async def _fetch_raw_entries(self) -> list[RegistryEntry]:
72
+ raw_url = os.getenv("MRP_BOOTSTRAP_REGISTRY_RAW") or MRP_BOOTSTRAP_REGISTRY_RAW
73
+ if not raw_url:
74
+ # No bootstrap configured; callers should rely on the hosted registry API.
75
+ return []
76
+
77
+ if raw_url.startswith("file://"):
78
+ path = raw_url[len("file://") :]
79
+ # Windows file URLs sometimes come through as /C:/...
80
+ if len(path) >= 3 and path[0] == "/" and path[2] == ":":
81
+ path = path[1:]
82
+ payload = json.loads(open(path, "r", encoding="utf-8").read())
83
+ else:
84
+ async with httpx.AsyncClient(timeout=self.timeout, follow_redirects=False) as client:
85
+ r = await client.get(raw_url, headers={"Accept": "application/json"})
86
+ r.raise_for_status()
87
+ payload = r.json()
88
+
89
+ if not isinstance(payload, list):
90
+ raise ValueError("bootstrap registry did not return a list")
91
+
92
+ entries: list[RegistryEntry] = []
93
+ for item in payload:
94
+ if not isinstance(item, dict) or not item.get("manifest_url"):
95
+ continue
96
+ entries.append(RegistryEntry.model_validate(item))
97
+
98
+ dedup: dict[str, RegistryEntry] = {}
99
+ for e in entries:
100
+ if e.id not in dedup:
101
+ dedup[e.id] = e
102
+ return list(dedup.values())
103
+
104
+
105
+ async def fetch_manifest(manifest_url: str, timeout: float = 10.0) -> dict:
106
+ if manifest_url.startswith("file://"):
107
+ path = manifest_url[len("file://") :]
108
+ if len(path) >= 3 and path[0] == "/" and path[2] == ":":
109
+ path = path[1:]
110
+ return json.loads(open(path, "r", encoding="utf-8").read())
111
+
112
+ async with httpx.AsyncClient(timeout=timeout, follow_redirects=False) as client:
113
+ r = await client.get(manifest_url, headers={"Accept": "application/mrp-manifest+json, application/json"})
114
+ r.raise_for_status()
115
+ return r.json()
mrpd/core/schema.py ADDED
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from importlib import resources
5
+ from typing import Any
6
+
7
+ import jsonschema
8
+
9
+
10
+ def load_schema_text(rel_path: str) -> str:
11
+ """Load a schema file shipped inside mrpd (package data)."""
12
+ # rel_path like: "schemas/envelope.schema.json"
13
+ return resources.files("mrpd.spec").joinpath(rel_path).read_text(encoding="utf-8")
14
+
15
+
16
+ def load_schema(rel_path: str) -> dict[str, Any]:
17
+ return json.loads(load_schema_text(rel_path))
18
+
19
+
20
+ def validate_envelope(envelope: dict[str, Any]) -> None:
21
+ schema = load_schema("schemas/envelope.schema.json")
22
+
23
+ # Resolve local $refs inside the shipped schema folder.
24
+ base = resources.files("mrpd.spec").joinpath("schemas")
25
+
26
+ def _load_by_rel(rel: str) -> dict[str, Any]:
27
+ p = rel
28
+ if p.startswith("./"):
29
+ p = p[2:]
30
+ text = base.joinpath(p).read_text(encoding="utf-8")
31
+ return json.loads(text)
32
+
33
+ def _https_handler(uri: str) -> Any:
34
+ """Map https://moltrouter.dev/schemas/mrp/0.1/... to bundled files.
35
+
36
+ jsonschema will resolve relative refs against the envelope $id (https://moltrouter.dev/...).
37
+ Without this handler, it tries to fetch over the network.
38
+ """
39
+
40
+ marker = "/schemas/mrp/0.1/"
41
+ if marker in uri:
42
+ rel = uri.split(marker, 1)[1]
43
+ return _load_by_rel(rel)
44
+ # Back-compat for older ids that used moltrouter.dev without the /schemas/mrp/0.1 prefix
45
+ uri2 = uri.replace("https://moltrouter.dev/", "https://www.moltrouter.dev/")
46
+ if marker in uri2:
47
+ rel = uri2.split(marker, 1)[1]
48
+ return _load_by_rel(rel)
49
+ # Fallback: try to treat the URL path as a relative file
50
+ # e.g. https://example/x/y.json -> y.json
51
+ rel = uri.rsplit("/", 1)[-1]
52
+ return _load_by_rel(rel)
53
+
54
+ # Handlers are selected by URI scheme.
55
+ resolver = jsonschema.RefResolver.from_schema(
56
+ schema,
57
+ handlers={
58
+ "": _load_by_rel, # relative refs
59
+ "https": _https_handler,
60
+ "http": _https_handler,
61
+ },
62
+ )
63
+
64
+ jsonschema.validate(instance=envelope, schema=schema, resolver=resolver)
mrpd/core/scoring.py ADDED
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Iterable
5
+
6
+ from mrpd.core.models import RegistryEntry
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class ScoreResult:
11
+ entry: RegistryEntry
12
+ score: float
13
+ required_matches: int
14
+ trust_score: float
15
+ proofs_count: int
16
+ reasons: tuple[str, ...]
17
+ missing: tuple[str, ...]
18
+
19
+ @property
20
+ def satisfied(self) -> bool:
21
+ return len(self.missing) == 0
22
+
23
+ def rank_key(self) -> tuple:
24
+ # Deterministic ordering: score, required matches, trust, proofs, name, id.
25
+ return (
26
+ -self.score,
27
+ -self.required_matches,
28
+ -self.trust_score,
29
+ -self.proofs_count,
30
+ (self.entry.name or "").lower(),
31
+ self.entry.id,
32
+ )
33
+
34
+
35
+ def score_entry(entry: RegistryEntry, *, capability: str | None, policy: str | None) -> ScoreResult:
36
+ """Deterministic scoring with explicit tie-breakers.
37
+
38
+ Score weights are simple and predictable; ties break by required matches,
39
+ trust score, proof count, then stable name/id ordering.
40
+ """
41
+
42
+ score = 0.0
43
+ reasons: list[str] = []
44
+ missing: list[str] = []
45
+ required_matches = 0
46
+
47
+ if capability:
48
+ if capability in entry.capabilities:
49
+ score += 50.0
50
+ required_matches += 1
51
+ reasons.append(f"capability match: {capability}")
52
+ else:
53
+ missing.append(f"capability:{capability}")
54
+
55
+ if policy:
56
+ if policy in entry.policies:
57
+ score += 20.0
58
+ required_matches += 1
59
+ reasons.append(f"policy match: {policy}")
60
+ else:
61
+ missing.append(f"policy:{policy}")
62
+
63
+ trust_score = 0.0
64
+ if entry.trust and entry.trust.score is not None:
65
+ trust_score = float(entry.trust.score)
66
+ score += 10.0 * trust_score
67
+ reasons.append(f"trust score: {trust_score:.2f}")
68
+
69
+ proofs_count = len(entry.proofs)
70
+ if proofs_count:
71
+ reasons.append(f"proofs: {proofs_count}")
72
+
73
+ if capability and policy and (capability in entry.capabilities) and (policy in entry.policies):
74
+ score += 5.0
75
+ reasons.append("capability+policy bonus")
76
+
77
+ return ScoreResult(
78
+ entry=entry,
79
+ score=score,
80
+ required_matches=required_matches,
81
+ trust_score=trust_score,
82
+ proofs_count=proofs_count,
83
+ reasons=tuple(reasons),
84
+ missing=tuple(missing),
85
+ )
86
+
87
+
88
+ def rank_entries(
89
+ entries: Iterable[RegistryEntry], *, capability: str | None, policy: str | None
90
+ ) -> list[ScoreResult]:
91
+ scored = [score_entry(e, capability=capability, policy=policy) for e in entries]
92
+ return sorted(scored, key=lambda r: r.rank_key())
mrpd/core/util.py ADDED
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import re
5
+ from datetime import datetime, timezone
6
+
7
+
8
+ def utc_now_rfc3339() -> str:
9
+ return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
10
+
11
+
12
+ def sha256_hex(data: bytes) -> str:
13
+ return hashlib.sha256(data).hexdigest()
14
+
15
+
16
+ def approx_tokens(text: str) -> int:
17
+ # rough heuristic: ~4 chars/token for English-ish text
18
+ return max(1, len(text) // 4)
19
+
20
+
21
+ def strip_html(html: str) -> str:
22
+ # extremely simple tag stripper for v0 (good enough for demo)
23
+ html = re.sub(r"<script[\s\S]*?</script>", " ", html, flags=re.IGNORECASE)
24
+ html = re.sub(r"<style[\s\S]*?</style>", " ", html, flags=re.IGNORECASE)
25
+ text = re.sub(r"<[^>]+>", " ", html)
26
+ text = re.sub(r"\s+", " ", text).strip()
27
+ return text
mrpd/spec/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __all__ = []
@@ -0,0 +1,8 @@
1
+ # MRP Fixtures
2
+
3
+ These fixtures are intended for interoperability testing.
4
+
5
+ - `valid/` should validate against `schemas/envelope.schema.json`
6
+ - `invalid/` should fail validation
7
+
8
+ Use them to build a conformance test suite in any language.
@@ -0,0 +1,8 @@
1
+ {
2
+ "mrp_version": "0.1",
3
+ "msg_id": "00000000-0000-0000-0000-000000000004",
4
+ "msg_type": "NOT_A_REAL_TYPE",
5
+ "timestamp": "2025-01-01T00:00:00Z",
6
+ "sender": {"id": "agent:moltbots/alpha"},
7
+ "payload": {}
8
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "mrp_version": "0.1",
3
+ "msg_type": "DISCOVER",
4
+ "timestamp": "2025-01-01T00:00:00Z",
5
+ "sender": {"id": "agent:moltbots/alpha"},
6
+ "payload": {"intent": "x"}
7
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "mrp_version": "0.1",
3
+ "msg_id": "00000000-0000-0000-0000-000000000001",
4
+ "msg_type": "DISCOVER",
5
+ "timestamp": "2025-01-01T00:00:00Z",
6
+ "sender": {"id": "agent:moltbots/alpha"},
7
+ "payload": {
8
+ "intent": "extract pricing from vendor docs",
9
+ "inputs": [{"type": "url", "value": "https://example.com/pricing"}],
10
+ "constraints": {"max_cost": 0.05, "policy": ["no_pii"]},
11
+ "proofs_required": ["attestation"]
12
+ }
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "mrp_version": "0.1",
3
+ "msg_id": "00000000-0000-0000-0000-000000000003",
4
+ "msg_type": "ERROR",
5
+ "timestamp": "2025-01-01T00:00:02Z",
6
+ "sender": {"id": "service:clawdbots/summarize"},
7
+ "payload": {
8
+ "code": "MRP_RATE_LIMITED",
9
+ "message": "Rate limit exceeded",
10
+ "retryable": true,
11
+ "retry_after_ms": 5000
12
+ }
13
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "mrp_version": "0.1",
3
+ "msg_id": "00000000-0000-0000-0000-000000000002",
4
+ "msg_type": "OFFER",
5
+ "timestamp": "2025-01-01T00:00:01Z",
6
+ "sender": {"id": "service:clawdbots/summarize"},
7
+ "receiver": {"id": "agent:moltbots/alpha"},
8
+ "payload": {
9
+ "offers": [
10
+ {
11
+ "route_id": "route-123",
12
+ "capability": "summarize",
13
+ "confidence": 0.9,
14
+ "policy": ["no_pii"],
15
+ "risk": {"data_retention_days": 0, "training_use": "none", "subprocessors": []},
16
+ "endpoint": "/mrp/negotiate"
17
+ }
18
+ ]
19
+ }
20
+ }
@@ -0,0 +1,102 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://www.moltrouter.dev/schemas/mrp/0.1/envelope.schema.json",
4
+ "title": "MRP Envelope (0.1)",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["mrp_version", "msg_id", "msg_type", "timestamp", "sender", "payload"],
8
+ "properties": {
9
+ "mrp_version": {"type": "string", "pattern": "^[0-9]+\\.[0-9]+$"},
10
+ "msg_id": {"type": "string", "minLength": 8},
11
+ "msg_type": {
12
+ "type": "string",
13
+ "enum": [
14
+ "HELLO",
15
+ "DISCOVER",
16
+ "OFFER",
17
+ "NEGOTIATE",
18
+ "EXECUTE",
19
+ "EVIDENCE",
20
+ "JOB_ACCEPTED",
21
+ "JOB_STATUS",
22
+ "STREAM_CHUNK",
23
+ "ERROR"
24
+ ]
25
+ },
26
+ "timestamp": {"type": "string", "format": "date-time"},
27
+ "sender": {
28
+ "type": "object",
29
+ "additionalProperties": false,
30
+ "required": ["id"],
31
+ "properties": {
32
+ "id": {"type": "string", "minLength": 3},
33
+ "proofs": {"type": "array", "items": {"type": "string"}}
34
+ }
35
+ },
36
+ "receiver": {
37
+ "type": "object",
38
+ "additionalProperties": false,
39
+ "required": ["id"],
40
+ "properties": {"id": {"type": "string", "minLength": 3}}
41
+ },
42
+ "in_reply_to": {"type": "string", "minLength": 8},
43
+ "trace": {
44
+ "type": "object",
45
+ "additionalProperties": false,
46
+ "properties": {
47
+ "root": {"type": "string"},
48
+ "parent": {"type": "string"}
49
+ }
50
+ },
51
+ "auth": {
52
+ "type": "object",
53
+ "additionalProperties": true,
54
+ "properties": {
55
+ "type": {"type": "string", "enum": ["bearer", "mTLS", "signed"]},
56
+ "token": {"type": "string"},
57
+ "scopes": {"type": "array", "items": {"type": "string"}},
58
+ "issuer": {"type": "string"},
59
+ "signature": {
60
+ "type": "object",
61
+ "additionalProperties": false,
62
+ "required": ["alg", "key_id", "value"],
63
+ "properties": {
64
+ "alg": {"type": "string"},
65
+ "key_id": {"type": "string"},
66
+ "value": {"type": "string"}
67
+ }
68
+ }
69
+ }
70
+ },
71
+ "nonce": {"type": "string"},
72
+ "expires_at": {"type": "string", "format": "date-time"},
73
+ "idempotency_key": {"type": "string"},
74
+ "payload": {"type": "object"}
75
+ },
76
+ "allOf": [
77
+ {
78
+ "if": {"properties": {"msg_type": {"const": "DISCOVER"}}},
79
+ "then": {"properties": {"payload": {"$ref": "./payloads/discover.schema.json"}}}
80
+ },
81
+ {
82
+ "if": {"properties": {"msg_type": {"const": "OFFER"}}},
83
+ "then": {"properties": {"payload": {"$ref": "./payloads/offer.schema.json"}}}
84
+ },
85
+ {
86
+ "if": {"properties": {"msg_type": {"const": "NEGOTIATE"}}},
87
+ "then": {"properties": {"payload": {"$ref": "./payloads/negotiate.schema.json"}}}
88
+ },
89
+ {
90
+ "if": {"properties": {"msg_type": {"const": "EXECUTE"}}},
91
+ "then": {"properties": {"payload": {"$ref": "./payloads/execute.schema.json"}}}
92
+ },
93
+ {
94
+ "if": {"properties": {"msg_type": {"const": "EVIDENCE"}}},
95
+ "then": {"properties": {"payload": {"$ref": "./payloads/evidence.schema.json"}}}
96
+ },
97
+ {
98
+ "if": {"properties": {"msg_type": {"const": "ERROR"}}},
99
+ "then": {"properties": {"payload": {"$ref": "./payloads/error.schema.json"}}}
100
+ }
101
+ ]
102
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://www.moltrouter.dev/schemas/mrp/0.1/manifest.schema.json",
4
+ "title": "MRP Capability Manifest (0.1)",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["capability_id", "capability", "version", "inputs", "outputs", "endpoints"],
8
+ "properties": {
9
+ "capability_id": {"type": "string"},
10
+ "capability": {"type": "string"},
11
+ "version": {"type": "string"},
12
+ "tags": {"type": "array", "items": {"type": "string"}},
13
+ "inputs": {
14
+ "type": "array",
15
+ "items": {
16
+ "type": "object",
17
+ "additionalProperties": false,
18
+ "required": ["type"],
19
+ "properties": {
20
+ "type": {"type": "string"},
21
+ "schema_ref": {"type": "string"}
22
+ }
23
+ }
24
+ },
25
+ "outputs": {
26
+ "type": "array",
27
+ "items": {
28
+ "type": "object",
29
+ "additionalProperties": false,
30
+ "required": ["type"],
31
+ "properties": {
32
+ "type": {"type": "string"},
33
+ "schema_ref": {"type": "string"}
34
+ }
35
+ }
36
+ },
37
+ "constraints": {"type": "object", "additionalProperties": true},
38
+ "cost": {"type": "object"},
39
+ "latency": {"type": "object"},
40
+ "proofs_required": {"type": "array", "items": {"type": "string"}},
41
+ "endpoints": {
42
+ "type": "object",
43
+ "additionalProperties": false,
44
+ "required": ["discover", "negotiate", "execute"],
45
+ "properties": {
46
+ "discover": {"type": "string"},
47
+ "negotiate": {"type": "string"},
48
+ "execute": {"type": "string"}
49
+ }
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://www.moltrouter.dev/schemas/mrp/0.1/payloads/discover.schema.json",
4
+ "title": "MRP DISCOVER Payload",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["intent"],
8
+ "properties": {
9
+ "intent": {"type": "string", "minLength": 1},
10
+ "inputs": {"type": "array", "items": {"$ref": "../types/input.schema.json"}},
11
+ "constraints": {
12
+ "type": "object",
13
+ "additionalProperties": true,
14
+ "properties": {
15
+ "max_cost": {"type": "number", "minimum": 0},
16
+ "max_latency_ms": {"type": "integer", "minimum": 0},
17
+ "data_residency": {"type": "string"},
18
+ "policy": {"type": "array", "items": {"type": "string"}}
19
+ }
20
+ },
21
+ "proofs_required": {"type": "array", "items": {"type": "string"}}
22
+ }
23
+ }