The Old Age Pension portal is the system of record. Two MCP servers over streamable HTTP let AI assistants apply or follow up as a citizen, or review and decide as a block officer. MCP tools call the same validation and write paths as the web forms. Agents cannot invent eligibility the portal would refuse.
Live URLs Admin login Citizen portal Officer dashboard MCP install Eval dashboard Local compose Design ยท MCP detail
| Surface | URL |
|---|---|
| Portal | https://seva-setu.ruchir.dev |
| Docs | https://seva-setu.ruchir.dev/docs/ |
| Eval dashboard | https://seva-setu.ruchir.dev/eval-dashboard |
| Citizen MCP | https://seva-setu.ruchir.dev/mcp/citizen |
| Officer MCP | https://seva-setu.ruchir.dev/mcp/officer |
| SMS OTP inbox | /__gateway/ |
For officer web flows and OAuth with scope=officer:
adminCAz4uecdpQIfoi2o2AM7xazaSign in at /admin.
/__gateway/api/messages?to=<mobile>.Track or withdraw a pending application at /status.
The password is the date of birth as DDMMYYYY. One active
application is allowed at a time. Statuses PENDING, APPROVED, and
DEEMED_APPROVED block a new apply until withdraw or a terminal reject.
Agent apply through the citizen MCP is limited to blocks Sonari, Rajapara, and Namti. Other blocks must use the portal or a CSC.
Full MCP notes: /docs/mcp.
Short version:
https://seva-setu.ruchir.dev/mcp/citizenhttps://seva-setu.ruchir.dev/mcp/officer/.well-known/oauth-authorization-server. Use PKCE S256,
scope=citizen or scope=officer, and
resource=<MCP URL>.Opening an MCP URL in a browser with GET returns 401. That is expected. The endpoint needs a Bearer token and an MCP client using streamable HTTP, not a plain page load.
{
"mcpServers": {
"seva-setu-citizen": {
"type": "http",
"url": "https://seva-setu.ruchir.dev/mcp/citizen"
},
"seva-setu-officer": {
"type": "http",
"url": "https://seva-setu.ruchir.dev/mcp/officer"
}
}
}
Claude Code also accepts:
claude mcp add --transport http seva-setu-citizen \ https://seva-setu.ruchir.dev/mcp/citizen claude mcp add --transport http seva-setu-officer \ https://seva-setu.ruchir.dev/mcp/officer
Cursor: put the same JSON in ~/.cursor/mcp.json or
.cursor/mcp.json. A remote entry with only url is
enough for OAuth discovery in the IDE.
Live results: /eval-dashboard.
Personas in evals/personas.json run against the real MCP
servers with real OAuth and Postgres assertions. The runner writes
evals/results/latest.json, which the portal renders.
ADMIN_PASSWORD='CAz4uecdpQIfoi2o2AM7xaza' \ PUBLIC_BASE_URL='https://seva-setu.ruchir.dev' \ CITIZEN_MCP_URL='https://seva-setu.ruchir.dev/mcp/citizen' \ OFFICER_MCP_URL='https://seva-setu.ruchir.dev/mcp/officer' \ DB_HOST=localhost \ python evals/runner.py
Citizen personas complete mobile OTP via the SMS gateway. The officer persona uses the admin password.
ADMIN_PASSWORD='pick-something' \ PUBLIC_BASE_URL='http://localhost:8000' \ CITIZEN_MCP_URL='http://localhost:8001/mcp/citizen' \ OFFICER_MCP_URL='http://localhost:8002/mcp/officer' \ docker compose up --build -d
If container-to-container TCP fails in your environment:
docker compose -f docker-compose.yml -f docker-compose.sandbox.yml up --build -d
| Surface | Local URL |
|---|---|
| Portal / docs | http://localhost:8000 and http://localhost:8000/docs/ |
| Citizen MCP | http://localhost:8001/mcp/citizen |
| Officer MCP | http://localhost:8002/mcp/officer |
| Unified gateway | http://localhost:8080 |
| SMS OTP inbox | http://localhost:8000/__gateway/ |
Point MCP clients at the local MCP URLs the same way as the live ones. Set
PUBLIC_BASE_URL and the MCP URLs so OAuth issuer and audience
match what the client calls.
See DESIGN.md in the repository for the three decisions that
mattered: portal as sole writer of scheme truth, two MCP resource servers on
one OAuth AS, and agent apply limited to three blocks. The note also records
what we would redo next for age-proof uploads.