plexus-mesh 0.2.2__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.
- plexus/__init__.py +27 -0
- plexus/__main__.py +4 -0
- plexus/cli.py +139 -0
- plexus/graph.py +38 -0
- plexus/manifest.py +117 -0
- plexus/mcp.py +156 -0
- plexus/mesh.py +88 -0
- plexus/plan.py +120 -0
- plexus/receipt.py +66 -0
- plexus/registry.py +418 -0
- plexus/run.py +44 -0
- plexus_mesh-0.2.2.dist-info/METADATA +378 -0
- plexus_mesh-0.2.2.dist-info/RECORD +17 -0
- plexus_mesh-0.2.2.dist-info/WHEEL +5 -0
- plexus_mesh-0.2.2.dist-info/entry_points.txt +2 -0
- plexus_mesh-0.2.2.dist-info/licenses/LICENSE +110 -0
- plexus_mesh-0.2.2.dist-info/top_level.txt +1 -0
plexus/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""plexus: capability discovery + auto-wiring for agent toolchains.
|
|
2
|
+
|
|
3
|
+
Point it at a set of tools that ship interop manifests and declarative discovery
|
|
4
|
+
wires producer to consumer into a pipeline, every edge tagged `declared` and
|
|
5
|
+
carrying the module its producer names. Discovery does not import or run cited
|
|
6
|
+
tools. Optional probe helpers in `plexus.registry` launch owned MCP servers only
|
|
7
|
+
when explicitly called. Zero runtime dependencies.
|
|
8
|
+
"""
|
|
9
|
+
from .graph import to_dot, to_mermaid
|
|
10
|
+
from .manifest import Manifest, Port, validate
|
|
11
|
+
from .mesh import Edge, Mesh, discover
|
|
12
|
+
from .plan import plan_to, route
|
|
13
|
+
from .receipt import build_plan_receipt, verify_plan
|
|
14
|
+
from .registry import builtin_manifests, load_dir
|
|
15
|
+
from .run import pipeline_script
|
|
16
|
+
|
|
17
|
+
__version__ = "0.2.2"
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"Manifest", "Port", "validate",
|
|
21
|
+
"Mesh", "Edge", "discover",
|
|
22
|
+
"plan_to", "route",
|
|
23
|
+
"build_plan_receipt", "verify_plan",
|
|
24
|
+
"to_mermaid", "to_dot", "pipeline_script",
|
|
25
|
+
"builtin_manifests", "load_dir",
|
|
26
|
+
"__version__",
|
|
27
|
+
]
|
plexus/__main__.py
ADDED
plexus/cli.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""cli.py — plexus command line.
|
|
2
|
+
|
|
3
|
+
plexus discover [--builtin] [--dir DIR] # the mesh: organs, edges, orphans
|
|
4
|
+
plexus wiring [...] # capability -> (producer, consumer) pairs
|
|
5
|
+
plexus plan --goal ORGAN [...] # upstream pipeline that feeds ORGAN
|
|
6
|
+
plexus route --from A --to B [...] # capability path from A to B
|
|
7
|
+
plexus validate [...] # manifest problems (exit 1 if any)
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
|
|
15
|
+
from . import __version__
|
|
16
|
+
from .graph import to_dot, to_mermaid
|
|
17
|
+
from .manifest import content_hash, duplicate_organs, validate
|
|
18
|
+
from .mesh import discover
|
|
19
|
+
from .plan import plan_to, route
|
|
20
|
+
from .registry import builtin_manifests, export_all, load_dir
|
|
21
|
+
from .run import pipeline_script
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load(args) -> list:
|
|
25
|
+
mans = list(builtin_manifests()) if (args.builtin or not args.dir) else []
|
|
26
|
+
if args.dir:
|
|
27
|
+
mans += load_dir(args.dir)
|
|
28
|
+
return mans
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _receipt(mesh) -> dict:
|
|
32
|
+
"""A re-runnable provenance receipt for the mesh: which manifests were read,
|
|
33
|
+
from where, and a content hash a stranger can recompute from the same bytes."""
|
|
34
|
+
return {
|
|
35
|
+
"plexus_version": __version__,
|
|
36
|
+
"generated_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
37
|
+
"manifests": [{"organ": m.organ, "source": m.source,
|
|
38
|
+
"sha256": content_hash(m)}
|
|
39
|
+
for m in sorted(mesh.manifests.values(), key=lambda m: m.organ)],
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _mesh_json(mesh) -> dict:
|
|
44
|
+
return {
|
|
45
|
+
"organs": mesh.organs,
|
|
46
|
+
"edges": [{"producer": e.producer, "consumer": e.consumer,
|
|
47
|
+
"capability": e.capability, "self_loop": e.self_loop,
|
|
48
|
+
"via": e.producer_module, "evidence": e.evidence}
|
|
49
|
+
for e in mesh.edges],
|
|
50
|
+
"orphans": mesh.orphans(),
|
|
51
|
+
"collisions": mesh.collisions,
|
|
52
|
+
"receipt": _receipt(mesh),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _add_source_flags(p):
|
|
57
|
+
p.add_argument("--builtin", action="store_true", help="include the built-in flagship manifests")
|
|
58
|
+
p.add_argument("--dir", default="", help="load *.interop.json manifests from this directory")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def main(argv: "list[str] | None" = None) -> int:
|
|
62
|
+
ap = argparse.ArgumentParser(prog="plexus", description=__doc__)
|
|
63
|
+
ap.add_argument("--version", action="version", version=f"plexus {__version__}")
|
|
64
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
65
|
+
|
|
66
|
+
for name in ("discover", "wiring", "validate"):
|
|
67
|
+
sp = sub.add_parser(name)
|
|
68
|
+
_add_source_flags(sp)
|
|
69
|
+
pp = sub.add_parser("plan"); _add_source_flags(pp)
|
|
70
|
+
pp.add_argument("--goal", required=True, help="organ you want to feed")
|
|
71
|
+
rp = sub.add_parser("route"); _add_source_flags(rp)
|
|
72
|
+
rp.add_argument("--from", dest="src", required=True)
|
|
73
|
+
rp.add_argument("--to", dest="dst", required=True)
|
|
74
|
+
vp = sub.add_parser("verify"); _add_source_flags(vp)
|
|
75
|
+
vp.add_argument("--plan", required=True,
|
|
76
|
+
help="a plan/route JSON file (from `plexus plan`/`route`) to re-derive "
|
|
77
|
+
"and re-verify against the current mesh")
|
|
78
|
+
gp = sub.add_parser("graph"); _add_source_flags(gp)
|
|
79
|
+
gp.add_argument("--format", default="mermaid", choices=["mermaid", "dot"])
|
|
80
|
+
up = sub.add_parser("run"); _add_source_flags(up)
|
|
81
|
+
up.add_argument("--goal", required=True, help="organ you want to feed")
|
|
82
|
+
ep = sub.add_parser("export")
|
|
83
|
+
ep.add_argument("--dir", default="manifests", help="write <organ>.interop.json files here")
|
|
84
|
+
sub.add_parser("mcp") # stdio MCP server: discover/wiring/plan/route as tools
|
|
85
|
+
|
|
86
|
+
args = ap.parse_args(argv)
|
|
87
|
+
|
|
88
|
+
if args.cmd == "mcp":
|
|
89
|
+
from .mcp import serve
|
|
90
|
+
return serve()
|
|
91
|
+
if args.cmd == "export":
|
|
92
|
+
print("\n".join(export_all(args.dir)))
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
mans = _load(args)
|
|
96
|
+
|
|
97
|
+
if args.cmd == "discover":
|
|
98
|
+
print(json.dumps(_mesh_json(discover(mans)), indent=2))
|
|
99
|
+
return 0
|
|
100
|
+
if args.cmd == "wiring":
|
|
101
|
+
print(json.dumps(discover(mans).wiring(), indent=2))
|
|
102
|
+
return 0
|
|
103
|
+
if args.cmd == "plan":
|
|
104
|
+
print(json.dumps(plan_to(discover(mans), args.goal), indent=2))
|
|
105
|
+
return 0
|
|
106
|
+
if args.cmd == "route":
|
|
107
|
+
print(json.dumps(route(discover(mans), args.src, args.dst), indent=2))
|
|
108
|
+
return 0
|
|
109
|
+
if args.cmd == "verify":
|
|
110
|
+
from .receipt import verify_plan
|
|
111
|
+
with open(args.plan, encoding="utf-8") as f:
|
|
112
|
+
plan = json.load(f)
|
|
113
|
+
ok = verify_plan(plan, discover(mans))
|
|
114
|
+
print(json.dumps({"verified": ok, "target": plan.get("target"),
|
|
115
|
+
"source": plan.get("source")}, indent=2))
|
|
116
|
+
return 0 if ok else 1
|
|
117
|
+
if args.cmd == "graph":
|
|
118
|
+
mesh = discover(mans)
|
|
119
|
+
print(to_mermaid(mesh) if args.format == "mermaid" else to_dot(mesh))
|
|
120
|
+
return 0
|
|
121
|
+
if args.cmd == "run":
|
|
122
|
+
print(pipeline_script(discover(mans), args.goal))
|
|
123
|
+
return 0
|
|
124
|
+
if args.cmd == "validate":
|
|
125
|
+
problems = {m.organ: validate(m) for m in mans}
|
|
126
|
+
report = {k: v for k, v in problems.items() if v}
|
|
127
|
+
dups = duplicate_organs(mans)
|
|
128
|
+
if dups:
|
|
129
|
+
# A set-level clash the per-organ dict above cannot show (its own key
|
|
130
|
+
# collapses the duplicates); name it so a collision can never launder
|
|
131
|
+
# to {"ok": true}.
|
|
132
|
+
report["duplicate_organs"] = dups
|
|
133
|
+
print(json.dumps(report or {"ok": True}, indent=2))
|
|
134
|
+
return 1 if report else 0
|
|
135
|
+
return 2
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
raise SystemExit(main())
|
plexus/graph.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""graph.py — render the mesh as a diagram (Mermaid / Graphviz DOT).
|
|
2
|
+
|
|
3
|
+
Discovery is only useful if you can see it. This turns the mesh into a picture:
|
|
4
|
+
organs as nodes, capability-labeled edges as producer -> consumer arrows, with
|
|
5
|
+
self-loops and the cross-tool wiring visible at a glance. Mermaid renders in any
|
|
6
|
+
Markdown viewer; DOT feeds Graphviz. Pure string output, zero deps.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .mesh import Mesh
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _san(node: str) -> str:
|
|
14
|
+
"""A Mermaid/DOT-safe node id (organs are simple slugs already, but be safe)."""
|
|
15
|
+
return "".join(c if c.isalnum() else "_" for c in node)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def to_mermaid(mesh: Mesh, *, direction: str = "LR") -> str:
|
|
19
|
+
"""A Mermaid flowchart. Cross-tool edges are solid; self-loops dotted."""
|
|
20
|
+
lines = [f"flowchart {direction}"]
|
|
21
|
+
for organ in mesh.organs:
|
|
22
|
+
lines.append(f' {_san(organ)}["{organ}"]')
|
|
23
|
+
for e in mesh.edges:
|
|
24
|
+
arrow = "-.->" if e.self_loop else "-->"
|
|
25
|
+
lines.append(f' {_san(e.producer)} {arrow}|"{e.capability}"| {_san(e.consumer)}')
|
|
26
|
+
return "\n".join(lines)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def to_dot(mesh: Mesh) -> str:
|
|
30
|
+
"""A Graphviz DOT digraph."""
|
|
31
|
+
lines = ["digraph plexus {", ' rankdir=LR;', ' node [shape=box];']
|
|
32
|
+
for organ in mesh.organs:
|
|
33
|
+
lines.append(f' "{organ}";')
|
|
34
|
+
for e in mesh.edges:
|
|
35
|
+
attrs = f'label="{e.capability}"' + (", style=dotted" if e.self_loop else "")
|
|
36
|
+
lines.append(f' "{e.producer}" -> "{e.consumer}" [{attrs}];')
|
|
37
|
+
lines.append("}")
|
|
38
|
+
return "\n".join(lines)
|
plexus/manifest.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""manifest.py — what a tool declares about how it plugs into others.
|
|
2
|
+
|
|
3
|
+
A manifest is a tool's interop contract: the capabilities it EMITS (re-checkable
|
|
4
|
+
artifacts another tool could consume) and the capabilities it CONSUMES (inputs it
|
|
5
|
+
accepts). A capability is a stable string: a schema id like
|
|
6
|
+
"mneme.crucible-export/2" or a shared-spine kind like
|
|
7
|
+
"project-telos.flagship-action/v1". Matching is by capability string, so an edge
|
|
8
|
+
forms when a producer DECLARES a capability the consumer DECLARES it accepts.
|
|
9
|
+
|
|
10
|
+
Every port carries `module` (a file:function pointer) the producer names as the
|
|
11
|
+
source. plexus does not import, resolve, or run that pointer, so the mesh is
|
|
12
|
+
DECLARED, not probed: the module is a self-reported citation to follow, not a
|
|
13
|
+
verified receipt. A manifest is a plain dict/JSON, so any tool in any language
|
|
14
|
+
can ship one; nothing here imports the tools it describes.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import hashlib
|
|
19
|
+
import json
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class Port:
|
|
25
|
+
"""One capability a tool emits or consumes."""
|
|
26
|
+
capability: str # the match key (schema id or shared kind)
|
|
27
|
+
title: str = "" # human label
|
|
28
|
+
module: str = "" # evidence: repo-relative file:function
|
|
29
|
+
summary: str = ""
|
|
30
|
+
consumable_as: tuple = () # extra capabilities an EMIT also satisfies
|
|
31
|
+
# (e.g. mneme.crucible-export/2 -> crucible.thesis)
|
|
32
|
+
|
|
33
|
+
def satisfies(self, capability: str) -> bool:
|
|
34
|
+
return capability == self.capability or capability in self.consumable_as
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class Manifest:
|
|
39
|
+
organ: str
|
|
40
|
+
invoke: dict = field(default_factory=dict) # {cli, mcp_server, python_import}
|
|
41
|
+
emits: list = field(default_factory=list) # list[Port]
|
|
42
|
+
consumes: list = field(default_factory=list) # list[Port]
|
|
43
|
+
evidence: list = field(default_factory=list) # file paths the manifest was grounded on
|
|
44
|
+
source: str = "" # provenance: "builtin:registry" or the file path read
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def from_dict(d: dict) -> "Manifest":
|
|
48
|
+
def ports(key):
|
|
49
|
+
out = []
|
|
50
|
+
for p in d.get(key, []):
|
|
51
|
+
if isinstance(p, str):
|
|
52
|
+
out.append(Port(capability=p))
|
|
53
|
+
elif isinstance(p, dict):
|
|
54
|
+
out.append(Port(
|
|
55
|
+
capability=p.get("capability", ""), title=p.get("title", ""),
|
|
56
|
+
module=p.get("module", ""), summary=p.get("summary", ""),
|
|
57
|
+
consumable_as=tuple(p.get("consumable_as", ()))))
|
|
58
|
+
return out
|
|
59
|
+
# Tolerant by contract: a missing organ or capability becomes an empty
|
|
60
|
+
# string that validate() reports; from_dict never raises on malformed input.
|
|
61
|
+
return Manifest(organ=d.get("organ", ""), invoke=d.get("invoke", {}),
|
|
62
|
+
emits=ports("emits"), consumes=ports("consumes"),
|
|
63
|
+
evidence=list(d.get("evidence", [])))
|
|
64
|
+
|
|
65
|
+
def to_dict(self) -> dict:
|
|
66
|
+
def dump(ports):
|
|
67
|
+
return [{"capability": p.capability, "title": p.title, "module": p.module,
|
|
68
|
+
"summary": p.summary, "consumable_as": list(p.consumable_as)}
|
|
69
|
+
for p in ports]
|
|
70
|
+
return {"organ": self.organ, "invoke": self.invoke,
|
|
71
|
+
"emits": dump(self.emits), "consumes": dump(self.consumes),
|
|
72
|
+
"evidence": self.evidence}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def content_hash(m) -> str:
|
|
76
|
+
"""sha256 over the manifest's canonical content (its to_dict, sorted keys).
|
|
77
|
+
Source is deliberately excluded, so the hash binds WHAT was declared, not
|
|
78
|
+
where it was read; a stranger can recompute it from the same bytes."""
|
|
79
|
+
canonical = json.dumps(m.to_dict(), sort_keys=True, separators=(",", ":"))
|
|
80
|
+
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def duplicate_organs(manifests: list) -> list:
|
|
84
|
+
"""Organ ids declared by more than one manifest in the set, sorted unique.
|
|
85
|
+
A set-level check: identity is self-declared, so a collision must be NAMED,
|
|
86
|
+
never silently resolved last-writer-wins."""
|
|
87
|
+
seen, dups = set(), set()
|
|
88
|
+
for m in manifests:
|
|
89
|
+
if m.organ in seen:
|
|
90
|
+
dups.add(m.organ)
|
|
91
|
+
seen.add(m.organ)
|
|
92
|
+
return sorted(dups)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def validate(m) -> list:
|
|
96
|
+
"""Problems with a manifest (empty list == valid). A validator reports
|
|
97
|
+
malformed input; it never raises on it."""
|
|
98
|
+
issues = []
|
|
99
|
+
if not isinstance(m, Manifest):
|
|
100
|
+
return ["not a Manifest"]
|
|
101
|
+
if not m.organ or not isinstance(m.organ, str):
|
|
102
|
+
issues.append("organ id missing or not a string")
|
|
103
|
+
seen = set()
|
|
104
|
+
for side in ("emits", "consumes"):
|
|
105
|
+
for i, p in enumerate(getattr(m, side)):
|
|
106
|
+
if not isinstance(p, Port) or not p.capability:
|
|
107
|
+
issues.append(f"{side}[{i}] has no capability")
|
|
108
|
+
continue
|
|
109
|
+
if side == "emits" and not p.module:
|
|
110
|
+
# The producing module is the evidence the "cites its module"
|
|
111
|
+
# claim rests on; an emit without it is a receipt-less claim.
|
|
112
|
+
issues.append(f"emits[{i}] {p.capability!r} has no module evidence")
|
|
113
|
+
key = (side, p.capability)
|
|
114
|
+
if key in seen:
|
|
115
|
+
issues.append(f"{side} declares {p.capability!r} twice")
|
|
116
|
+
seen.add(key)
|
|
117
|
+
return issues
|
plexus/mcp.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""mcp.py — a zero-dep MCP server so any agent can query the mesh live.
|
|
2
|
+
|
|
3
|
+
A CLI mesh is useful to a human; an agent needs it mid-task. This exposes
|
|
4
|
+
discover / wiring / plan / route as MCP tools over stdio JSON-RPC 2.0 (the same
|
|
5
|
+
shape every flagship speaks), so a harness can ask "what feeds crucible?" or
|
|
6
|
+
"how do gather and crucible connect?" while it works. No SDK, stdlib only.
|
|
7
|
+
|
|
8
|
+
The transport (`serve`) is a thin loop; all logic is in `handle`, which maps a
|
|
9
|
+
request dict to a response dict and is fully testable without any pipes.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from . import __version__
|
|
17
|
+
from .mesh import discover
|
|
18
|
+
from .plan import plan_to, route
|
|
19
|
+
from .registry import builtin_manifests, load_dir
|
|
20
|
+
|
|
21
|
+
PROTOCOL = "2025-06-18"
|
|
22
|
+
|
|
23
|
+
_DIR = {"dir": {"type": "string", "description": "load extra *.interop.json manifests from here"}}
|
|
24
|
+
|
|
25
|
+
TOOLS = [
|
|
26
|
+
{"name": "plexus_discover",
|
|
27
|
+
"description": "Discover the interop mesh: organs, producer->consumer edges (each with the module that produces it), and orphans (unmet inputs / unconsumed outputs).",
|
|
28
|
+
"inputSchema": {"type": "object", "properties": dict(_DIR)}},
|
|
29
|
+
{"name": "plexus_wiring",
|
|
30
|
+
"description": "The wiring map: capability -> list of (producer, consumer) pairs.",
|
|
31
|
+
"inputSchema": {"type": "object", "properties": dict(_DIR)}},
|
|
32
|
+
{"name": "plexus_plan",
|
|
33
|
+
"description": "The upstream pipeline that feeds a target organ, in dependency order, with any feedback loops reported.",
|
|
34
|
+
"inputSchema": {"type": "object", "required": ["goal"],
|
|
35
|
+
"properties": {"goal": {"type": "string", "description": "organ to feed"}, **_DIR}}},
|
|
36
|
+
{"name": "plexus_route",
|
|
37
|
+
"description": "The shortest capability path from one organ to another.",
|
|
38
|
+
"inputSchema": {"type": "object", "required": ["source", "target"],
|
|
39
|
+
"properties": {"source": {"type": "string"}, "target": {"type": "string"}, **_DIR}}},
|
|
40
|
+
{"name": "plexus.status",
|
|
41
|
+
"description": "Liveness and identity of the plexus MCP server (name, version, protocol). Network-free health probe.",
|
|
42
|
+
"inputSchema": {"type": "object", "properties": {}}},
|
|
43
|
+
{"name": "plexus.doctor",
|
|
44
|
+
"description": "Readiness diagnostic: identity plus the count of builtin interop manifests and the tools exposed.",
|
|
45
|
+
"inputSchema": {"type": "object", "properties": {}}},
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _mesh(args: dict):
|
|
50
|
+
mans = list(builtin_manifests())
|
|
51
|
+
if args.get("dir"):
|
|
52
|
+
mans += load_dir(args["dir"])
|
|
53
|
+
return discover(mans)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _mesh_json(mesh) -> dict:
|
|
57
|
+
return {"organs": mesh.organs,
|
|
58
|
+
"edges": [{"producer": e.producer, "consumer": e.consumer,
|
|
59
|
+
"capability": e.capability, "self_loop": e.self_loop,
|
|
60
|
+
"via": e.producer_module} for e in mesh.edges],
|
|
61
|
+
"orphans": mesh.orphans()}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _call(params: dict) -> dict:
|
|
65
|
+
name, args = params.get("name"), params.get("arguments", {}) or {}
|
|
66
|
+
if name in ("plexus.status", "plexus.doctor"):
|
|
67
|
+
info = {"ok": True, "server": "plexus", "version": __version__, "protocol": PROTOCOL}
|
|
68
|
+
if name == "plexus.doctor":
|
|
69
|
+
info["builtin_manifests"] = len(list(builtin_manifests()))
|
|
70
|
+
info["tools"] = [t["name"] for t in TOOLS]
|
|
71
|
+
return {"content": [{"type": "text", "text": json.dumps(info, indent=2)}]}
|
|
72
|
+
try:
|
|
73
|
+
mesh = _mesh(args)
|
|
74
|
+
if name == "plexus_discover":
|
|
75
|
+
result = _mesh_json(mesh)
|
|
76
|
+
elif name == "plexus_wiring":
|
|
77
|
+
result = mesh.wiring()
|
|
78
|
+
elif name == "plexus_plan":
|
|
79
|
+
result = plan_to(mesh, args.get("goal", ""))
|
|
80
|
+
elif name == "plexus_route":
|
|
81
|
+
result = route(mesh, args.get("source", ""), args.get("target", ""))
|
|
82
|
+
else:
|
|
83
|
+
return {"content": [{"type": "text", "text": f"unknown tool {name!r}"}], "isError": True}
|
|
84
|
+
except Exception as e: # a tool error is a result, not a crash
|
|
85
|
+
return {"content": [{"type": "text", "text": f"[error] {type(e).__name__}: {e}"}],
|
|
86
|
+
"isError": True}
|
|
87
|
+
return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _ok(rid, result):
|
|
91
|
+
return {"jsonrpc": "2.0", "id": rid, "result": result}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _err(rid, code, message):
|
|
95
|
+
return {"jsonrpc": "2.0", "id": rid, "error": {"code": code, "message": message}}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _valid_request_id(value) -> bool:
|
|
99
|
+
return type(value) is str or type(value) is int
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _safe_request_id(req: dict):
|
|
103
|
+
if "id" not in req:
|
|
104
|
+
return None
|
|
105
|
+
rid = req["id"]
|
|
106
|
+
return rid if _valid_request_id(rid) else None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def handle(req: dict):
|
|
110
|
+
"""Map one JSON-RPC request to a response dict (or None for notifications)."""
|
|
111
|
+
if not isinstance(req, dict):
|
|
112
|
+
return _err(None, -32600, "invalid request")
|
|
113
|
+
rid = _safe_request_id(req)
|
|
114
|
+
if "id" in req and not _valid_request_id(req["id"]):
|
|
115
|
+
return _err(None, -32600, "invalid request")
|
|
116
|
+
if req.get("jsonrpc") != "2.0":
|
|
117
|
+
return _err(rid, -32600, "invalid request")
|
|
118
|
+
method = req.get("method")
|
|
119
|
+
if not isinstance(method, str):
|
|
120
|
+
return _err(rid, -32600, "invalid request: method must be a string")
|
|
121
|
+
if "id" not in req:
|
|
122
|
+
return None
|
|
123
|
+
if method == "initialize":
|
|
124
|
+
return _ok(rid, {"protocolVersion": PROTOCOL, "capabilities": {"tools": {}},
|
|
125
|
+
"serverInfo": {"name": "plexus", "version": __version__}})
|
|
126
|
+
if method == "tools/list":
|
|
127
|
+
return _ok(rid, {"tools": TOOLS})
|
|
128
|
+
if method == "tools/call":
|
|
129
|
+
params = req["params"] if "params" in req else {}
|
|
130
|
+
if not isinstance(params, dict):
|
|
131
|
+
return _err(rid, -32602, "invalid params")
|
|
132
|
+
return _ok(rid, _call(params))
|
|
133
|
+
return _err(rid, -32601, f"method not found: {method}")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def serve(stdin=None, stdout=None) -> int:
|
|
137
|
+
stdin, stdout = stdin or sys.stdin, stdout or sys.stdout
|
|
138
|
+
for line in stdin:
|
|
139
|
+
line = line.strip()
|
|
140
|
+
if not line:
|
|
141
|
+
continue
|
|
142
|
+
try:
|
|
143
|
+
req = json.loads(line)
|
|
144
|
+
except json.JSONDecodeError:
|
|
145
|
+
stdout.write(json.dumps(_err(None, -32700, "parse error")) + "\n")
|
|
146
|
+
stdout.flush()
|
|
147
|
+
continue
|
|
148
|
+
resp = handle(req)
|
|
149
|
+
if resp is not None:
|
|
150
|
+
stdout.write(json.dumps(resp) + "\n")
|
|
151
|
+
stdout.flush()
|
|
152
|
+
return 0
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
if __name__ == "__main__":
|
|
156
|
+
raise SystemExit(serve())
|
plexus/mesh.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""mesh.py — discover the wiring: whose output plugs into whose input.
|
|
2
|
+
|
|
3
|
+
Given a set of manifests, the mesh computes producer -> consumer EDGES: for every
|
|
4
|
+
capability a tool consumes, it finds the tools that emit something satisfying it.
|
|
5
|
+
This is the layer MCP tool lists lack: MCP tells an agent that tools exist; the
|
|
6
|
+
mesh tells it how their OUTPUTS chain into each other's INPUTS.
|
|
7
|
+
|
|
8
|
+
An edge is DECLARED, not probed. It forms by matching capability strings the
|
|
9
|
+
manifests self-report; plexus never imports, resolves, or runs the tools, so it
|
|
10
|
+
cannot confirm the cited module exists or that the outputs truly compose. Every
|
|
11
|
+
edge is tagged `evidence="declared"` and carries the module its producer names,
|
|
12
|
+
a self-reported citation to follow, not a verified receipt.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
|
|
18
|
+
from .manifest import duplicate_organs
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class Edge:
|
|
23
|
+
producer: str # organ id
|
|
24
|
+
consumer: str # organ id
|
|
25
|
+
capability: str # the consumer's input capability that the producer satisfies
|
|
26
|
+
producer_module: str
|
|
27
|
+
consumer_module: str
|
|
28
|
+
self_loop: bool = False
|
|
29
|
+
evidence: str = "declared" # "declared": self-reported, never probed by plexus
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class Mesh:
|
|
34
|
+
organs: list # organ ids, sorted
|
|
35
|
+
edges: list # list[Edge]
|
|
36
|
+
manifests: dict # organ -> Manifest (deduped, last-writer-wins)
|
|
37
|
+
collisions: list = field(default_factory=list) # organ ids declared more than once
|
|
38
|
+
|
|
39
|
+
def wiring(self) -> dict:
|
|
40
|
+
"""capability -> sorted list of (producer, consumer) pairs."""
|
|
41
|
+
out: dict = {}
|
|
42
|
+
for e in self.edges:
|
|
43
|
+
out.setdefault(e.capability, []).append((e.producer, e.consumer))
|
|
44
|
+
return {k: sorted(v) for k, v in sorted(out.items())}
|
|
45
|
+
|
|
46
|
+
def producers_of(self, organ: str) -> list:
|
|
47
|
+
"""Organs whose output feeds `organ` (its upstream), sorted unique."""
|
|
48
|
+
return sorted({e.producer for e in self.edges
|
|
49
|
+
if e.consumer == organ and not e.self_loop})
|
|
50
|
+
|
|
51
|
+
def consumers_of(self, organ: str) -> list:
|
|
52
|
+
"""Organs that consume `organ`'s output (its downstream), sorted unique."""
|
|
53
|
+
return sorted({e.consumer for e in self.edges
|
|
54
|
+
if e.producer == organ and not e.self_loop})
|
|
55
|
+
|
|
56
|
+
def orphans(self) -> dict:
|
|
57
|
+
"""Honesty surface: capabilities consumed by someone but emitted by no
|
|
58
|
+
organ in the set (an unmet input), and emitted but never consumed."""
|
|
59
|
+
emitted, consumed = set(), set()
|
|
60
|
+
for m in self.manifests.values():
|
|
61
|
+
for p in m.emits:
|
|
62
|
+
emitted.add(p.capability)
|
|
63
|
+
emitted.update(p.consumable_as)
|
|
64
|
+
for p in m.consumes:
|
|
65
|
+
consumed.add(p.capability)
|
|
66
|
+
return {"unmet_inputs": sorted(consumed - emitted),
|
|
67
|
+
"unconsumed_outputs": sorted(emitted - consumed)}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def discover(manifests: list) -> Mesh:
|
|
71
|
+
"""Build the mesh. An edge (A -> B, cap) exists when B consumes capability
|
|
72
|
+
`cap` and A emits a port that satisfies `cap`."""
|
|
73
|
+
mans = {m.organ: m for m in manifests}
|
|
74
|
+
edges = []
|
|
75
|
+
for consumer in manifests:
|
|
76
|
+
for want in consumer.consumes:
|
|
77
|
+
for producer in manifests:
|
|
78
|
+
for emit in producer.emits:
|
|
79
|
+
if emit.satisfies(want.capability):
|
|
80
|
+
edges.append(Edge(
|
|
81
|
+
producer=producer.organ, consumer=consumer.organ,
|
|
82
|
+
capability=want.capability,
|
|
83
|
+
producer_module=emit.module, consumer_module=want.module,
|
|
84
|
+
self_loop=(producer.organ == consumer.organ),
|
|
85
|
+
evidence="declared"))
|
|
86
|
+
edges.sort(key=lambda e: (e.capability, e.producer, e.consumer))
|
|
87
|
+
return Mesh(organs=sorted(mans), edges=edges, manifests=mans,
|
|
88
|
+
collisions=duplicate_organs(manifests))
|