docs: update ARCHITECTURE.md, README.md, and plan with hosted remote server details

This commit is contained in:
Lucas Winther
2026-09-07 01:11:07 +02:00
parent d0dc27b2e3
commit c9ba15bca0
3 changed files with 61 additions and 35 deletions
+2 -1
View File
@@ -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. `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. - **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:<token>`) with zero credential storage inside the codebase. - **Secure Authentication**: Uses OpenProject Personal API tokens via HTTP Basic Auth (`apikey:<token>`) 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`). - **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.
--- ---
+43 -18
View File
@@ -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. - **Responsibility**: Manages the MCP connection lifecycle, tool definitions, input validation, and JSON-RPC dispatching.
- **Protocol**: MCP specification implemented via `@modelcontextprotocol/sdk`. - **Protocol**: MCP specification implemented via `@modelcontextprotocol/sdk`.
- **Default Transport**: `StdioServerTransport` for local integration with desktop clients and CLI tools. - **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. - **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 ### 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. - **Security**: Complete isolation. Zero network ports exposed.
#### Model B: Centralized Shared Server (SSE / HTTP Transport) #### 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. - **Isolation**: Connection and request-level isolation.
- **Credential Delivery via Headers**: - **Endpoints**:
- Each connecting client supplies their OpenProject API token via standard HTTP headers during connection handshake and request dispatch: - `GET /health`: Health probe returning server status and mode.
- `Authorization: Basic base64(apikey:<USER_API_KEY>)`, or - `GET /sse`: Persistent SSE stream establishing an isolated session.
- `X-OpenProject-API-Key: <USER_API_KEY>` - `POST /messages?sessionId=<uuid>`: JSON-RPC message ingestion routed to the active session.
- Optional: `X-OpenProject-Read-Only: true` - **Credential Delivery & Extraction Precedence**:
- The transport layer extracts the credentials, instantiates an ephemeral scoped `OpenProjectClient`, and executes tool calls inside `requestContextStorage.run(context, ...)`. - Each connecting client supplies their OpenProject API token via:
1. `Authorization: Bearer <key>`
2. `X-OpenProject-Api-Key: <key>`
3. `?apiKey=<key>` 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. - **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/ ├── docs/
│ ├── ARCHITECTURE.md # System architecture and specifications │ ├── ARCHITECTURE.md # System architecture and specifications
│ ├── DECISIONS.md # Architecture Decision Records (ADR) │ ├── DECISIONS.md # Architecture Decision Records (ADR)
│ ├── plans/ # Implementation plans
│ ├── specs/ # Technical designs and specifications
│ └── TODO.md # Task tracking and roadmap │ └── 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/ ├── src/
│ ├── index.ts # CLI entry point and startup │ ├── index.ts # Dual-transport CLI entry point (stdio & HTTP)
│ ├── server.ts # MCP server instance & tool registration │ ├── server.ts # Stdio MCP server factory & tool registration
│ ├── http-server.ts # Hosted HTTP/SSE multi-tenant server (Bun.serve)
│ ├── context.ts # RequestContext & AsyncLocalStorage │ ├── context.ts # RequestContext & AsyncLocalStorage
│ ├── client/ │ ├── client/
│ │ ├── api-client.ts # OpenProject REST API v3 HTTP client │ │ ├── api-client.ts # OpenProject REST API v3 HTTP client
@@ -206,15 +218,28 @@ openproject-mcp/
│ │ ├── projects.ts # Projects domain service │ │ ├── projects.ts # Projects domain service
│ │ ├── work-packages.ts # Work packages domain service │ │ ├── work-packages.ts # Work packages domain service
│ │ ├── queries.ts # Queries 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/ │ └── tools/
│ ├── project-tools.ts # MCP tool definitions for projects │ ├── common.ts # Common schemas and error formatters
│ ├── work-package-tools.ts# MCP tool definitions for work packages │ ├── index.ts # Tool registration and execution wrapper
│ └── metadata-tools.ts # MCP tool definitions for metadata │ ├── 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/ ├── tests/
│ ├── fixtures/ # HAL+JSON mock fixtures │ ├── fixtures/ # HAL+JSON mock fixtures
│ ├── client.test.ts # OpenProject client unit tests │ ├── 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 ├── AGENTS.md # Operating guidelines for AI agents
├── package.json # Project dependencies and scripts ├── package.json # Project dependencies and scripts
├── bun.lock # Bun dependency lockfile ├── bun.lock # Bun dependency lockfile
@@ -225,7 +250,7 @@ openproject-mcp/
## 8. Future Roadmap ## 8. Future Roadmap
- **Phase 1 (Current)**: Read/browse capability for projects, work packages, queries, and taxonomies with request-scoped context. - **Phase 1 (Completed)**: Read/browse capability for projects, work packages, queries, taxonomies, and OpenAPI introspection.
- **Phase 2**: Mutating operations (create/update work packages, add comments, log time). - **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 3**: Attachment inspection and download resources.
- **Phase 4**: SSE / Stream transport for remote multi-user server deployments.
@@ -35,7 +35,7 @@
- Consumes: `process.env`, CLI `process.argv` - 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. - 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 ```typescript
import { describe, test, expect, beforeEach, afterEach } from "bun:test"; 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` 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`. 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`: Update `AppConfig` and `loadConfig` in `src/config/index.ts`:
- Add `port?: number` and `host?: string` to `AppConfig`. - 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 defined, `apiKey` is optional (does not throw if missing).
- If `port` is undefined, require `apiKey` as before. - 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` Run: `bun test tests/config.test.ts`
Expected: PASS (all 4 tests pass). Expected: PASS (all 4 tests pass).
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add src/config/index.ts tests/config.test.ts 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<HttpServerInstance>; export function startHttpServer(config: AppConfig): Promise<HttpServerInstance>;
``` ```
- [ ] **Step 1: Write failing tests in `tests/http-server.test.ts`** - [x] **Step 1: Write failing tests in `tests/http-server.test.ts`**
```typescript ```typescript
import { describe, test, expect, beforeAll, afterAll } from "bun:test"; 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` Run: `bun test tests/http-server.test.ts`
Expected: FAIL because `src/http-server.ts` does not exist yet. 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`: Implement `src/http-server.ts`:
- Parse API key from request: - Parse API key from request:
@@ -277,12 +277,12 @@ Implement `src/http-server.ts`:
- Cleanup sessions on client disconnect. - Cleanup sessions on client disconnect.
- Export `startHttpServer`. - 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` Run: `bun test tests/http-server.test.ts`
Expected: PASS (all tests pass). Expected: PASS (all tests pass).
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add src/http-server.ts tests/http-server.test.ts 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` - 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. - 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`: In `src/index.ts`:
- Load config via `loadConfig()`. - Load config via `loadConfig()`.
@@ -314,7 +314,7 @@ In `src/index.ts`:
- Else: - Else:
- Run existing stdio server lifecycle. - 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`: Create `docker-compose.server.yml`:
```yaml ```yaml
@@ -338,11 +338,11 @@ services:
retries: 3 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. 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`: In `README.md`:
- Add "Hosted Remote MCP Server (Docker Compose)" section showing how to deploy `docker-compose.server.yml`. - 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`: In `docs/TODO.md`:
- Mark Phase 4 (Remote Transport) completed. - 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: Run:
`bun run typecheck` `bun run typecheck`
`bun test` `bun test`
Expected: 0 type errors, all tests pass. Expected: 0 type errors, all tests pass.
- [ ] **Step 6: Commit** - [x] **Step 6: Commit**
```bash ```bash
git add src/index.ts docker-compose.server.yml docs/DECISIONS.md docs/TODO.md README.md git add src/index.ts docker-compose.server.yml docs/DECISIONS.md docs/TODO.md README.md