pddikti
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pddiktifind universities in Yogyakarta"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
PDDikti API
Bun + Hono API providing structured access to Indonesia higher-education data (PDDikti): universities, study programs, lecturers, students, and aggregate statistics.
This service is a typed proxy in front of a private upstream PDDikti data source, with auto-generated OpenAPI docs.
Requirements
Bun ≥ 1.3
Related MCP server: YOKATLAS MCP Server
Setup
bun install
cp .env.example .env # set UPSTREAM_BASE_URL; supply Cloudflare creds at runtime (see below)Run
bun run dev # hot reload
bun run start # start serverOpen http://localhost:3000.
Docker setup
Run as three containers — the API + a FlareSolverr sidecar (hands-off Cloudflare
clearance) + Redis (response cache) — via the provided docker-compose.yml:
docker compose up -d # build + start api (:3000) + flaresolverr
docker compose logs -f api # follow API logs
docker compose down # stopWhat it wires:
api— built from the multi-stageDockerfile(oven/bun:1.3-alpine, frozen-lockfile deps-only install). Reads.env; compose also setsSOLVER_ENABLED=true,SOLVER_URL=http://flaresolverr:8191/v1, andCONFIG_STORE_PATH=/data/runtime-config.json.flaresolverr—ghcr.io/flaresolverr/flaresolverr, internal only (not published to the host); its headless browser solves the Cloudflare challenge on a403.redis—redis:7-alpine(password + AOF persistence), internal only; the API's cache.config-data/redis-datavolumes — persist the solvedcf_clearanceand the cache across restarts.
Only the API is reached (via the reverse proxy); FlareSolverr + Redis stay internal. For a
public-facing
setup put a TLS-terminating reverse proxy in front, and set PUBLIC_BASE_URL, API_KEY,
and CONFIG_PASSWORD accordingly.
Single image, no solver (refresh clearance manually via /#/config):
docker build -t pddikti .
docker run -p 3000:3000 --env-file .env pddiktiConfiguration
All config is read once from the environment via src/config.ts (the only place that touches
process.env). Bun auto-loads .env. Key variables:
Variable | Default | Purpose |
|
| Listen port |
|
| Absolute base for docs links + |
| — | PDDikti API base ( |
|
| Per-request upstream timeout |
| `` (off) | Gate |
|
| Session/config cookie lifetime |
| `` (off) | Enables the |
|
| Where runtime overrides + solved cookie persist |
|
| Auto-solve Cloudflare on |
| — | FlareSolverr |
|
| Optional Redis response cache |
|
| Attribution injected into every JSON response |
See .env.example for the full list.
Web UI
GET /— flat React PT profile explorer: search a university, pick one, and see its profile, stats, cost, study duration, study programs, all lecturers, student search, facilities, and name history. The selected university is carried in the URL hash (#/pt/<id>). Bundled natively by a singleBun.build(JS + Tailwind v4 CSS viabun-plugin-tailwind), no Vite. Client code is organized intocomponents/(UI + views),hooks/(use-endpoint,use-selected-pt,use-debounced-value), andlib/(constants,format,theme). Requires a configured upstream (below) to show live data.
Docs
Overview:
GET /api/Swagger UI:
/api/docsReDoc:
/api/redocOpenAPI spec:
/api/openapi.json(live)MCP tools:
/#/mcp(readable view) ·/mcp/tools(JSON catalog — the MCP analogue of the OpenAPI spec)Static spec:
openapi.jsonin the repo, for offline agent reference. Regenerate after route changes withbun run openapi:export(setPUBLIC_BASE_URLto stamp theserversURL, e.g.PUBLIC_BASE_URL=https://<host> bun run openapi:export).
MCP (AI agent access)
The same data is exposed to AI agents over the Model Context Protocol. Tools are
consolidated and enum-driven (~10): search_pddikti, get_university,
get_university_study_programs, get_university_lecturers, get_study_program,
get_study_program_lecturers, get_lecturer, get_student, get_statistics,
list_study_programs_by_field. Each routes through the same proxy egress, cache, and
credit envelope as the REST API.
Two transports:
stdio —
bun run mcp(runssrc/mcp/stdio.ts). For local clients.Streamable HTTP —
POST /mcpon the running server. For remote/hosted clients. Always public (no API key) — even when/api/*is gated byAPI_KEY— so browser clients like Claude.ai, which can't send a staticX-API-Key, can connect.
Claude Code (CLI)
Local (stdio) — from the repo root, env comes from .env:
claude mcp add pddikti -- bun run /abs/path/to/pddikti/src/mcp/stdio.tsOr commit a project-scoped .mcp.json (already included — fill the env):
{
"mcpServers": {
"pddikti": {
"command": "bun",
"args": ["run", "./src/mcp/stdio.ts"],
"env": {
"UPSTREAM_BASE_URL": "https://pddikti.kemdiktisaintek.go.id/api",
"CONFIG_STORE_PATH": "/abs/path/to/pddikti/runtime-config.json"
}
}
}
}Pointing CONFIG_STORE_PATH at the same runtime-config.json the config page writes lets
the stdio server reuse that Cloudflare clearance (or set SOLVER_ENABLED/SOLVER_URL to
auto-resolve). See Upstream config below.
Remote (HTTP) against a running instance:
claude mcp add --transport http pddikti https://<host>/mcpClaude Desktop
Edit claude_desktop_config.json (Settings → Developer → Edit Config) — same shape as
.mcp.json above under mcpServers. Restart the app; tools appear under the 🔌 menu.
Claude.ai (web)
Settings → Connectors → Add custom connector → URL https://<host>/mcp. The web UI
cannot send a static X-API-Key/Basic-auth header, so /mcp is intentionally public (no
key) regardless of API_KEY — the web connector works out of the box. Only the /api/*
REST surface is key-gated.
Quick test
bun run mcp:inspect # MCP Inspector against the stdio server
# Or hit the HTTP endpoint directly (initialize handshake):
curl -s https://<host>/mcp -X POST \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}'Upstream config
This API proxies the official PDDikti backend, which lives at .../api on the main domain
and is guarded by Cloudflare bot management. Only the base URL goes in .env:
UPSTREAM_BASE_URL=https://pddikti.kemdiktisaintek.go.id/apiCloudflare needs a cf_clearance cookie + the exact User-Agent it was issued to (a
mismatched/absent UA → 403). You provide these at runtime, not in .env, two ways:
Manually via the config page — set
CONFIG_PASSWORD, open the Konfigurasi view (/#/config, a React page backed by the/config/*JSON API), paste thecf_clearance+ User-Agent (or a full DevTools Copy as cURL, auto-parsed). Saved toCONFIG_STORE_PATH, applied live, persists across restarts. Refresh when it expires.Automatically via the solver — see Auto-solver below; a
403self-heals with no manual step.
(An optional UPSTREAM_HEADERS JSON env map is still accepted as a fallback baseline, but is
overridden by the runtime config and normally left unset.)
The upstream wraps payloads as {status,data} and returns opaque base64url ids (with =
padding, sent raw in the path) — both handled in lib/upstream.ts / lib/encoding.ts.
Without UPSTREAM_BASE_URL, /api/* returns a clean 503 envelope (no data).
Auto-solver (optional, hands-off clearance)
cf_clearance expires, so instead of refreshing it by hand you can let the app do it —
server-deployable via FlareSolverr (a headless-browser challenge solver that runs in its
own container). Set SOLVER_ENABLED=true + SOLVER_URL (the FlareSolverr /v1 endpoint).
On a 403, the proxy calls FlareSolverr to clear the challenge, saves the fresh
cf_clearance + User-Agent (to CONFIG_STORE_PATH), and retries the request once. Left off,
refresh manually via the /#/config page.
Run both with the provided docker-compose.yml:
docker compose up -d # starts the API + a flaresolverr sidecarThe browser lives inside FlareSolverr, so this works on a headless server (no display).
Architecture
Bun + Hono (
OpenAPIHono), exported asexport default { fetch, port }— noBun.serve, Vite, or Express.Single egress: every upstream call goes through
lib/upstream.ts(auth headers, timeout,{status,data}unwrap, error mapping,403auto-refresh), with an optional Redis cache (lib/cache.ts). Routes neverfetchdirectly.Zod-first: one schema drives both request validation and OpenAPI generation; strict TypeScript, no
any(upstream payloads are opaquez.unknown()).Two frontends, one core: the REST
/api/*routes and the in-process MCP server (/mcp) both calllib/directly — identical egress, cache, and credit envelope, no second hop.Native React SPA: bundled by a single
Bun.build(+ Tailwind v4 CSS-first), served by Hono;components/+hooks/+lib/layout, one shared stickyHeader.Definition of done:
bun run typecheck(clean) +bun test(green), no network in tests.
Full conventions live in CLAUDE.md.
Scripts
Script | Purpose |
| Hot-reloading dev server |
| Run server |
| Run the MCP server over stdio |
| MCP server (stdio) with hot reload |
| Launch MCP Inspector against the stdio server |
| Run tests |
|
|
Project conventions
See CLAUDE.md.
License
MIT — open source, free to use, copy, modify, and distribute.
Disclaimer
For educational and learning purposes only. This is a personal, non-commercial study project.
Not affiliated with or endorsed by PDDikti, Kemdiktisaintek, or Cloudflare. All upstream data is © PDDikti; names belong to their respective owners.
Provided "as is", with no warranty of accuracy, availability, or fitness (see the MIT terms).
No liability for misuse. The authors accept no responsibility for how this software is used or for any damages arising from it — you use it entirely at your own risk.
Your responsibility. You must comply with all applicable laws and the terms of service of any system it touches (including the upstream's terms and its anti-bot/rate-limit protections). Do not use it to overload, disrupt, mass-scrape, or abuse any service, or to handle personal data unlawfully.
See LICENSE for the full text.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/andikadevs/pddikti'
If you have feedback or need assistance with the MCP directory API, please join our Discord server