Skip to content

Commit a4c7f35

Browse files
authored
Merge pull request #40 from PostHog/add-dev-server
Add dev server with hot-rebuild of the resource zip file
2 parents a3dd7b2 + ba60245 commit a4c7f35

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"description": "PostHog example projects",
66
"scripts": {
77
"build:docs": "node scripts/build-examples-mcp-resources.js",
8+
"dev": "node scripts/dev-server.js",
89
"test:plugins": "vitest run scripts/plugins/tests",
910
"test:plugins:watch": "vitest scripts/plugins/tests"
1011
},

scripts/dev-server.js

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Development server for MCP resources
5+
*
6+
* Serves the generated ZIP file over HTTP and watches markdown files
7+
* for changes, automatically rebuilding when needed.
8+
*
9+
* Usage: npm run dev
10+
*
11+
* To use a different port:
12+
* PORT=3000 npm run dev
13+
*
14+
* Then update the MCP server command to match:
15+
* pnpm run dev:local-resources (and update wrangler --var flag)
16+
*/
17+
18+
const http = require('http');
19+
const fs = require('fs');
20+
const path = require('path');
21+
const { spawn } = require('child_process');
22+
23+
const PORT = process.env.PORT || 8765;
24+
const ZIP_PATH = path.join(__dirname, '..', 'dist', 'examples-mcp-resources.zip');
25+
26+
// Directories to watch for changes
27+
const WATCH_DIRS = [
28+
path.join(__dirname, '..', 'llm-prompts'),
29+
path.join(__dirname, '..', 'mcp-commands'),
30+
path.join(__dirname, '..', 'basics'),
31+
];
32+
33+
let isRebuilding = false;
34+
let rebuildQueued = false;
35+
36+
/**
37+
* Run the build script
38+
*/
39+
function rebuild() {
40+
if (isRebuilding) {
41+
rebuildQueued = true;
42+
return;
43+
}
44+
45+
console.log('\n🔨 Rebuilding resources...');
46+
isRebuilding = true;
47+
48+
const buildProcess = spawn('node', [path.join(__dirname, 'build-examples-mcp-resources.js')], {
49+
stdio: 'inherit',
50+
cwd: path.join(__dirname, '..')
51+
});
52+
53+
buildProcess.on('close', (code) => {
54+
isRebuilding = false;
55+
56+
if (code === 0) {
57+
console.log('✅ Rebuild complete!\n');
58+
} else {
59+
console.error(`❌ Build failed with code ${code}\n`);
60+
}
61+
62+
// If another rebuild was queued, run it now
63+
if (rebuildQueued) {
64+
rebuildQueued = false;
65+
rebuild();
66+
}
67+
});
68+
}
69+
70+
/**
71+
* Watch directories for markdown file changes
72+
*/
73+
function setupWatchers() {
74+
console.log('\n👀 Watching for changes in:');
75+
76+
WATCH_DIRS.forEach(dir => {
77+
if (!fs.existsSync(dir)) {
78+
console.log(` ⚠️ ${path.relative(path.join(__dirname, '..'), dir)} (not found, skipping)`);
79+
return;
80+
}
81+
82+
console.log(` 📁 ${path.relative(path.join(__dirname, '..'), dir)}`);
83+
84+
// Watch recursively
85+
fs.watch(dir, { recursive: true }, (eventType, filename) => {
86+
if (!filename) return;
87+
88+
// Only trigger on markdown or JSON files
89+
if (filename.endsWith('.md') || filename.endsWith('.json')) {
90+
console.log(`\n📝 Changed: ${filename}`);
91+
rebuild();
92+
}
93+
});
94+
});
95+
}
96+
97+
/**
98+
* Create HTTP server to serve the ZIP file
99+
*/
100+
function createServer() {
101+
const server = http.createServer((req, res) => {
102+
// Only serve the ZIP file
103+
if (req.url === '/examples-mcp-resources.zip' || req.url === '/') {
104+
if (!fs.existsSync(ZIP_PATH)) {
105+
res.writeHead(404, { 'Content-Type': 'text/plain' });
106+
res.end('ZIP file not found. Run build first.');
107+
return;
108+
}
109+
110+
const stat = fs.statSync(ZIP_PATH);
111+
const fileSize = stat.size;
112+
const fileStream = fs.createReadStream(ZIP_PATH);
113+
114+
res.writeHead(200, {
115+
'Content-Type': 'application/zip',
116+
'Content-Length': fileSize,
117+
'Content-Disposition': 'attachment; filename="examples-mcp-resources.zip"',
118+
'Cache-Control': 'no-cache, no-store, must-revalidate',
119+
'Pragma': 'no-cache',
120+
'Expires': '0'
121+
});
122+
123+
fileStream.pipe(res);
124+
125+
console.log(`📦 Served ZIP file (${(fileSize / 1024).toFixed(1)} KB)`);
126+
} else {
127+
res.writeHead(404, { 'Content-Type': 'text/plain' });
128+
res.end('Not found. Use /examples-mcp-resources.zip');
129+
}
130+
});
131+
132+
server.listen(PORT, () => {
133+
console.log('\n🚀 Development server started!');
134+
console.log(`\n📍 ZIP available at: http://localhost:${PORT}/examples-mcp-resources.zip`);
135+
console.log('\n💡 To use with MCP server, set environment variable:');
136+
console.log(` POSTHOG_MCP_LOCAL_EXAMPLES_URL=http://localhost:${PORT}/examples-mcp-resources.zip`);
137+
});
138+
}
139+
140+
/**
141+
* Main entry point
142+
*/
143+
async function main() {
144+
console.log('🎯 PostHog MCP Resources Development Server');
145+
console.log('==========================================');
146+
147+
// Initial build if ZIP doesn't exist
148+
if (!fs.existsSync(ZIP_PATH)) {
149+
console.log('\n⚠️ ZIP file not found. Running initial build...');
150+
await new Promise((resolve) => {
151+
const buildProcess = spawn('node', [path.join(__dirname, 'build-examples-mcp-resources.js')], {
152+
stdio: 'inherit',
153+
cwd: path.join(__dirname, '..')
154+
});
155+
buildProcess.on('close', resolve);
156+
});
157+
}
158+
159+
// Start server
160+
createServer();
161+
162+
// Setup file watchers
163+
setupWatchers();
164+
165+
console.log('\n✨ Ready for development!');
166+
console.log(' Press Ctrl+C to stop\n');
167+
}
168+
169+
// Handle graceful shutdown
170+
process.on('SIGINT', () => {
171+
console.log('\n\n👋 Shutting down dev server...');
172+
process.exit(0);
173+
});
174+
175+
// Run
176+
main().catch(err => {
177+
console.error('Fatal error:', err);
178+
process.exit(1);
179+
});

0 commit comments

Comments
 (0)