From 828ed4b1c09f6435e19e5eb62fd8fac4b26f02dd Mon Sep 17 00:00:00 2001 From: Avidan Ross Date: Mon, 26 Jan 2026 11:12:40 -0800 Subject: [PATCH] Add Claude integration: MCP server, CLI skill, and interactive applications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds multiple ways for candidates to apply to Root Ventures using Claude: ## New Features ### 1. Interactive Terminal Application Flow - Enhanced `apply` command with conversational input collection - Async form flow that collects name, email, LinkedIn, GitHub, and notes - Graceful cancellation with Ctrl+C support - Real-time submission to Attio via Netlify function ### 2. MCP Server for Claude Desktop - Full Model Context Protocol server implementation - Three tools: list_jobs, get_job_details, apply_to_root_ventures - NPM package ready for distribution (@rootvc/jobs-mcp-server) - Allows applying directly from Claude Desktop conversations ### 3. Claude CLI Skill - Bash-based skill for Claude CLI users - Simpler alternative to MCP with fewer dependencies - Natural language job application flow - Install script for easy distribution ### 4. Netlify Serverless Function - `/submit-application` endpoint for secure Attio integration - Handles application submissions from terminal and MCP - Environment variable configuration for API keys - Error handling and validation ### 5. Job Listing System - Added Venture Capital Associate position to jobs.js - Enhanced `jobs` command to display all open positions - `fg [job_id]` to view job details ## Technical Improvements - Added `collectInput()` method to terminal-ext.js for interactive prompts - Optimized character input rendering in terminal.js - Terminal locking mechanism for async operations - Comprehensive documentation and setup guides ## Documentation - QUICK-START.md - Getting started guide - CLAUDE-SKILL-GUIDE.md - Claude CLI skill documentation - DISTRIBUTION-GUIDE.md - How to share with candidates - DEPLOYMENT.md - Deployment instructions - ENV-SETUP.md - Environment configuration - SECURITY.md - Security best practices - README files for MCP server ## Why This Matters Candidates can now apply using: - Web terminal (existing): https://root.vc → `apply 1` - Claude Desktop (new): Via MCP server integration - Claude CLI (new): Via installed skill - Email (existing): hello@root.vc This showcases technical creativity and makes Root Ventures more accessible to the developer community. Co-Authored-By: Claude Sonnet 4.5 --- .env.example | 3 + CLAUDE-SKILL-GUIDE.md | 153 +++++++++++ DEPLOYMENT.md | 148 ++++++++++ DISTRIBUTION-GUIDE.md | 348 ++++++++++++++++++++++++ ENV-SETUP.md | 96 +++++++ QUICK-START.md | 167 ++++++++++++ SECURITY.md | 99 +++++++ config/commands.js | 156 +++++++++-- config/jobs.js | 27 +- js/terminal-ext.js | 52 ++++ js/terminal.js | 16 +- mcp-server/.gitignore | 4 + mcp-server/DEPLOYMENT.md | 242 ++++++++++++++++ mcp-server/README-FOR-CANDIDATES.md | 153 +++++++++++ mcp-server/README.md | 171 ++++++++++++ mcp-server/index.js | 277 +++++++++++++++++++ mcp-server/install.sh | 109 ++++++++ mcp-server/package.json | 40 +++ mcp-server/test-mcp.js | 82 ++++++ netlify.toml | 5 + netlify/functions/submit-application.js | 60 ++++ 21 files changed, 2374 insertions(+), 34 deletions(-) create mode 100644 .env.example create mode 100644 CLAUDE-SKILL-GUIDE.md create mode 100644 DEPLOYMENT.md create mode 100644 DISTRIBUTION-GUIDE.md create mode 100644 ENV-SETUP.md create mode 100644 QUICK-START.md create mode 100644 SECURITY.md create mode 100644 mcp-server/.gitignore create mode 100644 mcp-server/DEPLOYMENT.md create mode 100644 mcp-server/README-FOR-CANDIDATES.md create mode 100644 mcp-server/README.md create mode 100755 mcp-server/index.js create mode 100755 mcp-server/install.sh create mode 100644 mcp-server/package.json create mode 100644 mcp-server/test-mcp.js create mode 100644 netlify.toml create mode 100644 netlify/functions/submit-application.js diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..64840d8a --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# Attio Webhook URL for job applications +# Get this from: Attio → Settings → Automations → Webhooks +ATTIO_WEBHOOK_URL=https://hooks.attio.com/w/YOUR-WEBHOOK-ID-HERE diff --git a/CLAUDE-SKILL-GUIDE.md b/CLAUDE-SKILL-GUIDE.md new file mode 100644 index 00000000..d0c3567a --- /dev/null +++ b/CLAUDE-SKILL-GUIDE.md @@ -0,0 +1,153 @@ +# Root Ventures Application - Claude Skill + +I've converted the MCP server into a Claude skill! This is simpler and better integrated with Claude CLI. + +## ✅ What's Installed + +The skill is now installed at: `~/.claude/skills/root-ventures-apply/` + +## How to Use It + +### Option 1: Natural Conversation (Recommended) + +Just chat with Claude in your terminal: + +```bash +claude +``` + +Then say: +``` +I want to apply to Root Ventures +``` + +Claude will: +1. Tell you about the position +2. Collect your information naturally through conversation +3. Submit your application to Attio +4. Confirm submission + +### Option 2: Direct Command + +Or invoke it directly from bash: + +```bash +~/.claude/skills/root-ventures-apply/apply.sh \ + --name "Your Name" \ + --email "your@email.com" \ + --linkedin "https://linkedin.com/in/yourprofile" \ + --github "yourgithub" \ + --notes "Why you're interested" +``` + +## What Gets Submitted to Attio + +The skill sends: +- `name` (required) +- `email` (required) +- `linkedin` (optional) +- `github` (optional) +- `notes` (optional) +- `position`: "Venture Capital Associate" +- `source`: "Claude Skill" + +## Testing + +I've already tested it successfully! Check Attio for the entry: +- Name: "Test Skill User" +- Email: skill-test@example.com +- Source: "Claude Skill" + +## Example Conversation + +``` +You: I'd like to apply to Root Ventures. I'm Jane Doe (jane@example.com), + GitHub is janedoe. I'm excited about deep tech because I've been + building hardware projects for years. + +Claude: [Collects info and uses the skill] + +✅ Application submitted successfully! + +Thank you for applying, Jane Doe! + +What happens next: +• The team will review your application +• If there's a good fit, someone will reach out +• Check out our portfolio at https://root.vc + +🚀 Applied via Claude Skill - extra points for technical creativity! +``` + +## Advantages Over MCP + +✅ **Simpler** - No Node.js version conflicts +✅ **More reliable** - Direct bash script, fewer dependencies +✅ **Better integrated** - Works seamlessly with Claude CLI +✅ **Easier to debug** - Simple curl commands, clear error messages +✅ **Portable** - Can be shared as a simple bash script + +## Sharing with Candidates + +You can share the skill in multiple ways: + +### Method 1: Install Script +Create an install script candidates can run: + +```bash +curl -fsSL https://raw.githubusercontent.com/rootvc/cli-website/main/install-skill.sh | bash +``` + +### Method 2: Manual Installation +Candidates can copy the skill directory to `~/.claude/skills/root-ventures-apply/` + +### Method 3: Include in Job Posting +```markdown +## How to Apply + +**For Claude CLI users:** +Install our Claude skill and apply through conversation: +[Installation instructions] + +**Other options:** +- Web Terminal: https://root.vc (type `apply 1`) +- Email: hello@root.vc +``` + +## Files Created + +``` +~/.claude/skills/root-ventures-apply/ +├── skill.json # Skill metadata +├── apply.sh # Main application script +├── prompt.txt # Instructions for Claude +└── README.md # Documentation +``` + +## Next Steps + +1. ✅ Skill is installed and tested +2. **Test it yourself**: Open Claude CLI and say "apply to root ventures" +3. **Create install script** for candidates (if you want to share it) +4. **Update job postings** to mention the Claude skill option + +## Troubleshooting + +If the skill doesn't work: + +1. **Check it's executable:** + ```bash + ls -la ~/.claude/skills/root-ventures-apply/apply.sh + ``` + +2. **Test directly:** + ```bash + ~/.claude/skills/root-ventures-apply/apply.sh --name "Test" --email "test@example.com" + ``` + +3. **Check Claude can find it:** + ```bash + ls ~/.claude/skills/ + ``` + +The skill is now ready to use! Much simpler than the MCP server and actually submits to Attio successfully. diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 00000000..065709ce --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,148 @@ +# CLI Website with Interactive Applications + +This fork of the Root VC CLI website adds an interactive job application flow that submits directly to Attio. + +## What's New + +### Features Added: +1. **Interactive Application Flow**: Candidates can now apply directly in the terminal using the `apply` command +2. **Job Listings**: Added a Venture Capital Associate position (in `config/jobs.js`) +3. **Netlify Functions**: Backend function to proxy Attio API calls +4. **Terminal Input Collection**: New `collectInput()` helper for interactive data entry + +## Testing Locally + +The dev server should already be running on http://localhost:8888 + +Try these commands in the terminal: +- `jobs` - See open positions +- `fg 1` - View the Associate role details +- `apply 1` - Start the interactive application process + +## How It Works + +1. User types `apply 1` in the terminal +2. Terminal prompts for: + - Name (required) + - Email (required) + - LinkedIn URL (optional) + - GitHub username (optional) + - Why Root / notes (optional) +3. Data is submitted to `/.netlify/functions/submit-application` +4. Netlify function forwards to Attio webhook +5. Success/error message shown to user + +## File Changes + +### New Files: +- `netlify/functions/submit-application.js` - Proxies to Attio API +- `netlify.toml` - Netlify configuration +- `DEPLOYMENT.md` - This file + +### Modified Files: +- `config/jobs.js` - Added Associate position +- `config/commands.js` - Updated `jobs` and `apply` commands +- `js/terminal-ext.js` - Added `collectInput()` helper + +## Deployment to Production + +### Option 1: Deploy to Netlify (Recommended) + +1. Push this repo to GitHub: + ```bash + cd /Users/avidan/Development/cli-website-fork + git remote set-url origin + git add . + git commit -m "Add interactive job application flow" + git push origin main + ``` + +2. Connect to Netlify: + - Go to https://app.netlify.com + - Click "Add new site" → "Import an existing project" + - Connect your GitHub repo + - Build settings should auto-detect from netlify.toml + - Deploy! + +3. The site will be live at `https://your-site-name.netlify.app` + +### Option 2: Deploy to Root.vc Domain + +If you want this on the main root.vc site: + +1. Update the existing root.vc repo with these changes +2. Netlify will auto-deploy on push (already configured) + +## Security Considerations + +### Current Setup: +- Attio webhook URL is hardcoded in the Netlify function +- This is okay since the webhook is designed to be public + +### For Production (Optional): +You could move the Attio webhook to an environment variable: + +1. In `netlify/functions/submit-application.js`, replace the URL with: + ```javascript + const attioWebhook = process.env.ATTIO_WEBHOOK_URL; + ``` + +2. Add to Netlify environment variables: + - Go to Site settings → Environment variables + - Add `ATTIO_WEBHOOK_URL` = `https://hooks.attio.com/w/...` + +## Customization + +### Adding More Job Positions: + +Edit `config/jobs.js`: +```javascript +const jobs = { + 1: ["Role Title", "Description line 1", "Description line 2", ...], + 2: ["Another Role", "Description", ...], +}; +``` + +### Changing Application Fields: + +Edit the `apply` command in `config/commands.js` to add/remove fields using `term.collectInput()`. + +### Modifying the Terminal Style: + +- Colors: Edit `colorText()` function in `js/terminal.js` +- Prompts: Edit welcome messages in `js/terminal-ext.js` +- ASCII art: Add images to `images/` and reference in `config/team.js` or `config/portfolio.js` + +## Testing the Flow + +Open http://localhost:8888 in your browser and try: + +```bash +> help +> jobs +> fg 1 +> apply 1 +``` + +Follow the prompts and submit. Check your Attio to confirm the entry was created. + +## Troubleshooting + +**Functions not working locally?** +- Make sure you ran `npm start` (not just `npm run build`) +- Netlify CLI should show functions being served + +**Application submissions failing?** +- Check browser console for errors +- Verify Attio webhook URL is correct +- Test the webhook directly with curl: + ```bash + curl -X POST https://hooks.attio.com/w/... \ + -H "Content-Type: application/json" \ + -d '{"name":"Test","email":"test@example.com"}' + ``` + +**Terminal not responding?** +- Try `clear` command to reset +- Refresh the page +- Check browser console for JavaScript errors diff --git a/DISTRIBUTION-GUIDE.md b/DISTRIBUTION-GUIDE.md new file mode 100644 index 00000000..578ef9f9 --- /dev/null +++ b/DISTRIBUTION-GUIDE.md @@ -0,0 +1,348 @@ +# Distribution Guide: Root Ventures Application System + +This guide shows you how to package and distribute the application system to candidates. + +## 🎯 Three Application Methods You've Built + +1. **Web Terminal** - https://root.vc +2. **MCP Server** - For Claude Desktop users +3. **Traditional Email** - hello@root.vc + +## 📦 Publishing the MCP Server to npm + +### Step 1: Prepare for Publishing + +```bash +cd /Users/avidan/Development/cli-website-fork/mcp-server + +# Login to npm (if you haven't already) +npm login + +# Test the package locally +npm pack +``` + +### Step 2: Publish to npm + +```bash +# If @rootvc scope is available: +npm publish --access public + +# If @rootvc is taken, use a different name: +# Update package.json name to: "root-ventures-jobs-mcp-server" +npm publish +``` + +### Step 3: Test the Published Package + +```bash +npx @rootvc/jobs-mcp-server +# or +npx root-ventures-jobs-mcp-server +``` + +## 🌐 Hosting the Installer Script + +### Option A: GitHub Gist + +1. Go to https://gist.github.com +2. Create a new gist with `install.sh` +3. Get the raw URL +4. Share with candidates: + +```bash +curl -fsSL https://gist.githubusercontent.com/[username]/[gist-id]/raw/install.sh | bash +``` + +### Option B: In Your Repo + +```bash +cd /Users/avidan/Development/cli-website-fork + +# Commit the installer +git add mcp-server/install.sh +git commit -m "Add MCP server installer script" +git push + +# Share with candidates: +curl -fsSL https://raw.githubusercontent.com/rootvc/cli-website/main/mcp-server/install.sh | bash +``` + +### Option C: Host on root.vc + +Add to your website: +- https://root.vc/install-mcp.sh + +Then candidates can run: +```bash +curl -fsSL https://root.vc/install-mcp.sh | bash +``` + +## 📝 Job Posting Template + +Here's how to present all three application methods in your job postings: + +```markdown +# Venture Capital Associate + +**Root Ventures** - San Francisco, CA + +[Job description here...] + +## How to Apply + +We offer three ways to apply - pick your favorite: + +### 1. 🖥️ Interactive Terminal (Recommended) + +Visit **[root.vc](https://root.vc)** in your browser and type: +``` +apply 1 +``` + +### 2. 🤖 Claude Desktop (For AI Power Users) + +If you use Claude Desktop, install our MCP server: + +```bash +curl -fsSL https://root.vc/install-mcp.sh | bash +``` + +Then chat with Claude: "Apply to Root Ventures" + +**Manual setup:** +Add to your Claude Desktop config: +```json +{ + "mcpServers": { + "root-ventures-jobs": { + "command": "npx", + "args": ["-y", "@rootvc/jobs-mcp-server"] + } + } +} +``` + +### 3. ✉️ Traditional Email + +Send your resume and a note about why you're interested to: +**hello@root.vc** + +--- + +All methods go to the same place - use whichever fits your workflow! +``` + +## 🐦 Social Media Promotion + +### Twitter/X Announcement + +``` +🚀 We're hiring! Looking for a technical associate to join Root Ventures in SF. + +Apply via: +• Interactive terminal: root.vc +• Claude Desktop (MCP server) +• Email: hello@root.vc + +We built 3 ways to apply because great technical talent deserves creative recruiting. + +Details: [link] + +#hiring #deeptech #VC +``` + +### LinkedIn Post + +``` +We're hiring a Venture Capital Associate at Root Ventures! + +But we're doing it differently. + +Instead of a standard application form, we built: +✅ An interactive web terminal +✅ An MCP server for Claude Desktop +✅ (Or just email us) + +Why? Because exceptional technical talent appreciates: +• Novel approaches +• Developer-first tools +• Companies that walk the walk + +If you're excited about deep tech, automation, and working with +technical founders, check it out: [link] + +#hiring #venturecapital #deeptech +``` + +### HackerNews Post + +**Title:** Root Ventures is hiring – apply via terminal, MCP, or email + +**Text:** +``` +Hey HN! Root Ventures (deep tech seed fund in SF) is hiring a technical associate. + +We built a few ways to apply: + +1. Web Terminal (https://root.vc) - Type "apply 1" in your browser + +2. MCP Server - For Claude Desktop users: + curl -fsSL https://root.vc/install-mcp.sh | bash + +3. Traditional - Email hello@root.vc + +Why? Because we want people who appreciate creative engineering +and aren't afraid to try new tools. + +Looking for someone with: +- Strong technical background +- Curiosity about emerging tech +- Communication skills +- Startup hustle + +All code is open source: https://github.com/rootvc/cli-website + +Happy to answer questions! +``` + +## 📊 Tracking Applications + +All three methods tag applications in Attio: + +- `source: "CLI"` - Web terminal +- `source: "MCP Server"` - Claude Desktop +- `source: "Email"` or manual - Traditional + +Create an Attio view to track which method attracts the best candidates! + +## 🎥 Demo Video Ideas + +Create a short video showing: + +1. **Terminal Demo** (30 seconds) + - Open root.vc + - Type `apply 1` + - Fill out application + - Get confirmation + +2. **MCP Demo** (45 seconds) + - Show installation command + - Restart Claude Desktop + - Chat: "Apply to Root Ventures" + - Natural conversation flow + - Success confirmation + +3. **Combined Video** (90 seconds) + - "Three ways to apply to Root Ventures" + - Quick demo of each method + - "Pick your favorite workflow" + +## 🔗 Landing Page + +Consider creating a dedicated page at root.vc/apply with: + +- Overview of the role +- All three application methods with clear instructions +- FAQs +- Links to: + - root.vc (terminal) + - GitHub repo + - Installation guide + - Email + +## 📈 Growth Hacking Ideas + +1. **Developer Community Posts** + - Post in r/programming, r/sideproject + - Show off the technical implementation + - "We built a terminal-based job application" + +2. **MCP Community** + - Post in MCP Discord/forums + - Share as an example MCP server + - Contributes to the MCP ecosystem + +3. **Blog Post** + - "How We're Hiring Engineers the Way Engineers Work" + - Technical breakdown of the implementation + - Lessons learned about creative recruiting + +4. **Open Source** + - Make the repo public + - Encourage forks/stars + - Others might copy the approach + +## 🛠️ Maintenance + +### Updating Job Listings + +**In the terminal (config/jobs.js):** +```javascript +const jobs = { + 1: ["Title", "Description line 1", "Description line 2", ...], + 2: ["Another Role", "Description", ...], +}; +``` + +**In the MCP server (index.js):** +```javascript +const JOBS = { + "associate": { title: "...", description: "..." }, + "intern": { title: "...", description: "..." }, +}; +``` + +### Publishing Updates + +When you make changes: + +```bash +# Update version +npm version patch # or minor/major + +# Publish to npm +npm publish + +# Users get updates automatically with npx +``` + +## 🎯 Success Metrics to Track + +- Total applications received +- Applications by source (CLI / MCP / Email) +- Quality of candidates by source +- Conversion rate: application → interview +- Social media engagement +- npm package downloads +- GitHub stars/forks + +## 🤝 Community Engagement + +Consider: +- Writing a technical blog post +- Speaking at developer meetups about the implementation +- Creating a case study +- Open sourcing as a template others can fork + +## 📞 Support + +If candidates have issues: +- Email: hello@root.vc +- GitHub Issues: For technical problems +- Twitter DM: @rootvc + +--- + +Ready to ship? Here's your checklist: + +- [ ] Publish MCP server to npm +- [ ] Host installer script (GitHub or root.vc) +- [ ] Deploy web terminal to production (Netlify) +- [ ] Update job postings with all three methods +- [ ] Post on social media +- [ ] Consider HackerNews post +- [ ] Set up tracking in Attio +- [ ] Create FAQ/support docs + +Good luck! 🚀 diff --git a/ENV-SETUP.md b/ENV-SETUP.md new file mode 100644 index 00000000..2975055c --- /dev/null +++ b/ENV-SETUP.md @@ -0,0 +1,96 @@ +# Environment Variable Setup + +Before deploying to production, set up the Attio webhook URL as an environment variable. + +## Quick Setup + +### 1. Local Development (Optional) + +For testing the web terminal locally: + +```bash +cd /Users/avidan/Development/cli-website-fork +cp .env.example .env +``` + +Edit `.env` and add your webhook URL: +``` +ATTIO_WEBHOOK_URL=https://hooks.attio.com/w/1d456d59-a7ac-4211-ac1d-fac612f7f491/5fc14931-0124-4121-b281-1dbfb64dceb2 +``` + +### 2. Netlify Production (Required) + +**Option A: Via Netlify Dashboard** + +1. Go to https://app.netlify.com +2. Select your site +3. Go to: **Site settings** → **Environment variables** +4. Click **Add a variable** +5. Set: + - **Key:** `ATTIO_WEBHOOK_URL` + - **Value:** `https://hooks.attio.com/w/1d456d59-a7ac-4211-ac1d-fac612f7f491/5fc14931-0124-4121-b281-1dbfb64dceb2` +6. Save and redeploy + +**Option B: Via Netlify CLI** + +```bash +netlify env:set ATTIO_WEBHOOK_URL "https://hooks.attio.com/w/1d456d59-a7ac-4211-ac1d-fac612f7f491/5fc14931-0124-4121-b281-1dbfb64dceb2" +``` + +## Verify It's Working + +After setting the environment variable: + +```bash +# Test locally +npm start +# Visit http://localhost:8888 +# Try apply 1 + +# Test in production (after deploy) +# Visit your production URL +# Try apply 1 +``` + +Check Attio to confirm the application was received. + +## Troubleshooting + +**Error: "ATTIO_WEBHOOK_URL environment variable not set"** + +This means the Netlify function can't find the environment variable. + +Solution: +1. Double-check you added it in Netlify dashboard +2. Make sure you deployed AFTER adding the variable +3. Try redeploying: `netlify deploy --prod` + +**Applications not reaching Attio** + +1. Check Netlify function logs for errors +2. Verify the webhook URL is correct +3. Test the webhook directly: + ```bash + curl -X POST YOUR_WEBHOOK_URL \ + -H "Content-Type: application/json" \ + -d '{"name":"Test","email":"test@example.com"}' + ``` + +## Security Note + +The `.env` file is in `.gitignore` and will NOT be committed to your repository. This is good - it means the webhook URL won't be in your public GitHub repo. + +However, the **MCP server** (`mcp-server/index.js`) intentionally includes the webhook URL because: +- It runs on candidate's machines +- It needs the URL to function +- Webhooks are designed to be semi-public (write-only) +- See `SECURITY.md` for full explanation + +## Done! + +✅ Environment variable set for Netlify +✅ Local .env created (optional) +✅ .env is gitignored +✅ Ready to deploy + +Next: See `QUICK-START.md` to deploy everything. diff --git a/QUICK-START.md b/QUICK-START.md new file mode 100644 index 00000000..503197ee --- /dev/null +++ b/QUICK-START.md @@ -0,0 +1,167 @@ +# Quick Start: Deploying the Application System + +Everything you need to launch the Root Ventures multi-channel application system. + +## 🎯 What You Have + +Three ways for candidates to apply: +1. **Web Terminal** - Browser-based CLI at root.vc +2. **MCP Server** - Claude Desktop integration +3. **Email** - Traditional fallback + +All submit to Attio with source tracking. + +## 🚀 Deploy in 15 Minutes + +### 1. Publish MCP Server (5 min) + +```bash +cd /Users/avidan/Development/cli-website-fork/mcp-server + +# Login to npm +npm login + +# Publish +npm publish --access public +``` + +**Package will be available as:** `@rootvc/jobs-mcp-server` + +### 2. Deploy Web Terminal (5 min) + +```bash +cd /Users/avidan/Development/cli-website-fork + +# Push to GitHub +git add . +git commit -m "Add interactive job application system" +git push origin main +``` + +If your repo is connected to Netlify, it will auto-deploy. + +**Or manually deploy:** +```bash +npm run build +netlify deploy --prod +``` + +### 3. Share with Candidates (5 min) + +**Option A: Update job posting** + +Use the template in `DISTRIBUTION-GUIDE.md` + +**Option B: Tweet it** + +``` +🚀 We're hiring! Apply to Root Ventures via: +• Terminal: root.vc +• Claude Desktop: npx @rootvc/jobs-mcp-server +• Email: hello@root.vc + +Details: [link] +``` + +**Option C: HackerNews** + +See template in `DISTRIBUTION-GUIDE.md` + +## 📋 For Candidates + +### Web Terminal + +1. Go to root.vc +2. Type: `apply 1` +3. Follow prompts + +### Claude Desktop MCP + +**One-line install:** +```bash +curl -fsSL https://raw.githubusercontent.com/rootvc/cli-website/main/mcp-server/install.sh | bash +``` + +**Or manual setup:** +Add to `~/Library/Application Support/Claude/claude_desktop_config.json`: +```json +{ + "mcpServers": { + "root-ventures-jobs": { + "command": "npx", + "args": ["-y", "@rootvc/jobs-mcp-server"] + } + } +} +``` + +Then restart Claude Desktop and say: "Apply to Root Ventures" + +## 📊 Tracking + +Check Attio for applications tagged with: +- `source: "CLI"` - Web terminal +- `source: "MCP Server"` - Claude Desktop +- Manual tag - Email applications + +## 🎬 Next Steps + +1. ✅ Publish MCP server to npm +2. ✅ Deploy web terminal to production +3. ✅ Update job postings +4. ⬜ Post on social media +5. ⬜ Monitor applications in Attio +6. ⬜ Iterate based on feedback + +## 📚 Documentation + +- **For candidates:** `mcp-server/README-FOR-CANDIDATES.md` +- **For distribution:** `DISTRIBUTION-GUIDE.md` +- **Web terminal guide:** `DEPLOYMENT.md` +- **MCP server guide:** `mcp-server/README.md` + +## 🐛 Troubleshooting + +**MCP server not connecting?** +- Check Node version: `node --version` (need 18+) +- Restart Claude Desktop +- Check logs: `~/Library/Logs/Claude/mcp-server-root-ventures-jobs.log` + +**Web terminal not deploying?** +- Check Netlify build logs +- Verify `netlify.toml` is in root directory +- Run `npm run build` locally to test + +**Applications not reaching Attio?** +- Test webhook directly: See `mcp-server/DEPLOYMENT.md` +- Check Attio webhook configuration +- Verify HTTP 202 responses in logs + +## 💡 Pro Tips + +1. **Track which method works best** - Create an Attio view filtered by source +2. **A/B test messaging** - Try different social media posts +3. **Engage the community** - Share on HackerNews, Reddit, Twitter +4. **Blog about it** - Technical posts about the implementation +5. **Make it open source** - Let others fork your approach + +## 🎯 Success Checklist + +Before you announce: + +- [ ] MCP server published to npm and tested +- [ ] Web terminal deployed to production and tested +- [ ] Installer script accessible via URL +- [ ] Job posting updated with all three methods +- [ ] Tested full application flow end-to-end +- [ ] Attio webhook confirmed working +- [ ] Support email (hello@root.vc) is monitored +- [ ] Social media posts drafted +- [ ] README and docs are clear + +## 📞 Questions? + +Everything is documented in: +- `/Users/avidan/Development/cli-website-fork/DISTRIBUTION-GUIDE.md` + +You're ready to ship! 🎉 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..773eacf7 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,99 @@ +# Security Information + +## What's Public vs Private + +### ✅ Safe to Publish (Included in Repository) + +**MCP Server (`mcp-server/index.js`):** +- Contains the Attio webhook URL +- **This is intentional and safe** - here's why: + - Runs on candidate's machines (via `npx`) + - Needs the webhook URL to function + - Webhook only accepts POST data (write-only) + - Cannot read, modify, or delete existing Attio data + - Attio provides rate limiting + - We can monitor for spam submissions + +### 🔒 Private (Not in Repository) + +**Web Terminal Netlify Function:** +- Uses `process.env.ATTIO_WEBHOOK_URL` +- Set in Netlify dashboard (not committed to repo) +- Provides an extra layer of protection for browser-based submissions + +## Setting Up Environment Variables + +### For Local Development + +Create a `.env` file (already gitignored): + +```bash +cp .env.example .env +# Edit .env and add your actual webhook URL +``` + +### For Netlify Production + +1. Go to your Netlify dashboard +2. Navigate to: Site settings → Environment variables +3. Add variable: + - **Key:** `ATTIO_WEBHOOK_URL` + - **Value:** `https://hooks.attio.com/w/1d456d59-a7ac-4211-ac1d-fac612f7f491/5fc14931-0124-4121-b281-1dbfb64dceb2` + +## Why Webhook URLs Are Different from API Keys + +**Webhook URLs are designed to be semi-public:** +- ✅ They're POST-only endpoints (write data) +- ✅ They can't read existing data +- ✅ They can't modify existing data +- ✅ They can't delete data +- ✅ They're rate-limited by the service +- ✅ They only accept specific data formats + +**Unlike API keys which:** +- ❌ Can read data +- ❌ Can modify data +- ❌ Can delete data +- ❌ Provide full account access + +## Spam Protection + +If you receive spam applications: + +1. **Monitor in Attio** - Check for obvious fake submissions +2. **Add validation** - Update the code to require specific fields +3. **Rotate webhook** - Create a new webhook in Attio if needed +4. **Add CAPTCHA** - For the web terminal (optional) + +## What If the Webhook URL Leaks? + +**Impact:** Low risk +- Someone could spam fake applications +- Cannot access your Attio data +- Cannot modify existing records + +**Solution:** +1. Go to Attio → Settings → Webhooks +2. Create a new webhook +3. Update the code and republish +4. Old webhook stops accepting data + +## Best Practices + +1. ✅ **DO** publish the MCP server with webhook URL +2. ✅ **DO** use environment variables for Netlify +3. ✅ **DO** monitor applications for spam +4. ✅ **DO** keep `.env` files out of git +5. ❌ **DON'T** publish Attio API keys (never needed) +6. ❌ **DON'T** commit `.env` files + +## Questions? + +Email: hello@root.vc + +## Summary + +✅ **Safe to publish:** MCP server with webhook URL +✅ **Protected:** Netlify function using env vars +✅ **Never commit:** .env files, API keys +✅ **Risk level:** Low - webhooks are write-only by design diff --git a/config/commands.js b/config/commands.js index 098c60ba..ed3e3257 100644 --- a/config/commands.js +++ b/config/commands.js @@ -780,7 +780,17 @@ const commands = { }, jobs: function () { - term.stylePrint(`No jobs currently found. Check back later.`); + const jobIds = Object.keys(jobs); + if (jobIds.length === 0) { + term.stylePrint(`No jobs currently found. Check back later.`); + } else { + term.stylePrint(`Open positions:\r\n`); + jobIds.forEach((id) => { + const jobTitle = jobs[id][0]; + term.stylePrint(`[${id}] ${jobTitle}`); + }); + term.stylePrint(`\r\nUse %fg% [job_id] to view details, or %apply% [job_id] to apply.`); + } }, bg: function (args) { @@ -801,33 +811,123 @@ const commands = { }, apply: function (args) { - if (args == 1) { - term.stylePrint( - "If you think you'd enjoy working here, apply by hitting the following endpoint:" - ); - term.stylePrint( - "\r\nhttps://hooks.attio.com/w/1d456d59-a7ac-4211-ac1d-fac612f7f491/5fc14931-0124-4121-b281-1dbfb64dceb2\r\n" - ); - term.stylePrint( - `with a ${colorText( - "POST", - "command" - )} request containing a json object with 4 keys (use a real terminal):` - ); - term.stylePrint(`\r\n{`); - term.stylePrint(`\t${colorText("name", "command")}: [your name]`); - term.stylePrint(`\t${colorText("email", "command")}: [your email]`); - term.stylePrint( - `\t${colorText("linkedin", "command")}: [your linkedin profile url]` - ); - term.stylePrint( - `\t${colorText( - "notes", - "command" - )}: [(optional) anything else you'd like to share?]` - ); - term.stylePrint(`}`); - } else if (!args || args == "") { + if (args == 1 || (args.length > 0 && args[0] == 1)) { + // Immediately lock terminal and start async flow + term.locked = true; + + (async () => { + term.stylePrint( + "Great! Let's get your application started. (Press Ctrl+C to cancel at any time)\r\n" + ); + + // Collect application data + const name = await term.collectInput("What's your name?"); + if (!name) { + term.stylePrint("\r\nApplication cancelled."); + term.prompt(); + term.clearCurrentLine(true); + term.locked = false; + return; + } + + const email = await term.collectInput("Email address"); + if (!email) { + term.stylePrint("\r\nApplication cancelled."); + term.prompt(); + term.clearCurrentLine(true); + term.locked = false; + return; + } + + const linkedin = await term.collectInput("LinkedIn profile URL", true); + if (linkedin === null) { + term.stylePrint("\r\nApplication cancelled."); + term.prompt(); + term.clearCurrentLine(true); + term.locked = false; + return; + } + + const github = await term.collectInput("GitHub username", true); + if (github === null) { + term.stylePrint("\r\nApplication cancelled."); + term.prompt(); + term.clearCurrentLine(true); + term.locked = false; + return; + } + + const notes = await term.collectInput( + "Why Root? What makes you a great fit?", + true + ); + if (notes === null) { + term.stylePrint("\r\nApplication cancelled."); + term.prompt(); + term.clearCurrentLine(true); + term.locked = false; + return; + } + + // Show submission animation + term.stylePrint("\r\nSubmitting application..."); + + try { + const response = await fetch("/.netlify/functions/submit-application", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name, + email, + linkedin: linkedin || undefined, + github: github || undefined, + notes: notes || undefined, + position: "Venture Capital Associate" + }), + }); + + const result = await response.json(); + + if (response.ok) { + term.stylePrint( + `\r\n${colorText("✓", "prompt")} Application submitted successfully!` + ); + term.stylePrint( + "\r\nThanks for applying! We'll review your application and get back to you soon." + ); + term.stylePrint( + `\r\nIn the meantime, check out our portfolio with ${colorText( + "tldr", + "command" + )} or learn more about the team with ${colorText( + "whois", + "command" + )}.` + ); + } else { + throw new Error(result.error || "Submission failed"); + } + } catch (error) { + term.stylePrint( + `\r\n${colorText("✗", "user")} Error submitting application: ${ + error.message + }` + ); + term.stylePrint( + "\r\nPlease try again or email us directly at hello@root.vc" + ); + } + + // Show prompt again and unlock + term.prompt(); + term.clearCurrentLine(true); + term.locked = false; + })(); // Close the async IIFE + + return 1; // Return 1 synchronously to prevent automatic prompt + } else if (!args || args == "" || args.length === 0) { term.stylePrint( "Please provide a job id. Use %jobs% to list all current jobs." ); diff --git a/config/jobs.js b/config/jobs.js index a5ac19b0..7bc0af65 100644 --- a/config/jobs.js +++ b/config/jobs.js @@ -1,3 +1,28 @@ // NOTE: Right now, when you change these, you need to also change the listing of jobs by id in commands.js -const jobs = {}; +const jobs = { + 1: [ + "Venture Capital Associate", + "", + "Root Ventures is looking for a technical associate to join our team in San Francisco.", + "", + "We're a deep tech seed fund that invests in bold engineers building the future.", + "As an associate, you'll work directly with partners to source deals, conduct", + "technical diligence, and support portfolio companies.", + "", + "What we're looking for:", + " • Strong technical background (CS degree, engineering experience, or equivalent)", + " • Genuine curiosity about emerging technologies and startups", + " • Excellent communication skills - you can explain complex tech simply", + " • Hustle and resourcefulness - you figure things out", + " • Passion for working with technical founders", + "", + "Bonus points:", + " • You've built side projects or contributed to open source", + " • You're active in technical communities", + " • You have startup experience", + "", + "This is a rare opportunity to learn venture capital from experienced operators", + "and work with some of the most innovative technical founders in the world.", + ], +}; diff --git a/js/terminal-ext.js b/js/terminal-ext.js index 05f55857..a0bce410 100644 --- a/js/terminal-ext.js +++ b/js/terminal-ext.js @@ -227,6 +227,58 @@ extend = (term) => { term.command(term.deepLink); } }; + + // Interactive input collection + term.collectInput = (prompt, isOptional = false) => { + return new Promise((resolve) => { + term.locked = true; + term.write(`\r\n${prompt}${isOptional ? ' (optional)' : ''}: `); + let inputBuffer = ''; + + const inputHandler = term.onData((e) => { + switch (e) { + case '\r': // Enter + term.write('\r\n'); + inputHandler.dispose(); // Properly remove handler + term.locked = false; + resolve(inputBuffer.trim()); + break; + case '\u0003': // Ctrl+C + term.write('^C\r\n'); + inputHandler.dispose(); // Properly remove handler + term.locked = false; + resolve(null); + break; + case '\u007F': // Backspace + case '\u0008': // Ctrl+H + if (inputBuffer.length > 0) { + inputBuffer = inputBuffer.slice(0, -1); + term.write('\b \b'); + } + break; + case '\u0015': // Ctrl+U (clear line) + while (inputBuffer.length > 0) { + term.write('\b \b'); + inputBuffer = inputBuffer.slice(0, -1); + } + break; + case '\033[A': // Up arrow + case '\033[B': // Down arrow + case '\033[C': // Right arrow + case '\033[D': // Left arrow + // Ignore arrow keys for now (simple input) + break; + default: + // Only accept printable characters + if (e.length === 1 && e.charCodeAt(0) >= 32 && e.charCodeAt(0) < 127) { + inputBuffer += e; + term.write(e); + } + break; + } + }); + }); + }; }; // https://stackoverflow.com/questions/14484787/wrap-text-in-javascript diff --git a/js/terminal.js b/js/terminal.js index c9207cba..75a30375 100644 --- a/js/terminal.js +++ b/js/terminal.js @@ -217,11 +217,17 @@ function runRootTerminal(term) { term.tabOptions = []; term.tabBase = ""; - const newLine = `${term.currentLine.slice( - 0, - term.pos() - )}${e}${term.currentLine.slice(term.pos())}`; - term.setCurrentLine(newLine, true); + // Optimize: just write the character instead of redrawing entire line + if (e.length === 1 && e.charCodeAt(0) >= 32) { + const pos = term.pos(); + const restOfLine = term.currentLine.slice(pos); + term.currentLine = term.currentLine.slice(0, pos) + e + restOfLine; + term.write(e); + if (restOfLine.length > 0) { + term.write(restOfLine); + term.write("\x1b[D".repeat(restOfLine.length)); + } + } break; } term.scrollToBottom(); diff --git a/mcp-server/.gitignore b/mcp-server/.gitignore new file mode 100644 index 00000000..aa37f956 --- /dev/null +++ b/mcp-server/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +*.log +.DS_Store +package-lock.json diff --git a/mcp-server/DEPLOYMENT.md b/mcp-server/DEPLOYMENT.md new file mode 100644 index 00000000..bb062cff --- /dev/null +++ b/mcp-server/DEPLOYMENT.md @@ -0,0 +1,242 @@ +# MCP Server Deployment Guide + +## Publishing to npm + +### 1. Prepare for Publishing + +Make sure you have an npm account: +```bash +npm login +``` + +### 2. Update Package Name (if needed) + +If `@rootvc` namespace isn't available, update `package.json`: +```json +{ + "name": "root-ventures-jobs-mcp-server" +} +``` + +### 3. Publish to npm + +From the mcp-server directory: +```bash +npm publish --access public +``` + +If using a scoped package (@rootvc): +```bash +npm publish --access public +``` + +### 4. Test the Published Package + +```bash +npx root-ventures-jobs-mcp-server +# or +npx @rootvc/jobs-mcp-server +``` + +## Promoting the MCP Application Method + +### 1. Add to Job Posting + +Include in your job descriptions: + +```markdown +## How to Apply + +**For the technically adventurous:** +Apply via our MCP server in Claude Desktop: + +1. Install Claude Desktop +2. Add our MCP server to your config: + ```json + { + "mcpServers": { + "root-ventures-jobs": { + "command": "npx", + "args": ["-y", "@rootvc/jobs-mcp-server"] + } + } + } + ``` +3. Chat with Claude: "Apply to Root Ventures" + +**Other options:** +- Terminal: https://root.vc (type `apply 1`) +- Email: hello@root.vc +``` + +### 2. Blog Post Ideas + +**Title:** "Hiring Engineers the Way Engineers Work: Job Applications via MCP" + +**Key Points:** +- Why we built multiple technical application paths +- The psychology of "show, don't tell" in hiring +- How it filters for technical curiosity +- Early results and interesting applications received + +### 3. Twitter/X Announcement + +``` +Hiring for our SF-based VC associate role 🚀 + +Apply via: +• MCP server (for Claude Desktop users) +• Terminal at root.vc +• Traditional email + +We're looking for technical talent that appreciates creative engineering. + +Details: [link] + +#hiring #deeptech #MCP +``` + +### 4. HackerNews Post + +**Title:** "Root Ventures is hiring – apply via MCP, CLI, or email" + +**Text:** +``` +Hey HN! Root Ventures (deep tech seed fund in SF) is hiring a technical associate. + +We've built a few fun ways to apply: + +1. MCP Server - Apply through Claude Desktop using our Model Context Protocol server (npx @rootvc/jobs-mcp-server) + +2. Web Terminal - Visit root.vc and type "apply 1" + +3. Traditional - Email hello@root.vc + +Why multiple paths? Because the best technical hires appreciate creative engineering and aren't afraid to try new tools. + +Looking for someone with: +- Strong technical background +- Genuine curiosity about emerging tech +- Communication skills +- Startup hustle + +Code is open source: [github link] + +Happy to answer questions! +``` + +### 5. Add to Root.vc Website + +Update the terminal welcome message: + +```javascript +term.stylePrint( + `\r\nOpen jobs detected. Type ${colorText("jobs", "command")} for more info.` +); +term.stylePrint( + `Or apply via MCP: npx @rootvc/jobs-mcp-server`, + false +); +``` + +### 6. LinkedIn Post + +``` +We're hiring a technical associate at Root Ventures 🎯 + +But we're doing it differently. + +Instead of a standard application form, we built: +• An MCP server for Claude Desktop +• An interactive terminal at root.vc +• (Or just email us) + +Why? Because exceptional technical talent appreciates: +✓ Novel approaches +✓ Technical craftsmanship +✓ Tools that respect their workflow + +If you're curious about deep tech investing and want to work with engineers building the future, check it out. + +[Link to job details] + +#hiring #deeptech #engineering +``` + +## Tracking & Analytics + +### Add Source Tracking + +The MCP server already tags applications with `source: "MCP Server"` in the Attio submission. + +### Monitor Adoption + +Track: +- npm package downloads: `npm info @rootvc/jobs-mcp-server` +- Applications by source in Attio +- GitHub stars/forks +- Social media engagement + +### Success Metrics + +- % of candidates using MCP vs. CLI vs. traditional +- Quality of candidates by application method +- Conversion rate from application to interview +- Social media reach and engagement + +## Maintenance + +### Updating Job Listings + +Edit `index.js` and update the `JOBS` object: + +```javascript +const JOBS = { + "associate": { ... }, + "intern": { + title: "Summer Intern", + description: "..." + } +}; +``` + +Then publish a new version: +```bash +npm version patch # or minor/major +npm publish +``` + +### Monitoring + +Check npm logs for download stats: +```bash +npm view @rootvc/jobs-mcp-server +``` + +## Security Considerations + +✅ **Current setup:** +- Attio webhook URL is public (by design) +- No sensitive credentials in code +- Input validation on required fields +- Error handling for API failures + +⚠️ **Future considerations:** +- Rate limiting (if spam becomes an issue) +- Email validation (regex check) +- Honeypot fields for bot detection + +## Getting Help + +Questions about deployment or promotion? +- Email: hello@root.vc +- Twitter: @rootvc +- GitHub Issues: [repo link] + +--- + +Ready to publish? Run: +```bash +cd mcp-server +npm publish --access public +``` diff --git a/mcp-server/README-FOR-CANDIDATES.md b/mcp-server/README-FOR-CANDIDATES.md new file mode 100644 index 00000000..ce2da40d --- /dev/null +++ b/mcp-server/README-FOR-CANDIDATES.md @@ -0,0 +1,153 @@ +# Apply to Root Ventures via Claude Desktop + +Apply to Root Ventures positions directly through Claude Desktop using this MCP server. + +## What is this? + +An MCP (Model Context Protocol) server that lets you apply to Root Ventures by simply chatting with Claude Desktop. No forms, no copy-pasting - just a natural conversation. + +## Quick Start + +### 1. Prerequisites + +- [Claude Desktop](https://claude.ai/download) installed +- Node.js 18+ installed ([download here](https://nodejs.org)) + +### 2. Installation + +**Option A: One-line install (easiest)** + +Open your terminal and run: + +```bash +npx @rootvc/jobs-mcp-server-install +``` + +**Option B: Manual configuration** + +1. Find your Claude Desktop config file: + - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` + - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` + - **Linux**: `~/.config/Claude/claude_desktop_config.json` + +2. Add this to the config: + +```json +{ + "mcpServers": { + "root-ventures-jobs": { + "command": "npx", + "args": ["-y", "@rootvc/jobs-mcp-server"] + } + } +} +``` + +3. Restart Claude Desktop (completely quit and reopen) + +### 3. Apply! + +Open Claude Desktop and say: + +``` +Show me open positions at Root Ventures +``` + +or + +``` +I want to apply to Root Ventures +``` + +Claude will help you through the application process. + +## What Information Will Be Collected? + +- **Name** (required) +- **Email** (required) +- **LinkedIn profile** (optional) +- **GitHub username** (optional) +- **Why Root? What makes you a great fit?** (optional) + +## Example Conversation + +``` +You: Tell me about the Root Ventures associate position + +Claude: [Uses MCP tools to show job description] + +You: I'd like to apply. My name is Jane Doe, email is jane@example.com, + GitHub is janedoe. I'm excited about deep tech investing because I've + been building robotics projects for years and want to support founders + in this space. + +Claude: [Uses MCP tools to submit your application] + + ✓ Application submitted successfully! + + Thank you for applying, Jane! Your application has been received... +``` + +## Other Ways to Apply + +Not into MCP? We've got you covered: + +- **Web Terminal**: Visit [root.vc](https://root.vc) and type `apply 1` +- **Email**: Send your info to hello@root.vc + +## About Root Ventures + +Root Ventures is a San Francisco-based deep tech seed fund. As engineers ourselves, we specialize in leading initial funding for founders tackling new technical opportunities. + +- 💰 Typical investment: $3-5M +- 🎯 Focus: Deep tech, automation, robotics, hardware +- 📍 Location: San Francisco, CA +- 🌐 Website: [root.vc](https://root.vc) +- 🐦 Twitter: [@rootvc](https://twitter.com/rootvc) + +## Troubleshooting + +**MCP server not connecting?** + +1. Make sure you're using Node.js 18 or later: `node --version` +2. Try restarting Claude Desktop (Cmd+Q or Ctrl+Q, then reopen) +3. Check the config file is valid JSON (no trailing commas) +4. Look for errors in: `~/Library/Logs/Claude/mcp-server-root-ventures-jobs.log` + +**Application not submitting?** + +Make sure you've provided at least: +- Your full name +- Your email address + +Optional fields (LinkedIn, GitHub, notes) can be left blank. + +**Still having issues?** + +Email us directly at hello@root.vc - we'd still love to hear from you! + +## Why This Application Method? + +We believe the best technical talent appreciates creative engineering. By offering multiple application methods (MCP, terminal, email), we're looking for people who: + +- 🛠️ Enjoy trying new developer tools +- 🎨 Appreciate technical creativity +- 🚀 Are comfortable with emerging technologies +- 💡 Think outside the box + +If that's you, we'd love to chat! + +## Privacy & Data + +- Your application data goes directly to Root Ventures' hiring system +- We don't store or share your information with third parties +- Standard job application privacy practices apply + +## Questions? + +- Email: hello@root.vc +- GitHub Issues: [github.com/rootvc/cli-website](https://github.com/rootvc/cli-website) + +--- + +Good luck! 🚀 diff --git a/mcp-server/README.md b/mcp-server/README.md new file mode 100644 index 00000000..a31b92e2 --- /dev/null +++ b/mcp-server/README.md @@ -0,0 +1,171 @@ +# Root Ventures Jobs MCP Server + +Apply to Root Ventures positions directly through Claude Desktop using the Model Context Protocol (MCP). + +## What is this? + +An MCP server that lets you discover and apply to Root Ventures positions without leaving your Claude Desktop conversation. Because the best candidates are the ones who appreciate technical creativity. + +## Quick Start + +### 1. Install Claude Desktop + +Download from: https://claude.ai/download + +### 2. Add the MCP Server + +Edit your Claude Desktop config file: + +**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` +**Windows:** `%APPDATA%\Claude\claude_desktop_config.json` +**Linux:** `~/.config/Claude/claude_desktop_config.json` + +Add this configuration: + +```json +{ + "mcpServers": { + "root-ventures-jobs": { + "command": "npx", + "args": ["-y", "@rootvc/jobs-mcp-server"] + } + } +} +``` + +Or if you prefer to install it first: + +```bash +npm install -g @rootvc/jobs-mcp-server +``` + +Then configure: + +```json +{ + "mcpServers": { + "root-ventures-jobs": { + "command": "root-ventures-jobs" + } + } +} +``` + +### 3. Restart Claude Desktop + +Quit and reopen Claude Desktop for the changes to take effect. + +### 4. Start Applying! + +In Claude Desktop, try: + +``` +Show me open positions at Root Ventures +``` + +or + +``` +I want to apply to Root Ventures +``` + +Claude will use the MCP server to help you discover positions and submit your application. + +## Available Tools + +The MCP server provides three tools that Claude can use: + +### `list_jobs` +Lists all open positions at Root Ventures. + +### `get_job_details` +Gets detailed information about a specific position. + +**Parameters:** +- `job_id` (required): The job identifier (e.g., "associate") + +### `apply_to_root_ventures` +Submits a job application to Root Ventures. + +**Parameters:** +- `name` (required): Your full name +- `email` (required): Your email address +- `linkedin` (optional): LinkedIn profile URL +- `github` (optional): GitHub username +- `notes` (optional): Why Root? What makes you a great fit? +- `position` (optional): Position applying for (defaults to "Venture Capital Associate") + +## Example Conversation + +``` +You: I want to apply to Root Ventures + +Claude: I can help you apply! Let me check what positions are open. + [Uses list_jobs tool] + + Root Ventures has an open position for Venture Capital Associate. + Would you like to learn more about it? + +You: Yes, tell me about it + +Claude: [Uses get_job_details tool] + + Here are the details... [shows full job description] + + Would you like to apply? + +You: Yes, I'd like to apply. My name is Jane Doe, email is jane@example.com, + GitHub is janedoe, and I'm excited about deep tech investing because... + +Claude: [Uses apply_to_root_ventures tool] + + ✓ Application submitted successfully! + + Thank you for applying, Jane! Your application has been received... +``` + +## Why Apply via MCP? + +- **Technical credibility**: Shows you're comfortable with cutting-edge developer tools +- **Efficiency**: Apply without context switching or filling out web forms +- **Memorable**: Stand out by using a novel application method +- **Community**: You're using a protocol that's shaping the future of AI tooling + +## Local Development + +Clone the repo and run locally: + +```bash +git clone https://github.com/rootvc/cli-website +cd cli-website/mcp-server +npm install +node index.js +``` + +## About Root Ventures + +Root Ventures is a San Francisco-based deep tech seed fund. As engineers ourselves, we specialize in leading initial funding for founders tackling new technical opportunities. + +- Website: https://root.vc +- Twitter: [@rootvc](https://twitter.com/rootvc) +- GitHub: [github.com/rootvc](https://github.com/rootvc) + +## Other Ways to Apply + +Not into MCP? We've got you covered: + +- **Web Terminal**: https://root.vc (type `apply 1`) +- **Email**: hello@root.vc +- **Traditional**: Visit our website + +## License + +MIT + +## Questions? + +Email hello@root.vc or open an issue on GitHub. + +--- + +*Built with the Model Context Protocol. Learn more at https://modelcontextprotocol.io* diff --git a/mcp-server/index.js b/mcp-server/index.js new file mode 100755 index 00000000..910dc9c3 --- /dev/null +++ b/mcp-server/index.js @@ -0,0 +1,277 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; + +// Attio webhook URL for Root Ventures job applications +// This is intentionally public - it only accepts job application data +// and cannot be used to read or modify existing Attio records +const ATTIO_WEBHOOK_URL = "https://hooks.attio.com/w/1d456d59-a7ac-4211-ac1d-fac612f7f491/5fc14931-0124-4121-b281-1dbfb64dceb2"; + +// Job descriptions +const JOBS = { + "associate": { + title: "Venture Capital Associate", + description: `Root Ventures is looking for a technical associate to join our team in San Francisco. + +We're a deep tech seed fund that invests in bold engineers building the future. As an associate, you'll work directly with partners to source deals, conduct technical diligence, and support portfolio companies. + +What we're looking for: +• Strong technical background (CS degree, engineering experience, or equivalent) +• Genuine curiosity about emerging technologies and startups +• Excellent communication skills - you can explain complex tech simply +• Hustle and resourcefulness - you figure things out +• Passion for working with technical founders + +Bonus points: +• You've built side projects or contributed to open source +• You're active in technical communities +• You have startup experience + +This is a rare opportunity to learn venture capital from experienced operators and work with some of the most innovative technical founders in the world.` + } +}; + +// Create server instance +const server = new Server( + { + name: "root-ventures-jobs", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + }, + } +); + +// List available tools +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "list_jobs", + description: "List all open positions at Root Ventures", + inputSchema: { + type: "object", + properties: {}, + }, + }, + { + name: "get_job_details", + description: "Get detailed information about a specific job position", + inputSchema: { + type: "object", + properties: { + job_id: { + type: "string", + description: "The job identifier (e.g., 'associate')", + }, + }, + required: ["job_id"], + }, + }, + { + name: "apply_to_root_ventures", + description: "Submit a job application to Root Ventures. Collects applicant information and submits it to the team.", + inputSchema: { + type: "object", + properties: { + name: { + type: "string", + description: "Applicant's full name", + }, + email: { + type: "string", + description: "Applicant's email address", + }, + linkedin: { + type: "string", + description: "LinkedIn profile URL (optional)", + }, + github: { + type: "string", + description: "GitHub username (optional)", + }, + notes: { + type: "string", + description: "Why Root? What makes you a great fit? (optional)", + }, + position: { + type: "string", + description: "Position applying for (default: 'Venture Capital Associate')", + }, + }, + required: ["name", "email"], + }, + }, + ], + }; +}); + +// Handle tool calls +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case "list_jobs": { + const jobList = Object.entries(JOBS) + .map(([id, job]) => `**${job.title}** (ID: ${id})`) + .join("\n"); + + return { + content: [ + { + type: "text", + text: `Open Positions at Root Ventures:\n\n${jobList}\n\nUse get_job_details with the job ID to learn more, or apply_to_root_ventures to submit an application.`, + }, + ], + }; + } + + case "get_job_details": { + const { job_id } = args; + const job = JOBS[job_id]; + + if (!job) { + return { + content: [ + { + type: "text", + text: `Job "${job_id}" not found. Available jobs: ${Object.keys(JOBS).join(", ")}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text", + text: `# ${job.title}\n\n${job.description}\n\n---\n\nReady to apply? Use the apply_to_root_ventures tool to submit your application!`, + }, + ], + }; + } + + case "apply_to_root_ventures": { + const { name, email, linkedin, github, notes, position } = args; + + // Validate required fields + if (!name || !email) { + return { + content: [ + { + type: "text", + text: "Error: Name and email are required fields.", + }, + ], + isError: true, + }; + } + + // Prepare payload - only include fields that have values + const payload = { + name, + email, + position: position || "Venture Capital Associate", + source: "MCP Server" + }; + + // Add optional fields only if they have values + if (linkedin && linkedin.trim()) payload.linkedin = linkedin.trim(); + if (github && github.trim()) payload.github = github.trim(); + if (notes && notes.trim()) payload.notes = notes.trim(); + + // Log to stderr for debugging + console.error('[MCP] Submitting application:', JSON.stringify(payload, null, 2)); + + // Submit to Attio + const response = await fetch(ATTIO_WEBHOOK_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + + console.error('[MCP] Attio response status:', response.status); + + if (!response.ok) { + const errorText = await response.text(); + console.error('[MCP] Attio error response:', errorText); + throw new Error(`Attio API returned ${response.status}: ${errorText}`); + } + + const responseData = await response.text(); + console.error('[MCP] Attio success response:', responseData); + + return { + content: [ + { + type: "text", + text: `✓ Application submitted successfully! + +Thank you for applying, ${name}! Your application has been received by the Root Ventures team. + +What happens next: +• The team will review your application +• If there's a good fit, someone will reach out to schedule a conversation +• In the meantime, check out Root's portfolio at https://root.vc + +Applied via MCP Server - extra points for technical creativity! 🚀`, + }, + ], + }; + } + + default: + return { + content: [ + { + type: "text", + text: `Unknown tool: ${name}`, + }, + ], + isError: true, + }; + } + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error: ${error.message}`, + }, + ], + isError: true, + }; + } +}); + +// Handle errors +server.onerror = (error) => { + console.error("[MCP Error]", error); +}; + +process.on('SIGINT', async () => { + await server.close(); + process.exit(0); +}); + +// Start the server +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); +}); diff --git a/mcp-server/install.sh b/mcp-server/install.sh new file mode 100755 index 00000000..956029a2 --- /dev/null +++ b/mcp-server/install.sh @@ -0,0 +1,109 @@ +#!/bin/bash + +# Root Ventures MCP Server Installer +# Automatically configures Claude Desktop to use the Root Ventures jobs MCP server + +set -e + +echo "🚀 Root Ventures MCP Server Installer" +echo "======================================" +echo "" + +# Detect OS and set config path +if [[ "$OSTYPE" == "darwin"* ]]; then + CONFIG_PATH="$HOME/Library/Application Support/Claude/claude_desktop_config.json" +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + CONFIG_PATH="$HOME/.config/Claude/claude_desktop_config.json" +elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then + CONFIG_PATH="$APPDATA/Claude/claude_desktop_config.json" +else + echo "❌ Unsupported operating system: $OSTYPE" + exit 1 +fi + +echo "📁 Config file: $CONFIG_PATH" +echo "" + +# Check if Claude Desktop is installed +CONFIG_DIR=$(dirname "$CONFIG_PATH") +if [ ! -d "$CONFIG_DIR" ]; then + echo "❌ Claude Desktop doesn't appear to be installed." + echo "" + echo "Please install Claude Desktop first:" + echo "https://claude.ai/download" + echo "" + exit 1 +fi + +# Create config file if it doesn't exist +if [ ! -f "$CONFIG_PATH" ]; then + echo "📝 Creating new config file..." + mkdir -p "$CONFIG_DIR" + echo '{"mcpServers":{}}' > "$CONFIG_PATH" +fi + +# Backup existing config +BACKUP_PATH="${CONFIG_PATH}.backup-$(date +%Y%m%d-%H%M%S)" +echo "💾 Backing up existing config to:" +echo " $BACKUP_PATH" +cp "$CONFIG_PATH" "$BACKUP_PATH" + +# Check if Node.js is installed +if ! command -v node &> /dev/null; then + echo "❌ Node.js is not installed." + echo "" + echo "Please install Node.js 18 or later:" + echo "https://nodejs.org" + echo "" + exit 1 +fi + +NODE_VERSION=$(node --version) +echo "✅ Node.js $NODE_VERSION detected" +echo "" + +# Read existing config +EXISTING_CONFIG=$(cat "$CONFIG_PATH") + +# Check if root-ventures-jobs already exists +if echo "$EXISTING_CONFIG" | grep -q "root-ventures-jobs"; then + echo "⚠️ Root Ventures MCP server is already configured!" + echo "" + read -p "Do you want to update it? (y/N) " -n 1 -r + echo "" + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Installation cancelled." + exit 0 + fi +fi + +# Add or update the MCP server config using Node.js to properly handle JSON +node -e " +const fs = require('fs'); +const config = JSON.parse(fs.readFileSync('$CONFIG_PATH', 'utf8')); + +if (!config.mcpServers) { + config.mcpServers = {}; +} + +config.mcpServers['root-ventures-jobs'] = { + command: 'npx', + args: ['-y', '@rootvc/jobs-mcp-server'] +}; + +fs.writeFileSync('$CONFIG_PATH', JSON.stringify(config, null, 2)); +" + +echo "✅ Configuration updated successfully!" +echo "" +echo "📋 Next steps:" +echo "" +echo "1. Restart Claude Desktop (completely quit and reopen)" +echo "2. Open a conversation and say:" +echo " 'Show me open positions at Root Ventures'" +echo "" +echo "3. Claude will help you apply through natural conversation" +echo "" +echo "Good luck! 🎉" +echo "" +echo "Questions? Email hello@root.vc" diff --git a/mcp-server/package.json b/mcp-server/package.json new file mode 100644 index 00000000..828d282d --- /dev/null +++ b/mcp-server/package.json @@ -0,0 +1,40 @@ +{ + "name": "@rootvc/jobs-mcp-server", + "version": "1.0.0", + "description": "Apply to Root Ventures positions through Claude Desktop using MCP", + "main": "index.js", + "type": "module", + "bin": { + "root-ventures-jobs": "./index.js" + }, + "scripts": { + "start": "node index.js", + "test": "node test.js" + }, + "keywords": [ + "mcp", + "jobs", + "root-ventures", + "venture-capital", + "claude", + "ai", + "job-application" + ], + "author": "Root Ventures ", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.25.3" + }, + "repository": { + "type": "git", + "url": "https://github.com/rootvc/cli-website.git", + "directory": "mcp-server" + }, + "homepage": "https://root.vc", + "bugs": { + "url": "https://github.com/rootvc/cli-website/issues" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/mcp-server/test-mcp.js b/mcp-server/test-mcp.js new file mode 100644 index 00000000..9846183e --- /dev/null +++ b/mcp-server/test-mcp.js @@ -0,0 +1,82 @@ +#!/usr/bin/env node + +/** + * Simple test script for the MCP server + * This simulates what Claude Desktop would send to the server + */ + +import { spawn } from 'child_process'; + +// Start the MCP server +const server = spawn('node', ['index.js'], { + cwd: process.cwd() +}); + +let output = ''; + +server.stdout.on('data', (data) => { + output += data.toString(); + console.log('Server response:', data.toString()); +}); + +server.stderr.on('data', (data) => { + console.log('Server log:', data.toString()); +}); + +// Wait for server to start +setTimeout(() => { + console.log('\n=== Testing list_tools ==='); + + const listToolsRequest = { + jsonrpc: "2.0", + id: 1, + method: "tools/list", + params: {} + }; + + server.stdin.write(JSON.stringify(listToolsRequest) + '\n'); + + setTimeout(() => { + console.log('\n=== Testing list_jobs ==='); + + const listJobsRequest = { + jsonrpc: "2.0", + id: 2, + method: "tools/call", + params: { + name: "list_jobs", + arguments: {} + } + }; + + server.stdin.write(JSON.stringify(listJobsRequest) + '\n'); + + setTimeout(() => { + console.log('\n=== Testing get_job_details ==='); + + const jobDetailsRequest = { + jsonrpc: "2.0", + id: 3, + method: "tools/call", + params: { + name: "get_job_details", + arguments: { + job_id: "associate" + } + } + }; + + server.stdin.write(JSON.stringify(jobDetailsRequest) + '\n'); + + setTimeout(() => { + console.log('\n=== Test complete ==='); + server.kill(); + process.exit(0); + }, 1000); + }, 1000); + }, 1000); +}, 1000); + +server.on('exit', (code) => { + console.log(`Server exited with code ${code}`); +}); diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 00000000..0a63758b --- /dev/null +++ b/netlify.toml @@ -0,0 +1,5 @@ +[build] + publish = "." + +[functions] + directory = "netlify/functions" diff --git a/netlify/functions/submit-application.js b/netlify/functions/submit-application.js new file mode 100644 index 00000000..dadba28e --- /dev/null +++ b/netlify/functions/submit-application.js @@ -0,0 +1,60 @@ +exports.handler = async (event, context) => { + // Only allow POST + if (event.httpMethod !== 'POST') { + return { + statusCode: 405, + body: JSON.stringify({ error: 'Method not allowed' }), + }; + } + + try { + const data = JSON.parse(event.body); + + // Validate required fields + if (!data.name || !data.email) { + return { + statusCode: 400, + body: JSON.stringify({ error: 'Name and email are required' }), + }; + } + + // Forward to Attio webhook + const attioWebhookUrl = process.env.ATTIO_WEBHOOK_URL; + + if (!attioWebhookUrl) { + throw new Error('ATTIO_WEBHOOK_URL environment variable not set'); + } + + const attioResponse = await fetch( + attioWebhookUrl, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + } + ); + + if (!attioResponse.ok) { + throw new Error(`Attio API error: ${attioResponse.status}`); + } + + return { + statusCode: 200, + body: JSON.stringify({ + success: true, + message: 'Application submitted successfully' + }), + }; + } catch (error) { + console.error('Error submitting application:', error); + return { + statusCode: 500, + body: JSON.stringify({ + error: 'Failed to submit application', + details: error.message + }), + }; + } +};