Migrate all repos into monorepo context folders

Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
2026-06-30 20:39:52 +02:00
parent 2f2b295531
commit a5f8fb49ab
1717 changed files with 447332 additions and 0 deletions
@@ -0,0 +1 @@
{"specId": "3c666145-33fa-4fb9-ad25-9cb7a9d09ada", "workflowType": "requirements-first", "specType": "feature"}
@@ -0,0 +1,686 @@
# Design Document: DB Planet MCP Tools Refactoring
## Overview
This design describes the refactoring of the DB Planet MCP Server from a single monolithic `SearchDBPlanet` tool into ~27 focused, single-responsibility MCP tools organized across 8 capability areas. The current architecture creates a new `HaiiloClient` per tool invocation and bundles search + full content fetching into one tool. The refactored architecture shares a single `HaiiloClient` per session, separates concerns into individual tools, and adds consistent error handling with 401 retry logic.
The server continues to use StreamableHTTP transport on the existing `/servers/dbplanet/mcp` endpoint, preserving backward compatibility for transport and authentication.
## Architecture
### High-Level Structure
```mermaid
graph TD
A[Express Server<br/>server.ts] -->|creates per session| B[McpServer<br/>getToolServer.ts]
A -->|extracts credentials| C[HaiiloClient<br/>haiiloClient.ts<br/>shared per session]
B -->|registers tools from| D[Tool Registry Modules<br/>src/tools/]
D --> D1[searchTools.ts]
D --> D2[workspaceTools.ts]
D --> D3[newsTools.ts]
D --> D4[peopleTools.ts]
D --> D5[eventTools.ts]
D --> D6[socialTools.ts]
D --> D7[notificationTools.ts]
D --> D8[pageTools.ts]
D --> D9[contentTools.ts]
D1 --> C
D2 --> C
D3 --> C
D4 --> C
D5 --> C
D6 --> C
D7 --> C
D8 --> C
D9 --> C
C -->|OAuth2 + HTTP| E[Haiilo REST API]
```
### Key Design Decisions
1. **One HaiiloClient per session, not per tool call.** Currently `registerHaiiloTools` creates a new `HaiiloClient` on every invocation. Instead, `getToolServer.ts` will create the client once and pass it to all registration modules. The client already caches the access token internally.
2. **Tool registration modules by capability area.** Each file in `src/tools/` registers tools for one capability area. This keeps files small and allows independent evolution. A barrel `registerAllTools` function in a new `src/tools/index.ts` calls each module's registration function.
3. **401 retry in HaiiloClient.** The client will intercept 401 responses, clear the cached token, re-authenticate, and retry the request once. This is implemented as an Axios response interceptor.
4. **Consistent error mapping.** A shared `handleApiError` utility maps Axios errors to MCP `CallToolResult` with `isError: true`, including the HTTP status code and a human-readable message. All tools use this utility.
5. **Zod schemas with descriptions.** Each tool defines its input schema using Zod with `.describe()` annotations so AI agents understand parameter semantics.
### File Structure After Refactoring
```
src/
├── server.ts # Express server (unchanged)
├── getToolServer.ts # Creates McpServer, HaiiloClient, registers all tools
├── haiiloClient.ts # HaiiloClient with 401 retry (enhanced) — API client layer
├── errorHandler.ts # Shared error mapping utility
├── tools/
│ ├── index.ts # registerAllTools barrel function
│ ├── searchTools.ts # Search_FullText, Search_QuickEntity, Search_Grouped
│ ├── workspaceTools.ts # Workspace_List, Workspace_Get, Workspace_GetMembers, Workspace_Join, Workspace_Create
│ ├── newsTools.ts # News_GetFeed
│ ├── peopleTools.ts # People_GetMe, People_List, People_Search
│ ├── eventTools.ts # Event_Get, Event_GetMemberships, Event_UpdateStatus, Event_Create
│ ├── socialTools.ts # Social_GetComments, Social_PostComment, Social_GetLikes, Social_ToggleLike
│ ├── notificationTools.ts # Notification_List, Notification_GetStatus, Notification_MarkSeen
│ ├── pageTools.ts # Page_List, Page_Get, Page_GetMembers, Page_Create
│ └── contentTools.ts # Content_GetFull
```
## Components and Interfaces
### 1. HaiiloClient (Enhanced)
The existing `HaiiloClient` class is enhanced with:
- A generic `request` method for arbitrary API calls (GET, POST, PUT, DELETE)
- Axios response interceptor for automatic 401 retry with token refresh
- The existing `search`, `getContentByType`, `fetchWidgetContent`, `extractTextFromWidgets`, and `cleanHtml` methods remain for use by `Content_GetFull`
```typescript
interface HaiiloCredentials {
username: string;
password: string;
clientId: string;
clientSecret: string;
baseUrl: string;
}
class HaiiloClient {
// Existing fields
private client: AxiosInstance;
private credentials: HaiiloCredentials;
private accessToken?: string;
constructor(credentials: HaiiloCredentials);
// New generic request method used by all tools
async request<T = any>(config: {
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
path: string;
params?: Record<string, any>;
data?: any;
}): Promise<T>;
// Existing methods (kept for Content_GetFull)
private async getAccessToken(): Promise<string>;
async search(term: string): Promise<any>;
private async getContentByType(token: string, item: any): Promise<string>;
private async fetchWidgetContent(token: string, url: string): Promise<string | null>;
private extractTextFromWidgets(widgetData: any): string;
private cleanHtml(text: string): string;
}
```
The `request` method:
1. Calls `getAccessToken()` to get/reuse the cached token
2. Makes the HTTP request with `Authorization: Bearer {token}`
3. On 401 response: clears `accessToken`, calls `getAccessToken()` again, retries once
4. Returns `response.data`
### 2. Error Handler
A shared utility that converts Axios errors into MCP error responses:
```typescript
// src/tools/errorHandler.ts
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
function handleApiError(error: unknown): CallToolResult;
```
Mapping:
| HTTP Status | Message |
|---|---|
| 401 | Authentication failed. Check DB Planet credentials. |
| 403 | Insufficient permissions to access this resource. |
| 404 | The requested resource was not found. |
| ECONNABORTED / timeout | Request timed out. |
| Other | `Error: {status} - {statusText}` |
All error responses follow the shape:
```typescript
{
isError: true,
content: [{
type: 'text',
text: `[{status}] {message}`
}]
}
```
### 3. Tool Registration Modules
Each module exports a single function:
```typescript
type ToolRegistrationFn = (server: McpServer, client: HaiiloClient) => void;
```
Example — `searchTools.ts`:
```typescript
export function registerSearchTools(server: McpServer, client: HaiiloClient): void {
server.tool(
'Search_FullText',
'Full-text search across DB Planet. Returns result metadata (id, title, type, excerpt, modified date) without full content. Use Content_GetFull to fetch full content for a specific result.',
{
query: z.string().describe('Search query term'),
page: z.number().optional().describe('Page number (0-based)'),
pageSize: z.number().optional().describe('Results per page'),
},
async ({ query, page, pageSize }): Promise<CallToolResult> => {
try {
const data = await client.request({
method: 'GET',
path: '/api/search',
params: { term: query, _page: page, _pageSize: pageSize },
});
// Format and return metadata only
} catch (error) {
return handleApiError(error);
}
}
);
// ... Search_QuickEntity, Search_Grouped
}
```
### 4. Barrel Registration (`src/tools/index.ts`)
```typescript
export function registerAllTools(server: McpServer, client: HaiiloClient): void {
registerSearchTools(server, client);
registerWorkspaceTools(server, client);
registerNewsTools(server, client);
registerPeopleTools(server, client);
registerEventTools(server, client);
registerSocialTools(server, client);
registerNotificationTools(server, client);
registerPageTools(server, client);
registerContentTools(server, client);
}
```
### 5. Updated `getToolServer.ts`
```typescript
const getToolAPIServer = async (credentials: HaiiloCredentials) => {
const server = new McpServer({ name: 'DBPlanet-MCP-Server', version: '0.1.0' }, { capabilities: { logging: {} } });
validateCredentials(credentials);
const client = new HaiiloClient(credentials);
registerAllTools(server, client);
return server;
};
```
Credential validation moves to server creation time. If credentials are missing, the server still starts but tools return descriptive errors on invocation (lazy validation is also acceptable since some tools may not need all credentials).
### Complete Tool Inventory
| # | Tool Name | HTTP Method | API Path | Module |
|---|---|---|---|---|
| 1 | Search_FullText | GET | /api/search | searchTools |
| 2 | Search_QuickEntity | GET | /api/quick-entity-search | searchTools |
| 3 | Search_Grouped | GET | /api/search/grouped | searchTools |
| 4 | Workspace_List | GET | /api/workspaces | workspaceTools |
| 5 | Workspace_Get | GET | /api/workspaces/{id} | workspaceTools |
| 6 | Workspace_GetMembers | GET | /api/workspaces/{id}/members | workspaceTools |
| 7 | Workspace_Join | PUT | /api/workspaces/{id}/members/join | workspaceTools |
| 8 | Workspace_Create | POST | /api/workspaces | workspaceTools |
| 9 | News_GetFeed | GET | /web/blog/newsfeed | newsTools |
| 10 | People_GetMe | GET | /api/users/me | peopleTools |
| 11 | People_List | GET | /api/users | peopleTools |
| 12 | People_Search | GET | /api/users/chooser/search | peopleTools |
| 13 | Event_Get | GET | /api/events/{id} | eventTools |
| 14 | Event_GetMemberships | GET | /api/events/{id}/memberships | eventTools |
| 15 | Event_UpdateStatus | PUT | /api/events/{id}/memberships/status | eventTools |
| 16 | Event_Create | POST | /api/events | eventTools |
| 17 | Social_GetComments | GET | /api/comments | socialTools |
| 18 | Social_PostComment | POST | /api/comments | socialTools |
| 19 | Social_GetLikes | GET | /api/like-targets/{targetType}/{targetId} | socialTools |
| 20 | Social_ToggleLike | POST | /api/like-targets/{targetType}/{targetId}/likes/{userId} | socialTools |
| 21 | Notification_List | GET | /api/notifications | notificationTools |
| 22 | Notification_GetStatus | GET | /api/notifications/status | notificationTools |
| 23 | Notification_MarkSeen | PUT | /api/notifications/mark-seen | notificationTools |
| 24 | Page_List | GET | /api/pages | pageTools |
| 25 | Page_Get | GET | /api/pages/{id} | pageTools |
| 26 | Page_GetMembers | GET | /api/pages/{id}/members | pageTools |
| 27 | Page_Create | POST | /api/pages | pageTools |
| 28 | Content_GetFull | GET | (varies by type) | contentTools |
## Data Models
### Input Schemas (Zod)
Each tool defines its input schema using Zod. Below are the schemas grouped by capability area.
#### Search Tools
```typescript
// Search_FullText
{
query: z.string().describe('Search query term'),
page: z.number().optional().describe('Page number (0-based)'),
pageSize: z.number().optional().describe('Results per page'),
}
// Search_QuickEntity
{
query: z.string().describe('Quick search query'),
}
// Search_Grouped
{
query: z.string().describe('Search query term'),
}
```
#### Workspace Tools
```typescript
// Workspace_List
{} // no parameters
// Workspace_Get
{ id: z.string().describe('Workspace ID') }
// Workspace_GetMembers
{ id: z.string().describe('Workspace ID') }
// Workspace_Join
{ id: z.string().describe('Workspace ID to join') }
// Workspace_Create
{
name: z.string().describe('Workspace name'),
description: z.string().optional().describe('Workspace description'),
}
```
#### News Tools
```typescript
// News_GetFeed
{
page: z.number().optional().describe('Page number (0-based)'),
pageSize: z.number().optional().describe('Items per page'),
}
```
#### People Tools
```typescript
// People_GetMe
{} // no parameters
// People_List
{
page: z.number().optional().describe('Page number (0-based)'),
pageSize: z.number().optional().describe('Users per page'),
}
// People_Search
{
query: z.string().describe('Search query for people'),
}
```
#### Event Tools
```typescript
// Event_Get
{ id: z.string().describe('Event ID') }
// Event_GetMemberships
{ id: z.string().describe('Event ID') }
// Event_UpdateStatus
{
id: z.string().describe('Event ID'),
status: z.string().describe('Attendance status (e.g., accepted, declined, tentative)'),
}
// Event_Create
{
title: z.string().describe('Event title'),
description: z.string().optional().describe('Event description'),
startDate: z.string().describe('Start date/time in ISO 8601 format'),
endDate: z.string().describe('End date/time in ISO 8601 format'),
location: z.string().optional().describe('Event location'),
}
```
#### Social Tools
```typescript
// Social_GetComments
{ targetId: z.string().describe('ID of the entity to get comments for') }
// Social_PostComment
{
targetId: z.string().describe('ID of the entity to comment on'),
body: z.string().describe('Comment text'),
}
// Social_GetLikes
{
targetType: z.string().describe('Type of the like target (e.g., blog-article, timeline-item)'),
targetId: z.string().describe('ID of the like target'),
}
// Social_ToggleLike
{
targetType: z.string().describe('Type of the like target'),
targetId: z.string().describe('ID of the like target'),
userId: z.string().describe('User ID to toggle like for'),
}
```
#### Notification Tools
```typescript
// Notification_List
{
page: z.number().optional().describe('Page number (0-based)'),
pageSize: z.number().optional().describe('Notifications per page'),
}
// Notification_GetStatus
{} // no parameters
// Notification_MarkSeen
{} // no parameters
```
#### Page Tools
```typescript
// Page_List
{} // no parameters
// Page_Get
{ id: z.string().describe('Page ID or slug') }
// Page_GetMembers
{ id: z.string().describe('Page ID') }
// Page_Create
{
title: z.string().describe('Page title'),
content: z.string().optional().describe('Page content'),
}
```
#### Content Tools
```typescript
// Content_GetFull
{
contentId: z.string().describe('ID of the content item'),
contentType: z.string().describe('Content type: blog-article, wiki-article, page, workspace, app, timeline-item'),
appId: z.string().optional().describe('App ID (required for blog and wiki articles)'),
slug: z.string().optional().describe('Slug (alternative identifier for pages and workspaces)'),
}
```
### Response Shapes
All tools return `CallToolResult` from the MCP SDK:
```typescript
// Success
{
content: [{ type: 'text', text: string }]
}
// Error
{
isError: true,
content: [{ type: 'text', text: string }]
}
```
Tool responses format data as human-readable text (not JSON) for LLM consumption, consistent with the current `SearchDBPlanet` tool's approach. Each tool formats its response with clear labels and structure.
### HaiiloClient Request/Response
```typescript
// Generic request config
interface RequestConfig {
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
path: string; // e.g., '/api/search'
params?: Record<string, any>; // query parameters
data?: any; // request body (POST/PUT)
}
// The client prepends credentials.baseUrl to path and adds Authorization header
```
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property 1: Tool-to-endpoint routing correctness
*For any* registered tool and any valid input for that tool, the tool SHALL dispatch an HTTP request using the correct method (GET/POST/PUT) to the correct API path with the input parameters correctly mapped to query params or request body.
**Validates: Requirements 3.1, 3.3, 3.4, 4.1, 4.2, 4.3, 4.4, 4.5, 5.1, 6.1, 6.2, 6.3, 7.1, 7.2, 7.3, 7.4, 8.1, 8.2, 8.3, 8.4, 9.1, 9.3, 9.4, 10.1, 10.2, 10.3, 10.4, 11.1**
### Property 2: Pagination parameter forwarding
*For any* tool that accepts optional pagination parameters (page, pageSize), when those parameters are provided, they SHALL be forwarded as query parameters to the underlying API request. When omitted, the API request SHALL not include them.
**Validates: Requirements 3.2, 5.2, 6.4, 9.2**
### Property 3: Error handler maps HTTP status to correct MCP error
*For any* HTTP error response with a status code (401, 403, 404, timeout, or other), the `handleApiError` function SHALL produce a `CallToolResult` with `isError: true` containing both the HTTP status code and a human-readable message appropriate for that status.
**Validates: Requirements 12.1, 12.2, 12.3, 12.4, 12.5**
### Property 4: Tool metadata completeness
*For any* registered tool, it SHALL have a name matching the pattern `{Area}_{Action}`, a non-empty description string, and a Zod input schema.
**Validates: Requirements 2.3, 2.4, 2.5**
### Property 5: Token caching — single authentication per session
*For any* sequence of N requests made through the same `HaiiloClient` instance (where N > 1 and no 401 is received), the client SHALL make exactly one OAuth2 token request and reuse the cached token for all subsequent requests.
**Validates: Requirements 1.1, 1.2**
### Property 6: Missing credentials produce error
*For any* subset of the required credentials (username, password, clientId, clientSecret, baseUrl) where at least one field is missing or empty, invoking any tool SHALL return an MCP error response with a descriptive message about the missing credentials.
**Validates: Requirements 1.4**
### Property 7: Search_FullText returns metadata only
*For any* search query, the `Search_FullText` tool's response SHALL contain result metadata (id, title, type, excerpt, modified date) and SHALL NOT contain full rendered content for any result.
**Validates: Requirements 3.5**
### Property 8: HTML widget text extraction removes all tags
*For any* HTML string input, the `cleanHtml` function SHALL return a string containing no HTML tags (no `<...>` sequences), and all HTML entities SHALL be decoded to their character equivalents.
**Validates: Requirements 11.3**
### Property 9: Unsupported content type returns error
*For any* content type string that is not in the supported set (blog-article, wiki-article, page, workspace, app, timeline-item), the `Content_GetFull` tool SHALL return an MCP error response indicating the unsupported content type.
**Validates: Requirements 11.4**
### Property 10: 401 retry — re-authenticate and retry once
*For any* API request that receives a 401 response, the `HaiiloClient` SHALL clear the cached token, re-authenticate, and retry the request exactly once. If the retry also fails with 401, the error SHALL be propagated.
**Validates: Requirements 1.3**
## Error Handling
### Error Flow
```mermaid
graph TD
A[Tool Handler] -->|try| B[client.request]
B -->|success| C[Format & Return CallToolResult]
B -->|401| D[Clear token, re-auth, retry once]
D -->|success| C
D -->|fail| E[handleApiError]
B -->|other error| E
E -->|401| F["[401] Authentication failed. Check DB Planet credentials."]
E -->|403| G["[403] Insufficient permissions to access this resource."]
E -->|404| H["[404] The requested resource was not found."]
E -->|timeout| I["[TIMEOUT] Request timed out."]
E -->|other| J["[{status}] {statusText or message}"]
```
### Error Response Contract
Every tool handler wraps its logic in try/catch and delegates to `handleApiError`:
```typescript
async (params): Promise<CallToolResult> => {
try {
const data = await client.request({ method, path, params });
return { content: [{ type: 'text', text: formatResponse(data) }] };
} catch (error) {
return handleApiError(error);
}
}
```
The `handleApiError` function:
1. Checks if the error is an Axios error with a response status
2. Maps known status codes to human-readable messages
3. Handles network errors (ECONNABORTED, ECONNREFUSED, etc.)
4. Always returns `{ isError: true, content: [{ type: 'text', text }] }`
### Credential Validation
Credentials are validated lazily — when the first tool is invoked and `client.request()` calls `getAccessToken()`. If credentials are missing, the OAuth request fails and the error propagates through `handleApiError`. This avoids blocking server startup when credentials might be provided later via headers.
### 401 Retry Logic
Implemented as an Axios response interceptor on the `HaiiloClient`:
```typescript
this.client.interceptors.response.use(
response => response,
async (error) => {
if (error.response?.status === 401 && !error.config._retried) {
error.config._retried = true;
this.accessToken = undefined;
const token = await this.getAccessToken();
error.config.headers['Authorization'] = `Bearer ${token}`;
return this.client.request(error.config);
}
return Promise.reject(error);
}
);
```
The `_retried` flag prevents infinite retry loops.
## Testing Strategy
### Property-Based Testing Library
Use **fast-check** (`fc`) for TypeScript property-based testing. It integrates well with the existing TypeScript/ESM setup and supports custom arbitraries for generating tool inputs.
```bash
npm install --save-dev fast-check vitest @types/node
```
### Test Structure
```
tests/
├── properties/
│ ├── toolRouting.property.test.ts # Property 1: Tool-to-endpoint routing
│ ├── pagination.property.test.ts # Property 2: Pagination forwarding
│ ├── errorHandler.property.test.ts # Property 3: Error mapping
│ ├── toolMetadata.property.test.ts # Property 4: Tool metadata
│ ├── tokenCaching.property.test.ts # Property 5: Token caching
│ ├── credentials.property.test.ts # Property 6: Missing credentials
│ ├── searchMetadata.property.test.ts # Property 7: Search metadata only
│ ├── htmlExtraction.property.test.ts # Property 8: HTML extraction
│ ├── contentType.property.test.ts # Property 9: Unsupported content type
│ └── retryLogic.property.test.ts # Property 10: 401 retry
├── unit/
│ ├── errorHandler.test.ts # Specific error code examples
│ ├── haiiloClient.test.ts # Client construction, auth flow
│ ├── contentTools.test.ts # Content type routing examples
│ └── registration.test.ts # All tools registered, backward compat
```
### Property-Based Testing Approach
Each property test:
- Runs a minimum of 100 iterations
- Uses fast-check arbitraries to generate random inputs
- References the design property in a comment tag
- Mocks the Axios client to verify request dispatch without hitting real APIs
Example property test structure:
```typescript
import fc from 'fast-check';
import { describe, it, expect } from 'vitest';
describe('Tool-to-endpoint routing', () => {
// Feature: dbplanet-mcp-tools, Property 1: Tool-to-endpoint routing correctness
it('dispatches to correct HTTP method and path for any valid input', () => {
fc.assert(
fc.property(
toolInputArbitrary, // generates { toolName, input } pairs
({ toolName, input }) => {
// Mock client.request, invoke tool, verify method + path
const expectedRoute = TOOL_ROUTE_MAP[toolName];
// ... assertion
}
),
{ numRuns: 100 }
);
});
});
```
### Unit Testing Approach
Unit tests cover:
- Specific examples for each content type in `Content_GetFull` (Req 11.2)
- Backward compatibility: server listens on port 3000, /health returns 200, headers accepted (Req 13.1-13.4)
- All 28 tools registered after initialization (Req 2.2)
- 401 retry with mock returning 401 then 200 (Req 1.3)
- Content fetch failure returns descriptive error (Req 11.5)
### Mocking Strategy
- Mock `axios.create()` to return a mock Axios instance
- Mock the OAuth token endpoint to return a test token
- Mock API endpoints to return fixture data
- Use Axios interceptor spies to verify retry behavior
### Test Configuration
Each property-based test must include:
- Comment tag: `// Feature: dbplanet-mcp-tools, Property {N}: {title}`
- Minimum 100 iterations: `{ numRuns: 100 }`
- Clear assertion messages for debugging failures
@@ -0,0 +1,165 @@
# Requirements Document
## Introduction
This document specifies the requirements for refactoring the DB Planet MCP Server from a single monolithic tool (SearchDBPlanet) into a suite of focused, single-responsibility MCP tools. The current server exposes one tool that combines search and full content fetching. The refactored server will expose ~27 tools organized across 8 capability areas, enabling AI agents to select the right tool for each task with clear inputs and outputs.
## Glossary
- **MCP_Server**: The Model Context Protocol server that registers and exposes tools to AI agents via the StreamableHTTP transport
- **Tool**: A single MCP tool registered on the MCP_Server with a unique name, description, input schema, and handler function
- **HaiiloClient**: The API client class responsible for authenticating with the Haiilo platform via OAuth2 and making HTTP requests
- **Tool_Registry**: The module that registers all tools on the MCP_Server instance, organized by capability area
- **Capability_Area**: A logical grouping of related tools (e.g., Search, Workspaces, People)
- **Haiilo_API**: The REST API exposed by the Haiilo (DB Planet) platform
## Requirements
### Requirement 1: Shared API Client Infrastructure
**User Story:** As a developer, I want all tools to share a single authenticated HaiiloClient instance per session, so that OAuth2 tokens are reused and connection setup is not duplicated.
#### Acceptance Criteria
1. THE MCP_Server SHALL create one HaiiloClient instance per session and share it across all registered tools
2. THE HaiiloClient SHALL cache the OAuth2 access token after the first successful authentication and reuse it for subsequent API calls within the same session
3. IF the HaiiloClient receives a 401 response from the Haiilo_API, THEN THE HaiiloClient SHALL re-authenticate and retry the request once
4. IF required credentials are missing, THEN THE Tool SHALL return an MCP error response with a descriptive message
### Requirement 2: Tool Registration Architecture
**User Story:** As a developer, I want tools organized into separate registration modules by capability area, so that the codebase is maintainable and each area can evolve independently.
#### Acceptance Criteria
1. THE Tool_Registry SHALL organize tool registration functions into separate modules, one per Capability_Area
2. THE MCP_Server SHALL register all tools from all Capability_Area modules during server initialization
3. THE Tool_Registry SHALL assign each Tool a unique, descriptive name following the pattern `{Area}_{Action}` (e.g., `Search_FullText`, `Workspace_List`)
4. THE Tool_Registry SHALL provide each Tool with a description that explains the tool's purpose, expected inputs, and output format
5. THE Tool_Registry SHALL define each Tool's input schema using Zod validation with descriptive parameter annotations
### Requirement 3: Information Discovery and Search Tools
**User Story:** As an AI agent, I want separate search tools for full-text search, quick entity lookup, and grouped search, so that I can choose the right search strategy for each query.
#### Acceptance Criteria
1. WHEN a full-text search query is provided, THE Search_FullText Tool SHALL call GET /api/search with the query term and return the list of search results with metadata (id, title, type, excerpt, modified date)
2. WHEN a full-text search query is provided, THE Search_FullText Tool SHALL accept optional pagination parameters (page number, page size)
3. WHEN a quick entity search query is provided, THE Search_QuickEntity Tool SHALL call GET /api/quick-entity-search with the query and return matching entities
4. WHEN a grouped search query is provided, THE Search_Grouped Tool SHALL call GET /api/search/grouped with the query and return results organized by content type
5. THE Search_FullText Tool SHALL return result metadata only, without fetching full content for each result
### Requirement 4: Workspace Management Tools
**User Story:** As an AI agent, I want tools to list, inspect, join, and create workspaces, so that I can help users manage their workspace memberships and discover workspace content.
#### Acceptance Criteria
1. WHEN workspace listing is requested, THE Workspace_List Tool SHALL call GET /api/workspaces and return the list of available workspaces
2. WHEN a workspace ID is provided, THE Workspace_Get Tool SHALL call GET /api/workspaces/{id} and return the workspace details including name, description, and settings
3. WHEN a workspace ID is provided, THE Workspace_GetMembers Tool SHALL call GET /api/workspaces/{id}/members and return the list of workspace members
4. WHEN a workspace ID is provided, THE Workspace_Join Tool SHALL call PUT /api/workspaces/{id}/members/join to add the current user as a member of the workspace
5. WHEN workspace creation parameters are provided, THE Workspace_Create Tool SHALL call POST /api/workspaces with the provided name and description to create a new workspace
### Requirement 5: News and Content Tools
**User Story:** As an AI agent, I want a tool to retrieve the news feed, so that I can help users stay informed about recent company news and blog posts.
#### Acceptance Criteria
1. WHEN the news feed is requested, THE News_GetFeed Tool SHALL call GET /web/blog/newsfeed and return the list of recent news items
2. THE News_GetFeed Tool SHALL accept optional pagination parameters (page number, page size)
3. THE News_GetFeed Tool SHALL return each news item with its title, author, publication date, and excerpt
### Requirement 6: People Directory Tools
**User Story:** As an AI agent, I want tools to look up the current user's profile, list users, and search for people, so that I can help users find colleagues and retrieve profile information.
#### Acceptance Criteria
1. WHEN the current user's profile is requested, THE People_GetMe Tool SHALL call GET /api/users/me and return the authenticated user's profile information
2. WHEN a user listing is requested, THE People_List Tool SHALL call GET /api/users and return a paginated list of users
3. WHEN a people search query is provided, THE People_Search Tool SHALL call GET /api/users/chooser/search with the query and return matching user profiles
4. THE People_List Tool SHALL accept optional pagination parameters (page number, page size)
### Requirement 7: Event Discovery Tools
**User Story:** As an AI agent, I want tools to view event details, check memberships, update attendance status, and create events, so that I can help users manage their event participation.
#### Acceptance Criteria
1. WHEN an event ID is provided, THE Event_Get Tool SHALL call GET /api/events/{id} and return the event details including title, description, date, and location
2. WHEN an event ID is provided, THE Event_GetMemberships Tool SHALL call GET /api/events/{id}/memberships and return the list of event attendees and their statuses
3. WHEN an event ID and attendance status are provided, THE Event_UpdateStatus Tool SHALL call PUT /api/events/{id}/memberships/status to update the current user's attendance status
4. WHEN event creation parameters are provided, THE Event_Create Tool SHALL call POST /api/events with the provided details to create a new event
### Requirement 8: Social Engagement Tools
**User Story:** As an AI agent, I want tools to read and post comments and manage likes, so that I can help users engage with content on the platform.
#### Acceptance Criteria
1. WHEN a target entity ID is provided, THE Social_GetComments Tool SHALL call GET /api/comments with the target ID and return the list of comments
2. WHEN a comment body and target entity ID are provided, THE Social_PostComment Tool SHALL call POST /api/comments to create a new comment on the target entity
3. WHEN a like target ID is provided, THE Social_GetLikes Tool SHALL call GET /api/like-targets/{targetType}/{targetId} and return the like count and whether the current user has liked the target
4. WHEN a like target ID is provided, THE Social_ToggleLike Tool SHALL call POST /api/like-targets/{targetType}/{targetId}/likes/{userId} to toggle the current user's like on the target
### Requirement 9: Notification Tools
**User Story:** As an AI agent, I want tools to retrieve notifications, check notification status, and mark notifications as seen, so that I can help users stay on top of platform activity.
#### Acceptance Criteria
1. WHEN notifications are requested, THE Notification_List Tool SHALL call GET /api/notifications and return the list of notifications with their read/unread status
2. THE Notification_List Tool SHALL accept optional pagination parameters (page number, page size)
3. WHEN notification status is requested, THE Notification_GetStatus Tool SHALL call GET /api/notifications/status and return the count of unseen notifications
4. WHEN a mark-seen request is made, THE Notification_MarkSeen Tool SHALL call PUT /api/notifications/mark-seen to mark all notifications as seen
### Requirement 10: Page Management Tools
**User Story:** As an AI agent, I want tools to list, view, inspect members of, and create pages, so that I can help users manage intranet page content.
#### Acceptance Criteria
1. WHEN page listing is requested, THE Page_List Tool SHALL call GET /api/pages and return the list of available pages
2. WHEN a page ID or slug is provided, THE Page_Get Tool SHALL call GET /api/pages/{id} and return the page details including title, content widgets, and metadata
3. WHEN a page ID is provided, THE Page_GetMembers Tool SHALL call GET /api/pages/{id}/members and return the list of page members and their roles
4. WHEN page creation parameters are provided, THE Page_Create Tool SHALL call POST /api/pages with the provided title and content to create a new page
### Requirement 11: Content Fetching Tool
**User Story:** As an AI agent, I want a dedicated tool to fetch the full rendered content of a specific item by its ID and type, so that I can retrieve detailed content on demand after discovering items via search.
#### Acceptance Criteria
1. WHEN a content ID and content type are provided, THE Content_GetFull Tool SHALL fetch the full rendered content for the specified item using the appropriate Haiilo widget API endpoint
2. THE Content_GetFull Tool SHALL support blog articles, wiki articles, pages, workspaces, apps, and timeline items as content types
3. THE Content_GetFull Tool SHALL extract text from widget HTML and return clean, readable text
4. IF the content type is not recognized, THEN THE Content_GetFull Tool SHALL return an error indicating the unsupported content type
5. IF the content cannot be fetched, THEN THE Content_GetFull Tool SHALL return an error with a descriptive message
### Requirement 12: Error Handling Consistency
**User Story:** As a developer, I want all tools to handle errors in a consistent manner, so that AI agents receive predictable error responses regardless of which tool is called.
#### Acceptance Criteria
1. IF a tool receives a 401 Unauthorized response from the Haiilo_API, THEN THE Tool SHALL return an MCP error response indicating authentication failure
2. IF a tool receives a 403 Forbidden response from the Haiilo_API, THEN THE Tool SHALL return an MCP error response indicating insufficient permissions
3. IF a tool receives a 404 Not Found response from the Haiilo_API, THEN THE Tool SHALL return an MCP error response indicating the requested resource was not found
4. IF a tool encounters a network timeout, THEN THE Tool SHALL return an MCP error response indicating a timeout occurred
5. THE Tool SHALL include the HTTP status code and a human-readable message in all error responses
### Requirement 13: Backward Compatibility
**User Story:** As a developer, I want the refactored server to maintain the same transport and authentication mechanism, so that existing clients can connect without changes.
#### Acceptance Criteria
1. THE MCP_Server SHALL continue to use the StreamableHTTP transport on the /servers/dbplanet/mcp endpoint
2. THE MCP_Server SHALL continue to accept credentials via the x-dbplanet-* HTTP headers
3. THE MCP_Server SHALL continue to listen on port 3000
4. THE MCP_Server SHALL continue to expose the /health endpoint returning a 200 status
@@ -0,0 +1,187 @@
# Implementation Plan: DB Planet MCP Tools Refactoring
## Overview
Refactor the DB Planet MCP Server from a single monolithic `SearchDBPlanet` tool into ~28 focused, single-responsibility MCP tools organized across 9 registration modules. The implementation proceeds bottom-up: shared infrastructure first (error handler, enhanced client), then tool registration modules by capability area, then wiring everything together in the barrel and server entry point.
## Tasks
- [x] 1. Set up test infrastructure and shared utilities
- [x] 1.1 Add vitest and fast-check dev dependencies
- Run `npm install --save-dev vitest fast-check`
- Add `"test": "vitest --run"` script to package.json
- Add vitest config if needed (ESM + TypeScript)
- _Requirements: N/A (infrastructure)_
- [x] 1.2 Create the shared error handler at `src/errorHandler.ts`
- Implement `handleApiError(error: unknown): CallToolResult`
- Map 401 → authentication failed, 403 → insufficient permissions, 404 → not found, timeout → timed out, other → status + message
- All responses: `{ isError: true, content: [{ type: 'text', text: '[{status}] {message}' }] }`
- _Requirements: 12.1, 12.2, 12.3, 12.4, 12.5_
- [ ]* 1.3 Write property test for error handler (Property 3)
- **Property 3: Error handler maps HTTP status to correct MCP error**
- Generate arbitrary HTTP status codes and verify the output shape and message mapping
- **Validates: Requirements 12.1, 12.2, 12.3, 12.4, 12.5**
- [x] 2. Enhance HaiiloClient with generic request method and 401 retry
- [x] 2.1 Add generic `request<T>` method to `src/haiiloClient.ts`
- Accepts `{ method, path, params?, data? }` config
- Calls `getAccessToken()`, makes request with Bearer token, returns `response.data`
- _Requirements: 1.1, 1.2_
- [x] 2.2 Add Axios response interceptor for 401 retry
- On 401: clear `accessToken`, re-authenticate, retry once using `_retried` flag
- If retry also 401, propagate error
- _Requirements: 1.3_
- [ ]* 2.3 Write property test for token caching (Property 5)
- **Property 5: Token caching — single authentication per session**
- Mock OAuth endpoint, make N requests, verify exactly 1 token request
- **Validates: Requirements 1.1, 1.2**
- [ ]* 2.4 Write property test for 401 retry logic (Property 10)
- **Property 10: 401 retry — re-authenticate and retry once**
- Mock API returning 401 then success, verify retry behavior and token refresh
- **Validates: Requirements 1.3**
- [ ]* 2.5 Write property test for missing credentials (Property 6)
- **Property 6: Missing credentials produce error**
- Generate arbitrary subsets of missing credentials, verify MCP error response
- **Validates: Requirements 1.4**
- [x] 3. Checkpoint — Ensure shared infrastructure tests pass
- Ensure all tests pass, ask the user if questions arise.
- [x] 4. Implement search tool registration module
- [x] 4.1 Create `src/tools/searchTools.ts` with `registerSearchTools(server, client)`
- Register `Search_FullText`: GET /api/search with query, page, pageSize params; return metadata only (id, title, type, excerpt, modified date)
- Register `Search_QuickEntity`: GET /api/quick-entity-search with query param
- Register `Search_Grouped`: GET /api/search/grouped with query param
- All tools use Zod schemas with `.describe()` annotations and delegate errors to `handleApiError`
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5_
- [ ]* 4.2 Write property test for pagination forwarding (Property 2)
- **Property 2: Pagination parameter forwarding**
- Generate optional page/pageSize values, verify they are forwarded as query params when present and omitted when absent
- **Validates: Requirements 3.2, 5.2, 6.4, 9.2**
- [ ]* 4.3 Write property test for Search_FullText metadata only (Property 7)
- **Property 7: Search_FullText returns metadata only**
- Mock search response, verify result contains metadata fields and no full content
- **Validates: Requirements 3.5**
- [x] 5. Implement workspace tool registration module
- [x] 5.1 Create `src/tools/workspaceTools.ts` with `registerWorkspaceTools(server, client)`
- Register `Workspace_List`: GET /api/workspaces
- Register `Workspace_Get`: GET /api/workspaces/{id}
- Register `Workspace_GetMembers`: GET /api/workspaces/{id}/members
- Register `Workspace_Join`: PUT /api/workspaces/{id}/members/join
- Register `Workspace_Create`: POST /api/workspaces with name, description
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
- [x] 6. Implement news, people, and event tool registration modules
- [x] 6.1 Create `src/tools/newsTools.ts` with `registerNewsTools(server, client)`
- Register `News_GetFeed`: GET /web/blog/newsfeed with optional page, pageSize
- _Requirements: 5.1, 5.2, 5.3_
- [x] 6.2 Create `src/tools/peopleTools.ts` with `registerPeopleTools(server, client)`
- Register `People_GetMe`: GET /api/users/me
- Register `People_List`: GET /api/users with optional page, pageSize
- Register `People_Search`: GET /api/users/chooser/search with query
- _Requirements: 6.1, 6.2, 6.3, 6.4_
- [x] 6.3 Create `src/tools/eventTools.ts` with `registerEventTools(server, client)`
- Register `Event_Get`: GET /api/events/{id}
- Register `Event_GetMemberships`: GET /api/events/{id}/memberships
- Register `Event_UpdateStatus`: PUT /api/events/{id}/memberships/status with id, status
- Register `Event_Create`: POST /api/events with title, description, startDate, endDate, location
- _Requirements: 7.1, 7.2, 7.3, 7.4_
- [x] 7. Implement social, notification, and page tool registration modules
- [x] 7.1 Create `src/tools/socialTools.ts` with `registerSocialTools(server, client)`
- Register `Social_GetComments`: GET /api/comments with targetId
- Register `Social_PostComment`: POST /api/comments with targetId, body
- Register `Social_GetLikes`: GET /api/like-targets/{targetType}/{targetId}
- Register `Social_ToggleLike`: POST /api/like-targets/{targetType}/{targetId}/likes/{userId}
- _Requirements: 8.1, 8.2, 8.3, 8.4_
- [x] 7.2 Create `src/tools/notificationTools.ts` with `registerNotificationTools(server, client)`
- Register `Notification_List`: GET /api/notifications with optional page, pageSize
- Register `Notification_GetStatus`: GET /api/notifications/status
- Register `Notification_MarkSeen`: PUT /api/notifications/mark-seen
- _Requirements: 9.1, 9.2, 9.3, 9.4_
- [x] 7.3 Create `src/tools/pageTools.ts` with `registerPageTools(server, client)`
- Register `Page_List`: GET /api/pages
- Register `Page_Get`: GET /api/pages/{id}
- Register `Page_GetMembers`: GET /api/pages/{id}/members
- Register `Page_Create`: POST /api/pages with title, content
- _Requirements: 10.1, 10.2, 10.3, 10.4_
- [x] 8. Implement content fetching tool registration module
- [x] 8.1 Create `src/tools/contentTools.ts` with `registerContentTools(server, client)`
- Register `Content_GetFull`: uses existing `getContentByType`, `fetchWidgetContent`, `extractTextFromWidgets`, `cleanHtml` logic from HaiiloClient
- Accept contentId, contentType, optional appId, optional slug
- Support blog-article, wiki-article, page, workspace, app, timeline-item
- Return clean text extracted from widget HTML
- Return error for unsupported content types
- _Requirements: 11.1, 11.2, 11.3, 11.4, 11.5_
- [ ]* 8.2 Write property test for HTML widget text extraction (Property 8)
- **Property 8: HTML widget text extraction removes all tags**
- Generate arbitrary HTML strings, verify output contains no `<...>` sequences and entities are decoded
- **Validates: Requirements 11.3**
- [ ]* 8.3 Write property test for unsupported content type (Property 9)
- **Property 9: Unsupported content type returns error**
- Generate arbitrary strings not in the supported set, verify MCP error response
- **Validates: Requirements 11.4**
- [ ] 9. Checkpoint — Ensure all tool module tests pass
- Ensure all tests pass, ask the user if questions arise.
- [x] 10. Wire everything together and update server entry point
- [x] 10.1 Create `src/tools/index.ts` barrel with `registerAllTools(server, client)`
- Import and call all 9 registration functions in order
- _Requirements: 2.1, 2.2_
- [x] 10.2 Update `src/getToolServer.ts` to use new architecture
- Create one `HaiiloClient` instance per session (not per tool call)
- Call `registerAllTools(server, client)` instead of `registerHaiiloTools`
- Remove old import of `registerHaiiloTools`
- _Requirements: 1.1, 2.2_
- [x] 10.3 Remove old `src/tools/haiiloTools.ts`
- Delete the monolithic tool registration file
- _Requirements: 2.1_
- [ ]* 10.4 Write property test for tool metadata completeness (Property 4)
- **Property 4: Tool metadata completeness**
- Instantiate McpServer, register all tools, verify each tool has `{Area}_{Action}` name pattern, non-empty description, and Zod schema
- **Validates: Requirements 2.3, 2.4, 2.5**
- [ ]* 10.5 Write property test for tool-to-endpoint routing (Property 1)
- **Property 1: Tool-to-endpoint routing correctness**
- For each registered tool with arbitrary valid input, mock `client.request`, invoke tool, verify correct HTTP method and path
- **Validates: Requirements 3.1, 3.3, 3.4, 4.14.5, 5.1, 6.16.3, 7.17.4, 8.18.4, 9.1, 9.3, 9.4, 10.110.4, 11.1**
- [ ] 11. Verify backward compatibility
- [ ] 11.1 Verify `server.ts` is unchanged
- Confirm StreamableHTTP transport on `/servers/dbplanet/mcp`, credentials via `x-dbplanet-*` headers, port 3000, `/health` endpoint
- _Requirements: 13.1, 13.2, 13.3, 13.4_
- [ ]* 11.2 Write unit tests for backward compatibility
- Test that server listens on port 3000, /health returns 200, all 28 tools are registered
- _Requirements: 13.1, 13.2, 13.3, 13.4, 2.2_
- [ ] 12. Final checkpoint — Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Property tests validate universal correctness properties from the design document
- The existing `server.ts` should remain unchanged — only `getToolServer.ts` and `src/tools/` are modified
- The `HaiiloClient` content-fetching methods (`getContentByType`, `fetchWidgetContent`, `extractTextFromWidgets`, `cleanHtml`) need to be made accessible (public or package-internal) for use by `contentTools.ts`