mrpd 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.
- mrpd-0.1.0/MANIFEST.in +1 -0
- mrpd-0.1.0/PKG-INFO +104 -0
- mrpd-0.1.0/README.md +88 -0
- mrpd-0.1.0/mrpd/__init__.py +1 -0
- mrpd-0.1.0/mrpd/adapters/__init__.py +1 -0
- mrpd-0.1.0/mrpd/api/app.py +8 -0
- mrpd-0.1.0/mrpd/api/routes.py +200 -0
- mrpd-0.1.0/mrpd/cli.py +182 -0
- mrpd-0.1.0/mrpd/commands/__init__.py +1 -0
- mrpd-0.1.0/mrpd/commands/bridge_mcp.py +189 -0
- mrpd-0.1.0/mrpd/commands/bridge_openapi.py +279 -0
- mrpd-0.1.0/mrpd/commands/init_provider.py +176 -0
- mrpd-0.1.0/mrpd/commands/mrpify_mcp.py +80 -0
- mrpd-0.1.0/mrpd/commands/mrpify_openapi.py +80 -0
- mrpd-0.1.0/mrpd/commands/publish.py +102 -0
- mrpd-0.1.0/mrpd/commands/route.py +126 -0
- mrpd-0.1.0/mrpd/commands/run.py +169 -0
- mrpd-0.1.0/mrpd/commands/serve.py +13 -0
- mrpd-0.1.0/mrpd/commands/validate.py +71 -0
- mrpd-0.1.0/mrpd/core/__init__.py +1 -0
- mrpd-0.1.0/mrpd/core/artifacts.py +39 -0
- mrpd-0.1.0/mrpd/core/config.py +26 -0
- mrpd-0.1.0/mrpd/core/defaults.py +7 -0
- mrpd-0.1.0/mrpd/core/envelopes.py +29 -0
- mrpd-0.1.0/mrpd/core/errors.py +37 -0
- mrpd-0.1.0/mrpd/core/evidence.py +17 -0
- mrpd-0.1.0/mrpd/core/models.py +37 -0
- mrpd-0.1.0/mrpd/core/provider.py +100 -0
- mrpd-0.1.0/mrpd/core/registry.py +115 -0
- mrpd-0.1.0/mrpd/core/schema.py +64 -0
- mrpd-0.1.0/mrpd/core/scoring.py +92 -0
- mrpd-0.1.0/mrpd/core/util.py +27 -0
- mrpd-0.1.0/mrpd/spec/__init__.py +1 -0
- mrpd-0.1.0/mrpd/spec/fixtures/README.md +8 -0
- mrpd-0.1.0/mrpd/spec/fixtures/invalid/bad_msg_type.json +8 -0
- mrpd-0.1.0/mrpd/spec/fixtures/invalid/missing_required_fields.json +7 -0
- mrpd-0.1.0/mrpd/spec/fixtures/valid/discover.json +13 -0
- mrpd-0.1.0/mrpd/spec/fixtures/valid/error.json +13 -0
- mrpd-0.1.0/mrpd/spec/fixtures/valid/offer.json +20 -0
- mrpd-0.1.0/mrpd/spec/schemas/envelope.schema.json +102 -0
- mrpd-0.1.0/mrpd/spec/schemas/manifest.schema.json +52 -0
- mrpd-0.1.0/mrpd/spec/schemas/payloads/discover.schema.json +23 -0
- mrpd-0.1.0/mrpd/spec/schemas/payloads/error.schema.json +15 -0
- mrpd-0.1.0/mrpd/spec/schemas/payloads/evidence.schema.json +17 -0
- mrpd-0.1.0/mrpd/spec/schemas/payloads/execute.schema.json +14 -0
- mrpd-0.1.0/mrpd/spec/schemas/payloads/negotiate.schema.json +15 -0
- mrpd-0.1.0/mrpd/spec/schemas/payloads/offer.schema.json +30 -0
- mrpd-0.1.0/mrpd/spec/schemas/types/artifact.schema.json +15 -0
- mrpd-0.1.0/mrpd/spec/schemas/types/input.schema.json +20 -0
- mrpd-0.1.0/mrpd.egg-info/PKG-INFO +104 -0
- mrpd-0.1.0/mrpd.egg-info/SOURCES.txt +55 -0
- mrpd-0.1.0/mrpd.egg-info/dependency_links.txt +1 -0
- mrpd-0.1.0/mrpd.egg-info/entry_points.txt +2 -0
- mrpd-0.1.0/mrpd.egg-info/requires.txt +7 -0
- mrpd-0.1.0/mrpd.egg-info/top_level.txt +1 -0
- mrpd-0.1.0/pyproject.toml +31 -0
- mrpd-0.1.0/setup.cfg +4 -0
mrpd-0.1.0/MANIFEST.in
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
recursive-include mrpd/spec *.json *.md
|
mrpd-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mrpd
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Moltrouter Protocol Daemon: serve, route, bridge, validate
|
|
5
|
+
Author: Thor
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: fastapi>=0.110
|
|
10
|
+
Requires-Dist: uvicorn[standard]>=0.27
|
|
11
|
+
Requires-Dist: pydantic>=2.6
|
|
12
|
+
Requires-Dist: httpx>=0.27
|
|
13
|
+
Requires-Dist: typer>=0.12
|
|
14
|
+
Requires-Dist: pyyaml>=6.0
|
|
15
|
+
Requires-Dist: jsonschema>=4.21
|
|
16
|
+
|
|
17
|
+
# mrpd — Moltrouter Protocol Daemon
|
|
18
|
+
|
|
19
|
+
One installable package that can:
|
|
20
|
+
- **serve** MRP endpoints for local tools (`mrpd serve`)
|
|
21
|
+
- **route** intents as a client (`mrpd route ...`) (v0: registry query + ranking)
|
|
22
|
+
- **bridge** other tool ecosystems (OpenAPI/MCP) into MRP (`mrpd bridge ...`)
|
|
23
|
+
- **mrpify** existing systems with a guided flow (`mrpd mrpify ...`)
|
|
24
|
+
- **validate** MRP envelopes against schemas/fixtures (`mrpd validate`) (working)
|
|
25
|
+
|
|
26
|
+
## Status
|
|
27
|
+
Minimal server + validation are working.
|
|
28
|
+
|
|
29
|
+
Implemented:
|
|
30
|
+
- Bundled canonical JSON Schemas + fixtures
|
|
31
|
+
- `mrpd validate` (including `--fixtures`)
|
|
32
|
+
- `mrpd route` (v0: query + rank + fetch manifests)
|
|
33
|
+
- `mrpd bridge openapi` + `mrpd mrpify openapi`
|
|
34
|
+
- `mrpd bridge mcp` (scaffold) + `mrpd mrpify mcp`
|
|
35
|
+
|
|
36
|
+
Planned next:
|
|
37
|
+
- negotiate/execute
|
|
38
|
+
- MCP bridge execute (stdio)
|
|
39
|
+
|
|
40
|
+
## Dev
|
|
41
|
+
```bash
|
|
42
|
+
python -m venv .venv
|
|
43
|
+
. .venv/bin/activate # (Windows PowerShell: .\.venv\Scripts\Activate.ps1)
|
|
44
|
+
pip install -e .
|
|
45
|
+
|
|
46
|
+
mrpd serve --reload
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Bridge / Mrpify (v0)
|
|
50
|
+
OpenAPI:
|
|
51
|
+
```bash
|
|
52
|
+
mrpd bridge openapi --spec openapi.yaml --out-dir ./mrp-openapi-bridge
|
|
53
|
+
mrpd mrpify openapi --spec openapi.yaml --out-dir ./mrp-openapi-bridge \
|
|
54
|
+
--provider-id service:openapi/bridge --public-base-url https://YOUR_DOMAIN
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
MCP (stdio scaffold):
|
|
58
|
+
```bash
|
|
59
|
+
mrpd bridge mcp --tools-json tools.json --mcp-command node --mcp-arg server.js
|
|
60
|
+
mrpd mrpify mcp --tools-json tools.json --mcp-command node --mcp-arg server.js \
|
|
61
|
+
--provider-id service:mcp/bridge --public-base-url https://YOUR_DOMAIN
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Validate
|
|
65
|
+
Run the bundled conformance fixtures:
|
|
66
|
+
```bash
|
|
67
|
+
mrpd validate --fixtures
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Validate a single envelope JSON file:
|
|
71
|
+
```bash
|
|
72
|
+
mrpd validate --path path/to/envelope.json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Route (v0)
|
|
76
|
+
`mrpd route` queries a registry and prints ranked candidates. **Registry entries must include `manifest_url`.**
|
|
77
|
+
|
|
78
|
+
Defaults:
|
|
79
|
+
- Registry base URL: `https://www.moltrouter.dev`
|
|
80
|
+
- No raw bootstrap fallback is used unless explicitly configured (see below).
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
```bash
|
|
84
|
+
mrpd route "inspect router capability" --capability router --policy no_pii --limit 5
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Optional bootstrap (mostly for offline/dev):
|
|
88
|
+
|
|
89
|
+
- Env var: `MRP_BOOTSTRAP_REGISTRY_RAW` (supports `file://...`)
|
|
90
|
+
- Or CLI: `mrpd route ... --bootstrap-raw file:///C:/path/to/registry.json`
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
```bash
|
|
94
|
+
mrpd route "inspect router capability" --capability router --policy no_pii \
|
|
95
|
+
--bootstrap-raw "file:///C:/path/to/registry.json"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Endpoints (initial)
|
|
99
|
+
- `GET /.well-known/mrp.json`
|
|
100
|
+
- `GET /mrp/manifest`
|
|
101
|
+
- `POST /mrp/hello`
|
|
102
|
+
- `POST /mrp/discover`
|
|
103
|
+
- `POST /mrp/negotiate`
|
|
104
|
+
- `POST /mrp/execute`
|
mrpd-0.1.0/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# mrpd — Moltrouter Protocol Daemon
|
|
2
|
+
|
|
3
|
+
One installable package that can:
|
|
4
|
+
- **serve** MRP endpoints for local tools (`mrpd serve`)
|
|
5
|
+
- **route** intents as a client (`mrpd route ...`) (v0: registry query + ranking)
|
|
6
|
+
- **bridge** other tool ecosystems (OpenAPI/MCP) into MRP (`mrpd bridge ...`)
|
|
7
|
+
- **mrpify** existing systems with a guided flow (`mrpd mrpify ...`)
|
|
8
|
+
- **validate** MRP envelopes against schemas/fixtures (`mrpd validate`) (working)
|
|
9
|
+
|
|
10
|
+
## Status
|
|
11
|
+
Minimal server + validation are working.
|
|
12
|
+
|
|
13
|
+
Implemented:
|
|
14
|
+
- Bundled canonical JSON Schemas + fixtures
|
|
15
|
+
- `mrpd validate` (including `--fixtures`)
|
|
16
|
+
- `mrpd route` (v0: query + rank + fetch manifests)
|
|
17
|
+
- `mrpd bridge openapi` + `mrpd mrpify openapi`
|
|
18
|
+
- `mrpd bridge mcp` (scaffold) + `mrpd mrpify mcp`
|
|
19
|
+
|
|
20
|
+
Planned next:
|
|
21
|
+
- negotiate/execute
|
|
22
|
+
- MCP bridge execute (stdio)
|
|
23
|
+
|
|
24
|
+
## Dev
|
|
25
|
+
```bash
|
|
26
|
+
python -m venv .venv
|
|
27
|
+
. .venv/bin/activate # (Windows PowerShell: .\.venv\Scripts\Activate.ps1)
|
|
28
|
+
pip install -e .
|
|
29
|
+
|
|
30
|
+
mrpd serve --reload
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Bridge / Mrpify (v0)
|
|
34
|
+
OpenAPI:
|
|
35
|
+
```bash
|
|
36
|
+
mrpd bridge openapi --spec openapi.yaml --out-dir ./mrp-openapi-bridge
|
|
37
|
+
mrpd mrpify openapi --spec openapi.yaml --out-dir ./mrp-openapi-bridge \
|
|
38
|
+
--provider-id service:openapi/bridge --public-base-url https://YOUR_DOMAIN
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
MCP (stdio scaffold):
|
|
42
|
+
```bash
|
|
43
|
+
mrpd bridge mcp --tools-json tools.json --mcp-command node --mcp-arg server.js
|
|
44
|
+
mrpd mrpify mcp --tools-json tools.json --mcp-command node --mcp-arg server.js \
|
|
45
|
+
--provider-id service:mcp/bridge --public-base-url https://YOUR_DOMAIN
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Validate
|
|
49
|
+
Run the bundled conformance fixtures:
|
|
50
|
+
```bash
|
|
51
|
+
mrpd validate --fixtures
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Validate a single envelope JSON file:
|
|
55
|
+
```bash
|
|
56
|
+
mrpd validate --path path/to/envelope.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Route (v0)
|
|
60
|
+
`mrpd route` queries a registry and prints ranked candidates. **Registry entries must include `manifest_url`.**
|
|
61
|
+
|
|
62
|
+
Defaults:
|
|
63
|
+
- Registry base URL: `https://www.moltrouter.dev`
|
|
64
|
+
- No raw bootstrap fallback is used unless explicitly configured (see below).
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
```bash
|
|
68
|
+
mrpd route "inspect router capability" --capability router --policy no_pii --limit 5
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Optional bootstrap (mostly for offline/dev):
|
|
72
|
+
|
|
73
|
+
- Env var: `MRP_BOOTSTRAP_REGISTRY_RAW` (supports `file://...`)
|
|
74
|
+
- Or CLI: `mrpd route ... --bootstrap-raw file:///C:/path/to/registry.json`
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
```bash
|
|
78
|
+
mrpd route "inspect router capability" --capability router --policy no_pii \
|
|
79
|
+
--bootstrap-raw "file:///C:/path/to/registry.json"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Endpoints (initial)
|
|
83
|
+
- `GET /.well-known/mrp.json`
|
|
84
|
+
- `GET /mrp/manifest`
|
|
85
|
+
- `POST /mrp/hello`
|
|
86
|
+
- `POST /mrp/discover`
|
|
87
|
+
- `POST /mrp/negotiate`
|
|
88
|
+
- `POST /mrp/execute`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__ = []
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__ = []
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter
|
|
4
|
+
|
|
5
|
+
from mrpd.core.errors import mrp_error
|
|
6
|
+
from mrpd.core.schema import validate_envelope
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def response_envelope(envelope: dict, *, msg_type: str, payload: dict) -> dict:
|
|
12
|
+
"""Build a response envelope.
|
|
13
|
+
|
|
14
|
+
IMPORTANT: response message ids must be new, and in_reply_to must reference
|
|
15
|
+
the triggering request msg_id.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from mrpd.core.util import utc_now_rfc3339
|
|
19
|
+
import uuid
|
|
20
|
+
|
|
21
|
+
sender_id = (envelope.get("sender") or {}).get("id")
|
|
22
|
+
req_msg_id = envelope.get("msg_id")
|
|
23
|
+
|
|
24
|
+
resp = {
|
|
25
|
+
"mrp_version": envelope.get("mrp_version", "0.1"),
|
|
26
|
+
"msg_id": str(uuid.uuid4()),
|
|
27
|
+
"msg_type": msg_type,
|
|
28
|
+
"timestamp": utc_now_rfc3339(),
|
|
29
|
+
"sender": {"id": "service:mrpd"},
|
|
30
|
+
"payload": payload,
|
|
31
|
+
}
|
|
32
|
+
if sender_id:
|
|
33
|
+
resp["receiver"] = {"id": sender_id}
|
|
34
|
+
if req_msg_id:
|
|
35
|
+
resp["in_reply_to"] = req_msg_id
|
|
36
|
+
return resp
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@router.get("/.well-known/mrp.json")
|
|
40
|
+
async def well_known() -> dict:
|
|
41
|
+
return {
|
|
42
|
+
"mrp_version": "0.1",
|
|
43
|
+
"capabilities": ["summarize_url"],
|
|
44
|
+
"manifest_url": "/mrp/manifest",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@router.get("/mrp/manifest")
|
|
49
|
+
async def manifest() -> dict:
|
|
50
|
+
# Provider manifest for the built-in demo capability.
|
|
51
|
+
# NOTE: this endpoint returns relative URLs; clients may prefer absolute.
|
|
52
|
+
# Our `mrpd run` prefers manifests from registry entries (absolute endpoints).
|
|
53
|
+
return {
|
|
54
|
+
"capability_id": "capability:mrp/summarize_url",
|
|
55
|
+
"capability": "summarize_url",
|
|
56
|
+
"version": "0.1",
|
|
57
|
+
"tags": ["mrp", "summarize", "web"],
|
|
58
|
+
"inputs": [{"type": "url"}],
|
|
59
|
+
"outputs": [{"type": "markdown"}, {"type": "artifact"}],
|
|
60
|
+
"constraints": {"policy": ["no_pii"]},
|
|
61
|
+
"proofs_required": [],
|
|
62
|
+
"endpoints": {
|
|
63
|
+
"discover": "/mrp/discover",
|
|
64
|
+
"negotiate": "/mrp/negotiate",
|
|
65
|
+
"execute": "/mrp/execute",
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@router.post("/mrp/hello")
|
|
71
|
+
async def hello(envelope: dict) -> dict:
|
|
72
|
+
try:
|
|
73
|
+
validate_envelope(envelope)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
sender_id = (envelope.get("sender") or {}).get("id")
|
|
76
|
+
msg_id = envelope.get("msg_id")
|
|
77
|
+
return mrp_error(
|
|
78
|
+
msg_id=msg_id,
|
|
79
|
+
timestamp=envelope.get("timestamp"),
|
|
80
|
+
receiver_id=sender_id,
|
|
81
|
+
in_reply_to=msg_id,
|
|
82
|
+
code="MRP_INVALID_REQUEST",
|
|
83
|
+
message=str(e),
|
|
84
|
+
retryable=False,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return response_envelope(
|
|
88
|
+
envelope,
|
|
89
|
+
msg_type="HELLO",
|
|
90
|
+
payload={"ok": True, "schemas": ["0.1"]},
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@router.post("/mrp/discover")
|
|
95
|
+
async def discover(envelope: dict) -> dict:
|
|
96
|
+
try:
|
|
97
|
+
validate_envelope(envelope)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
sender_id = (envelope.get("sender") or {}).get("id")
|
|
100
|
+
msg_id = envelope.get("msg_id")
|
|
101
|
+
return mrp_error(
|
|
102
|
+
msg_id=msg_id,
|
|
103
|
+
timestamp=envelope.get("timestamp"),
|
|
104
|
+
receiver_id=sender_id,
|
|
105
|
+
in_reply_to=msg_id,
|
|
106
|
+
code="MRP_INVALID_REQUEST",
|
|
107
|
+
message=str(e),
|
|
108
|
+
retryable=False,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
from mrpd.core.provider import offers_for_discover
|
|
112
|
+
|
|
113
|
+
offers = offers_for_discover(envelope.get("payload") or {})
|
|
114
|
+
return response_envelope(
|
|
115
|
+
envelope,
|
|
116
|
+
msg_type="OFFER",
|
|
117
|
+
payload={"offers": offers},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@router.post("/mrp/negotiate")
|
|
122
|
+
async def negotiate(envelope: dict) -> dict:
|
|
123
|
+
try:
|
|
124
|
+
validate_envelope(envelope)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
sender_id = (envelope.get("sender") or {}).get("id")
|
|
127
|
+
msg_id = envelope.get("msg_id")
|
|
128
|
+
return mrp_error(
|
|
129
|
+
msg_id=msg_id,
|
|
130
|
+
timestamp=envelope.get("timestamp"),
|
|
131
|
+
receiver_id=sender_id,
|
|
132
|
+
in_reply_to=msg_id,
|
|
133
|
+
code="MRP_INVALID_REQUEST",
|
|
134
|
+
message=str(e),
|
|
135
|
+
retryable=False,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return response_envelope(
|
|
139
|
+
envelope,
|
|
140
|
+
msg_type="NEGOTIATE",
|
|
141
|
+
payload={"accepted": False, "reason": "not implemented"},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@router.post("/mrp/execute")
|
|
146
|
+
async def execute(envelope: dict) -> dict:
|
|
147
|
+
try:
|
|
148
|
+
validate_envelope(envelope)
|
|
149
|
+
except Exception as e:
|
|
150
|
+
sender_id = (envelope.get("sender") or {}).get("id")
|
|
151
|
+
msg_id = envelope.get("msg_id")
|
|
152
|
+
return mrp_error(
|
|
153
|
+
msg_id=msg_id,
|
|
154
|
+
timestamp=envelope.get("timestamp"),
|
|
155
|
+
receiver_id=sender_id,
|
|
156
|
+
in_reply_to=msg_id,
|
|
157
|
+
code="MRP_INVALID_REQUEST",
|
|
158
|
+
message=str(e),
|
|
159
|
+
retryable=False,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
from mrpd.core.provider import execute_summarize_url
|
|
163
|
+
|
|
164
|
+
payload = envelope.get("payload") or {}
|
|
165
|
+
route_id = payload.get("route_id")
|
|
166
|
+
inputs = payload.get("inputs") or []
|
|
167
|
+
job_id = (payload.get("job") or {}).get("id")
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
if route_id == "route:mrpd/summarize_url@0.1":
|
|
171
|
+
evidence = await execute_summarize_url(inputs)
|
|
172
|
+
else:
|
|
173
|
+
sender_id = (envelope.get("sender") or {}).get("id")
|
|
174
|
+
msg_id = envelope.get("msg_id")
|
|
175
|
+
return mrp_error(
|
|
176
|
+
msg_id=msg_id,
|
|
177
|
+
timestamp=envelope.get("timestamp"),
|
|
178
|
+
receiver_id=sender_id,
|
|
179
|
+
in_reply_to=msg_id,
|
|
180
|
+
code="MRP_INVALID_REQUEST",
|
|
181
|
+
message=f"Unknown route_id: {route_id}",
|
|
182
|
+
retryable=False,
|
|
183
|
+
)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
sender_id = (envelope.get("sender") or {}).get("id")
|
|
186
|
+
msg_id = envelope.get("msg_id")
|
|
187
|
+
return mrp_error(
|
|
188
|
+
msg_id=msg_id,
|
|
189
|
+
timestamp=envelope.get("timestamp"),
|
|
190
|
+
receiver_id=sender_id,
|
|
191
|
+
in_reply_to=msg_id,
|
|
192
|
+
code="MRP_INTERNAL_ERROR",
|
|
193
|
+
message=str(e),
|
|
194
|
+
retryable=False,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
response_payload = {"route_id": route_id, **evidence}
|
|
198
|
+
if job_id:
|
|
199
|
+
response_payload["job_id"] = job_id
|
|
200
|
+
return response_envelope(envelope, msg_type="EVIDENCE", payload=response_payload)
|
mrpd-0.1.0/mrpd/cli.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from mrpd.commands.bridge_mcp import bridge_mcp
|
|
6
|
+
from mrpd.commands.bridge_openapi import bridge_openapi
|
|
7
|
+
from mrpd.commands.init_provider import init_provider
|
|
8
|
+
from mrpd.commands.mrpify_mcp import mrpify_mcp
|
|
9
|
+
from mrpd.commands.mrpify_openapi import mrpify_openapi
|
|
10
|
+
from mrpd.commands.publish import publish
|
|
11
|
+
from mrpd.commands.route import route
|
|
12
|
+
from mrpd.commands.run import run
|
|
13
|
+
from mrpd.commands.serve import serve
|
|
14
|
+
from mrpd.commands.validate import validate
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(add_completion=False)
|
|
17
|
+
|
|
18
|
+
bridge_app = typer.Typer(help="Generate MRP provider wrappers (bridges)")
|
|
19
|
+
app.add_typer(bridge_app, name="bridge")
|
|
20
|
+
|
|
21
|
+
mrpify_app = typer.Typer(help="MRP-ify existing systems with a guided flow")
|
|
22
|
+
app.add_typer(mrpify_app, name="mrpify")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.command()
|
|
26
|
+
def version() -> None:
|
|
27
|
+
"""Print version."""
|
|
28
|
+
typer.echo("mrpd 0.1.0")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command(name="serve")
|
|
32
|
+
def serve_cmd(
|
|
33
|
+
host: str = typer.Option("127.0.0.1", "--host"),
|
|
34
|
+
port: int = typer.Option(8787, "--port"),
|
|
35
|
+
reload: bool = typer.Option(False, "--reload"),
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Run the MRP HTTP server."""
|
|
38
|
+
serve(host=host, port=port, reload=reload)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@app.command(name="validate")
|
|
42
|
+
def validate_cmd(
|
|
43
|
+
path: str = typer.Option("-", "--path", help="JSON file path or '-' for stdin"),
|
|
44
|
+
fixtures: bool = typer.Option(False, "--fixtures", help="Validate bundled fixtures (valid must pass, invalid must fail)"),
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Validate an MRP envelope against the bundled JSON Schemas."""
|
|
47
|
+
validate(path, fixtures=fixtures)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.command(name="route")
|
|
51
|
+
def route_cmd(
|
|
52
|
+
intent: str = typer.Argument(..., help="High-level intent (human text)"),
|
|
53
|
+
capability: str | None = typer.Option(None, "--capability", help="Capability filter"),
|
|
54
|
+
policy: str | None = typer.Option(None, "--policy", help="Policy filter"),
|
|
55
|
+
registry: str | None = typer.Option(None, "--registry", help="Registry base URL (default: https://www.moltrouter.dev)"),
|
|
56
|
+
bootstrap_raw: str | None = typer.Option(None, "--bootstrap-raw", help="Override fallback raw registry JSON (URL or file://path)"),
|
|
57
|
+
limit: int = typer.Option(10, "--limit", min=1, max=50),
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Query registry + rank candidates for an intent."""
|
|
60
|
+
route(intent=intent, capability=capability, policy=policy, registry=registry, limit=limit, bootstrap_raw=bootstrap_raw)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.command(name="run")
|
|
64
|
+
def run_cmd(
|
|
65
|
+
intent: str = typer.Argument(..., help="High-level intent (human text)"),
|
|
66
|
+
url: str = typer.Option(..., "--url", help="URL input"),
|
|
67
|
+
capability: str = typer.Option("summarize_url", "--capability", help="Capability to request"),
|
|
68
|
+
policy: str | None = typer.Option(None, "--policy", help="Policy requirement"),
|
|
69
|
+
registry: str | None = typer.Option(None, "--registry", help="Registry base URL (default: https://www.moltrouter.dev)"),
|
|
70
|
+
manifest_url: str | None = typer.Option(None, "--manifest-url", help="Skip registry and use this provider manifest URL (useful for local testing)"),
|
|
71
|
+
max_tokens: int | None = typer.Option(None, "--max-tokens", help="Soft max context tokens (constraint hint)"),
|
|
72
|
+
max_cost: float | None = typer.Option(None, "--max-cost", help="Max cost (constraint hint)"),
|
|
73
|
+
) -> None:
|
|
74
|
+
"""End-to-end: DISCOVER -> EXECUTE against the best matching provider."""
|
|
75
|
+
run(intent=intent, url=url, capability=capability, policy=policy, registry=registry, manifest_url=manifest_url, max_tokens=max_tokens, max_cost=max_cost)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@app.command(name="publish")
|
|
79
|
+
def publish_cmd(
|
|
80
|
+
manifest_url: str = typer.Option(..., "--manifest-url", help="Provider manifest URL to self-register"),
|
|
81
|
+
registry: str | None = typer.Option(None, "--registry", help="Registry base URL (default: https://www.moltrouter.dev)"),
|
|
82
|
+
poll_seconds: float = typer.Option(5.0, "--poll-seconds", min=1.0, max=60.0),
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Self-register a provider in the public registry using HTTP-01 challenge."""
|
|
85
|
+
publish(manifest_url=manifest_url, registry=registry, poll_seconds=poll_seconds)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.command(name="init-provider")
|
|
89
|
+
def init_provider_cmd(
|
|
90
|
+
out_dir: str = typer.Option("./mrp-provider", "--out-dir", help="Output directory"),
|
|
91
|
+
capability: str = typer.Option(..., "--capability", help="Capability name (e.g. summarize_url)"),
|
|
92
|
+
provider_id: str = typer.Option("service:example/provider", "--provider-id", help="Provider sender id"),
|
|
93
|
+
name: str = typer.Option("MRP Provider", "--name"),
|
|
94
|
+
description: str = typer.Option("", "--description"),
|
|
95
|
+
policy: list[str] = typer.Option([], "--policy", help="Policy strings (repeatable)")
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Scaffold a minimal FastAPI MRP provider wrapper."""
|
|
98
|
+
init_provider(out_dir=out_dir, capability=capability, provider_id=provider_id, name=name, description=description, policy=policy)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@bridge_app.command(name="openapi")
|
|
102
|
+
def bridge_openapi_cmd(
|
|
103
|
+
spec: str = typer.Option(..., "--spec", help="Path to OpenAPI spec (json/yaml)"),
|
|
104
|
+
out_dir: str = typer.Option("./mrp-openapi-bridge", "--out-dir", help="Output directory"),
|
|
105
|
+
provider_id: str = typer.Option("service:openapi/bridge", "--provider-id", help="Provider sender id"),
|
|
106
|
+
backend_base_url: str | None = typer.Option(None, "--backend-base-url", help="Override OpenAPI servers[0].url"),
|
|
107
|
+
capability_prefix: str | None = typer.Option(None, "--capability-prefix", help="Prefix for generated capabilities (e.g. svc_)"),
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Generate an MRP provider wrapper from an OpenAPI spec."""
|
|
110
|
+
bridge_openapi(spec=spec, out_dir=out_dir, provider_id=provider_id, backend_base_url=backend_base_url, capability_prefix=capability_prefix)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@bridge_app.command(name="mcp")
|
|
114
|
+
def bridge_mcp_cmd(
|
|
115
|
+
tools_json: str = typer.Option(..., "--tools-json", help="Path to MCP tools list JSON"),
|
|
116
|
+
out_dir: str = typer.Option("./mrp-mcp-bridge", "--out-dir", help="Output directory"),
|
|
117
|
+
provider_id: str = typer.Option("service:mcp/bridge", "--provider-id", help="Provider sender id"),
|
|
118
|
+
mcp_command: str = typer.Option(..., "--mcp-command", help="MCP server command (stdio)"),
|
|
119
|
+
mcp_args: list[str] = typer.Option([], "--mcp-arg", help="MCP server argument (repeatable)"),
|
|
120
|
+
) -> None:
|
|
121
|
+
"""Generate an MRP provider wrapper scaffold from an MCP tool list."""
|
|
122
|
+
bridge_mcp(tools_json=tools_json, out_dir=out_dir, provider_id=provider_id, mcp_command=mcp_command, mcp_args=mcp_args)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@mrpify_app.command(name="openapi")
|
|
126
|
+
def mrpify_openapi_cmd(
|
|
127
|
+
spec: str = typer.Option(..., "--spec", help="Path to OpenAPI spec (json/yaml)"),
|
|
128
|
+
out_dir: str = typer.Option("./mrp-openapi-bridge", "--out-dir", help="Output directory"),
|
|
129
|
+
provider_id: str = typer.Option("service:openapi/bridge", "--provider-id", help="Provider sender id"),
|
|
130
|
+
backend_base_url: str | None = typer.Option(None, "--backend-base-url", help="Override OpenAPI servers[0].url"),
|
|
131
|
+
capability_prefix: str | None = typer.Option(None, "--capability-prefix", help="Prefix for generated capabilities (e.g. svc_)"),
|
|
132
|
+
public_base_url: str | None = typer.Option(None, "--public-base-url", help="Deployed public base URL, e.g. https://api.example.com"),
|
|
133
|
+
publish_now: bool = typer.Option(False, "--publish", help="Run mrpd publish for each generated manifest URL (requires public HTTPS)"),
|
|
134
|
+
yes: bool = typer.Option(False, "--yes", help="Skip confirmation prompts"),
|
|
135
|
+
registry: str | None = typer.Option(None, "--registry", help="Registry base URL (default: https://www.moltrouter.dev)"),
|
|
136
|
+
poll_seconds: float = typer.Option(5.0, "--poll-seconds", min=1.0, max=60.0),
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Guided flow: OpenAPI -> MRP bridge -> (optional) publish commands."""
|
|
139
|
+
mrpify_openapi(
|
|
140
|
+
spec=spec,
|
|
141
|
+
out_dir=out_dir,
|
|
142
|
+
provider_id=provider_id,
|
|
143
|
+
backend_base_url=backend_base_url,
|
|
144
|
+
capability_prefix=capability_prefix,
|
|
145
|
+
public_base_url=public_base_url,
|
|
146
|
+
do_publish=publish_now,
|
|
147
|
+
yes=yes,
|
|
148
|
+
registry=registry,
|
|
149
|
+
poll_seconds=poll_seconds,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@mrpify_app.command(name="mcp")
|
|
154
|
+
def mrpify_mcp_cmd(
|
|
155
|
+
tools_json: str = typer.Option(..., "--tools-json", help="Path to MCP tools list JSON"),
|
|
156
|
+
out_dir: str = typer.Option("./mrp-mcp-bridge", "--out-dir", help="Output directory"),
|
|
157
|
+
provider_id: str = typer.Option("service:mcp/bridge", "--provider-id", help="Provider sender id"),
|
|
158
|
+
mcp_command: str = typer.Option(..., "--mcp-command", help="MCP server command (stdio)"),
|
|
159
|
+
mcp_args: list[str] = typer.Option([], "--mcp-arg", help="MCP server argument (repeatable)"),
|
|
160
|
+
public_base_url: str | None = typer.Option(None, "--public-base-url", help="Deployed public base URL, e.g. https://api.example.com"),
|
|
161
|
+
publish_now: bool = typer.Option(False, "--publish", help="Run mrpd publish for each generated manifest URL (requires public HTTPS)"),
|
|
162
|
+
yes: bool = typer.Option(False, "--yes", help="Skip confirmation prompts"),
|
|
163
|
+
registry: str | None = typer.Option(None, "--registry", help="Registry base URL (default: https://www.moltrouter.dev)"),
|
|
164
|
+
poll_seconds: float = typer.Option(5.0, "--poll-seconds", min=1.0, max=60.0),
|
|
165
|
+
) -> None:
|
|
166
|
+
"""Guided flow: MCP -> MRP bridge -> (optional) publish commands."""
|
|
167
|
+
mrpify_mcp(
|
|
168
|
+
tools_json=tools_json,
|
|
169
|
+
out_dir=out_dir,
|
|
170
|
+
provider_id=provider_id,
|
|
171
|
+
mcp_command=mcp_command,
|
|
172
|
+
mcp_args=mcp_args,
|
|
173
|
+
public_base_url=public_base_url,
|
|
174
|
+
do_publish=publish_now,
|
|
175
|
+
yes=yes,
|
|
176
|
+
registry=registry,
|
|
177
|
+
poll_seconds=poll_seconds,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
app()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__ = []
|