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