From c9ba15bca08acc71afe7182b66018ff024deaa71 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Mon, 7 Sep 2026 01:11:07 +0200 Subject: [PATCH] docs: update ARCHITECTURE.md, README.md, and plan with hosted remote server details --- README.md | 3 +- docs/ARCHITECTURE.md | 61 +++++++++++++------ .../2026-09-06-hosted-remote-server-plan.md | 32 +++++----- 3 files changed, 61 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 94c3e04..bf2d944 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ `openproject-mcp` is a Model Context Protocol (MCP) server that connects AI assistants (such as Claude Desktop, Cursor, and Antigravity) to [OpenProject](https://github.com/opf/openproject) via OpenProject's REST API v3. -It allows agents to browse, query, and reason about OpenProject workspaces using personal API keys over standard MCP transports (stdio). +It allows agents to browse, query, and reason about OpenProject workspaces using personal API keys over standard MCP transports (stdio and HTTP/SSE). --- @@ -16,6 +16,7 @@ It allows agents to browse, query, and reason about OpenProject workspaces using - **HAL+JSON Normalization**: Converts OpenProject's verbose HAL+JSON representations into concise, token-efficient structures. - **Secure Authentication**: Uses OpenProject Personal API tokens via HTTP Basic Auth (`apikey:`) with zero credential storage inside the codebase. - **Multi-Platform Docker Images**: Official multi-architecture images (`linux/amd64` and `linux/arm64`) published to GitHub Container Registry (`ghcr.io`). +- **Hosted Remote MCP Server**: Run as a shared multi-tenant server over HTTP/SSE with Docker Compose, allowing multiple users and AI clients (Cursor, Claude Desktop, autonomous agents) to connect with their own personal API keys. --- diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e53157d..ae733b0 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -55,6 +55,7 @@ The system is organized into four modular layers: - **Responsibility**: Manages the MCP connection lifecycle, tool definitions, input validation, and JSON-RPC dispatching. - **Protocol**: MCP specification implemented via `@modelcontextprotocol/sdk`. - **Default Transport**: `StdioServerTransport` for local integration with desktop clients and CLI tools. +- **Remote Transport**: Native `Bun.serve` HTTP/SSE server (`src/http-server.ts`) supporting multi-tenant sessions over Server-Sent Events (`text/event-stream`). - **Schema Validation**: Each tool declares its arguments using Zod schemas, automatically generating standard JSON Schema definitions for the LLM. ### 2.2. Domain Services / Tool Providers @@ -114,16 +115,21 @@ The server is architected from the ground up to support concurrent, multi-user o - **Security**: Complete isolation. Zero network ports exposed. #### Model B: Centralized Shared Server (SSE / HTTP Transport) -- **Deployment**: A single shared `openproject-mcp` daemon runs centrally for a team or cluster. +- **Deployment**: A single shared `openproject-mcp` daemon runs centrally for a team or cluster (via `docker-compose.server.yml` or `PORT=3000`). - **Isolation**: Connection and request-level isolation. -- **Credential Delivery via Headers**: - - Each connecting client supplies their OpenProject API token via standard HTTP headers during connection handshake and request dispatch: - - `Authorization: Basic base64(apikey:)`, or - - `X-OpenProject-API-Key: ` - - Optional: `X-OpenProject-Read-Only: true` - - The transport layer extracts the credentials, instantiates an ephemeral scoped `OpenProjectClient`, and executes tool calls inside `requestContextStorage.run(context, ...)`. +- **Endpoints**: + - `GET /health`: Health probe returning server status and mode. + - `GET /sse`: Persistent SSE stream establishing an isolated session. + - `POST /messages?sessionId=`: JSON-RPC message ingestion routed to the active session. +- **Credential Delivery & Extraction Precedence**: + - Each connecting client supplies their OpenProject API token via: + 1. `Authorization: Bearer ` + 2. `X-OpenProject-Api-Key: ` + 3. `?apiKey=` query parameter + - Unauthenticated connection requests are rejected immediately with HTTP 401 Unauthorized. + - The transport layer instantiates an ephemeral, per-session `OpenProjectClient` and executes tool calls inside `runWithContext({ client, isReadOnly }, ...)`. - **RBAC Enforcement**: All calls execute against OpenProject REST API v3 using that specific user's token. OpenProject's internal Role-Based Access Control guarantees users only retrieve projects and work packages they have permission to access. -- **Lifecycle**: Ephemeral client instances and tokens reside only in active session memory and are garbage collected upon connection close. +- **Lifecycle & Memory Safety**: Abort signal listeners (`req.signal`) and stream cancellation handlers evict closed sessions from active memory, guaranteeing zero memory leaks. --- @@ -190,10 +196,16 @@ openproject-mcp/ ├── docs/ │ ├── ARCHITECTURE.md # System architecture and specifications │ ├── DECISIONS.md # Architecture Decision Records (ADR) +│ ├── plans/ # Implementation plans +│ ├── specs/ # Technical designs and specifications │ └── TODO.md # Task tracking and roadmap +├── docker-compose.yml # Local OpenProject 17 testing stack +├── docker-compose.server.yml # Production hosted remote MCP server stack +├── Dockerfile # Multi-stage container image definition ├── src/ -│ ├── index.ts # CLI entry point and startup -│ ├── server.ts # MCP server instance & tool registration +│ ├── index.ts # Dual-transport CLI entry point (stdio & HTTP) +│ ├── server.ts # Stdio MCP server factory & tool registration +│ ├── http-server.ts # Hosted HTTP/SSE multi-tenant server (Bun.serve) │ ├── context.ts # RequestContext & AsyncLocalStorage │ ├── client/ │ │ ├── api-client.ts # OpenProject REST API v3 HTTP client @@ -206,15 +218,28 @@ openproject-mcp/ │ │ ├── projects.ts # Projects domain service │ │ ├── work-packages.ts # Work packages domain service │ │ ├── queries.ts # Queries domain service -│ │ └── metadata.ts # Types, statuses, priorities, users +│ │ ├── metadata.ts # Types, statuses, priorities, users +│ │ └── openapi.ts # OpenAPI specification discovery & caching │ └── tools/ -│ ├── project-tools.ts # MCP tool definitions for projects -│ ├── work-package-tools.ts# MCP tool definitions for work packages -│ └── metadata-tools.ts # MCP tool definitions for metadata +│ ├── common.ts # Common schemas and error formatters +│ ├── index.ts # Tool registration and execution wrapper +│ ├── metadata.ts # MCP tool definitions for metadata +│ ├── openapi.ts # MCP tool definition for OpenAPI introspection +│ ├── projects.ts # MCP tool definitions for projects +│ ├── queries.ts # MCP tool definitions for saved queries +│ └── work-packages.ts # MCP tool definitions for work packages ├── tests/ │ ├── fixtures/ # HAL+JSON mock fixtures │ ├── client.test.ts # OpenProject client unit tests -│ └── tools.test.ts # MCP tool integration tests +│ ├── config.test.ts # Configuration loader unit tests +│ ├── docker.test.ts # Docker packaging and compose tests +│ ├── http-server.test.ts # Hosted HTTP/SSE server and protocol tests +│ ├── mcp-server.test.ts # MCP server stdio integration tests +│ ├── openapi.test.ts # OpenAPI service and tool tests +│ ├── read-only.test.ts # Read-only execution mode guard tests +│ ├── services.test.ts # Domain services integration tests +│ ├── smoke.test.ts # Metadata and smoke tests +│ └── tools.test.ts # Tool registration unit tests ├── AGENTS.md # Operating guidelines for AI agents ├── package.json # Project dependencies and scripts ├── bun.lock # Bun dependency lockfile @@ -225,7 +250,7 @@ openproject-mcp/ ## 8. Future Roadmap -- **Phase 1 (Current)**: Read/browse capability for projects, work packages, queries, and taxonomies with request-scoped context. -- **Phase 2**: Mutating operations (create/update work packages, add comments, log time). +- **Phase 1 (Completed)**: Read/browse capability for projects, work packages, queries, taxonomies, and OpenAPI introspection. +- **Phase 4 (Completed)**: Hosted remote MCP server (HTTP/SSE transport via `Bun.serve`) with multi-tenant per-session credential scoping and Docker Compose deployment. +- **Phase 2 (Upcoming)**: Mutating operations (create/update work packages, add comments, log time). - **Phase 3**: Attachment inspection and download resources. -- **Phase 4**: SSE / Stream transport for remote multi-user server deployments. diff --git a/docs/plans/2026-09-06-hosted-remote-server-plan.md b/docs/plans/2026-09-06-hosted-remote-server-plan.md index 07a3945..d1207e9 100644 --- a/docs/plans/2026-09-06-hosted-remote-server-plan.md +++ b/docs/plans/2026-09-06-hosted-remote-server-plan.md @@ -35,7 +35,7 @@ - Consumes: `process.env`, CLI `process.argv` - Produces: Updated `AppConfig` interface and `loadConfig()` function supporting `port?: number`, `host?: string`, and optional `apiKey` when `port` is set. -- [ ] **Step 1: Write failing tests in `tests/config.test.ts`** +- [x] **Step 1: Write failing tests in `tests/config.test.ts`** ```typescript import { describe, test, expect, beforeEach, afterEach } from "bun:test"; @@ -100,12 +100,12 @@ describe("Configuration Loader (HTTP & Stdio Support)", () => { }); ``` -- [ ] **Step 2: Run test to verify it fails** +- [x] **Step 2: Run test to verify it fails** Run: `bun test tests/config.test.ts` Expected: FAIL because `port`, `host`, and optional `apiKey` logic are not yet implemented in `src/config/index.ts`. -- [ ] **Step 3: Update `src/config/index.ts`** +- [x] **Step 3: Update `src/config/index.ts`** Update `AppConfig` and `loadConfig` in `src/config/index.ts`: - Add `port?: number` and `host?: string` to `AppConfig`. @@ -115,12 +115,12 @@ Update `AppConfig` and `loadConfig` in `src/config/index.ts`: - If `port` is defined, `apiKey` is optional (does not throw if missing). - If `port` is undefined, require `apiKey` as before. -- [ ] **Step 4: Run test to verify it passes** +- [x] **Step 4: Run test to verify it passes** Run: `bun test tests/config.test.ts` Expected: PASS (all 4 tests pass). -- [ ] **Step 5: Commit** +- [x] **Step 5: Commit** ```bash git add src/config/index.ts tests/config.test.ts @@ -148,7 +148,7 @@ git commit -m "feat(config): support port and host options for HTTP/SSE transpor export function startHttpServer(config: AppConfig): Promise; ``` -- [ ] **Step 1: Write failing tests in `tests/http-server.test.ts`** +- [x] **Step 1: Write failing tests in `tests/http-server.test.ts`** ```typescript import { describe, test, expect, beforeAll, afterAll } from "bun:test"; @@ -258,12 +258,12 @@ describe("Hosted Remote MCP Server (HTTP/SSE)", () => { }); ``` -- [ ] **Step 2: Run test to verify it fails** +- [x] **Step 2: Run test to verify it fails** Run: `bun test tests/http-server.test.ts` Expected: FAIL because `src/http-server.ts` does not exist yet. -- [ ] **Step 3: Implement `src/http-server.ts`** +- [x] **Step 3: Implement `src/http-server.ts`** Implement `src/http-server.ts`: - Parse API key from request: @@ -277,12 +277,12 @@ Implement `src/http-server.ts`: - Cleanup sessions on client disconnect. - Export `startHttpServer`. -- [ ] **Step 4: Run test to verify it passes** +- [x] **Step 4: Run test to verify it passes** Run: `bun test tests/http-server.test.ts` Expected: PASS (all tests pass). -- [ ] **Step 5: Commit** +- [x] **Step 5: Commit** ```bash git add src/http-server.ts tests/http-server.test.ts @@ -304,7 +304,7 @@ git commit -m "feat(http): implement hosted remote MCP server with HTTP/SSE tran - Consumes: `src/http-server.ts`, `src/server.ts`, `src/config/index.ts` - Produces: Unified CLI entrypoint supporting both stdio and HTTP transports, production Docker Compose configuration, and updated documentation. -- [ ] **Step 1: Wire `src/index.ts` for dual-transport execution** +- [x] **Step 1: Wire `src/index.ts` for dual-transport execution** In `src/index.ts`: - Load config via `loadConfig()`. @@ -314,7 +314,7 @@ In `src/index.ts`: - Else: - Run existing stdio server lifecycle. -- [ ] **Step 2: Create `docker-compose.server.yml`** +- [x] **Step 2: Create `docker-compose.server.yml`** Create `docker-compose.server.yml`: ```yaml @@ -338,11 +338,11 @@ services: retries: 3 ``` -- [ ] **Step 3: Record ADR-016 in `docs/DECISIONS.md`** +- [x] **Step 3: Record ADR-016 in `docs/DECISIONS.md`** Record ADR-016 documenting the hosted remote server architecture, credential extraction strategy, and session lifecycle. -- [ ] **Step 4: Update `README.md` and `docs/TODO.md`** +- [x] **Step 4: Update `README.md` and `docs/TODO.md`** In `README.md`: - Add "Hosted Remote MCP Server (Docker Compose)" section showing how to deploy `docker-compose.server.yml`. @@ -350,14 +350,14 @@ In `README.md`: In `docs/TODO.md`: - Mark Phase 4 (Remote Transport) completed. -- [ ] **Step 5: Run full test suite and type check** +- [x] **Step 5: Run full test suite and type check** Run: `bun run typecheck` `bun test` Expected: 0 type errors, all tests pass. -- [ ] **Step 6: Commit** +- [x] **Step 6: Commit** ```bash git add src/index.ts docker-compose.server.yml docs/DECISIONS.md docs/TODO.md README.md