Skip to main content
Glama
utkarsh21123

about-utkarsh-mcp

by utkarsh21123
README.md
# about-utkarsh-mcp

An MCP (Model Context Protocol) server that exposes information about **Utkarsh** —
bio, skills, work experience, and portfolio projects — to any MCP-compatible client
(Claude, Claude Code, ChatGPT, Codex, etc.).

It ships two transports from one shared server implementation:

| Transport | File | Used by |
|---|---|---|
| **stdio** | `src/transports/stdio.ts` | Local clients that launch the server as a subprocess: Claude Desktop, Claude Code, Codex CLI |
| **Streamable HTTP** (remote) | `src/transports/http.ts` | Remote clients that connect over a URL: ChatGPT connectors, remote Claude/Claude Code, any HTTP-based MCP client |

The remote HTTP transport is protected with **OAuth 2.0**, including **Dynamic Client
Registration (DCR, RFC 7591)** — so a new MCP client can self-register and connect
without any manual "create an API key" step, exactly as the MCP Authorization spec
expects for remote servers.

**Live deployment:** `https://about-utkarsh-mcp.onrender.com/mcp`
**Repo:** `https://github.com/utkarsh21123/about-utkarsh-mcp`

## Architecture

```
src/
  data/profile.ts        # Single source of truth: bio, skills, experience, projects
  mcpServer.ts            # Shared MCP server: tools + one resource, used by both transports
  transports/
    stdio.ts               # Local subprocess transport
    http.ts                 # Remote Streamable HTTP transport (OAuth-protected)
  oauth/
    store.ts                # In-memory client/auth-code store
    router.ts               # DCR, authorize (PKCE), token, discovery metadata endpoints
```

### MCP tools exposed

- `get_bio` — name, title, location, experience summary, links
- `get_skills` — skills by category (languages/frontend/backend/databases/infra/spoken)
- `get_experience` — work history
- `list_projects` — all portfolio projects (summary)
- `get_project_details` — full detail for one named project
- `get_contact` — how to reach out

Plus one resource: `profile://utkarsh/full` (the whole profile as JSON).

### OAuth / DCR flow (remote HTTP transport)

1. Client discovers the server via `GET /.well-known/oauth-protected-resource`
   (RFC 9728) and `GET /.well-known/oauth-authorization-server` (RFC 8414).
2. Client **dynamically registers itself**: `POST /register` (RFC 7591) — no
   pre-shared client id/secret needed.
3. Client runs the standard Authorization Code + PKCE flow: `GET /authorize`
   (a one-click "Approve" page — there's no personal login here, just consent
   to read a public profile) → `POST /token`.
4. Client calls `POST/GET/DELETE /mcp` with `Authorization: Bearer <token>`.

This was verified end-to-end (registration → authorize → token exchange →
authenticated `initialize` and `tools/call`) both locally and against the live
Render deployment.

---

## Running it locally — full step by step

### Prerequisites

- Node.js 18+ (tested on Node 22) and npm
- `git`
- `curl` (for manual testing) — optional but recommended

### 1. Clone and install

```bash
git clone https://github.com/utkarsh21123/about-utkarsh-mcp.git
cd about-utkarsh-mcp
npm install
```

### 2. Build

```bash
npm run build
```

This compiles `src/` (TypeScript) to `dist/` (JavaScript) via `tsc`. Re-run this
any time you edit a `.ts` file — the running server uses the compiled `dist/` output.

### 3a. Run the stdio server (local client mode)

```bash
npm run start:stdio
```

You should see, on **stderr** (not stdout — stdout is reserved for the MCP
JSON-RPC protocol):

```
about-utkarsh-mcp: stdio server ready
```

It's now waiting for a client to talk to it over stdin/stdout. Two ways to test:

**Option A — connect a real client.** Add this to Claude Desktop's
`claude_desktop_config.json` or Claude Code's `.mcp.json`:

```json
{
  "mcpServers": {
    "about-utkarsh": {
      "command": "node",
      "args": ["/absolute/path/to/about-utkarsh-mcp/dist/transports/stdio.js"]
    }
  }
}
```

Or with the Claude Code CLI:
```bash
claude mcp add about-utkarsh -- node /absolute/path/to/about-utkarsh-mcp/dist/transports/stdio.js
```

Restart the client, then ask it something like "What are Utkarsh's skills?" and
confirm it invokes a tool (not just answers from general knowledge).

**Option B — manual JSON-RPC test**, no client needed:

```bash
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}\n{"jsonrpc":"2.0","method":"notifications/initialized"}\n{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_bio","arguments":{}}}\n' | node dist/transports/stdio.js
```

You should get back an `initialize` response followed by a `tools/call` result
containing your bio as JSON.

### 3b. Run the HTTP server (remote client mode)

```bash
npm run start:http
```

Defaults to port 3000. You'll see:

```
about-utkarsh-mcp: HTTP server listening on port 3000
  MCP endpoint:     http://localhost:3000/mcp
  OAuth discovery:  http://localhost:3000/.well-known/oauth-authorization-server
  Auth required:    true
```

To test without dealing with OAuth first (useful while developing):

```bash
REQUIRE_AUTH=false npm run start:http
curl -s -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
```

### 4. Full local test of the OAuth/DCR flow

With the server running (`REQUIRE_AUTH=true`, the default), in a second terminal:

```bash
URL=http://localhost:3000

# 1. Health check
curl -s $URL/health

# 2. Discovery metadata
curl -s $URL/.well-known/oauth-authorization-server
curl -s $URL/.well-known/oauth-protected-resource

# 3. Confirm auth is enforced (expect 401)
curl -s -i -X POST $URL/mcp -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | head -6

# 4. Dynamic Client Registration
REG=$(curl -s -X POST $URL/register -H "Content-Type: application/json" -d '{
  "client_name": "Test Client",
  "redirect_uris": ["https://client.example.com/callback"],
  "token_endpoint_auth_method": "none"
}')
echo "$REG"
CLIENT_ID=$(echo "$REG" | node -e "process.stdin.on('data',d=>console.log(JSON.parse(d).client_id))")

# 5. PKCE challenge/verifier
VERIFIER=$(node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))")
CHALLENGE=$(node -e "console.log(require('crypto').createHash('sha256').update('$VERIFIER').digest('base64url'))")

# 6. Authorize + approve (simulates clicking "Approve" in the browser)
LOC=$(curl -s -o /dev/null -w '%{redirect_url}' "$URL/authorize/approve?client_id=$CLIENT_ID&redirect_uri=https://client.example.com/callback&state=xyz&code_challenge=$CHALLENGE&code_challenge_method=S256")
CODE=$(node -e "console.log(new URL('$LOC').searchParams.get('code'))")

# 7. Exchange code for token
TOKEN_RESP=$(curl -s -X POST $URL/token -H "Content-Type: application/json" \
  -d "{\"grant_type\":\"authorization_code\",\"code\":\"$CODE\",\"redirect_uri\":\"https://client.example.com/callback\",\"client_id\":\"$CLIENT_ID\",\"code_verifier\":\"$VERIFIER\"}")
echo "$TOKEN_RESP"
ACCESS_TOKEN=$(echo "$TOKEN_RESP" | node -e "process.stdin.on('data',d=>console.log(JSON.parse(d).access_token))")

# 8. Authenticated MCP call
curl -s -i -X POST $URL/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
```

Expect a `200 OK` on the last call with an `mcp-session-id` header and the
server's `initialize` payload in the body.

### 5. (Optional) Develop without rebuilding each time

```bash
npm run dev:stdio   # tsx, runs src/transports/stdio.ts directly
npm run dev:http    # tsx, runs src/transports/http.ts directly
```

These use `tsx` to run the TypeScript source directly — no `npm run build` step
needed between edits.

---

## Deployment (remote HTTP server)

The included `Dockerfile` builds a production image; `render.yaml` is a one-click
blueprint for [Render](https://render.com), but this deploys the same way on
Railway, Fly.io, or any container host.

### Render (recommended, free tier available)

1. Push this repo to GitHub.
2. On Render: **New → Blueprint**, point it at the repo — `render.yaml` configures
   everything (including generating a random `JWT_SECRET`).
3. Once deployed, set `PUBLIC_BASE_URL` to the assigned `https://<app>.onrender.com`
   URL in the service's environment settings and it'll redeploy automatically, so
   OAuth metadata advertises the correct absolute URLs.
4. Your MCP endpoint is `https://<app>.onrender.com/mcp`.

**Note on the free tier:** Render's free instances spin down after a period of
inactivity and take up to ~50 seconds to spin back up on the next request. If
you need consistently fast/always-on responses, upgrade the Render service to a
paid instance type.

### Any other Docker host (Railway, Fly.io, a VPS, etc.)

```bash
docker build -t about-utkarsh-mcp .
docker run -p 3000:3000 \
  -e JWT_SECRET=$(node -e "console.log(require('crypto').randomBytes(48).toString('hex'))") \
  -e PUBLIC_BASE_URL=https://your-deployed-domain.example.com \
  about-utkarsh-mcp
```

## Connecting clients

### Claude Desktop / Claude Code (local, stdio)

See step 3a above.

### Claude (remote, HTTP + OAuth)

In Claude's connector settings, add a custom connector pointing at:
```
https://about-utkarsh-mcp.onrender.com/mcp
```
Claude auto-discovers the OAuth metadata, registers itself via DCR, and walks
you through the one-click approval — no manual credentials required.

### ChatGPT (connectors / remote MCP)

Same idea: add a custom connector pointing at the same `/mcp` URL. ChatGPT
performs the same discovery → DCR → OAuth flow.

### Codex

- **Codex CLI** (local): same config as Claude Code, pointing at
  `node dist/transports/stdio.js`.
- **Codex web/cloud** (remote): use the deployed `/mcp` URL as above.

## Notes on the OAuth implementation

- Storage for registered clients/auth codes is **in-memory** (`src/oauth/store.ts`)
  — fine for an assessment/demo; swap in a real database for production so
  registrations survive restarts and multiple instances.
- Access tokens are short-lived HS256 JWTs (1 hour). There's no user login screen
  because the "resource" being protected is a public profile with no per-user
  data — the approval screen exists to satisfy the OAuth consent step the MCP
  spec expects, not to gate a private account.
- PKCE (RFC 7636) is enforced for public clients (`token_endpoint_auth_method: none`),
  which is what DCR-registered MCP clients typically use.

## Keeping the data current

Everything the tools return lives in `src/data/profile.ts`. Update that file,
`npm run build`, and redeploy to change what the server tells clients about Utkarsh.

TDQS

A4.2/5.0

Scored across 6 tools

Disambiguation5/5

Each tool retrieves a distinct piece of information (bio, skills, experience, projects, project details, contact) with no overlap in purpose, making selection unambiguous.

Naming Consistency5/5

All tools follow a consistent 'get_' + noun pattern (e.g., get_bio, get_skills), providing a predictable and clear naming convention.

Tool Count5/5

Six tools is well-scoped for a personal profile server, covering all key areas without being excessive or insufficient.

Completeness5/5

The tool set fully covers the domain of a personal portfolio: biography, skills, experience, projects (with details), and contact information, with no obvious gaps.

Maintenance

ActivitySlowing
ResponsivenessNo issues