These instructions are for AI assistants working in this project.
Always open @/openspec/AGENTS.md when the request:
- Mentions planning or proposals (words like proposal, spec, change, plan)
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
- Sounds ambiguous and you need the authoritative spec before coding
Use @/openspec/AGENTS.md to learn:
- How to create and apply change proposals
- Spec format and conventions
- Project structure and guidelines
Keep this managed block so 'openspec update' can refresh the instructions.
- Repo:
ckan-mcp-server - Stack: Node.js + TypeScript (ESM)
- Tests: Vitest
- Build: esbuild
- Cloudflare Workers:
wrangler
- No
.cursor/rules,.cursorrules, or.github/copilot-instructions.mdfound
Install: npm install
Build: npm run build | npm run build:tsc | npm run build:worker
Run: npm start | npm run dev | npm run watch | npm run dev:worker | npm run deploy
Test: npm test | npm run test:watch | npm run test:coverage
Single test: npm test -- tests/unit/http.test.ts | npm test -- -t "testName"
Before deploying, you can test the current dev build by pointing your MCP client at the Node entrypoint in dist/:
- Build:
npm run build - Example absolute path:
/home/aborruso/git/idee/ckan-mcp-server/dist/index.js(adjust to your local checkout)
When creating issues with multi-line bodies, avoid literal \n in --body. Use a here-doc
or -F - to preserve newlines:
cat <<'EOF' | gh issue create --title "Title" --body-file - --repo ondata/ckan-mcp-server
Line 1
Line 2
EOFWhen writing GitHub release notes, avoid literal \n in --notes. Always pass a here-doc
via --notes-file - so line breaks render correctly:
cat <<'EOF' | gh release create v0.X.Y --title "v0.X.Y - Title" --notes-file -
## What's New
### Changes
- Item 1
- Item 2
### No Breaking Changes
- All existing functionality preserved
**Full Changelog**: https://github.com/ondata/ckan-mcp-server/compare/v0.X-1.Y...v0.X.Y
EOFUse strict typing, avoid any unless from CKAN payloads. Prefer explicit return types, type aliases, ESM imports with .js extensions. Keep noUnusedLocals and noUnusedParameters clean.
Use import type for type-only imports. Group by kind: external, internal, types. Double quotes, relative paths.
camelCase vars/functions, PascalCase types/classes, UPPER_SNAKE_CASE constants. Tool names match MCP tool ids.
Wrap tool handlers in try/catch. Return { isError: true } for MCP errors. Include context, map HTTP errors to readable messages, preserve cause when rethrowing.
Use ResponseFormat for markdown vs JSON. truncateText for large payloads. Pretty-print JSON, include structuredContent for JSON mode.
Vitest with globals: true. Place tests in tests/unit or tests/integration. AAA pattern, mock via fixtures in tests/fixtures, descriptive names.
Never use demo.ckan.org for tests. Always use https://www.dati.gov.it/opendata.
Node >=18. Worker build in wrangler.toml. Vitest coverage thresholds enforced.
Minimal focused diffs. No unrelated refactors. Update tests for behavior changes. Avoid editing dist/.
Before committing and pushing any locally testable change:
- Build:
npm run build - Automated tests:
npm test— all must pass - Manual queries: run real requests against the built server to verify end-to-end behavior
# Terminal 1 — start server
TRANSPORT=http PORT=3001 node dist/index.js
# Terminal 2 — call a tool
curl -s -X POST http://localhost:3001/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc":"2.0",
"method":"tools/call",
"params":{
"name":"ckan_package_search",
"arguments":{
"server_url":"https://www.dati.gov.it/opendata",
"q":"ambiente",
"page":1,
"page_size":3
}
},
"id":1
}'- Always include both
Content-Type: application/jsonandAccept: application/json, text/event-stream - Use
node dist/index.jsdirectly, notnpm start - Use port 3001 to avoid conflicts
src/index.ts entry, src/server.ts wiring, src/tools/ handlers, src/utils/ helpers, src/resources/ templates, src/transport/ stdio/HTTP. tests/unit/ utilities, tests/integration/ behavior, tests/fixtures/ mocks.
For exploring CSV resources from datasets, use duckdb CLI (already installed) with direct HTTP URL:
duckdb -jsonlines -c "DESCRIBE SELECT * FROM read_csv('http://url/file.csv')"
duckdb -jsonlines -c "SUMMARIZE SELECT * FROM read_csv('http://url/file.csv')"
duckdb -jsonlines -c "SELECT * FROM read_csv('http://url/file.csv') USING SAMPLE 10"Use direct resource URLs (http/https), not GitHub view/blob URLs. The -jsonlines parameter outputs in JSONL format, easier for AI to parse.
For random sampling, use USING SAMPLE N syntax (where N is the number of rows):
duckdb -jsonlines -c "SELECT * FROM read_csv('http://url/file.csv') USING SAMPLE 10"