@vibevibes/mcp 0.1.0 → 0.3.0
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.
- package/LICENSE +21 -0
- package/README.md +51 -0
- package/bin/cli.js +11 -11
- package/bin/postinstall.js +39 -0
- package/bin/serve.js +41 -0
- package/dist/bundler.d.ts +36 -0
- package/dist/bundler.js +254 -0
- package/dist/index.d.ts +4 -8
- package/dist/index.js +767 -221
- package/dist/protocol.d.ts +85 -0
- package/dist/protocol.js +240 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.js +1947 -0
- package/dist/tick-engine.d.ts +81 -0
- package/dist/tick-engine.js +151 -0
- package/dist/viewer/index.html +689 -0
- package/hooks/logic.js +258 -0
- package/hooks/stop-hook.js +341 -0
- package/package.json +59 -33
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 vibevibes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @vibevibes/mcp
|
|
2
|
+
|
|
3
|
+
MCP server + runtime engine for [vibevibes](https://github.com/vibevibes/sdk) experiences.
|
|
4
|
+
|
|
5
|
+
Agents connect via MCP. Humans connect via browser. Same room, same state.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @vibevibes/mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Start the server for an experience
|
|
17
|
+
npx vibevibes-serve ./my-experience
|
|
18
|
+
|
|
19
|
+
# Or use as MCP server (for AI agents)
|
|
20
|
+
npx vibevibes-mcp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## How It Works
|
|
24
|
+
|
|
25
|
+
1. Define an experience using [@vibevibes/sdk](https://github.com/vibevibes/sdk)
|
|
26
|
+
2. This server loads and runs it
|
|
27
|
+
3. Agents connect via MCP tools (`connect`, `act`, `look`)
|
|
28
|
+
4. Humans open the browser to see the canvas
|
|
29
|
+
5. Everyone shares the same state — tools are the only mutation path
|
|
30
|
+
|
|
31
|
+
## MCP Tools
|
|
32
|
+
|
|
33
|
+
| Tool | Purpose |
|
|
34
|
+
|------|---------|
|
|
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 |
|
|
39
|
+
|
|
40
|
+
## Ecosystem
|
|
41
|
+
|
|
42
|
+
| Package | Description |
|
|
43
|
+
|---------|-------------|
|
|
44
|
+
| [@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 |
|
|
47
|
+
| [experiences](https://github.com/vibevibes/experiences) | Example experiences — fork and remix |
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT
|
package/bin/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @vibevibes/mcp CLI
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* npx @vibevibes/mcp # localhost:4321
|
|
8
|
-
* npx @vibevibes/mcp https://xyz.trycloudflare.com # remote shared room
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import "../dist/index.js";
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @vibevibes/mcp CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx @vibevibes/mcp # localhost:4321
|
|
8
|
+
* npx @vibevibes/mcp https://xyz.trycloudflare.com # remote shared room
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import "../dist/index.js";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Postinstall: writes .mcp.json into the project root so Claude Code
|
|
5
|
+
* can discover the vibevibes MCP server automatically.
|
|
6
|
+
*
|
|
7
|
+
* Skipped during self-install (e.g. when building the monorepo).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { writeFileSync, existsSync } from "node:fs";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
|
|
13
|
+
// npm sets INIT_CWD to the directory where `npm install` was run
|
|
14
|
+
const projectRoot = process.env.INIT_CWD;
|
|
15
|
+
if (!projectRoot) process.exit(0);
|
|
16
|
+
|
|
17
|
+
// Skip if we're installing inside the vibevibes monorepo itself
|
|
18
|
+
const isMonorepo = existsSync(resolve(projectRoot, "sdk")) && existsSync(resolve(projectRoot, "mcp"));
|
|
19
|
+
if (isMonorepo) process.exit(0);
|
|
20
|
+
|
|
21
|
+
const mcpJsonPath = resolve(projectRoot, ".mcp.json");
|
|
22
|
+
|
|
23
|
+
// Don't overwrite an existing .mcp.json
|
|
24
|
+
if (existsSync(mcpJsonPath)) process.exit(0);
|
|
25
|
+
|
|
26
|
+
const config = {
|
|
27
|
+
mcpServers: {
|
|
28
|
+
vibevibes: {
|
|
29
|
+
command: "npx",
|
|
30
|
+
args: ["vibevibes-mcp"],
|
|
31
|
+
env: {
|
|
32
|
+
VIBEVIBES_SERVER_URL: "http://localhost:4321",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
39
|
+
console.log("vibevibes: wrote .mcp.json for Claude Code");
|
package/bin/serve.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Start the vibevibes server for an experience.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx vibevibes-serve # serves from current directory
|
|
8
|
+
* npx vibevibes-serve ./my-exp # serves from a specific path
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import { writeFileSync, existsSync } from "node:fs";
|
|
13
|
+
import { execSync } from "node:child_process";
|
|
14
|
+
import { startServer } from "../dist/server.js";
|
|
15
|
+
|
|
16
|
+
const projectRoot = resolve(process.argv[2] || ".");
|
|
17
|
+
const port = parseInt(process.env.PORT || "4321", 10);
|
|
18
|
+
|
|
19
|
+
// Find git root (where Claude Code reads .mcp.json from)
|
|
20
|
+
let mcpJsonDir = projectRoot;
|
|
21
|
+
try {
|
|
22
|
+
mcpJsonDir = execSync("git rev-parse --show-toplevel", { cwd: projectRoot, encoding: "utf-8" }).trim();
|
|
23
|
+
} catch {}
|
|
24
|
+
|
|
25
|
+
// Auto-generate .mcp.json so Claude Code can connect via /mcp
|
|
26
|
+
const mcpJsonPath = resolve(mcpJsonDir, ".mcp.json");
|
|
27
|
+
if (!existsSync(mcpJsonPath)) {
|
|
28
|
+
const config = {
|
|
29
|
+
mcpServers: {
|
|
30
|
+
vibevibes: {
|
|
31
|
+
command: "npx",
|
|
32
|
+
args: ["vibevibes-mcp"],
|
|
33
|
+
env: { VIBEVIBES_SERVER_URL: `http://localhost:${port}` },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
38
|
+
console.log(` .mcp.json: wrote ${mcpJsonPath}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
startServer({ projectRoot, port });
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experience bundler — produces server and client bundles from src/index.tsx.
|
|
3
|
+
*
|
|
4
|
+
* Server bundle: CJS, eval'd via new Function() to extract tools + manifest.
|
|
5
|
+
* Client bundle: ESM, loaded in browser via blob URL + dynamic import().
|
|
6
|
+
*
|
|
7
|
+
* Extracted from create-experience/runtime/bundler.ts into @vibevibes/runtime.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Bundle for server-side tool execution (Node.js eval).
|
|
11
|
+
* Returns the raw ExperienceModule extracted via new Function().
|
|
12
|
+
*/
|
|
13
|
+
export declare function bundleForServer(entryPath: string): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Evaluate a server bundle and extract the ExperienceModule.
|
|
16
|
+
*/
|
|
17
|
+
export declare function evalServerBundle(serverCode: string): Promise<unknown>;
|
|
18
|
+
/**
|
|
19
|
+
* Bundle for client-side Canvas rendering (browser).
|
|
20
|
+
* Returns pure ESM. External imports (react, zod, etc.) are left as-is —
|
|
21
|
+
* the viewer's import map resolves them in the browser.
|
|
22
|
+
*/
|
|
23
|
+
export declare function bundleForClient(entryPath: string): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Build both bundles from an entry file.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildExperience(entryPath: string): Promise<{
|
|
28
|
+
serverCode: string;
|
|
29
|
+
clientCode: string;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Validate a client bundle for common issues.
|
|
33
|
+
* Lightweight — esbuild already validates syntax. This just checks the output exists.
|
|
34
|
+
* Returns null if OK, or an error message string.
|
|
35
|
+
*/
|
|
36
|
+
export declare function validateClientBundle(code: string): string | null;
|
package/dist/bundler.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experience bundler — produces server and client bundles from src/index.tsx.
|
|
3
|
+
*
|
|
4
|
+
* Server bundle: CJS, eval'd via new Function() to extract tools + manifest.
|
|
5
|
+
* Client bundle: ESM, loaded in browser via blob URL + dynamic import().
|
|
6
|
+
*
|
|
7
|
+
* Extracted from create-experience/runtime/bundler.ts into @vibevibes/runtime.
|
|
8
|
+
*/
|
|
9
|
+
import * as esbuild from "esbuild";
|
|
10
|
+
const EXTERNALS = ["react", "react/jsx-runtime", "react-dom", "react-dom/client", "yjs", "zod", "@vibevibes/sdk", "@vibevibes/runtime"];
|
|
11
|
+
// Additional externals for server-only bundles — heavy rendering libraries that
|
|
12
|
+
// aren't needed for tool/test execution. Canvas components are never called server-side.
|
|
13
|
+
const SERVER_ONLY_EXTERNALS = [
|
|
14
|
+
...EXTERNALS,
|
|
15
|
+
"three", "three/*",
|
|
16
|
+
"@react-three/fiber", "@react-three/fiber/*",
|
|
17
|
+
"@react-three/drei", "@react-three/drei/*",
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* Strip esbuild's CJS annotation: `0 && (module.exports = {...})`.
|
|
21
|
+
* Uses brace-depth counting to handle nested objects/functions in the annotation,
|
|
22
|
+
* unlike a simple `[^}]*` regex which breaks on nested braces.
|
|
23
|
+
*/
|
|
24
|
+
function stripCjsAnnotation(code) {
|
|
25
|
+
const marker = /0\s*&&\s*\(module\.exports\s*=\s*\{/g;
|
|
26
|
+
let match;
|
|
27
|
+
let result = code;
|
|
28
|
+
while ((match = marker.exec(code)) !== null) {
|
|
29
|
+
const start = match.index;
|
|
30
|
+
let depth = 1; // we matched the opening `{`
|
|
31
|
+
let i = match.index + match[0].length;
|
|
32
|
+
while (i < code.length && depth > 0) {
|
|
33
|
+
if (code[i] === "{")
|
|
34
|
+
depth++;
|
|
35
|
+
else if (code[i] === "}")
|
|
36
|
+
depth--;
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
// Skip the closing `)` and optional `;`
|
|
40
|
+
if (i < code.length && code[i] === ")")
|
|
41
|
+
i++;
|
|
42
|
+
if (i < code.length && code[i] === ";")
|
|
43
|
+
i++;
|
|
44
|
+
result = result.slice(0, start) + "/* [vibevibes] stripped CJS annotation */" + result.slice(i);
|
|
45
|
+
break; // Only one annotation per bundle
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Strip import/export statements for external packages.
|
|
51
|
+
* The runtime provides these via globalThis (browser) or function args (server).
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* Strip external imports from a CJS server bundle so it can be eval'd with new Function().
|
|
55
|
+
* Only used for server bundles — client bundles keep imports (resolved by browser import map).
|
|
56
|
+
*/
|
|
57
|
+
function stripExternalImports(code, externals = EXTERNALS) {
|
|
58
|
+
let result = code;
|
|
59
|
+
for (const ext of externals) {
|
|
60
|
+
let escaped;
|
|
61
|
+
if (ext.endsWith("/*")) {
|
|
62
|
+
const base = ext.slice(0, -2).replace(/[.*+?^${}()|[\]\\\/]/g, "\\$&");
|
|
63
|
+
escaped = `${base}\\/[^"']+`;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
escaped = ext.replace(/[.*+?^${}()|[\]\\\/]/g, "\\$&");
|
|
67
|
+
}
|
|
68
|
+
// CJS: var import_X = __toESM(require("pkg"), N); or var import_X = require("pkg");
|
|
69
|
+
result = result.replace(new RegExp(`var\\s+\\w+\\s*=\\s*(?:__toESM\\()?require\\(["']${escaped}["']\\)[^;]{0,500};`, "g"), "");
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* CJS shim definitions for server-side eval (new Function()).
|
|
75
|
+
* Maps esbuild-generated variable names to runtime-provided globals.
|
|
76
|
+
*/
|
|
77
|
+
const SDK_CORE_SHIM = "{ defineExperience: defineExperience, defineTool: defineTool, defineTest: defineTest, defineStream: defineStream, createChatTools: createChatTools, default: { defineExperience: defineExperience, defineTool: defineTool, defineTest: defineTest, defineStream: defineStream, createChatTools: createChatTools } }";
|
|
78
|
+
const CJS_BASE_SHIMS = {
|
|
79
|
+
import_react: "{ default: React, __esModule: true, createElement: React.createElement, Fragment: React.Fragment, useState: React.useState, useEffect: React.useEffect, useCallback: React.useCallback, useMemo: React.useMemo, useRef: React.useRef, useContext: React.useContext, useReducer: React.useReducer, createContext: React.createContext, forwardRef: React.forwardRef, memo: React.memo }",
|
|
80
|
+
import_zod: "{ z: z, default: z }",
|
|
81
|
+
import_yjs: "{ default: Y }",
|
|
82
|
+
import_sdk: SDK_CORE_SHIM,
|
|
83
|
+
import_vibevibes_sdk: SDK_CORE_SHIM,
|
|
84
|
+
import_runtime: SDK_CORE_SHIM,
|
|
85
|
+
import_vibevibes_runtime: SDK_CORE_SHIM,
|
|
86
|
+
import_react_dom: "{ default: {}, __esModule: true }",
|
|
87
|
+
import_client: "{ default: {}, __esModule: true }",
|
|
88
|
+
// Proxy stubs for rendering libraries (server-side only — Canvas is never called)
|
|
89
|
+
import_three: "(new Proxy({}, { get: (_, p) => typeof p === 'string' ? function(){} : undefined }))",
|
|
90
|
+
import_fiber: "(new Proxy({}, { get: (_, p) => typeof p === 'string' ? function(){} : undefined }))",
|
|
91
|
+
import_drei: "(new Proxy({}, { get: (_, p) => typeof p === 'string' ? function(){} : undefined }))",
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Inject CJS shim variables for server-side eval.
|
|
95
|
+
*/
|
|
96
|
+
function injectCjsShims(code) {
|
|
97
|
+
const lines = [];
|
|
98
|
+
// Emit base shims
|
|
99
|
+
for (const [name, value] of Object.entries(CJS_BASE_SHIMS)) {
|
|
100
|
+
lines.push(`var ${name} = ${value};`);
|
|
101
|
+
}
|
|
102
|
+
// Scan for numbered variants (e.g. import_react2, import_zod3) and alias them
|
|
103
|
+
for (const baseName of Object.keys(CJS_BASE_SHIMS)) {
|
|
104
|
+
const pattern = new RegExp(`\\b(${baseName}(\\d+))\\b`, "g");
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
let match;
|
|
107
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
108
|
+
const numberedName = match[1];
|
|
109
|
+
if (!seen.has(numberedName)) {
|
|
110
|
+
seen.add(numberedName);
|
|
111
|
+
lines.push(`var ${numberedName} = ${baseName};`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return lines.join("\n");
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Format esbuild errors into actionable messages with file:line and suggestions.
|
|
119
|
+
*/
|
|
120
|
+
function formatEsbuildError(err, target) {
|
|
121
|
+
const buildErr = err;
|
|
122
|
+
if (buildErr.errors && Array.isArray(buildErr.errors)) {
|
|
123
|
+
const formatted = buildErr.errors.map((e) => {
|
|
124
|
+
const loc = e.location
|
|
125
|
+
? `${e.location.file}:${e.location.line}:${e.location.column}`
|
|
126
|
+
: "unknown location";
|
|
127
|
+
return ` ${loc}: ${e.text}`;
|
|
128
|
+
}).join("\n");
|
|
129
|
+
return new Error(`Build failed (${target} bundle):\n${formatted}\n\n` +
|
|
130
|
+
`Common fixes:\n` +
|
|
131
|
+
`- Check for syntax errors at the indicated location\n` +
|
|
132
|
+
`- Ensure all imports resolve to existing files in src/\n` +
|
|
133
|
+
`- Verify @vibevibes/sdk imports match the available exports`);
|
|
134
|
+
}
|
|
135
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Bundle for server-side tool execution (Node.js eval).
|
|
139
|
+
* Returns the raw ExperienceModule extracted via new Function().
|
|
140
|
+
*/
|
|
141
|
+
export async function bundleForServer(entryPath) {
|
|
142
|
+
let result;
|
|
143
|
+
try {
|
|
144
|
+
result = await esbuild.build({
|
|
145
|
+
entryPoints: [entryPath],
|
|
146
|
+
bundle: true,
|
|
147
|
+
format: "cjs",
|
|
148
|
+
platform: "node",
|
|
149
|
+
target: "es2022",
|
|
150
|
+
write: false,
|
|
151
|
+
external: SERVER_ONLY_EXTERNALS,
|
|
152
|
+
jsx: "transform",
|
|
153
|
+
jsxFactory: "React.createElement",
|
|
154
|
+
jsxFragment: "React.Fragment",
|
|
155
|
+
logLevel: "silent",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
throw formatEsbuildError(err, "server");
|
|
160
|
+
}
|
|
161
|
+
const outputFile = result.outputFiles?.[0];
|
|
162
|
+
if (!outputFile)
|
|
163
|
+
throw new Error("esbuild produced no output files for server bundle");
|
|
164
|
+
let code = outputFile.text;
|
|
165
|
+
code = stripExternalImports(code, SERVER_ONLY_EXTERNALS);
|
|
166
|
+
// Strip user-code React hook destructuring (already provided by CJS shims)
|
|
167
|
+
code = code.replace(/(?:const|let|var)\s+\{[^}]*?\b(?:useState|useEffect|useCallback|useMemo|useRef|useContext|useReducer)\b[^}]*?\}\s*=\s*(?:React|import_react\w*)\s*;/g, "/* [vibevibes] stripped duplicate React destructuring */");
|
|
168
|
+
// Inject CJS shims for esbuild-generated variable references
|
|
169
|
+
// Pass code so we can detect numbered variants (import_react2, etc.)
|
|
170
|
+
code = injectCjsShims(code) + "\n" + code;
|
|
171
|
+
// Strip esbuild's CJS annotation `0 && (module.exports = {...})` — dead code that
|
|
172
|
+
// causes syntax errors when module.exports is replaced with var assignment.
|
|
173
|
+
// Uses brace-depth counting to handle nested objects/functions in the annotation.
|
|
174
|
+
code = stripCjsAnnotation(code);
|
|
175
|
+
// Replace module.exports/export default with variable assignment
|
|
176
|
+
code = code.replace(/module\.exports\s*=\s*/g, "var __experience_export__ = ");
|
|
177
|
+
code = code.replace(/exports\.default(?!\w)\s*=\s*/g, "var __experience_export__ = ");
|
|
178
|
+
return code;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Evaluate a server bundle and extract the ExperienceModule.
|
|
182
|
+
*/
|
|
183
|
+
export async function evalServerBundle(serverCode) {
|
|
184
|
+
const sdk = await import("@vibevibes/sdk");
|
|
185
|
+
const { defineExperience, defineTool, defineTest, defineStream, createChatTools } = sdk;
|
|
186
|
+
const noop = () => null;
|
|
187
|
+
const stubReact = {
|
|
188
|
+
createElement: noop, Fragment: "Fragment",
|
|
189
|
+
useState: (init) => [typeof init === "function" ? init() : init, noop],
|
|
190
|
+
useEffect: noop, useCallback: (fn) => fn,
|
|
191
|
+
useMemo: (fn) => fn(), useRef: (init) => ({ current: init ?? null }),
|
|
192
|
+
useContext: noop, useReducer: noop,
|
|
193
|
+
createContext: noop, forwardRef: noop, memo: (x) => x,
|
|
194
|
+
};
|
|
195
|
+
const zodModule = await import("zod");
|
|
196
|
+
const z = zodModule.z ?? zodModule.default ?? zodModule;
|
|
197
|
+
const fn = new Function("globalThis", "process", "global", "React", "Y", "z", "defineExperience", "defineTool", "defineTest", "defineStream", "createChatTools", "require", "exports", "module", "console", `"use strict";\n${serverCode}\nreturn typeof __experience_export__ !== 'undefined' ? __experience_export__ : (typeof module !== 'undefined' ? module.exports : undefined);`);
|
|
198
|
+
const fakeModule = { exports: {} };
|
|
199
|
+
const sandboxGlobal = Object.create(null);
|
|
200
|
+
const sandboxProcess = { env: { NODE_ENV: "production" } };
|
|
201
|
+
const result = fn(sandboxGlobal, sandboxProcess, sandboxGlobal, stubReact, {}, z, defineExperience, defineTool, defineTest, defineStream, createChatTools, (id) => { throw new Error(`require('${id}') is not supported in the vibevibes server sandbox. Add '${id}' to EXTERNALS in bundler.ts.`); }, fakeModule.exports, fakeModule, console);
|
|
202
|
+
const exports = fakeModule.exports;
|
|
203
|
+
return result?.default ?? result ?? exports?.default ?? exports;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Bundle for client-side Canvas rendering (browser).
|
|
207
|
+
* Returns pure ESM. External imports (react, zod, etc.) are left as-is —
|
|
208
|
+
* the viewer's import map resolves them in the browser.
|
|
209
|
+
*/
|
|
210
|
+
export async function bundleForClient(entryPath) {
|
|
211
|
+
let result;
|
|
212
|
+
try {
|
|
213
|
+
result = await esbuild.build({
|
|
214
|
+
entryPoints: [entryPath],
|
|
215
|
+
bundle: true,
|
|
216
|
+
format: "esm",
|
|
217
|
+
platform: "browser",
|
|
218
|
+
target: "es2020",
|
|
219
|
+
write: false,
|
|
220
|
+
external: EXTERNALS,
|
|
221
|
+
jsx: "transform",
|
|
222
|
+
jsxFactory: "React.createElement",
|
|
223
|
+
jsxFragment: "React.Fragment",
|
|
224
|
+
logLevel: "silent",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
throw formatEsbuildError(err, "client");
|
|
229
|
+
}
|
|
230
|
+
const clientOutputFile = result.outputFiles?.[0];
|
|
231
|
+
if (!clientOutputFile)
|
|
232
|
+
throw new Error("esbuild produced no output files for client bundle");
|
|
233
|
+
return clientOutputFile.text;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Build both bundles from an entry file.
|
|
237
|
+
*/
|
|
238
|
+
export async function buildExperience(entryPath) {
|
|
239
|
+
const [serverCode, clientCode] = await Promise.all([
|
|
240
|
+
bundleForServer(entryPath),
|
|
241
|
+
bundleForClient(entryPath),
|
|
242
|
+
]);
|
|
243
|
+
return { serverCode, clientCode };
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Validate a client bundle for common issues.
|
|
247
|
+
* Lightweight — esbuild already validates syntax. This just checks the output exists.
|
|
248
|
+
* Returns null if OK, or an error message string.
|
|
249
|
+
*/
|
|
250
|
+
export function validateClientBundle(code) {
|
|
251
|
+
if (!code || !code.trim())
|
|
252
|
+
return "Client bundle is empty";
|
|
253
|
+
return null;
|
|
254
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* vibevibes-mcp —
|
|
2
|
+
* vibevibes-mcp — MCP server for agent participation in vibevibes experiences.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The `connect` tool is the single entry point: it joins the server, writes
|
|
5
|
+
* the state file (for the stop hook to poll), and returns room info.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
* npx vibevibes-mcp # defaults to http://localhost:4321
|
|
8
|
-
* npx vibevibes-mcp https://xyz.trycloudflare.com # join a shared room
|
|
9
|
-
* VIBEVIBES_SERVER_URL=https://... npx vibevibes-mcp
|
|
10
|
-
*
|
|
11
|
-
* 5 tools: connect, watch, act, memory, screenshot
|
|
7
|
+
* 4 tools: connect, act, look, disconnect
|
|
12
8
|
*/
|
|
13
9
|
export {};
|