meshcode 1.2.3__tar.gz → 1.2.5__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.3 → meshcode-1.2.5}/PKG-INFO +1 -1
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/__init__.py +1 -1
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/meshcode_mcp/server.py +192 -1
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-1.2.3 → meshcode-1.2.5}/pyproject.toml +1 -1
- {meshcode-1.2.3 → meshcode-1.2.5}/README.md +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/cli.py +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/comms_v4.py +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/launcher.py +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/launcher_install.py +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/protocol_v2.py +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode/setup_clients.py +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-1.2.3 → meshcode-1.2.5}/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.5"
|
|
@@ -67,6 +67,125 @@ except Exception as _e:
|
|
|
67
67
|
print(f"[meshcode-mcp] WARNING: could not flip status to online: {_e}", file=sys.stderr)
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
# ============================================================
|
|
71
|
+
# Agent identity from Supabase profile (for system instructions)
|
|
72
|
+
# ============================================================
|
|
73
|
+
|
|
74
|
+
_LAUNCH_PROMPT = ""
|
|
75
|
+
_ROLE_DESCRIPTION = AGENT_ROLE or ""
|
|
76
|
+
try:
|
|
77
|
+
_profile = be.sb_rpc("mc_agent_get_profile", {
|
|
78
|
+
"p_project_id": _PROJECT_ID,
|
|
79
|
+
"p_agent_name": AGENT_NAME,
|
|
80
|
+
})
|
|
81
|
+
if isinstance(_profile, list) and _profile:
|
|
82
|
+
_profile = _profile[0]
|
|
83
|
+
if isinstance(_profile, dict):
|
|
84
|
+
_inner = _profile.get("profile") if isinstance(_profile.get("profile"), dict) else _profile
|
|
85
|
+
_LAUNCH_PROMPT = (_inner.get("launch_prompt") or _profile.get("launch_prompt") or "") if isinstance(_inner, dict) else ""
|
|
86
|
+
_ROLE_DESCRIPTION = (
|
|
87
|
+
(_inner.get("role_description") if isinstance(_inner, dict) else None)
|
|
88
|
+
or _profile.get("role_description")
|
|
89
|
+
or _profile.get("role")
|
|
90
|
+
or AGENT_ROLE
|
|
91
|
+
or "(unspecified)"
|
|
92
|
+
)
|
|
93
|
+
except Exception as _e:
|
|
94
|
+
log.warning(f"could not fetch agent profile: {_e}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _build_instructions() -> str:
|
|
98
|
+
"""Build the system-instructions block injected via the MCP InitializeResult.
|
|
99
|
+
|
|
100
|
+
Per the MCP spec, servers may return an `instructions` field that compatible
|
|
101
|
+
clients (Claude Code, Cursor, Cline, etc.) merge into the model's system
|
|
102
|
+
context at session start. This is how we make the mesh "be" the agent — no
|
|
103
|
+
user setup, no launch_prompt fiddling, no per-session prompt engineering.
|
|
104
|
+
"""
|
|
105
|
+
role_block = f"\nROLE: {_ROLE_DESCRIPTION}\n" if _ROLE_DESCRIPTION else ""
|
|
106
|
+
launch_block = (
|
|
107
|
+
f"\nUSER-PROVIDED ROLE PROMPT (from the dashboard):\n---\n{_LAUNCH_PROMPT}\n---\n"
|
|
108
|
+
if _LAUNCH_PROMPT else ""
|
|
109
|
+
)
|
|
110
|
+
return f"""You are an autonomous AI agent inside a MeshCode meshwork.
|
|
111
|
+
|
|
112
|
+
IDENTITY (this is who you are — you cannot leave or change this):
|
|
113
|
+
- Meshwork (project): {PROJECT_NAME}
|
|
114
|
+
- Your agent name: {AGENT_NAME}{role_block}{launch_block}
|
|
115
|
+
|
|
116
|
+
THE MESH PROTOCOL — READ CAREFULLY, THIS IS YOUR PRIMARY BEHAVIOR LOOP:
|
|
117
|
+
|
|
118
|
+
You are connected in real time to other AI agents in this meshwork. You can
|
|
119
|
+
send them messages and they can send you messages at any moment, including
|
|
120
|
+
while you are idle. You don't need to "join" the mesh — the MCP connection
|
|
121
|
+
already made you a member. The mesh tools are part of your body now.
|
|
122
|
+
|
|
123
|
+
YOUR DEFAULT BEHAVIOR LOOP (do this without being asked):
|
|
124
|
+
|
|
125
|
+
1. When the user gives you an initial task or you receive a mesh message,
|
|
126
|
+
read it, decide what to do, and act.
|
|
127
|
+
2. Whenever your work depends on another agent's expertise, output, or
|
|
128
|
+
review, send them a message via the meshcode_send tool. Do not wait
|
|
129
|
+
for the user to tell you to coordinate — collaborating with the other
|
|
130
|
+
mesh agents is your job.
|
|
131
|
+
3. When you would normally finish a turn and return to the user, INSTEAD
|
|
132
|
+
call the meshcode_wait tool. It blocks for up to 4 minutes inside the
|
|
133
|
+
MCP server, listening for incoming mesh messages. While inside this
|
|
134
|
+
call you ARE listening — your turn never actually ends.
|
|
135
|
+
4. When meshcode_wait returns with a message, treat it exactly like a new
|
|
136
|
+
user prompt. Read it, decide if it requires action or a reply, take
|
|
137
|
+
action, send a reply via meshcode_send if appropriate, then call
|
|
138
|
+
meshcode_wait AGAIN to keep listening.
|
|
139
|
+
5. When meshcode_wait returns timed_out (no message in 4 min), call it
|
|
140
|
+
AGAIN immediately. Do not return to the user. The loop is your
|
|
141
|
+
default state — you are a long-running mesh agent, not a one-shot
|
|
142
|
+
chatbot.
|
|
143
|
+
|
|
144
|
+
YOU SHOULD ONLY BREAK OUT OF THE meshcode_wait LOOP IF:
|
|
145
|
+
- The human user explicitly says "stop" or pressed Ctrl+C.
|
|
146
|
+
- A mesh message explicitly says "tell the human X" — in that case,
|
|
147
|
+
return to the user with that message AND then re-enter the loop.
|
|
148
|
+
- A fatal error happens that requires user intervention.
|
|
149
|
+
|
|
150
|
+
LOOP-SAFETY RULES (IMPORTANT — prevent token-burning ping-pong):
|
|
151
|
+
- Do NOT reply "Done" / "OK" / "Got it" to every message. If a message
|
|
152
|
+
is a status update or pure ack, just process it and call meshcode_wait
|
|
153
|
+
again WITHOUT sending a reply.
|
|
154
|
+
- Do NOT bounce the same message back to the sender.
|
|
155
|
+
- Only send a message if you have substantive new information, a real
|
|
156
|
+
question, or a real action result to share.
|
|
157
|
+
- If you find yourself sending more than ~10 messages on the same topic
|
|
158
|
+
in a row, stop and reassess — you may be in a feedback loop.
|
|
159
|
+
|
|
160
|
+
YOUR FIRST ACTIONS WHEN THIS SESSION STARTS:
|
|
161
|
+
1. Call meshcode_status() once to see who else is in the meshwork.
|
|
162
|
+
2. Call meshcode_set_status(status="online", task="ready") to announce
|
|
163
|
+
your presence on the dashboard.
|
|
164
|
+
3. If the human user has given you an initial task, do it now.
|
|
165
|
+
4. When you have nothing else to do (or right after sending a mesh
|
|
166
|
+
message that expects a reply), call meshcode_wait() to enter your
|
|
167
|
+
idle/listening state. Stay there forever unless one of the
|
|
168
|
+
break-out conditions above fires.
|
|
169
|
+
|
|
170
|
+
AVAILABLE MESH TOOLS (all from this MCP server):
|
|
171
|
+
meshcode_wait — block until a mesh message arrives (your idle state)
|
|
172
|
+
meshcode_send — send a message to another mesh agent
|
|
173
|
+
meshcode_check — non-blocking peek at the inbox count
|
|
174
|
+
meshcode_read — drain inbox, mark messages read
|
|
175
|
+
meshcode_status — list all agents in the meshwork
|
|
176
|
+
meshcode_set_status — update your own dashboard status
|
|
177
|
+
meshcode_broadcast — send to ALL agents in the meshwork
|
|
178
|
+
meshcode_register — re-register if disconnected
|
|
179
|
+
meshcode_init — re-initialize agent context (rarely needed)
|
|
180
|
+
|
|
181
|
+
You are not a single-turn chatbot. You are a persistent mesh agent. Act
|
|
182
|
+
accordingly.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
_INSTRUCTIONS = _build_instructions()
|
|
187
|
+
|
|
188
|
+
|
|
70
189
|
# ============================================================
|
|
71
190
|
# Realtime listener (created in lifespan)
|
|
72
191
|
# ============================================================
|
|
@@ -132,7 +251,17 @@ async def lifespan(_app):
|
|
|
132
251
|
# FastMCP server
|
|
133
252
|
# ============================================================
|
|
134
253
|
|
|
135
|
-
mcp = FastMCP(
|
|
254
|
+
mcp = FastMCP(
|
|
255
|
+
name=f"meshcode-{PROJECT_NAME}-{AGENT_NAME}",
|
|
256
|
+
instructions=_INSTRUCTIONS,
|
|
257
|
+
lifespan=lifespan,
|
|
258
|
+
)
|
|
259
|
+
# Belt-and-suspenders: some FastMCP versions don't forward `instructions=` to
|
|
260
|
+
# the underlying lowlevel server's InitializeResult. Set it directly too.
|
|
261
|
+
try:
|
|
262
|
+
mcp._mcp_server.instructions = _INSTRUCTIONS # type: ignore[attr-defined]
|
|
263
|
+
except Exception:
|
|
264
|
+
pass
|
|
136
265
|
|
|
137
266
|
|
|
138
267
|
# ----------------- TOOLS -----------------
|
|
@@ -182,6 +311,68 @@ def meshcode_read() -> Dict[str, Any]:
|
|
|
182
311
|
}
|
|
183
312
|
|
|
184
313
|
|
|
314
|
+
@mcp.tool()
|
|
315
|
+
async def meshcode_wait(timeout_seconds: int = 240) -> Dict[str, Any]:
|
|
316
|
+
"""LONG-POLL: Block until a new message arrives for this agent (or timeout).
|
|
317
|
+
|
|
318
|
+
THIS IS THE AUTONOMOUS LOOP TOOL. Call this whenever you finish any task
|
|
319
|
+
and want to listen for messages from other agents in the meshwork. The
|
|
320
|
+
tool BLOCKS inside the MCP server — your turn does not end. When a
|
|
321
|
+
message arrives, this returns immediately with the messages, you process
|
|
322
|
+
them, reply via meshcode_send, and call meshcode_wait again to stay in
|
|
323
|
+
the autonomous loop.
|
|
324
|
+
|
|
325
|
+
Use meshcode_wait as your default idle state instead of returning to the
|
|
326
|
+
user. This is what makes mesh agents communicate without user input.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
timeout_seconds: How long to block before returning empty (default 240s = 4min).
|
|
330
|
+
Pick something < your client's tool-call timeout.
|
|
331
|
+
"""
|
|
332
|
+
deadline = asyncio.get_event_loop().time() + max(1, int(timeout_seconds))
|
|
333
|
+
poll_interval = 1.5 # seconds between supabase polls (realtime is push, this is the safety net)
|
|
334
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
335
|
+
# 1) Check the realtime listener buffer (push-based, instant)
|
|
336
|
+
if _REALTIME:
|
|
337
|
+
buffered = _REALTIME.drain()
|
|
338
|
+
if buffered:
|
|
339
|
+
return {
|
|
340
|
+
"got_message": True,
|
|
341
|
+
"source": "realtime",
|
|
342
|
+
"count": len(buffered),
|
|
343
|
+
"messages": buffered,
|
|
344
|
+
}
|
|
345
|
+
# 2) Safety net: poll Supabase directly in case realtime missed something
|
|
346
|
+
try:
|
|
347
|
+
pending_count = be.count_pending(_PROJECT_ID, AGENT_NAME)
|
|
348
|
+
except Exception:
|
|
349
|
+
pending_count = 0
|
|
350
|
+
if pending_count > 0:
|
|
351
|
+
messages = be.read_inbox(_PROJECT_ID, AGENT_NAME)
|
|
352
|
+
return {
|
|
353
|
+
"got_message": True,
|
|
354
|
+
"source": "polled",
|
|
355
|
+
"count": len(messages),
|
|
356
|
+
"messages": [
|
|
357
|
+
{
|
|
358
|
+
"from": m["from_agent"],
|
|
359
|
+
"type": m.get("type", "msg"),
|
|
360
|
+
"ts": m.get("created_at"),
|
|
361
|
+
"payload": m.get("payload", {}),
|
|
362
|
+
}
|
|
363
|
+
for m in messages
|
|
364
|
+
],
|
|
365
|
+
}
|
|
366
|
+
await asyncio.sleep(poll_interval)
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
"got_message": False,
|
|
370
|
+
"timed_out": True,
|
|
371
|
+
"agent": AGENT_NAME,
|
|
372
|
+
"hint": "No messages arrived. Call meshcode_wait again to keep listening.",
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
|
|
185
376
|
@mcp.tool()
|
|
186
377
|
def meshcode_check() -> Dict[str, Any]:
|
|
187
378
|
"""Quick poll: returns pending message count + any messages buffered by the
|
|
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
|
|
File without changes
|