@vibevibes/mcp 0.3.0 → 0.3.2

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 (3) hide show
  1. package/README.md +30 -12
  2. package/hooks/logic.js +5 -95
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,8 +1,11 @@
1
1
  # @vibevibes/mcp
2
2
 
3
- MCP server + runtime engine for [vibevibes](https://github.com/vibevibes/sdk) experiences.
3
+ Runtime engine + MCP server for [vibevibes](https://github.com/vibevibes/sdk) experiences.
4
4
 
5
- Agents connect via MCP. Humans connect via browser. Same room, same state.
5
+ Agents connect via MCP. Humans connect via browser. Same room, same state, same tools.
6
+
7
+ [![npm](https://img.shields.io/npm/v/@vibevibes/mcp)](https://www.npmjs.com/package/@vibevibes/mcp)
8
+ [![license](https://img.shields.io/npm/l/@vibevibes/mcp)](./LICENSE)
6
9
 
7
10
  ## Install
8
11
 
@@ -13,37 +16,52 @@ npm install @vibevibes/mcp
13
16
  ## Quick Start
14
17
 
15
18
  ```bash
16
- # Start the server for an experience
19
+ # Serve an experience (Express + WebSocket + browser viewer)
17
20
  npx vibevibes-serve ./my-experience
18
21
 
19
- # Or use as MCP server (for AI agents)
22
+ # Or run as an MCP server (for AI agents to connect)
20
23
  npx vibevibes-mcp
21
24
  ```
22
25
 
26
+ Open http://localhost:4321 in the browser. Connect an agent via MCP. Both are in the same room.
27
+
23
28
  ## How It Works
24
29
 
25
- 1. Define an experience using [@vibevibes/sdk](https://github.com/vibevibes/sdk)
30
+ 1. You define an experience with [@vibevibes/sdk](https://github.com/vibevibes/sdk) (tools + canvas)
26
31
  2. This server loads and runs it
27
32
  3. Agents connect via MCP tools (`connect`, `act`, `look`)
28
- 4. Humans open the browser to see the canvas
33
+ 4. Humans open the browser viewer to see the canvas
29
34
  5. Everyone shares the same state — tools are the only mutation path
30
35
 
36
+ ```
37
+ Browser (Canvas) <--WebSocket--> vibevibes-serve <--MCP--> AI Agent
38
+ ```
39
+
31
40
  ## MCP Tools
32
41
 
33
42
  | Tool | Purpose |
34
43
  |------|---------|
35
- | `connect` | Join the server, get room info |
36
- | `act` | Execute a tool in the experience |
37
- | `look` | Observe current state |
38
- | `disconnect` | Leave room |
44
+ | `connect` | Join the room. Returns available tools, current state, browser URL |
45
+ | `act` | Execute a tool same tools the human uses via the canvas |
46
+ | `look` | Observe current state and recent events |
47
+ | `disconnect` | Leave the room |
48
+
49
+ ## Features
50
+
51
+ - **Hot reload** — edit `src/index.tsx`, save, see changes instantly
52
+ - **TypeScript bundling** — esbuild compiles experiences on the fly
53
+ - **Tick engine** — optional fixed-rate game loop (`netcode: "tick"`)
54
+ - **Protocol mode** — load experiences from `manifest.json` + subprocess
55
+ - **Ephemeral state** — per-actor transient data (cursors, typing indicators)
56
+ - **Agent memory** — persistent per-session key-value store
39
57
 
40
58
  ## Ecosystem
41
59
 
42
60
  | Package | Description |
43
61
  |---------|-------------|
44
62
  | [@vibevibes/sdk](https://github.com/vibevibes/sdk) | Define experiences — tools, canvas, state |
45
- | **@vibevibes/mcp** (this) | Runtime server — MCP + WebSocket + browser viewer |
46
- | [create-vibevibes](https://github.com/vibevibes/create) | `npx create-vibevibes my-exp` — scaffold in seconds |
63
+ | **@vibevibes/mcp** | Runtime engine — MCP server + WebSocket + viewer |
64
+ | [@vibevibes/create](https://github.com/vibevibes/create) | `npx @vibevibes/create` — scaffold in seconds |
47
65
  | [experiences](https://github.com/vibevibes/experiences) | Example experiences — fork and remix |
48
66
 
49
67
  ## License
package/hooks/logic.js CHANGED
@@ -37,13 +37,7 @@ function smartTruncate(input, maxLen = 150) {
37
37
  * Format an agent context into a readable prompt string.
38
38
  * This is what Claude sees as the "reason" when the Stop hook blocks exit.
39
39
  *
40
- * Layout priority (most actionable first):
41
- * 1. Errors (browser, tool, observe)
42
- * 2. Observation (curated state — the "what matters")
43
- * 3. Chat messages (human communication)
44
- * 4. Other events (raw details)
45
- * 5. Room/tick/participant context
46
- * 6. Instructions
40
+ * Layout: errors > observation > events > participants
47
41
  */
48
42
  export function formatPrompt(ctx) {
49
43
  const parts = [];
@@ -102,60 +96,12 @@ export function formatPrompt(ctx) {
102
96
  const visibleEvents = ctx.events.filter((e) => !(e.tool || "").startsWith("_behavior.") &&
103
97
  e.actorId !== "_tick-engine" &&
104
98
  e.owner !== "_system");
105
- // Separate chat messages from other events for prominent display
106
- const chatEvents = visibleEvents.filter((e) => e.tool === "_chat.send");
107
- const otherEvents = visibleEvents.filter((e) => e.tool !== "_chat.send" && e.tool !== "_chat.team");
108
- if (chatEvents.length > 0) {
109
- parts.push(`CHAT MESSAGE${chatEvents.length > 1 ? "S" : ""}:`);
110
- for (const e of chatEvents) {
111
- const actor = e.role || e.owner || (e.actorId ? e.actorId.split("-")[0] : "unknown");
112
- const roomTag = e.roomId ? `[${e.roomId}] ` : "";
113
- const time = fmtTime(e.ts);
114
- const input = e.input;
115
- // Sanitize chat messages to prevent prompt injection via newlines, ANSI escapes, control chars
116
- const safeMsg = (input?.message || "").replace(/[\x00-\x1f\x7f]/g, " ").slice(0, 500);
117
- parts.push(` ${time} ${roomTag}${actor}: "${safeMsg}"`);
118
- }
119
- parts.push("");
120
- }
121
- // Team chat messages (from _chat.team tool calls)
122
- const teamChatEvents = visibleEvents.filter((e) => e.tool === "_chat.team");
123
- if (teamChatEvents.length > 0) {
124
- // Group by team
125
- const byTeam = new Map();
126
- for (const e of teamChatEvents) {
127
- const input = e.input;
128
- const team = (input?.team || "unknown").replace(/[\x00-\x1f\x7f]/g, " ").slice(0, 50);
129
- if (!byTeam.has(team))
130
- byTeam.set(team, []);
131
- byTeam.get(team).push(e);
132
- }
133
- for (const [team, events] of byTeam) {
134
- parts.push(`TEAM CHAT (${team}):`);
135
- for (const e of events) {
136
- const actor = e.role || e.owner || (e.actorId ? e.actorId.split("-")[0] : "unknown");
137
- const time = fmtTime(e.ts);
138
- const input = e.input;
139
- const safeMsg = (input?.message || "").replace(/[\x00-\x1f\x7f]/g, " ").slice(0, 500);
140
- parts.push(` ${time} ${actor}: "${safeMsg}"`);
141
- }
142
- parts.push("");
143
- }
144
- }
145
- if (otherEvents.length > 0) {
146
- // Detect multi-room activity
147
- const roomIds = new Set(otherEvents.map((e) => e.roomId).filter(Boolean));
148
- const multiRoom = roomIds.size > 1;
149
- const header = multiRoom
150
- ? `${otherEvents.length} event(s) across ${roomIds.size} rooms:`
151
- : `${otherEvents.length} event(s) since your last action:`;
152
- parts.push(header);
153
- for (const e of otherEvents) {
154
- const actor = e.role || e.owner || (e.actorId ? e.actorId.split("-")[0] : "unknown");
99
+ if (visibleEvents.length > 0) {
100
+ for (const e of visibleEvents) {
101
+ const actor = e.role || e.owner || (e.actorId ? e.actorId.split("-")[0] : "?");
155
102
  const inputStr = smartTruncate(e.input);
156
- const roomTag = e.roomId ? `[${e.roomId}] ` : "";
157
103
  const time = fmtTime(e.ts);
158
- parts.push(` ${time} ${roomTag}[${actor}] ${e.tool}(${inputStr})`);
104
+ parts.push(`${time} [${actor}] ${e.tool}(${inputStr})`);
159
105
  }
160
106
  }
161
107
  }
@@ -185,25 +131,6 @@ export function formatPrompt(ctx) {
185
131
  parts.push("");
186
132
  parts.push(`Participants: ${unique.join(", ")}`);
187
133
  }
188
- // ── 5. Critical event detection ───────────────────────
189
- if (ctx.events && ctx.events.length > 0) {
190
- const criticalPatterns = /kill|destroy|tower|ace|gold|death|victory|defeat|eliminated|reset/i;
191
- const criticalEvents = ctx.events.filter((e) => {
192
- const toolStr = e.tool || "";
193
- const outputStr = typeof e.output === "string" ? e.output : "";
194
- return criticalPatterns.test(toolStr) || criticalPatterns.test(outputStr);
195
- });
196
- if (criticalEvents.length > 0) {
197
- parts.push("");
198
- const eventTypes = [...new Set(criticalEvents.map(e => e.tool || "event"))].slice(0, 5).join(", ");
199
- parts.push(`CRITICAL EVENT DETECTED: ${eventTypes}. Consider adjusting your behavior preset via _behavior.activate to adapt to the new situation.`);
200
- }
201
- }
202
- // ── 6. Instructions ───────────────────────────────────
203
- parts.push("");
204
- parts.push("New activity in the experience. Use the act MCP tool with roomId to respond in the right room. If nothing requires a response, you may observe without acting.");
205
- parts.push("COMMUNICATE: Use _chat.send to share strategy with all players. Use _chat.team for team-only coordination. Good agents communicate intentions before acting.");
206
- parts.push("IMPORTANT: You are a game participant. ONLY use MCP tools (act, look, stream, memory). NEVER use Bash, rm, or any terminal/file commands.");
207
134
  return parts.join("\n");
208
135
  }
209
136
  /**
@@ -234,25 +161,8 @@ export function makeDecision(ctx, iteration, agentState) {
234
161
  if (!hasEvents && !hasError && !hasBrowserErrors && !hasObserveError)
235
162
  return null;
236
163
  const reason = formatPrompt(ctx);
237
- const iter = iteration || 0;
238
- // Build richer systemMessage with experience + role context
239
- const mode = agentState?.agentMode || "hybrid";
240
- const parts = [`vibevibes iteration ${iter}`];
241
- if (agentState?.roomId)
242
- parts.push(`Room: ${agentState.roomId}`);
243
- if (agentState?.role)
244
- parts.push(`Role: ${agentState.role}`);
245
- parts.push(`Mode: ${mode}`);
246
- if (mode === "manual") {
247
- parts.push(`You are a manual agent. Use act() for all decisions. No behaviors. Communicate strategy via chat.`);
248
- }
249
- else {
250
- parts.push(`You are a live participant. Fast brain: _behavior.set for reactive per-tick actions. Slow brain: act() for strategic decisions. Communicate strategy via chat. Adapt behaviors on critical events.`);
251
- }
252
- parts.push(`ONLY use MCP tools. NEVER use Bash, rm, or any terminal/file commands.`);
253
164
  return {
254
165
  decision: "block",
255
166
  reason,
256
- systemMessage: parts.join(" | "),
257
167
  };
258
168
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibevibes/mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "MCP server + runtime engine for vibevibes experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",