saagarpatel.dev Portfolio
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Open source Repository Open in the app JSON README (API)
About
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Details
- Kind
- MCP servers
- Topic
- Finance & crypto
- Publisher
- saagpatel
- Origin
- official
- Category
- ferramentas
- Transport
- http
- Version
- 0.1.0
- Open pull requests
- 1
- Last push
- 2026-09-05T16:04:29Z
- Repository state
- ativo
- Language
- TypeScript
- License
- MIT
- Added
- 2026-08-29 04:01:20
- Updated
- 2026-08-29 04:01:20
- Origin id
io.github.saagpatel/portfolio-mcp
README
# portfolio-mcp
[](https://glama.ai/mcp/servers/saagpatel/portfolio-mcp)
The agent-native layer of [saagarpatel.dev](https://saagarpatel.dev): a Model
Context Protocol server that lets any AI agent query Saagar's writing, projects,
public-safe repo profiles, and benchmark results directly, instead of scraping HTML.
Read-only. Stateless. Public. No auth, no tracking, no database, no runtime egress.
## How it fits
The website stays a pure static site. This server is a **sibling**, not a backend
bolted onto it:
- **Layer 0** (in the `portfolio-index` repo): the build emits a static machine
corpus — `corpus-index.json`, per-document `corpus/<id>.json`, and
`.well-known/mcp.json` — plus public-safe repo profile artifacts when present,
served alongside the HTML. Already public.
- **Layer 1** (this repo, `src/index.ts`): a stateless Cloudflare Worker that bakes
the Layer 0 corpus into its bundle and serves it over MCP (streamable HTTP, the
`WebStandardStreamableHTTPServerTransport`). Zero runtime fetches.
- **Layer 2** (this repo, `src/stdio.ts`): the same server over stdio, for running
locally via `npx saagar-portfolio-mcp`. Identical tool/resource/prompt surface.
The shared core (`src/server.ts`) is transport-agnostic; both layers wrap it.
## Tool surface
All read-only (`readOnlyHint: true`). No tool takes a URL or filesystem path (no
SSRF / exfil surface).
| Tool | Purpose |
|---|---|
| `search` | BM25 over the whole corpus; optional `section` filter, `limit` |
| `get_document` | Full Markdown of one document by `id` |
| `list_corpus` | The table of contents; optional `type` filter |
| `get_profile` | The "who is this" card (about / now / uses) |
| `list_projects` | Curated public-safe projects + anonymized aggregates |
| `list_repo_profiles` | Public-safe repo profile index with freshness and proof counts |
| `get_repo_profile` | One repo answering profile by `repo_id` |
| `get_operant_results` | Public, sanitized OPERANT calibration results (per-model OCS) |
Documents are also exposed as **Resources** (`portfolio://essays/{slug}`, `book/{slug}`,
`notes/{slug}`, `portfolio://profile`), and there are two **Prompts**:
`introduce_saagar` and `summarize_writing_on` (grounded in a live `search`).
## Retrieval
BM25 over a baked index (no embeddings in v1 — the corpus is ~50 small docs and the
calling LLM supplies the semantics). Titles are boosted. Embeddings are a measured
Phase 3 upgrade, added only if retrieval quality proves insufficient.
## Layout
```
src/
types.ts corpus + projects + operant shapes
bm25.ts dependency-free BM25 + snippet (pure)
tools.ts createTools(corpus) -> the 8 tools (pure, injectable)
corpus.ts loads the baked corpus + accessors
corpus.generated.ts AUTO-GENERATED by build:corpus
server.ts buildServer(): shared MCP core (tools + resources + prompts)
index.ts Cloudflare Worker transport (streamable HTTP)
stdio.ts Layer 2 stdio transport (the npx CLI)
scripts/
build-corpus.mjs bakes Layer 0 (+ OPERANT) into corpus.generated.ts
probe-mcp.mjs probes an MCP HTTP endpoint (saagar-mcp-kit driver + domain calls)
audit-mcp.sh connected MCPAudit scan of this server (dogfood)
test/ vitest: bm25, tools, full-protocol server tests
```
## Develop
```sh
npm install
npm run build:corpus # bake from ../portfolio-index (or --url=https://saagarpatel.dev)
npm run typecheck
npm test
npm run dev # wrangler dev -> http://localhost:8787/mcp
npm run smoke # end-to-end MCP smoke under the real workerd runtime (saagar-mcp-kit)
npm run probe:mcp # live Worker probe, or set PORTFOLIO_MCP_ENDPOINT
```
Inspect either transport with the MCP inspector:
```sh
npx @modelcontextprotocol/inspector http://localhost:8787/mcp # Layer 1 (HTTP)
npx @modelcontextprotocol/inspector node dist/stdio.js # Layer 2 (stdio, after build:cli)
```
## Deploy (Layer 1)
```sh
npm run build:corpus && npm run deploy # wrangler deploy
npm run probe:mcp # post-deploy live MCP readback
```
Operator-gated (needs Cloudflare auth). v1 still deploys to the default
`portfolio-mcp.<account>.workers.dev` URL, and `npm run probe:mcp` uses that stable
Worker URL by default. Public discovery now advertises the verified custom endpoint
`https://mcp.saagarpatel.dev/mcp`; after any deploy, verify both the Worker and the
website manifest/readback path before changing `.well-known/mcp.json`.
`wrangler.jsonc` pins `workers_dev: true` so the public Worker URL stays live during any
future custom-domain experiments; do not remove it unless the website manifest has already
moved to a verified replacement endpoint.
## Publish (Layer 2)
```sh
npm run build:corpus && npm run build:cli # -> dist/stdio.js
npm login && npm publish # public package: saagar-portfolio-mcp
```
Once published, anyone can run it locally with `npx saagar-portfolio-mcp` (no install).
## Sign the manifest (optional trust signal)
Ed25519-sign `.well-known/mcp.json` so an agent or registry can verify it authentically
comes from Saagar (via `saagar-mcp-kit`'s signing CLI, Node built-in crypto):
```sh
npx mcp-kit-sign gen-key --manifest=../portfolio-index/.well-known/mcp.json # one-time; private key -> .signing/ (gitignored, NEVER commit)
npm run sign # writes <manifest>.sig + publishes mcp-ed25519.pub
npm run sign:verify # checks manifest bytes against .sig + public key
```
Override paths with `--manifest=`/`--key=`/`--pub=`/`--sig=`.
Commit the `.sig` + `mcp-ed25519.pub` (never the private key) into portfolio-index next to the manifest, then
redeploy the site. Re-run `sign` whenever the manifest changes (it signs the exact served bytes).
## Audit posture
Designed to pass `MCPAudit` / `mcp-trust` (Saagar's own tools): only the inbound MCP
transport, no `shell_execution` / `file_access` / `destructive` / `exfiltration`, and no
caller-controlled egress (the corpus is baked). All tools are annotated read-only with
plain, non-injectable descriptions. `bash scripts/audit-mcp.sh` runs a connected scan.
Dogfooding this server surfaced a substring-matching false-positive bug in MCPAudit (it
matched `port` inside `portfolio://`); that fix lives in the MCPAudit repo and cut this
server's findings 62 → 14. The genuine tool surface scans clean (`high_risk_servers: 0`).
## Status
- **Built + locally verified:** Layers 0–2. Shared core + 8 tools + Resources + 2 prompts +
`get_operant_results`. typecheck clean; test suite passes (incl. full MCP protocol via the
fetch handler). Live Worker probe and deploy remain operator-gated. Public discovery
advertises `mcp.saagarpatel.dev` with a valid Ed25519-signed manifest.
- **Gated / next:** publish the stdio package (`npm publish`, after removing
`"private": true` by explicit operator approval only), glama.ai registry listing, and
continued signed-manifest readback checks after website manifest changes.