docs: update ARCHITECTURE.md, README.md, and plan with hosted remote server details
This commit is contained in:
@@ -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:<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`).
|
||||
- **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
@@ -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:<USER_API_KEY>)`, or
|
||||
- `X-OpenProject-API-Key: <USER_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=<uuid>`: 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 <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.
|
||||
- **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.
|
||||
|
||||
@@ -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<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
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user