docs: sync from docs/wiki/ at commit ea7f484c

Gitea Actions 2026-05-23 22:15:22 +00:00
parent 05a6ba4ddf
commit 1ab9d0a7cb
4 changed files with 482 additions and 2 deletions

@ -2,7 +2,7 @@
## Overview ## Overview
TFTSR uses **SQLite** via `rusqlite` with the `bundled-sqlcipher` feature for AES-256 encryption in production. 17 versioned migrations are tracked in the `_migrations` table. TFTSR uses **SQLite** via `rusqlite` with the `bundled-sqlcipher` feature for AES-256 encryption in production. 18 versioned migrations are tracked in the `_migrations` table.
**DB file location:** `{app_data_dir}/tftsr.db` **DB file location:** `{app_data_dir}/tftsr.db`
@ -38,7 +38,7 @@ pub fn init_db(data_dir: &Path) -> anyhow::Result<Connection> {
--- ---
## Schema (17 Migrations) ## Schema (18 Migrations)
### 001 — issues ### 001 — issues
@ -290,6 +290,60 @@ CREATE INDEX idx_timeline_events_time ON timeline_events(created_at);
- Non-blocking writes: Timeline events recorded asynchronously at key triage moments - Non-blocking writes: Timeline events recorded asynchronously at key triage moments
- Cascade delete from issues ensures cleanup - Cascade delete from issues ensures cleanup
### 018 — mcp_servers, mcp_tools, mcp_resources (MCP Server Support)
**MCP server registry:**
```sql
CREATE TABLE mcp_servers (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
url TEXT NOT NULL,
transport_type TEXT NOT NULL CHECK(transport_type IN ('stdio', 'http')),
transport_config TEXT NOT NULL DEFAULT '{}',
auth_type TEXT NOT NULL CHECK(auth_type IN ('none', 'api_key', 'bearer', 'oauth2')),
auth_value TEXT, -- AES-256-GCM encrypted
enabled INTEGER NOT NULL DEFAULT 1,
last_discovered_at TEXT,
discovery_status TEXT NOT NULL DEFAULT 'pending'
CHECK(discovery_status IN ('pending','connected','unreachable','error')),
discovery_error TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
```
**Discovered tools (populated by discovery):**
```sql
CREATE TABLE mcp_tools (
id TEXT PRIMARY KEY,
server_id TEXT NOT NULL,
name TEXT NOT NULL, -- Original tool name from server
tool_key TEXT NOT NULL, -- Sanitised key: mcp_{server}_{tool}
description TEXT,
parameters TEXT NOT NULL DEFAULT '{}', -- JSON Schema
FOREIGN KEY(server_id) REFERENCES mcp_servers(id) ON DELETE CASCADE
);
```
**Discovered resources:**
```sql
CREATE TABLE mcp_resources (
id TEXT PRIMARY KEY,
server_id TEXT NOT NULL,
uri TEXT NOT NULL,
name TEXT,
description TEXT,
FOREIGN KEY(server_id) REFERENCES mcp_servers(id) ON DELETE CASCADE
);
```
**Design notes:**
- `auth_value` stored as AES-256-GCM ciphertext (same encryption as integration credentials)
- `transport_type` and `auth_type` enforce valid values via CHECK constraints
- `discovery_status` tracks connection state: `pending``connected` | `unreachable` | `error`
- Cascade deletes ensure removing a server cleans up all associated tools and resources
- Tools and resources are replaced atomically on each discovery run (delete-all + re-insert)
--- ---
## Key Design Notes ## Key Design Notes

@ -412,6 +412,109 @@ Updates work item fields. Uses JSON-PATCH format.
--- ---
## MCP Server Commands
> **Status:** Fully Implemented (v0.3.0+)
### `list_mcp_servers`
```typescript
listMcpServersCmd() → McpServer[]
```
Returns all registered MCP servers. `auth_value` is always `null` in responses (scrubbed server-side).
```typescript
interface McpServer {
id: string;
name: string;
url: string;
transport_type: "stdio" | "http";
transport_config: string; // JSON
auth_type: "none" | "api_key" | "bearer" | "oauth2";
auth_value?: string; // Always null in responses
enabled: boolean;
last_discovered_at?: string;
discovery_status: "pending" | "connected" | "unreachable" | "error";
discovery_error?: string;
created_at: string;
updated_at: string;
}
```
### `create_mcp_server`
```typescript
createMcpServerCmd(request: CreateMcpServerRequest) → McpServer
```
Creates a new MCP server record. Auth value is encrypted with AES-256-GCM before persistence.
```typescript
interface CreateMcpServerRequest {
name: string;
url: string;
transport_type: "stdio" | "http";
transport_config: string;
auth_type: "none" | "api_key" | "bearer" | "oauth2";
auth_value?: string;
enabled: boolean;
}
```
### `update_mcp_server`
```typescript
updateMcpServerCmd(id: string, request: UpdateMcpServerRequest) → McpServer
```
Partial update. Only provided fields are changed. If `auth_value` is provided, it replaces the encrypted value.
```typescript
interface UpdateMcpServerRequest {
name?: string;
url?: string;
transport_type?: "stdio" | "http";
transport_config?: string;
auth_type?: "none" | "api_key" | "bearer" | "oauth2";
auth_value?: string;
enabled?: boolean;
}
```
### `delete_mcp_server`
```typescript
deleteMcpServerCmd(id: string) → void
```
Deletes the server record and all associated tools/resources (cascade). Also removes the live connection from memory.
### `toggle_mcp_server`
```typescript
toggleMcpServerCmd(id: string, enabled: boolean) → void
```
Enables or disables a server. Disabled servers are excluded from AI tool injection and startup discovery.
### `discover_mcp_server`
```typescript
discoverMcpServerCmd(id: string) → McpServerStatus
```
Connects to the server, enumerates its tools and resources, and persists them. Returns the updated status.
```typescript
interface McpServerStatus {
server_id: string;
status: "pending" | "connected" | "unreachable" | "error";
error?: string;
tool_count: number;
resource_count: number;
last_discovered_at?: string;
}
```
### `get_mcp_server_status`
```typescript
getMcpServerStatusCmd(id: string) → McpServerStatus
```
Returns current discovery status, tool count, and resource count without triggering a new connection.
### `initiate_mcp_oauth`
```typescript
initiateMcpOauthCmd(id: string) → void
```
Opens a WebView window for OAuth2 authentication. Requires `auth_type = "oauth2"` and `transport_config` containing `auth_endpoint`, `token_endpoint`, and `client_id`. After successful authentication, the access token is encrypted and stored.
---
## Common Types ## Common Types
### `ConnectionResult` ### `ConnectionResult`

269
MCP-Servers.md Normal file

@ -0,0 +1,269 @@
# MCP Servers
## Overview
**Model Context Protocol (MCP)** is an open standard that allows AI models to invoke external tools and access external resources through a standardised JSON-RPC interface. TFTSR integrates MCP as a first-class feature, enabling the AI triage assistant to call tools exposed by any compliant MCP server — file search, database queries, monitoring APIs, runbook automation, and more.
MCP support extends the AI's capabilities beyond conversation: during incident triage, the model can autonomously invoke registered tools to gather diagnostic data, check system status, or execute remediation steps — all within the app's security and audit framework.
---
## Architecture
```
┌──────────────────────────────────────────────┐
│ TFTSR App │
│ │
│ ┌────────┐ ┌──────────┐ ┌───────────┐ │
│ │Frontend│──▶│ Commands │──▶│ Store │ │
│ │ React │ │(IPC/Tauri)│ │ (SQLite) │ │
│ └────────┘ └────┬─────┘ └───────────┘ │
│ │ │
│ ┌─────▼─────┐ │
│ │ Discovery │ │
│ └─────┬─────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ │ │ │ │
│ ┌────▼────┐ ┌────▼────┐ ┌───▼────┐ │
│ │ stdio │ │ HTTP │ │Adapter │ │
│ │Transport│ │Transport│ │(AI glue)│ │
│ └────┬────┘ └────┬────┘ └────────┘ │
└───────┼─────────────┼────────────────────────┘
│ │
▼ ▼
Local process Remote HTTP
(e.g. npx) MCP endpoint
```
**Module layout** (`src-tauri/src/mcp/`):
| File | Responsibility |
|------|----------------|
| `models.rs` | Struct definitions: `McpServer`, `McpTool`, `McpResource`, request types |
| `store.rs` | CRUD operations against SQLite (encrypted at rest) |
| `transport/stdio.rs` | Stdio process spawn via `rmcp` (absolute path enforced) |
| `transport/http.rs` | Streamable HTTP transport via `rmcp` |
| `client.rs` | Connection lifecycle, tool listing, tool invocation |
| `adapter.rs` | Name sanitisation, `McpTool` → AI `Tool` conversion |
| `discovery.rs` | Per-server and bulk startup discovery orchestration |
| `commands.rs` | 8 Tauri IPC command handlers |
---
## Database Schema
Three tables are created by **Migration 018** (`018_mcp_servers`):
### `mcp_servers`
| Column | Type | Constraints |
|--------|------|-------------|
| `id` | TEXT | PRIMARY KEY |
| `name` | TEXT | NOT NULL |
| `url` | TEXT | NOT NULL |
| `transport_type` | TEXT | NOT NULL, CHECK IN (`'stdio'`, `'http'`) |
| `transport_config` | TEXT | NOT NULL DEFAULT `'{}'` (JSON) |
| `auth_type` | TEXT | NOT NULL, CHECK IN (`'none'`, `'api_key'`, `'bearer'`, `'oauth2'`) |
| `auth_value` | TEXT | Nullable — AES-256-GCM encrypted |
| `enabled` | INTEGER | NOT NULL DEFAULT 1 |
| `last_discovered_at` | TEXT | Nullable UTC timestamp |
| `discovery_status` | TEXT | NOT NULL DEFAULT `'pending'`, CHECK IN (`'pending'`, `'connected'`, `'unreachable'`, `'error'`) |
| `discovery_error` | TEXT | Nullable |
| `created_at` | TEXT | NOT NULL DEFAULT `datetime('now')` |
| `updated_at` | TEXT | NOT NULL DEFAULT `datetime('now')` |
### `mcp_tools`
| Column | Type | Constraints |
|--------|------|-------------|
| `id` | TEXT | PRIMARY KEY |
| `server_id` | TEXT | NOT NULL, FK → `mcp_servers(id)` ON DELETE CASCADE |
| `name` | TEXT | NOT NULL (original tool name from server) |
| `tool_key` | TEXT | NOT NULL (sanitised key used by AI) |
| `description` | TEXT | Nullable |
| `parameters` | TEXT | NOT NULL DEFAULT `'{}'` (JSON Schema) |
### `mcp_resources`
| Column | Type | Constraints |
|--------|------|-------------|
| `id` | TEXT | PRIMARY KEY |
| `server_id` | TEXT | NOT NULL, FK → `mcp_servers(id)` ON DELETE CASCADE |
| `uri` | TEXT | NOT NULL |
| `name` | TEXT | Nullable |
| `description` | TEXT | Nullable |
Cascade deletes ensure that removing a server automatically cleans up its tools and resources.
---
## Transport Types
### stdio
The app spawns a local process and communicates over its stdin/stdout using the MCP JSON-RPC protocol.
**Configuration** (`transport_config` JSON):
```json
{
"command": "/usr/local/bin/my-mcp-server",
"args": ["--port", "0", "--mode", "stdio"]
}
```
- `command`**must be an absolute path**. Relative paths are rejected to prevent path traversal attacks.
- `args` — optional array of command-line arguments.
The process is spawned via Tokio and wrapped with `rmcp::transport::TokioChildProcess`.
### http (Streamable HTTP)
The app connects to a remote MCP server over HTTP(S) using the Streamable HTTP transport from `rmcp`.
**Configuration:**
- `url` field on the server record — the HTTP endpoint (e.g., `https://mcp.example.com/v1`).
- If `auth_type` is `bearer` or `api_key`, the decrypted auth value is attached as an `Authorization` header.
```json
{
"url": "https://mcp.example.com/v1",
"transport_type": "http",
"auth_type": "bearer"
}
```
The `transport_config` field for HTTP servers is typically `{}` — connection details come from `url` and `auth_value`.
---
## Authentication Types
| Type | Description | Storage |
|------|-------------|---------|
| `none` | No authentication required | — |
| `api_key` | API key sent as Authorization header | Encrypted in `auth_value` |
| `bearer` | Bearer token sent as Authorization header | Encrypted in `auth_value` |
| `oauth2` | OAuth2 PKCE flow via WebView | Token encrypted in `auth_value` after exchange |
All auth values are encrypted with **AES-256-GCM** before storage (same encryption system as integration credentials). The plaintext is never returned to the frontend — `list_mcp_servers` strips `auth_value` from responses.
### OAuth2 Flow
For servers requiring OAuth2:
1. `transport_config` must include `auth_endpoint`, `token_endpoint`, `client_id`, and optionally `scope`.
2. Call `initiate_mcp_oauth(server_id)` — opens a WebView window at the authorization URL.
3. User authenticates with the MCP provider.
4. On redirect, the code is exchanged for an access token.
5. Token is encrypted and stored in `auth_value`.
---
## Configuration Guide
### Adding an MCP Server (UI)
Navigate to **Settings > MCP Servers** (`/settings/mcp`) to manage servers.
1. Click **Add Server**.
2. Fill in:
- **Name** — Human-readable label (e.g., "Weather API", "Filesystem Tools").
- **URL** — For HTTP: the server endpoint. For stdio: can be left as the command path for display.
- **Transport**`stdio` or `http`.
- **Transport Config** — JSON. For stdio: `{"command": "/path/to/binary", "args": [...]}`. For HTTP: typically `{}`.
- **Auth Type**`none`, `api_key`, `bearer`, or `oauth2`.
- **Auth Value** — The token/key (will be encrypted on save). Leave blank for `none`.
- **Enabled** — Toggle on/off.
3. Click **Save**. The server record is persisted.
4. Click **Discover** to connect and enumerate available tools and resources.
### Discovery
Discovery connects to the server, queries its tool and resource manifests, and persists them locally. Status transitions:
```
pending → connected (success)
pending → error (connection/protocol failure)
pending → unreachable (startup failure, non-fatal)
```
After successful discovery, tools from the server appear in AI conversations automatically.
---
## Tool Naming Convention
When tools are discovered, each gets a **tool key** used by the AI model:
```
mcp_{server_name}_{tool_name}
```
Both parts are sanitised:
- Lowercased
- Non-alphanumeric characters replaced with `_`
- Consecutive underscores collapsed
- Leading/trailing underscores trimmed
**Examples:**
| Server Name | Tool Name | Tool Key |
|-------------|-----------|----------|
| My Weather API | get_forecast | `mcp_my_weather_api_get_forecast` |
| Filesystem | search files | `mcp_filesystem_search_files` |
| simple | ping | `mcp_simple_ping` |
The AI model calls tools by their `tool_key`. The adapter layer resolves this back to the original server and tool name for execution.
---
## Startup Discovery
On application launch, `init_all_servers()` iterates all **enabled** servers and attempts discovery for each:
- Successful connections are stored in `AppState.mcp_connections` (a `HashMap<String, Arc<TokioMutex<McpConnection>>>>`).
- Failed connections are marked as `unreachable` in the database with the error message. A warning is logged, but startup continues.
- This is a best-effort, non-blocking operation — the app launches regardless of MCP server availability.
---
## AI Integration
Enabled MCP tools are automatically injected into AI conversations:
1. `get_enabled_mcp_tools()` queries tools from servers that are both `enabled = 1` and `discovery_status = 'connected'`.
2. Each `McpTool` is converted to an AI `Tool` definition (name, description, JSON Schema parameters).
3. When the AI responds with a tool call matching an `mcp_*` key, the adapter routes it to `call_tool()` on the appropriate live connection.
4. The tool result is fed back to the AI as a tool response message.
---
## IPC Commands
| Command | Parameters | Returns |
|---------|-----------|---------|
| `list_mcp_servers` | — | `McpServer[]` (auth_value always null) |
| `create_mcp_server` | `CreateMcpServerRequest` | `McpServer` |
| `update_mcp_server` | `id`, `UpdateMcpServerRequest` | `McpServer` |
| `delete_mcp_server` | `id` | `void` |
| `toggle_mcp_server` | `id`, `enabled` | `void` |
| `discover_mcp_server` | `id` | `McpServerStatus` |
| `get_mcp_server_status` | `id` | `McpServerStatus` |
| `initiate_mcp_oauth` | `id` | `void` (opens WebView) |
See [IPC Commands](IPC-Commands#mcp-servers) for full type signatures.
---
## Security
- **Encrypted auth values** — AES-256-GCM, same key derivation as integration credentials (`TFTSR_ENCRYPTION_KEY`)
- **Server-side scrubbing**`auth_value` set to `None` before any response to the frontend
- **Audit logging**`write_audit_event` called before every MCP tool execution
- **PII scan** — Tool call arguments are scanned for PII patterns (non-blocking warning to user)
- **Absolute path enforcement** — stdio transport rejects relative paths to prevent traversal attacks
- **Cascade deletes** — Removing a server removes all associated tools and resources
- **TLS** — HTTP transport uses `reqwest` with certificate verification for HTTPS endpoints
See [Security Model](Security-Model#mcp-server-security) for the full threat analysis.

@ -129,6 +129,60 @@ CI/CD currently uses internal `http://` endpoints for self-hosted Gitea release
--- ---
## MCP Server Security
MCP server support introduces external tool execution capabilities. The following controls mitigate the associated risks.
### Auth Value Storage
- Auth tokens (API keys, bearer tokens, OAuth2 access tokens) are encrypted with **AES-256-GCM** before persistence in `mcp_servers.auth_value`.
- Encryption uses the same key derivation as integration credentials (`TFTSR_ENCRYPTION_KEY` → SHA-256 → 32-byte AES key).
- Random 96-bit nonce per encryption operation.
- Format: `base64(nonce || ciphertext || tag)`.
### Server-Side Response Scrubbing
- `list_mcp_servers` and all CRUD commands set `auth_value = None` before returning to the frontend.
- The encrypted ciphertext never reaches the WebView layer.
- Decryption only occurs internally when establishing a connection (discovery) or executing a tool call.
### Audit Trail
- `write_audit_event` is called **before** every MCP tool execution with:
- `action`: `"mcp_tool_call"`
- `entity_type`: `"mcp_tool"`
- `entity_id`: the tool key being invoked
- `details`: JSON containing server ID, tool name, and argument hash
- This provides a complete, tamper-evident record of all external tool invocations.
### PII Scan on Arguments
- Before dispatching a tool call, the arguments JSON is scanned through the PII detection pipeline.
- If PII is detected, a **non-blocking warning** is surfaced to the user.
- This prevents inadvertent leakage of credentials, email addresses, or IP addresses to external MCP servers.
### Stdio Transport Path Validation
- `build_stdio_transport()` rejects any `command` that is not an absolute path.
- This prevents:
- Path traversal attacks (e.g., `../../malicious`)
- Reliance on `$PATH` resolution which could be manipulated
- Unintended execution of relative-path binaries
### Network Boundaries
- HTTP transport uses `reqwest` with TLS certificate verification for HTTPS endpoints.
- stdio transport communicates only with locally spawned processes (no network exposure).
- MCP server URLs should be added to the Content Security Policy `connect-src` if fetched from the WebView layer.
### Cascade Deletes
- Removing an MCP server cascades to delete all associated `mcp_tools` and `mcp_resources` records.
- The live connection is also removed from the in-memory connection pool.
- No orphaned tool definitions can persist after server removal.
---
## Security Checklist for New Features ## Security Checklist for New Features
- [ ] Does it send data externally? → Add audit log entry - [ ] Does it send data externally? → Add audit log entry