docs: sync from docs/wiki/ at commit ea7f484c
parent
05a6ba4ddf
commit
1ab9d0a7cb
58
Database.md
58
Database.md
@ -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
|
||||||
|
|||||||
103
IPC-Commands.md
103
IPC-Commands.md
@ -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
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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user