Skip to main content
Glama
README.md
# portfolio-mcp

An MCP (Model Context Protocol) server that enables AI assistants like Claude to manage blog posts and projects for a personal portfolio site. Supports creating, reading, updating, and deleting MDX blog posts and project entries, with Git integration for committing and pushing changes.

## How It Works

This server connects to a separate portfolio repository (e.g., `suthar-portfolio`) and provides tools for content management. It can run in three modes:

- **Local MCP server** -- Connects directly to Claude Desktop via stdio, reads and writes files on the local filesystem
- **REST API server** -- An Express HTTP server for web-based or programmatic access
- **Vercel serverless deployment** -- A serverless function that uses the GitHub API to read/write files without local filesystem access

## Features

- **Post Management**: Full CRUD operations on MDX blog posts with YAML frontmatter
- **Project Management**: Full CRUD operations on the `projectsData.js` file
- **Git Integration**: View repository status and commit/push changes with a two-step confirmation workflow
- **API Key Authentication**: Bearer token auth for securing the REST API and Vercel endpoints
- **CORS Support**: Configurable origin restrictions
- **MCP Proxy**: A bridge that lets Claude Desktop connect to a remote Vercel-deployed backend

## Setup

### 1. Install dependencies

```bash
npm install
```

### 2. Configure

Copy the example config and update it with the path to your portfolio repository:

```bash
cp portfolio-mcp.config.json.example portfolio-mcp.config.json
```

Edit `portfolio-mcp.config.json`:

```json
{
  "portfolioPath": "/path/to/your/portfolio-repo",
  "postsDir": "data/posts",
  "projectsFile": "data/projectsData.js",
  "git": {
    "remote": "origin",
    "branch": "main",
    "requireConfirmation": true
  },
  "api": {
    "port": 3333,
    "host": "localhost"
  }
}
```

Alternatively, set environment variables (takes priority over the config file):

```
PORTFOLIO_PATH=/path/to/your/portfolio-repo
POSTS_DIR=data/posts
PROJECTS_FILE=data/projectsData.js
GIT_REMOTE=origin
GIT_BRANCH=main
API_PORT=3000
API_HOST=localhost
API_KEY=your-secret-key
ALLOWED_ORIGIN=https://yourdomain.com
```

### 3. Build

```bash
npm run build
```

## Usage

### MCP Server (Claude Desktop)

```bash
npm start
```

Add to your Claude Desktop `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "portfolio-mcp": {
      "command": "node",
      "args": ["/path/to/portfolio-mcp/dist/index.js"],
      "cwd": "/path/to/portfolio-mcp"
    }
  }
}
```

### REST API Server

```bash
npm run start:api
```

### Development (ts-node, no build step)

```bash
npm run dev          # MCP server
npm run dev:api      # REST API server
```

## MCP Tools

| Tool | Description |
|------|-------------|
| `list_posts` | List all blog posts with frontmatter |
| `get_post` | Get a post's full content by slug |
| `create_post` | Create a new MDX blog post |
| `update_post` | Update an existing post's frontmatter or content |
| `delete_post` | Delete a post by slug |
| `list_projects` | List all projects |
| `create_project` | Add a new project |
| `update_project` | Update a project by title |
| `delete_project` | Remove a project by title |
| `git_status` | Show pending changes in the portfolio repo |
| `git_commit_push` | Commit and push changes (dry-run first, then confirm) |

## REST API Endpoints

All endpoints (except `/health`) require a Bearer token in the `Authorization` header when `API_KEY` is set.

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/health` | Health check (no auth required) |
| `GET` | `/api/posts` | List all posts |
| `GET` | `/api/posts/:slug` | Get a single post |
| `POST` | `/api/posts` | Create a post |
| `PUT` | `/api/posts/:slug` | Update a post |
| `DELETE` | `/api/posts/:slug` | Delete a post |
| `GET` | `/api/projects` | List all projects |
| `POST` | `/api/projects` | Create a project |
| `PUT` | `/api/projects/:title` | Update a project |
| `DELETE` | `/api/projects/:title` | Delete a project |
| `GET` | `/api/git/status` | Git status |
| `POST` | `/api/git/commit-push` | Commit and push |

## Data Formats

### Blog Posts

MDX files with YAML frontmatter, stored in `data/posts/`:

```yaml
---
title: 'Post Title'
date: '2024-06-27'
tags: [tag1, tag2, tag3]
draft: false
summary: 'Short description'
---

MDX body content here...
```

Optional frontmatter fields: `pinned` (boolean), `pinnedtext` (string).

### Projects

Managed in `data/projectsData.js` as a JavaScript array:

```js
const projectsData = [
  {
    title: 'Project Name',
    description: 'Description',
    imgSrc: '/image.jpg',
    href: 'https://example.com',
    github: 'https://github.com/...',
    tech1: 'React',
    tech2: 'TypeScript',
    tech3: 'Node.js'
  }
];
export default projectsData;
```

Required fields: `title`, `description`, `imgSrc`, `href`. Optional: `github`, `tech1`, `tech2`, `tech3`.

## Git Workflow

The `git_commit_push` tool uses a two-step confirmation:

1. **First call** (without `confirmed`): Returns a dry-run preview of what would be committed
2. **Second call** (with `confirmed: true`): Executes the commit and push

This prevents accidental commits and lets the user review changes first.

## Vercel Deployment

The `api/portfolio.ts` handler is a self-contained Vercel serverless function that uses the GitHub Contents API directly, so it does not require local filesystem access to the portfolio repo.

### Required environment variables on Vercel

| Variable | Description |
|----------|-------------|
| `GITHUB_TOKEN` | GitHub personal access token with `repo` scope |
| `GITHUB_OWNER` | GitHub username (default: `sohumsuthar`) |
| `GITHUB_REPO` | Repository name (default: `suthar-portfolio`) |
| `GITHUB_BRANCH` | Branch to read/write (default: `main`) |
| `API_KEY` | Secret key for Bearer token auth |
| `ALLOWED_ORIGIN` | CORS allowed origin |

### MCP Proxy for Remote Access

To connect Claude Desktop to a Vercel-deployed backend, use the MCP proxy:

```json
{
  "mcpServers": {
    "portfolio-mcp": {
      "command": "node",
      "args": ["/path/to/portfolio-mcp/dist/mcp-proxy.js"],
      "env": {
        "PORTFOLIO_API_URL": "https://your-deployment.vercel.app",
        "PORTFOLIO_API_KEY": "your-api-key"
      }
    }
  }
}
```

## Security

- API key authentication via Bearer tokens
- CORS restricted to configured origins
- Environment variables for all secrets (never committed to git)
- Request logging on the REST API server
- Health check endpoint available without authentication

## License

MIT