docs(spec): add OpenAPI tool design specification
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
# Specification: OpenProject OpenAPI Specification Tool (`openproject_get_openapi_spec`)
|
||||
|
||||
## 1. Overview & Objectives
|
||||
|
||||
OpenProject REST API v3 provides an instance-specific OpenAPI 3.0 specification at `GET /api/v3/openapi.json`. This dynamic document describes all API endpoints, parameters, request bodies, and schema models supported by the connected OpenProject instance.
|
||||
|
||||
However, the raw JSON document is approximately **1.18 MB** (over 220 path endpoints, 60+ tags, and dozens of component schemas). Returning this payload unconditionally to an LLM would consume ~300,000 tokens, degrading agent reasoning or causing token limit exceptions.
|
||||
|
||||
### Primary Objectives
|
||||
1. Provide an MCP tool `openproject_get_openapi_spec` for dynamic endpoint and schema introspection.
|
||||
2. Implement **Smart Scoping & Filtering** so agents can query:
|
||||
- **Summary mode** (default): High-level overview of available tags and endpoints without blowing up the context window.
|
||||
- **Path mode**: Exact or prefix match for a specific endpoint (e.g. `/api/v3/work_packages`).
|
||||
- **Tag mode**: Operations grouped by functional domain (e.g. `"Work Packages"`, `"Projects"`).
|
||||
- **Schema mode**: Component models from `components.schemas` (e.g. `"WorkPackageModel"`).
|
||||
- **Full mode**: Complete raw document when explicitly requested (`full: true`).
|
||||
3. Implement **In-Memory Caching** keyed by `baseUrl` to avoid re-fetching ~1.18 MB on every tool call, with an optional `refresh: true` cache bypass.
|
||||
4. Mark the tool as `readOnly: true`, making it available in both read-only and read-write modes.
|
||||
|
||||
---
|
||||
|
||||
## 2. Architecture & Components
|
||||
|
||||
```
|
||||
MCP Client (Claude / Antigravity / Cursor)
|
||||
│
|
||||
▼
|
||||
McpServer (src/server.ts)
|
||||
│
|
||||
▼ wrapExecute (RequestContext)
|
||||
openproject_get_openapi_spec (src/tools/openapi.ts)
|
||||
│
|
||||
▼
|
||||
getOpenApiSpec (src/services/openapi.ts)
|
||||
│
|
||||
┌────────────┴────────────┐
|
||||
▼ ▼
|
||||
[In-Memory Cache] OpenProjectClient.get("openapi.json")
|
||||
(by baseUrl) │
|
||||
▼
|
||||
OpenProject 17 API v3
|
||||
```
|
||||
|
||||
### 2.1 Domain Service Layer (`src/services/openapi.ts`)
|
||||
- **Query Options**:
|
||||
```typescript
|
||||
export interface OpenApiQueryOptions {
|
||||
path?: string;
|
||||
tag?: string;
|
||||
schema?: string;
|
||||
full?: boolean;
|
||||
refresh?: boolean;
|
||||
}
|
||||
```
|
||||
- **Caching Mechanism**:
|
||||
- `const openApiCache = new Map<string, unknown>();`
|
||||
- Cached parsed JSON object keyed by `client.baseUrl`.
|
||||
- When `options.refresh` is true, the cached entry is evicted and a fresh `GET /api/v3/openapi.json` is performed.
|
||||
- **Path Normalization**:
|
||||
- Automatically handles `/api/v3` prefix differences (e.g. `work_packages`, `/work_packages`, and `/api/v3/work_packages` resolve identically).
|
||||
- Performs case-insensitive matching.
|
||||
- **Summary Extraction**:
|
||||
- Generates an overview object containing `title`, `version`, `openapi`, `totalPaths`, `tags` (with endpoint counts), and an indexed list of available paths with query instructions.
|
||||
|
||||
### 2.2 MCP Tool Layer (`src/tools/openapi.ts`)
|
||||
- Tool name: `openproject_get_openapi_spec`
|
||||
- `readOnly: true`
|
||||
- Zod schema:
|
||||
- `path`: `z.string().optional()`
|
||||
- `tag`: `z.string().optional()`
|
||||
- `schema`: `z.string().optional()`
|
||||
- `full`: `z.boolean().optional().default(false)`
|
||||
- `refresh`: `z.boolean().optional().default(false)`
|
||||
- Registration via `registerOpenApiTools` and integration in `allTools` (`src/tools/index.ts`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Tool Parameters & Output Schemas
|
||||
|
||||
### 3.1 Tool Arguments
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
|-----------|------|----------|---------|-------------|
|
||||
| `path` | string | No | - | Endpoint path to inspect (e.g. `"/api/v3/work_packages"` or `"projects"`). |
|
||||
| `tag` | string | No | - | Tag / category to filter endpoints by (e.g. `"Work Packages"`, `"Queries"`). |
|
||||
| `schema` | string | No | - | Component schema model name to inspect (e.g. `"WorkPackageModel"`). |
|
||||
| `full` | boolean | No | `false` | When `true`, returns the complete raw OpenAPI document. |
|
||||
| `refresh` | boolean | No | `false` | When `true`, forces cache invalidation and re-fetches from server. |
|
||||
|
||||
### 3.2 Success Output Formats
|
||||
|
||||
#### Summary Mode (Default when no filters provided):
|
||||
```json
|
||||
{
|
||||
"title": "OpenProject API V3 (Stable)",
|
||||
"version": "3",
|
||||
"openapi": "3.0.3",
|
||||
"totalPaths": 226,
|
||||
"tags": [
|
||||
{ "name": "Work Packages", "endpointCount": 8 },
|
||||
{ "name": "Projects", "endpointCount": 12 },
|
||||
{ "name": "Queries", "endpointCount": 5 }
|
||||
],
|
||||
"availablePaths": [
|
||||
"/api/v3",
|
||||
"/api/v3/actions",
|
||||
"/api/v3/work_packages",
|
||||
"/api/v3/work_packages/{id}"
|
||||
],
|
||||
"instructions": "Call openproject_get_openapi_spec with 'path' (e.g. '/api/v3/work_packages'), 'tag' (e.g. 'Work Packages'), or 'schema' (e.g. 'WorkPackageModel') to inspect specific schemas and operations."
|
||||
}
|
||||
```
|
||||
|
||||
#### Path Mode (`path: "/api/v3/work_packages"`):
|
||||
```json
|
||||
{
|
||||
"path": "/api/v3/work_packages",
|
||||
"operations": {
|
||||
"get": {
|
||||
"summary": "List work packages",
|
||||
"operationId": "list_work_packages",
|
||||
"tags": ["Work Packages"],
|
||||
"description": "Returns a collection of work packages.",
|
||||
"parameters": [...]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create work package",
|
||||
"operationId": "create_work_package",
|
||||
"tags": ["Work Packages"],
|
||||
"parameters": [...],
|
||||
"requestBody": {...}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Tag Mode (`tag: "Queries"`):
|
||||
```json
|
||||
{
|
||||
"tag": "Queries",
|
||||
"totalPaths": 3,
|
||||
"paths": {
|
||||
"/api/v3/queries": { ... },
|
||||
"/api/v3/queries/{id}": { ... },
|
||||
"/api/v3/queries/{id}/results": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Schema Mode (`schema: "WorkPackageModel"`):
|
||||
```json
|
||||
{
|
||||
"schemaName": "WorkPackageModel",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Error Handling
|
||||
|
||||
- Non-existent path: returns `Error [OPENPROJECT_NOT_FOUND]: OpenAPI path '<path>' not found in specification. Available paths include: [...]`
|
||||
- Non-existent tag: returns `Error [OPENPROJECT_NOT_FOUND]: OpenAPI tag '<tag>' not found. Available tags include: [...]`
|
||||
- Non-existent schema: returns `Error [OPENPROJECT_NOT_FOUND]: OpenAPI component schema '<schema>' not found. Available schemas include: [...]`
|
||||
- API / Connection failures: captured by `OpenProjectError` and serialized as standard MCP `isError: true` responses.
|
||||
|
||||
---
|
||||
|
||||
## 5. Testing & Verification
|
||||
|
||||
1. **Unit & Mock Tests (`tests/openapi.test.ts`)**:
|
||||
- Summary mode returns expected structure and instructions.
|
||||
- Path lookup handles exact and normalized matches.
|
||||
- Tag lookup filters paths properly.
|
||||
- Schema lookup extracts matching component definition.
|
||||
- Cache hit avoids duplicate HTTP requests; `refresh: true` triggers re-fetch.
|
||||
- Validation rejects invalid argument types.
|
||||
- 404 Not Found returns appropriate error messages for missing paths/tags/schemas.
|
||||
2. **Live Container Tests (`tests/openapi.test.ts`)**:
|
||||
- Live query against OpenProject 17 at `http://localhost:8080/api/v3/openapi.json`.
|
||||
- Verifies live summary, path inspection (`/api/v3/work_packages`), and tag filtering (`"Work Packages"`).
|
||||
3. **Existing Suite Updates**:
|
||||
- Update `tests/tools.test.ts` and `tests/read-only.test.ts` tool count assertions from 10 to 11.
|
||||
Reference in New Issue
Block a user