docs: add read-only mode specification, architecture, and ADR

- Specify OPENPROJECT_READ_ONLY and --read-only in docs/ARCHITECTURE.md

- Add ADR-011 in docs/DECISIONS.md for tool manifest filtering and execution guards

- Update docs/TODO.md with read-only implementation tasks

- Update README.md, AGENTS.md, and .env.example with read-only configuration reference
This commit is contained in:
Lucas Winther
2026-09-05 16:50:53 +02:00
parent 11a57c25b7
commit 7029ca296a
6 changed files with 51 additions and 1 deletions
+1
View File
@@ -22,3 +22,4 @@ SECRET_KEY_BASE=openproject-secret-key-base-for-local-testing-at-least-64-charac
# OPENPROJECT_API_KEY will be automatically written to .env.test
OPENPROJECT_BASE_URL=http://localhost:8080
OPENPROJECT_API_KEY=
OPENPROJECT_READ_ONLY=false
+1
View File
@@ -79,6 +79,7 @@ Welcome to the `openproject-mcp` project. This document defines operating princi
- **Secrets Management**: Read `OPENPROJECT_BASE_URL` and `OPENPROJECT_API_KEY` from environment variables. Never hardcode credentials in tests, scripts, or examples.
- **Input Validation**: All tool parameters must be validated via Zod schemas before being used in HTTP requests.
- **Safe Error Propagation**: Sanitize error messages returned to the MCP client. Never leak authorization headers or system paths in tool error responses.
- **Read-Only Mode Enforcement**: Support `OPENPROJECT_READ_ONLY=true` and `--read-only`. When active, mutating tools must not be registered in the MCP tool manifest and must be rejected by execution guards with `SERVER_READ_ONLY`.
---
+13
View File
@@ -126,10 +126,23 @@ The MCP server accepts configuration through environment variables or a `.env` f
| :--- | :--- | :--- |
| `OPENPROJECT_BASE_URL` | Base URL of the OpenProject instance | `http://localhost:8080` |
| `OPENPROJECT_API_KEY` | Personal API token (created under My Account > Access tokens) | - |
| `OPENPROJECT_READ_ONLY` | Run server in read-only mode (`true` / `false` or `--read-only`) | `false` |
| `PORT` | Local port for the Docker Compose web container | `8080` |
| `TAG` | OpenProject container image tag | `17-slim` |
| `POSTGRES_VERSION` | PostgreSQL container image tag | `17` |
### Read-Only Mode
To ensure AI assistants cannot make any modifications to your OpenProject instance, enable read-only mode:
```bash
OPENPROJECT_READ_ONLY=true bun run src/index.ts
# or
bun run src/index.ts --read-only
```
When active, write tools are omitted from the tool manifest and blocked by execution guards.
---
## Development Roadmap
+8
View File
@@ -71,8 +71,15 @@ Domain services map MCP tool calls to concrete business logic and OpenProject AP
- Parses and validates configuration at startup:
- `OPENPROJECT_BASE_URL`: Base URL of the OpenProject instance (e.g., `https://openproject.example.com`).
- `OPENPROJECT_API_KEY`: User API key generated in OpenProject under "My Account > Access tokens".
- `OPENPROJECT_READ_ONLY`: Boolean flag (`true` / `false`, default: `false`). Can also be set via `--read-only` CLI argument.
- Fails fast with actionable setup advice if required credentials are missing or invalid.
### 2.5. Read-Only Execution Mode
The server supports a dedicated read-only operating mode designed for auditing, reporting, and exploratory agent workflows:
- **Tool Filtering**: When read-only mode is active, only read/browse/query tools are registered in the MCP tool registry. Mutating tools (creating work packages, updating attributes, logging time) are completely excluded from the tool manifest exposed to the LLM.
- **Defense-in-Depth Guard**: In addition to tool manifest omission, the internal tool router validates whether an operation is a mutating action. Any write attempt is immediately rejected with a structured error (`SERVER_READ_ONLY: Server is operating in read-only mode. Write operations are disabled.`).
- **Safe Auditing**: Guarantees zero unintentional data modification or state changes in the OpenProject instance.
---
## 3. Tool Specifications (Browse & Query Scope)
@@ -125,6 +132,7 @@ The client layer provides helper builders so tools can accept friendly parameter
2. **Permission Boundary**: The MCP server operates with the exact permission scope of the provided API key. No elevated access is granted beyond what the user possesses in OpenProject.
3. **Input Sanitization**: All arguments from the LLM are validated via Zod schemas prior to making network requests, preventing request-smuggling or path traversal.
4. **Transport Isolation**: The default stdio transport communicates only through standard input/output with the host client process, with no exposed network ports.
5. **Read-Only Enforcement**: Configurable read-only mode (`OPENPROJECT_READ_ONLY=true`) guarantees zero write side-effects on the OpenProject instance when operating in exploratory or untrusted agent sessions.
---
+25
View File
@@ -234,4 +234,29 @@ Adopt **Bun** (>= 1.2 / 1.3) as the exclusive runtime, package manager, and test
- **Negative**:
- Requires the host machine to have Bun installed rather than traditional Node.js/npm.
---
## ADR-011: Configurable Read-Only Execution Mode
### Status
Accepted
### Context
When integrating MCP servers with LLMs, users and administrators require granular safety boundaries. In many scenarios (e.g., exploratory analysis, code reviews, status summaries, onboarding, or automated audit agents), users want to grant the LLM visibility into OpenProject projects, work packages, and queries without granting write permissions that could accidentally modify tickets or corrupt project state.
### Decision
Implement an explicit, configurable **Read-Only Mode**:
1. Enabled via the `OPENPROJECT_READ_ONLY=true` environment variable or `--read-only` command-line argument.
2. **Tool Manifest Level**: When read-only mode is active, mutating tools (Phase 2 write tools such as create/update work package) are entirely omitted from the `ListTools` response sent to the MCP client. The LLM only sees browsing and query tools.
3. **Execution Guard Level**: As defense-in-depth, the server's tool execution dispatcher checks the read-only flag before invoking any tool marked as mutating, immediately rejecting write attempts with `SERVER_READ_ONLY`.
### Consequences
- **Positive**:
- Complete safety for users wanting strictly observational LLM capabilities.
- Zero token waste exposing mutation tool definitions that the agent is forbidden from using.
- Clean separation between queries and commands.
- **Negative**:
- Tools must explicitly declare whether they are mutating or read-only during registration.
+3 -1
View File
@@ -45,7 +45,7 @@
## 2. Next Up (Phase 1: Read/Browse Implementation)
- [ ] **Task 1: OpenProject REST API v3 Client Layer**
- [ ] Implement `src/config/index.ts` to parse and validate `OPENPROJECT_BASE_URL` and `OPENPROJECT_API_KEY` via Zod.
- [ ] Implement `src/config/index.ts` to parse and validate `OPENPROJECT_BASE_URL`, `OPENPROJECT_API_KEY`, and `OPENPROJECT_READ_ONLY` (or `--read-only`) via Zod.
- [ ] Implement `src/client/api-client.ts` with native `fetch`, HTTP Basic Auth injection, error normalization, and rate/timeout handling.
- [ ] Implement `src/client/hal-parser.ts` to unpack HAL+JSON (`_links`, `_embedded`) into clean, token-efficient JSON models.
- [ ] Implement `src/client/filter-builder.ts` to translate LLM-friendly filter arguments into OpenProject's JSON filter syntax.
@@ -73,8 +73,10 @@
- [ ] **Task 4: MCP Stdio Server Assembly & Integration**
- [ ] Wire domain tools into `@modelcontextprotocol/sdk` Server using `StdioServerTransport` in `src/server.ts` and `src/index.ts`.
- [ ] Implement read-only mode tool filtering (omits mutating tools when `readOnly` is 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.ts` verifying tool calling against the running OpenProject 17 instance.
- [ ] Add read-only mode verification tests in `tests/read-only.test.ts`.
---