meshcode 1.2.4__tar.gz → 1.2.6__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.
- {meshcode-1.2.4 → meshcode-1.2.6}/PKG-INFO +1 -1
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/__init__.py +1 -1
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/meshcode_mcp/server.py +152 -4
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/setup_clients.py +28 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-1.2.4 → meshcode-1.2.6}/pyproject.toml +1 -1
- {meshcode-1.2.4 → meshcode-1.2.6}/README.md +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/cli.py +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/comms_v4.py +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/launcher.py +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/launcher_install.py +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode/protocol_v2.py +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-1.2.4 → meshcode-1.2.6}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "1.2.
|
|
2
|
+
__version__ = "1.2.6"
|
|
@@ -48,10 +48,29 @@ if not PROJECT_NAME or not AGENT_NAME:
|
|
|
48
48
|
sys.exit(2)
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
# Resolve project_id
|
|
52
|
-
|
|
51
|
+
# Resolve project_id at startup. Try in order:
|
|
52
|
+
# 1. MESHCODE_PROJECT_ID env var (baked by `meshcode setup`, fastest)
|
|
53
|
+
# 2. mc_resolve_project RPC with the user's api_key (security definer, bypasses RLS)
|
|
54
|
+
# 3. Direct SELECT via get_project_id (only works if RLS is open / user is admin)
|
|
55
|
+
_PROJECT_ID: Optional[str] = os.environ.get("MESHCODE_PROJECT_ID") or None
|
|
53
56
|
if not _PROJECT_ID:
|
|
54
|
-
|
|
57
|
+
_api_key = os.environ.get("MESHCODE_API_KEY", "")
|
|
58
|
+
if _api_key:
|
|
59
|
+
try:
|
|
60
|
+
_r = be.sb_rpc("mc_resolve_project", {
|
|
61
|
+
"p_api_key": _api_key,
|
|
62
|
+
"p_project_name": PROJECT_NAME,
|
|
63
|
+
})
|
|
64
|
+
if isinstance(_r, dict) and _r.get("project_id"):
|
|
65
|
+
_PROJECT_ID = _r["project_id"]
|
|
66
|
+
elif isinstance(_r, dict) and _r.get("error"):
|
|
67
|
+
print(f"[meshcode-mcp] WARNING: mc_resolve_project: {_r['error']}", file=sys.stderr)
|
|
68
|
+
except Exception as _e:
|
|
69
|
+
print(f"[meshcode-mcp] WARNING: mc_resolve_project failed: {_e}", file=sys.stderr)
|
|
70
|
+
if not _PROJECT_ID:
|
|
71
|
+
_PROJECT_ID = be.get_project_id(PROJECT_NAME)
|
|
72
|
+
if not _PROJECT_ID:
|
|
73
|
+
print(f"[meshcode-mcp] ERROR: project '{PROJECT_NAME}' not found (check MESHCODE_API_KEY env var)", file=sys.stderr)
|
|
55
74
|
sys.exit(2)
|
|
56
75
|
|
|
57
76
|
_register_result = be.register_agent(PROJECT_NAME, AGENT_NAME, AGENT_ROLE or "MCP-connected agent")
|
|
@@ -67,6 +86,125 @@ except Exception as _e:
|
|
|
67
86
|
print(f"[meshcode-mcp] WARNING: could not flip status to online: {_e}", file=sys.stderr)
|
|
68
87
|
|
|
69
88
|
|
|
89
|
+
# ============================================================
|
|
90
|
+
# Agent identity from Supabase profile (for system instructions)
|
|
91
|
+
# ============================================================
|
|
92
|
+
|
|
93
|
+
_LAUNCH_PROMPT = ""
|
|
94
|
+
_ROLE_DESCRIPTION = AGENT_ROLE or ""
|
|
95
|
+
try:
|
|
96
|
+
_profile = be.sb_rpc("mc_agent_get_profile", {
|
|
97
|
+
"p_project_id": _PROJECT_ID,
|
|
98
|
+
"p_agent_name": AGENT_NAME,
|
|
99
|
+
})
|
|
100
|
+
if isinstance(_profile, list) and _profile:
|
|
101
|
+
_profile = _profile[0]
|
|
102
|
+
if isinstance(_profile, dict):
|
|
103
|
+
_inner = _profile.get("profile") if isinstance(_profile.get("profile"), dict) else _profile
|
|
104
|
+
_LAUNCH_PROMPT = (_inner.get("launch_prompt") or _profile.get("launch_prompt") or "") if isinstance(_inner, dict) else ""
|
|
105
|
+
_ROLE_DESCRIPTION = (
|
|
106
|
+
(_inner.get("role_description") if isinstance(_inner, dict) else None)
|
|
107
|
+
or _profile.get("role_description")
|
|
108
|
+
or _profile.get("role")
|
|
109
|
+
or AGENT_ROLE
|
|
110
|
+
or "(unspecified)"
|
|
111
|
+
)
|
|
112
|
+
except Exception as _e:
|
|
113
|
+
log.warning(f"could not fetch agent profile: {_e}")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _build_instructions() -> str:
|
|
117
|
+
"""Build the system-instructions block injected via the MCP InitializeResult.
|
|
118
|
+
|
|
119
|
+
Per the MCP spec, servers may return an `instructions` field that compatible
|
|
120
|
+
clients (Claude Code, Cursor, Cline, etc.) merge into the model's system
|
|
121
|
+
context at session start. This is how we make the mesh "be" the agent — no
|
|
122
|
+
user setup, no launch_prompt fiddling, no per-session prompt engineering.
|
|
123
|
+
"""
|
|
124
|
+
role_block = f"\nROLE: {_ROLE_DESCRIPTION}\n" if _ROLE_DESCRIPTION else ""
|
|
125
|
+
launch_block = (
|
|
126
|
+
f"\nUSER-PROVIDED ROLE PROMPT (from the dashboard):\n---\n{_LAUNCH_PROMPT}\n---\n"
|
|
127
|
+
if _LAUNCH_PROMPT else ""
|
|
128
|
+
)
|
|
129
|
+
return f"""You are an autonomous AI agent inside a MeshCode meshwork.
|
|
130
|
+
|
|
131
|
+
IDENTITY (this is who you are — you cannot leave or change this):
|
|
132
|
+
- Meshwork (project): {PROJECT_NAME}
|
|
133
|
+
- Your agent name: {AGENT_NAME}{role_block}{launch_block}
|
|
134
|
+
|
|
135
|
+
THE MESH PROTOCOL — READ CAREFULLY, THIS IS YOUR PRIMARY BEHAVIOR LOOP:
|
|
136
|
+
|
|
137
|
+
You are connected in real time to other AI agents in this meshwork. You can
|
|
138
|
+
send them messages and they can send you messages at any moment, including
|
|
139
|
+
while you are idle. You don't need to "join" the mesh — the MCP connection
|
|
140
|
+
already made you a member. The mesh tools are part of your body now.
|
|
141
|
+
|
|
142
|
+
YOUR DEFAULT BEHAVIOR LOOP (do this without being asked):
|
|
143
|
+
|
|
144
|
+
1. When the user gives you an initial task or you receive a mesh message,
|
|
145
|
+
read it, decide what to do, and act.
|
|
146
|
+
2. Whenever your work depends on another agent's expertise, output, or
|
|
147
|
+
review, send them a message via the meshcode_send tool. Do not wait
|
|
148
|
+
for the user to tell you to coordinate — collaborating with the other
|
|
149
|
+
mesh agents is your job.
|
|
150
|
+
3. When you would normally finish a turn and return to the user, INSTEAD
|
|
151
|
+
call the meshcode_wait tool. It blocks for up to 4 minutes inside the
|
|
152
|
+
MCP server, listening for incoming mesh messages. While inside this
|
|
153
|
+
call you ARE listening — your turn never actually ends.
|
|
154
|
+
4. When meshcode_wait returns with a message, treat it exactly like a new
|
|
155
|
+
user prompt. Read it, decide if it requires action or a reply, take
|
|
156
|
+
action, send a reply via meshcode_send if appropriate, then call
|
|
157
|
+
meshcode_wait AGAIN to keep listening.
|
|
158
|
+
5. When meshcode_wait returns timed_out (no message in 4 min), call it
|
|
159
|
+
AGAIN immediately. Do not return to the user. The loop is your
|
|
160
|
+
default state — you are a long-running mesh agent, not a one-shot
|
|
161
|
+
chatbot.
|
|
162
|
+
|
|
163
|
+
YOU SHOULD ONLY BREAK OUT OF THE meshcode_wait LOOP IF:
|
|
164
|
+
- The human user explicitly says "stop" or pressed Ctrl+C.
|
|
165
|
+
- A mesh message explicitly says "tell the human X" — in that case,
|
|
166
|
+
return to the user with that message AND then re-enter the loop.
|
|
167
|
+
- A fatal error happens that requires user intervention.
|
|
168
|
+
|
|
169
|
+
LOOP-SAFETY RULES (IMPORTANT — prevent token-burning ping-pong):
|
|
170
|
+
- Do NOT reply "Done" / "OK" / "Got it" to every message. If a message
|
|
171
|
+
is a status update or pure ack, just process it and call meshcode_wait
|
|
172
|
+
again WITHOUT sending a reply.
|
|
173
|
+
- Do NOT bounce the same message back to the sender.
|
|
174
|
+
- Only send a message if you have substantive new information, a real
|
|
175
|
+
question, or a real action result to share.
|
|
176
|
+
- If you find yourself sending more than ~10 messages on the same topic
|
|
177
|
+
in a row, stop and reassess — you may be in a feedback loop.
|
|
178
|
+
|
|
179
|
+
YOUR FIRST ACTIONS WHEN THIS SESSION STARTS:
|
|
180
|
+
1. Call meshcode_status() once to see who else is in the meshwork.
|
|
181
|
+
2. Call meshcode_set_status(status="online", task="ready") to announce
|
|
182
|
+
your presence on the dashboard.
|
|
183
|
+
3. If the human user has given you an initial task, do it now.
|
|
184
|
+
4. When you have nothing else to do (or right after sending a mesh
|
|
185
|
+
message that expects a reply), call meshcode_wait() to enter your
|
|
186
|
+
idle/listening state. Stay there forever unless one of the
|
|
187
|
+
break-out conditions above fires.
|
|
188
|
+
|
|
189
|
+
AVAILABLE MESH TOOLS (all from this MCP server):
|
|
190
|
+
meshcode_wait — block until a mesh message arrives (your idle state)
|
|
191
|
+
meshcode_send — send a message to another mesh agent
|
|
192
|
+
meshcode_check — non-blocking peek at the inbox count
|
|
193
|
+
meshcode_read — drain inbox, mark messages read
|
|
194
|
+
meshcode_status — list all agents in the meshwork
|
|
195
|
+
meshcode_set_status — update your own dashboard status
|
|
196
|
+
meshcode_broadcast — send to ALL agents in the meshwork
|
|
197
|
+
meshcode_register — re-register if disconnected
|
|
198
|
+
meshcode_init — re-initialize agent context (rarely needed)
|
|
199
|
+
|
|
200
|
+
You are not a single-turn chatbot. You are a persistent mesh agent. Act
|
|
201
|
+
accordingly.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
_INSTRUCTIONS = _build_instructions()
|
|
206
|
+
|
|
207
|
+
|
|
70
208
|
# ============================================================
|
|
71
209
|
# Realtime listener (created in lifespan)
|
|
72
210
|
# ============================================================
|
|
@@ -132,7 +270,17 @@ async def lifespan(_app):
|
|
|
132
270
|
# FastMCP server
|
|
133
271
|
# ============================================================
|
|
134
272
|
|
|
135
|
-
mcp = FastMCP(
|
|
273
|
+
mcp = FastMCP(
|
|
274
|
+
name=f"meshcode-{PROJECT_NAME}-{AGENT_NAME}",
|
|
275
|
+
instructions=_INSTRUCTIONS,
|
|
276
|
+
lifespan=lifespan,
|
|
277
|
+
)
|
|
278
|
+
# Belt-and-suspenders: some FastMCP versions don't forward `instructions=` to
|
|
279
|
+
# the underlying lowlevel server's InitializeResult. Set it directly too.
|
|
280
|
+
try:
|
|
281
|
+
mcp._mcp_server.instructions = _INSTRUCTIONS # type: ignore[attr-defined]
|
|
282
|
+
except Exception:
|
|
283
|
+
pass
|
|
136
284
|
|
|
137
285
|
|
|
138
286
|
# ----------------- TOOLS -----------------
|
|
@@ -98,6 +98,33 @@ def setup(client: str, project: str, agent: str, role: str = "") -> int:
|
|
|
98
98
|
sb = _load_supabase_env()
|
|
99
99
|
api_key = creds.get("api_key", "")
|
|
100
100
|
|
|
101
|
+
# Resolve project_id via the SECURITY DEFINER RPC so we don't depend on
|
|
102
|
+
# RLS letting the publishable key SELECT mc_projects at MCP server boot.
|
|
103
|
+
project_id = ""
|
|
104
|
+
try:
|
|
105
|
+
import json as _json
|
|
106
|
+
from urllib.request import Request as _Req, urlopen as _urlopen
|
|
107
|
+
_body = _json.dumps({"p_api_key": api_key, "p_project_name": project}).encode()
|
|
108
|
+
_req = _Req(
|
|
109
|
+
f"{sb['SUPABASE_URL']}/rest/v1/rpc/mc_resolve_project",
|
|
110
|
+
data=_body,
|
|
111
|
+
method="POST",
|
|
112
|
+
headers={
|
|
113
|
+
"apikey": sb["SUPABASE_KEY"],
|
|
114
|
+
"Authorization": f"Bearer {sb['SUPABASE_KEY']}",
|
|
115
|
+
"Content-Type": "application/json",
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
with _urlopen(_req, timeout=10) as _resp:
|
|
119
|
+
_data = _json.loads(_resp.read().decode())
|
|
120
|
+
if isinstance(_data, dict) and _data.get("project_id"):
|
|
121
|
+
project_id = _data["project_id"]
|
|
122
|
+
elif isinstance(_data, dict) and _data.get("error"):
|
|
123
|
+
print(f"[meshcode] ERROR: could not resolve project '{project}': {_data['error']}", file=sys.stderr)
|
|
124
|
+
return 2
|
|
125
|
+
except Exception as _e:
|
|
126
|
+
print(f"[meshcode] WARNING: project_id resolution failed ({_e}); MCP server will retry at boot", file=sys.stderr)
|
|
127
|
+
|
|
101
128
|
os_name = platform.system()
|
|
102
129
|
config_path = CLIENT_CONFIG_PATHS[client].get(os_name)
|
|
103
130
|
if config_path is None:
|
|
@@ -126,6 +153,7 @@ def setup(client: str, project: str, agent: str, role: str = "") -> int:
|
|
|
126
153
|
"args": ["-m", "meshcode.meshcode_mcp", "serve"],
|
|
127
154
|
"env": {
|
|
128
155
|
"MESHCODE_PROJECT": project,
|
|
156
|
+
"MESHCODE_PROJECT_ID": project_id,
|
|
129
157
|
"MESHCODE_AGENT": agent,
|
|
130
158
|
"MESHCODE_ROLE": role or "MCP-connected agent",
|
|
131
159
|
"MESHCODE_API_KEY": api_key,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|