{
  "markdown": "# Chakudya MCP Server\n\nAn [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server that exposes the **Chakudya\nNutrition Registry (CNR)** API as a set of MCP tools, so any MCP-compatible client (Claude, Claude Code,\nother LLM agents) can search Malawian food data, run clinical nutrition lookups, and query the RAG\nknowledge base directly.\n\n**This is a new, separate layer. It does not replace or modify the Chakudya Worker.** It's a small\nNode/TypeScript HTTP service that sits in front of your existing API and translates MCP tool calls into\nplain HTTP requests against the routes your Worker already serves.\n\n```\nMCP Client (Claude, etc.)\n        │  Streamable HTTP (JSON-RPC over HTTP + SSE)\n        ▼\nChakudya MCP Server  (this project)\n        │  plain HTTPS fetch()\n        ▼\nChakudya Worker API  (unchanged) → Supabase / Cohere / Groq / USDA / OFF / FatSecret\n```\n\n## Why a separate server, not a Worker\n\nThe official MCP TypeScript SDK's `StreamableHTTPServerTransport` is built for Node's\n`http.IncomingMessage`/`ServerResponse`. Cloudflare Workers use the Fetch API instead, and the SDK's\nweb-standard variant (`WebStandardStreamableHTTPServerTransport`) is newer and less battle-tested for\nproduction session management. Running this as a plain Node service (Docker, Render, Fly.io, a VPS,\netc.) is the more standard, better-documented path today, and it keeps this concern fully decoupled from\nyour Worker's deploy cycle. Nothing stops you from porting it to the web-standard transport on Workers\nlater if you want a single-platform deploy — the tool logic in `src/tools/*` doesn't care which\ntransport wraps it.\n\n## Tools\n\nAll 31 tools either call your existing Chakudya Worker over HTTPS, or are pure in-process calculation/table\nlookups — none of them touch Supabase, Cohere, or Groq directly, and none of them need `ADMIN_API_KEY`\n(every route they use is public).\n\n| Tool | Chakudya route(s) used |\n|---|---|\n| `search_food` | `GET /foods` → falls back to `GET /foods/lookup` |\n| `get_food_details` | `GET /foods/:id` |\n| `calculate_nutrients` | `GET /foods` or `/foods/:id`, then scales per-100g values in-process |\n| `analyze_meal` | same as above, looped and summed across multiple items |\n| `barcode_lookup` | `GET /packaged?barcode=` → falls back to `GET /foods/lookup?barcode=` |\n| `packaged_food_search` | `GET /packaged` and/or `GET /products` |\n| `diabetes_exchange_lookup` | `GET /exchange` |\n| `renal_exchange_lookup` | `GET /renal` |\n| `enteral_formula_lookup` | `GET /formulas` |\n| `nutrition_calculator` | none — pure BMI/BMR (Mifflin-St Jeor)/TDEE math |\n| `rag_retrieve` | `POST /rag/retrieve` |\n| `search_guidelines` | `POST /rag/ask` (`context: \"clinical\"`) |\n| `retrieve_evidence` | `POST /rag/ask` (`context: \"both\"`, higher `top_k`) |\n| `disease_information` | `POST /rag/ask`, query framed for educational disease overview |\n| `medicine_information` | `POST /rag/ask`, query explicitly instructed to exclude dosing/prescribing |\n| `pediatric_fluid_requirements` | none — pure Holliday-Segar math |\n| `pediatric_energy_requirements` | none — pure Schofield/WHO BMR + DRI/FAO 2004 + DRI/IOM 2006 math |\n| `pediatric_protein_requirements` | none — pure IOM 2005 / ASPEN sick-child / preterm table lookup |\n| `pediatric_growth_velocity` | none — pure ASPEN handbook growth-velocity table lookup |\n| `pediatric_enteral_feed_advancement` | none — pure enteral feed protocol table lookup |\n| `iom_dri_eer_calculator` | none — pure IOM/DRI (2002/2005) EER prediction-equation math, all life stages |\n| `met_activity_energy_calculator` | none — pure MET x weight x duration math |\n| `alcohol_kcal_calculator` | none — pure volume x proof math |\n| `respiratory_quotient_interpreter` | none — pure RQ reference-value interpretation |\n| `preterm_fluid_energy_requirements` | none — pure preterm fluid/energy table lookup |\n| `macronutrient_distribution_check` | none — pure DRI macronutrient % range table lookup |\n| `tee_activity_band_estimator` | none — pure REE x activity-band multiplier math |\n| `fever_stress_ree_adjustment` | none — pure fever REE adjustment math |\n| `atwater_food_energy_calculator` | none — pure Atwater factor (4/9/4/7) math |\n| `dri_eer_reference_lookup` | none — pure DRI Table 2.2 reference table lookup |\n| `who_growth_zscore` | none — pure WHO growth reference LMS z-score/percentile calculation (weight-for-age, height-for-age, BMI-for-age 0-5y, BMI-for-age 5-19y, head-circumference-for-age, weight-for-length, weight-for-height) |\n\n`disease_information` and `medicine_information` always return an educational disclaimer alongside the\nanswer and are prompted to avoid diagnosis/prescribing language — but they're still LLM-generated text\ngrounded on whatever's in your RAG knowledge base, not a verified medical reference. Treat them as a\nstarting point for a learner, same as the rest of the RAG-backed tools.\n\n`pediatric_*` tools (source: BND 415 Clinical Nutrition — Paediatric Medicine Resources) and\n`iom_dri_eer_calculator`/`met_activity_energy_calculator`/`alcohol_kcal_calculator`/\n`respiratory_quotient_interpreter` (source: Nelms/Ireton-Jones, *Nutrition Therapy and Pathophysiology*,\nCh. 2) are pure calculation/lookup tools — no network call, no CNR data dependency. Same estimate-only\ncaveat applies: not a substitute for individualized clinical assessment or measured indirect calorimetry.\n\n## Project layout\n\n```\nsrc/\n├── index.ts                 Express app, Streamable HTTP session wiring, graceful shutdown\n├── config/env.ts            Zod-validated environment config, loaded once at startup\n├── clients/chakudyaClient.ts  Fetch wrapper for the Chakudya Worker (GET/POST, error normalization)\n├── server/\n│   ├── createServer.ts      Builds one McpServer instance and registers all tool modules\n│   └── security.ts          Bearer auth + per-IP rate limiting for this server's /mcp endpoint\n├── tools/\n│   ├── foodTools.ts\n│   ├── clinicalTools.ts\n│   ├── ragTools.ts\n│   ├── educationTools.ts\n│   ├── pediatricTools.ts        Pediatric fluid/energy/protein/growth/enteral-feed calculators\n│   └── energyExpenditureTools.ts  IOM/DRI EER, MET activity, alcohol kcal, RQ interpreter\n│   └── whoGrowthTools.ts        WHO Child Growth Standards z-score/percentile calculator (LMS)\n├── data/\n│   └── who/                     WHO Child Growth Standards LMS tables (JSON, per standard+sex)\n└── utils/\n    ├── logger.ts             Structured JSON logging\n    └── toolResult.ts         Consistent success/error shaping for every tool handler\n```\n\n## Environment variables\n\nCopy `.env.example` to `.env` and fill in:\n\n| Variable | Required | Notes |\n|---|---|---|\n| `CHAKUDYA_API_BASE_URL` | no (defaults to the maintainer's own Worker) | If you're forking this repo to front your own CNR instance, set this to your own Worker's URL instead of relying on the default |\n| `CHAKUDYA_ADMIN_API_KEY` | no | Not used by any current tool; only needed if you add an admin-gated tool later |\n| `PORT` | no (default `8787`) | |\n| `MCP_AUTH_TOKEN` | **yes in production** | Bearer token MCP clients must send. Server refuses to start in production without it |\n| `MCP_ALLOWED_ORIGINS` | no | Comma-separated CORS origins; leave blank to disable browser access |\n| `MCP_RATE_LIMIT_PER_MIN` | no (default `60`) | Per-IP cap on this server's own `/mcp` endpoint |\n| `NODE_ENV` | no (default `development`) | Set to `production` for deploys |\n\n## Security considerations\n\n- **Auth is mandatory in production.** `env.ts` exits the process at startup if `NODE_ENV=production` and\n  `MCP_AUTH_TOKEN` is unset — this is a deliberate fail-closed check, not just a warning.\n- **This server sits in front of your rate-limited RAG routes.** `/rag/ask` on your Worker is capped at\n  15 req/min per IP — but that's per *client* IP as seen by the Worker, which would be *this server's*\n  IP once deployed, shared across everyone using it. The MCP-level rate limiter\n  (`MCP_RATE_LIMIT_PER_MIN`) exists so one misbehaving MCP client can't silently exhaust that budget for\n  everyone else. Tune it down if you expect multiple concurrent MCP clients.\n- **No admin key is embedded or required.** Every tool calls a public CNR route. If you add an\n  admin-gated tool later, keep `CHAKUDYA_ADMIN_API_KEY` server-side only — never expose it to the MCP\n  client.\n- **Session state is in-memory, per-process.** Fine for a single instance. If you ever scale to multiple\n  instances behind a load balancer, either enable sticky sessions (route by `Mcp-Session-Id`) or swap the\n  `transports` map in `src/index.ts` for a shared store.\n- **CORS is off by default.** Only enable `MCP_ALLOWED_ORIGINS` if you have a specific browser-based MCP\n  client; server-to-server MCP clients (Claude Desktop, Claude Code, etc.) don't need it.\n\n## Running locally\n\n```bash\ncd ~\ngit clone https://github.com/edisontaimu9-ui/chakudya-mcp-server.git\ncd chakudya-mcp-server\ncp .env.example .env\n# edit .env: set MCP_AUTH_TOKEN to a long random string\nnpm install\nnpm run build\nnpm start\n```\n\nOr for iterative dev with auto-reload:\n\n```bash\nnpm run dev\n```\n\nHealth check: `curl http://localhost:8787/health`\n\n## Connecting an MCP client\n\nPoint any Streamable-HTTP-capable MCP client at:\n\n```\nPOST/GET/DELETE  https://<your-deployed-host>/mcp\nHeader: Authorization: Bearer <MCP_AUTH_TOKEN>\n```\n\nFor Claude Desktop / Claude Code, add it as a remote MCP server pointing at that URL with the same\nbearer token. Consult Anthropic's current docs for the exact config file syntax, since that's changed\nover time — check `https://docs.claude.com` for the latest `mcpServers` remote-server format.\n\n## Deployment: Render (recommended — free, no credit card)\n\nThis repo includes `render.yaml`, so Render's Blueprint feature deploys it without any manual dashboard\nconfiguration.\n\n1. Push this repo to GitHub (commands below).\n2. In the Render dashboard: **New → Blueprint**, connect your GitHub account, pick the\n   `chakudya-mcp-server` repo. Render reads `render.yaml` automatically.\n3. Render provisions the service on the **Free** plan and auto-generates a random `MCP_AUTH_TOKEN`\n   (via `generateValue: true`). After the first deploy, go to the service's **Environment** tab to copy\n   that generated token — you'll need it in your MCP client config.\n4. Deploy. Your MCP endpoint will be `https://<your-service-name>.onrender.com/mcp` (check the Render\n   dashboard for your actual generated URL — it may include a random suffix if your chosen name is\n   taken).\n\n### The free-tier sleep problem, and the fix\n\nRender's free web services spin down after 15 minutes with no traffic, then take 30-60 seconds to wake\non the next request. That's fine for a health check, but it can drop an in-progress MCP session (session\nstate lives in memory — see `src/index.ts`) if the client goes quiet mid-conversation for too long.\n\nFix: keep it warm with a free uptime monitor pinging `/health` every 5-10 minutes.\n\n1. Sign up at [uptimerobot.com](https://uptimerobot.com) (free plan, no card).\n2. Add a new **HTTP(s)** monitor:\n   - URL: `https://<your-service>.onrender.com/health`\n   - Interval: 5 minutes\n3. Save. `/health` is unauthenticated by design, specifically so this monitor doesn't need your\n   `MCP_AUTH_TOKEN`.\n\nThis keeps the service warm 24/7 within the free plan's 750 hrs/month (well under the cap for one\nservice pinged this way).\n\n### Updating after a code change\n\nRender auto-redeploys on every push to your connected branch — no extra step needed:\n\n```bash\ngit add .\ngit commit -m \"Update MCP server\"\ngit push\n```\n\nWatch the deploy in the Render dashboard's **Events** tab; it typically finishes in 1-2 minutes for a\nproject this size.\n\n## Other deployment options\n\n### Docker anywhere\n\n```bash\ndocker build -t chakudya-mcp-server .\ndocker run -d -p 8787:8787 \\\n  -e NODE_ENV=production \\\n  -e MCP_AUTH_TOKEN=<long-random-string> \\\n  -e CHAKUDYA_API_BASE_URL=<your-chakudya-worker-url> \\\n  --name chakudya-mcp chakudya-mcp-server\n```\n\n### Plain VPS with a process manager\n\n```bash\nnpm install --omit=dev\nnpm run build\nnpx pm2 start dist/index.js --name chakudya-mcp\n```\n\nPut it behind Nginx/Caddy for TLS termination if you're not already fronting it with something that\nhandles HTTPS.\n\n## Updating via the command line\n\n```bash\ncd ~\n# first time only:\ngit clone https://github.com/edisontaimu9-ui/chakudya-mcp-server.git\ncd chakudya-mcp-server\n\n# after any file update:\ncp <path-to-updated-file>.ts src/<path>/<updated-file>.ts\ngit add .\ngit commit -m \"Update MCP server\"\ngit push\n```\n\nThen redeploy on whichever platform you chose (Render/Railway/Fly auto-redeploy on push if you connected\nthe GitHub repo; otherwise trigger a manual redeploy or re-run the Docker/pm2 commands above on your\nhost).\n",
  "bytes": 12737,
  "sha": "6a50834294f2cb3c2c6402d9bab6f2c3511f25535322947611e247fb5927c3c2",
  "repo_slug": "edisontaimu9-ui/chakudya-mcp-server",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_edisontaimu9_ui_chakudya_mcp_s_03063286/readme"
}