Documents the fuzzy-search-by-default change shipped across tasks 1-10: appends ADR-019 (fuzzy default, hand-rolled zero-dependency scorer, why fuse.js/fastest-levenshtein were rejected), describes the src/search/ pure computation unit in ARCHITECTURE.md, updates README.md's tool table/count (18 -> 19) and adds a Search behaviour section, and updates TODO.md with the completed milestone, two accepted limitations, and three tracked follow-ups. No source or test files were changed; bun test (349 pass) and bun run typecheck remain unchanged. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
15 KiB
Project Progress & Roadmap
Important
CRITICAL AGENT POLICY: Never continue automatically to the next task or phase. After finishing any individual task, milestone, or commit, the agent must always stop and check in with the user. Present what was completed, outline the next logical step, and await explicit user approval before proceeding.
1. Completed Tasks
-
Agentic Development Guidelines & Infrastructure
- Created AGENTS.md defining architectural conventions, coding practices, and agent workflow.
- Created CLAUDE.md and GEMINI.md pointing to AGENTS.md.
- Created docs/ARCHITECTURE.md documenting layers, data flow, tool specifications, and API specifics.
- Created docs/DECISIONS.md establishing initial ADRs (stack, transport, auth, scoping, testing).
- Created root .gitignore protecting credentials, cache, and IDE artifacts.
-
OpenProject 17 Local Docker Stack & Seeding
- Created docker-compose.yml configured for OpenProject 17 (
openproject/openproject:17-slim), PostgreSQL 17, and Memcached. - Created .env.example documenting all stack and MCP client environment variables.
- Created scripts/seed-test-data.rb (Rails runner script) to:
- Activate default admin user and remove password reset requirements.
- Generate a live personal API token via
Token::API.create_and_return_value(admin). - Seed dedicated test project
mcp-test-project(ID: 4). - Seed 4 custom work packages (IDs: 38-41) across Task, Bug, Feature types.
- Seed saved query
MCP Active Tasks(ID: 30).
- Created scripts/seed.sh orchestrating health checks and automated writing to
.env.testand.env.local. - Created lifecycle scripts scripts/start.sh and scripts/stop.sh.
- Verified live container execution and validated HTTP 200 API v3 access (
/api/v3/projects) with generated token. - Recorded ADR-009 targeting OpenProject 17.
- Created docker-compose.yml configured for OpenProject 17 (
-
Bun Runtime Migration & Tooling
- Switched project runtime from Node.js to Bun (v1.3.14).
- Created package.json with
@modelcontextprotocol/sdkandzod. - Created tsconfig.json with Bun bundler settings and strict typing.
- Generated bun.lock.
- Added src/index.ts entrypoint stub and tests/smoke.test.ts using
bun:test. - Updated AGENTS.md, CLAUDE.md, and GEMINI.md with Bun init rules.
- Recorded ADR-010 adopting Bun.
- Updated README.md with Bun prerequisites and quickstart commands.
2. Next Up (Phase 1: Read/Browse Implementation)
-
Task 1: OpenProject REST API v3 Client Layer
- Implement
src/config/index.tsto parse and validateOPENPROJECT_BASE_URL,OPENPROJECT_API_KEY, andOPENPROJECT_READ_ONLY(or--read-only) via Zod. - Implement
src/context.tsprovidingRequestContextinterface andAsyncLocalStoragescoping (getRequestContext,runWithContext) for concurrent multi-user safety. - Implement
src/client/api-client.tswith nativefetch, HTTP Basic Auth injection, error normalization, and rate/timeout handling. - Implement
src/client/hal-parser.tsto unpack HAL+JSON (_links,_embedded) into clean, token-efficient JSON models. - Implement
src/client/filter-builder.tsto translate LLM-friendly filter arguments into OpenProject's JSON filter syntax. - Add unit tests in
tests/client.test.tsverifying authentication headers, response parsing, and error sanitization against live container.
- Implement
-
Task 2: Domain Services
- Implement
src/services/helper.ts(resolveClientfor ambientRequestContextresolution). - Implement
src/services/projects.ts(listProjects,getProject,getProjectSchema). - Implement
src/services/work-packages.ts(listWorkPackages,getWorkPackage,searchWorkPackages). - Implement
src/services/queries.ts(listQueries,getQuery,getQueryResults). - Implement
src/services/metadata.ts(listStatuses,listTypes,listPriorities,listUsers). - Implement
src/services/openapi.ts(getOpenApiSpecwith in-memory caching, summary mode, path endpoint lookup, tag filtering, schema inspection). - Add service tests in
tests/services.test.ts(unit tests and live container integration suite passing).
- Implement
-
Task 3: MCP Tool Definitions & Schema Registration
- Define Zod schemas and register tools in
src/tools/:openproject_list_projectsopenproject_get_projectopenproject_list_work_packagesopenproject_get_work_packageopenproject_list_queriesopenproject_get_queryopenproject_list_typesopenproject_list_statusesopenproject_list_prioritiesopenproject_list_usersopenproject_get_openapi_spec
- Define Zod schemas and register tools in
-
Task 4: MCP Stdio Server Assembly & Integration
- Wire domain tools into
@modelcontextprotocol/sdkServer usingStdioServerTransportinsrc/server.tsandsrc/index.ts. - Implement read-only mode tool filtering (omits mutating tools when
readOnlyis enabled) and execution guard (SERVER_READ_ONLY). - Ensure all logging is strictly redirected to
stderr. - Add end-to-end integration tests in
tests/mcp-server.test.tsverifying tool calling against the running OpenProject 17 instance. - Add read-only mode verification tests in
tests/read-only.test.ts.
- Wire domain tools into
-
Task 5: OpenAPI Introspection Tool & Service
- Implement
src/services/openapi.tswith in-memory caching, multi-mode queries (summary, path, tag, schema), and ambient client resolution. - Implement
src/tools/openapi.tsregisteringopenproject_get_openapi_spec(readOnly: true) intoallTools(bringing tool count to 11). - Add comprehensive test suite in
tests/openapi.test.tscovering service methods, MCP client execution, error handling, and live OpenProject 17 container integration. - Update suite regressions and assertions across all test suites (
tests/tools.test.ts,tests/read-only.test.ts,tests/mcp-server.test.ts).
- Implement
-
Task 6: Docker Container Packaging & CI/CD Pipeline
- Created multi-stage Dockerfile using
oven/bun:1-slimbuilder and unprivilegedbunuser runner. - Created .dockerignore excluding secrets, local dependencies, test files, and docs from build context.
- Implemented GitHub Actions CI workflow .github/workflows/ci.yml with automated type checking, testing, and multi-arch Docker image publishing (
linux/amd64,linux/arm64) toghcr.io. - Added automated test coverage in tests/docker.test.ts and tests/ci-workflow.test.ts.
- Recorded ADR-015.
- Updated README.md with Docker Quickstart, read-only mode instructions, and client integration settings (Claude Desktop & Cursor).
- Created multi-stage Dockerfile using
-
Task 7: Hosted Remote MCP Server (HTTP/SSE Transport) & Multi-Tenant Credential Scoping
- Extend configuration loader with
portandhostoptions (src/config/index.ts,tests/config.test.ts). - Implement hosted HTTP/SSE MCP server with
Bun.serveand Web Streams SSE (src/http-server.ts,tests/http-server.test.ts). - Implement credential extraction hierarchy (
Authorization: Bearer,X-OpenProject-Api-Key,?apiKey=) with 401 unauthorized rejection. - Implement per-session client isolation and
runWithContextdynamic scoping. - Wire dual-transport execution in CLI entrypoint (
src/index.ts). - Create production
docker-compose.server.ymlwith/healthcheck probe. - Record ADR-016.
- Update documentation with hosted deployment guide and remote client setups for Cursor and Claude Desktop in README.md.
- Extend configuration loader with
-
Task 8: OpenAPI 3.1.0 Specification & REST Tool Execution Bridge
- Implement dynamic OpenAPI specification generator
src/openapi-spec.ts. - Expose
GET /openapi.jsonandGET /swagger.jsoninsrc/http-server.ts. - Expose REST tool execution endpoint
POST /api/tools/:toolNamewith Zod parameter validation andRequestContextexecution. - Add automated test coverage in
tests/http-server.test.ts. - Record ADR-017 in
docs/DECISIONS.md. - Document OpenAPI connection mode and server endpoints reference table in README.md.
- Implement dynamic OpenAPI specification generator
-
Task 9: Meetings, Wikis, and Activities Extension (18 Tools Total)
- Implemented
listWorkPackageActivitiesinsrc/services/work-packages.tsseparating comments from field mutations withonlyCommentsfilter. - Implemented
listMeetings,getMeeting(with embedded agenda items/outcomes), andsearchMeetings(deep search across titles, locations, and agenda item notes) insrc/services/meetings.ts. - Implemented
getWikiPage(with attachments),listWikiPageLinks, andsearchWikiPageswith smart discovery caching and consecutive 404 cutoff insrc/services/wikis.ts. - Registered
openproject_list_work_package_activitiesinsrc/tools/work-packages.ts. - Registered
openproject_list_meetings,openproject_get_meeting,openproject_search_meetingsinsrc/tools/meetings.ts. - Registered
openproject_get_wiki_page,openproject_search_wiki_pages,openproject_list_wiki_page_linksinsrc/tools/wikis.ts. - Wired all 18 tools into server registry (
src/tools/index.ts,src/services/index.ts) and updated all test suite assertions to 18 tools. - Added live OpenProject 17 container integration tests in
tests/services.test.tsandtests/mcp-server.test.ts. - Recorded ADR-018 and updated ARCHITECTURE.md, TODO.md, and README.md.
- Implemented
-
Task 10: Fuzzy Search by Default (19 Tools Total)
- Implemented a pure, zero-dependency fuzzy-matching unit in
src/search/(tokenize.ts,score.ts,snippet.ts,rank.ts,pipeline.ts), with no I/O and no module-level mutable state. - Made fuzzy matching the default across all search tools via an optional
matchMode: "fuzzy" | "exact"parameter;"exact"reproduces the previous substring-containment behavior byte for byte. - Widened content coverage: wiki page body text, work package descriptions and comments, and meeting participants and project names are now searchable, not just titles/subjects.
- Added
openproject_search_work_packages, exposing the previously tool-unreachablesearchWorkPackagesservice helper as a ranked search tool over subjects, descriptions, and comments. - Added optional
query/matchModeranking toopenproject_list_work_package_activities(ranks already-fetched activities client-side, no extra API cost). - Fixed two live bugs: a
.catch(() => undefined)in meeting search that turned an expired token into a silent "no results found", and an unboundedPromise.allover up to 250 candidates in meeting search, replaced by the bounded two-phase pipeline (25-item enrichment window, concurrency 8). - Added
tests/search/unit tests (tokenize,score,snippet,rank,pipeline) and a recall suite (tests/search/recall.test.ts) covering realistic natural-language and misspelled queries. - Recorded ADR-019 and updated ARCHITECTURE.md, TODO.md, and README.md.
- Implemented a pure, zero-dependency fuzzy-matching unit in
Known Limitations (Accepted)
- Two-phase recall ceiling: A record whose only match is in deep content (a work package description/comment, a meeting agenda note or outcome) is findable only if it lands in the 25-item enrichment window (top-by-shallow-score, backfilled by recency). Beyond that window, deep-only matches are missed. See
src/search/pipeline.ts(DEFAULT_ENRICH_LIMIT). - Wiki discovery probe window:
searchWikiPagesbuilds its cache by probing wiki page IDs 1..50 with a 5-consecutive-miss cutoff. Pages beyond that window, or past a gap of five consecutive deleted/missing IDs, are invisible to search regardless of ranking quality. Seesrc/services/wikis.ts.
Follow-ups (Tracked, Out of This Plan's Scope)
getMeetinginsrc/services/meetings.tsstill swallows auth errors via a.catch(() => undefined)on its agenda-item fetch (same bug class fixed forsearchMeetings). An expired token can still surface as a silently empty meeting detail on that path.tests/search/recall.test.tsunder-tests ranking-under-competition: most cases have only one corpus document with any lexical overlap, so the suite largely proves "clears the minScore threshold" rather than "ranks the right document above plausible near-misses". Widening the corpus with near-miss documents would strengthen it.SearchWorkPackagesOptions.typeId/assigneeId(insrc/services/work-packages.ts) are unreachable from the MCP tool —searchWorkPackagesShapeinsrc/tools/work-packages.tsnever exposes them. Dead surface area worth trimming or wiring up.
3. Backlog & Future Phases
- Phase 4: Remote Transport (Completed)
- Add SSE (Server-Sent Events) transport option for remote deployments.
- Multi-tenant credential scoping with per-session isolation.
- Phase 3 Extension: Meetings, Wikis, and Activities (Completed - 18 Tools Total)
- Meetings listing, detail inspection, and deep agenda search.
- Wiki pages retrieval, smart cached discovery search, and work package links.
- Work package activities and comments filtering.
- Phase 3.5: Fuzzy Search by Default (Completed - 19 Tools Total)
- Zero-dependency fuzzy scorer (
src/search/) applied by default across all search tools, withmatchMode: "exact"opt-out. - Widened content coverage (wiki bodies, work package descriptions/comments, meeting participants/project names).
- New
openproject_search_work_packagestool; ranked activities viaopenproject_list_work_package_activities. - Two known, accepted limitations recorded above (two-phase recall ceiling, wiki discovery probe window).
- Zero-dependency fuzzy scorer (
- Phase 2: Mutating Operations
- Create work package tool (
openproject_create_work_package). - Update work package tool (
openproject_update_work_package). - Add comments / activities to work packages.
- Time logging tool (
openproject_log_time).
- Create work package tool (
- Phase 3 (Future): Binary Attachments & File Resources
- Inspect and download binary attachment file contents via MCP resources.