docs: sync from docs/wiki/ at commit ea7f484c
parent
05a6ba4ddf
commit
1ab9d0a7cb
58
Database.md
58
Database.md
@ -2,7 +2,7 @@
|
||||
|
||||
## 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`
|
||||
|
||||
@ -38,7 +38,7 @@ pub fn init_db(data_dir: &Path) -> anyhow::Result<Connection> {
|
||||
|
||||
---
|
||||
|
||||
## Schema (17 Migrations)
|
||||
## Schema (18 Migrations)
|
||||
|
||||
### 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
|
||||
- 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
|
||||
|
||||
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
|
||||
|
||||
### `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
|
||||
|
||||
- [ ] Does it send data externally? → Add audit log entry
|
||||
|
||||
Loading…
Reference in New Issue
Block a user