Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ internal/server/testdata/*.golden.json text eol=lf
# comparisons fail with a trailing \r.
internal/server/testdata/**/*.golden.json text eol=lf

# Spec 098 tools/list merge-base snapshots (FR-015 byte-identity): these are
# plain .json (not *.golden.json), so the patterns above do not reach them.
# Without this Windows checks out CRLF and all three
# TestToolsListSnapshot_MatchesMergeBaseGoldens surfaces fail on \r alone.
internal/server/testdata/toolslist_goldens/*.json text eol=lf

# Self-contained verification/QA reports embed base64 PNG screenshots, so a
# single file is multiple MB of "HTML". They are point-in-time artifacts, not
# source. Mark them linguist-generated so GitHub's language stats reflect the
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,6 @@ tail -f ~/Library/Logs/mcpproxy/main.log # main log (macOS; Linux: ~/.mcpproxy/
- **Windows installer**: [docs/github-actions-windows-wix-research.md](docs/github-actions-windows-wix-research.md). **Prerelease** (`next` branch + `v*-rc.*` tags, opt-in, off stable channels): [docs/prerelease-builds.md](docs/prerelease-builds.md).

## Recent Changes
- 098-tools-preflight: Added Go 1.24 module toolchain (repo builds with local Go 1.25) + existing only — chi (httpapi), bbolt (storage), Bleve (index), zap (logging), Cobra (CLI), swaggo/swag v2 (contract regen). **No new dependencies.**
- 097-stored-scripts: Added Go 1.25 (os.Root/Root.ReadFile available — R1) + stdlib only (os.Root). **No new dependencies.**
- 096-batched-call-tools: Added Go 1.24 module toolchain (repo builds with local Go 1.25) + existing only — goja (sandbox), mark3labs/mcp-go (tool surface), zap. **No new dependencies.**
- 095-update-failure-ux: Added Swift 5.9 (tray, AppKit + Sparkle 2.9.3 vendored via SwiftPM) · Go 1.24 module toolchain (repo builds with local Go 1.25) + existing only — Sparkle 2.9.3 (`SPUUpdater`, `SPUStandardUserDriver`), chi (httpapi), bbolt (diagnostics counters), swaggo/swag v2 (contract regen). **No new dependencies.**
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,72 @@ See [Configuration](https://docs.mcpproxy.app/configuration/config-file/) and [U

---

## How AI Agents Work Through MCPProxy

Once connected, your agent sees a handful of built-in MCPProxy tools instead of hundreds of upstream schemas. A typical session has three beats — discover, call, audit — plus an optional preflight gate for unattended automations.

### 1. Discover — spend one query, not your context window

The agent asks for what it needs in plain keywords via `retrieve_tools`:

```json
{ "query": "create github issue", "limit": 5 }
```

MCPProxy runs a BM25 search across every connected server and returns only the top-ranked matches — each with a `call_with` hint recommending the right call variant for its annotations:

```json
{
"tools": [
{ "name": "github:create_issue", "score": 0.89, "call_with": "call_tool_write" },
{ "name": "gitlab:create_issue", "score": 0.72, "call_with": "call_tool_write" }
]
}
```

This is where the token savings come from: the schemas of the hundreds of tools the agent *didn't* need never enter its context. The agent loads full schemas on demand with `describe_tool` (batch up to 5 ids) only for the tools it's about to use.

### 2. Call — with declared intent

The agent executes the tool through the variant matching its intent (`call_tool_read`, `call_tool_write`, or `call_tool_destructive`), addressing it as `server:tool`:

```json
{
"name": "github:create_issue",
"args_json": "{\"repo\": \"acme/api\", \"title\": \"Bug report\"}",
"intent": { "operation_type": "write", "reason": "Filing bug per user request" }
}
```

MCPProxy validates the intent against the tool's annotations (a "read" call can't reach a destructive tool), checks quarantine and approval state, and scans arguments and responses for sensitive data before anything leaves the machine.

### 3. Audit — every call is on the record

Every call lands in the local [Activity Log](https://docs.mcpproxy.app/features/activity-log/) with a request ID, so you can reconstruct exactly what an agent did:

```bash
mcpproxy activity list # everything, newest first
mcpproxy activity list --request-id <id> # one workflow, correlated
```

### Gate automations before they burn tokens

For recurring headless jobs (cron, CI, n8n), don't let the agent discover a missing tool the expensive way. One preflight command checks that every required tool is ready — without contacting any upstream server — and reports exactly why when it isn't (server quarantined, tool changed since approval, OAuth expired, typo'd id):

```bash
mcpproxy tools preflight gh-ops:sync_issues slack:post_message --wait 10s
case $? in
0) run-agent-session ;; # all ready — go
10) exit 75 ;; # transient (server starting) — let the next cron tick retry
11) page-operator ;; # blocked — someone must approve / enable / log in
12) fail-pipeline ;; # unknown tool id — the automation itself is misconfigured
esac
```

See [Required-Tools Preflight](https://docs.mcpproxy.app/features/tools-preflight/) for the full reason taxonomy, REST endpoint, and GitHub Actions / n8n recipes.

---

## 🔐 Optional HTTPS Setup

MCPProxy works with HTTP by default for easy setup. HTTPS is optional and primarily useful for production environments or when stricter security is required.
Expand Down Expand Up @@ -295,6 +361,7 @@ curl -k https://localhost:8080/api/v1/status
- [OAuth Authentication](https://docs.mcpproxy.app/features/oauth-authentication/)
- [Code Execution](https://docs.mcpproxy.app/features/code-execution/)
- [Activity Log](https://docs.mcpproxy.app/features/activity-log/)
- [Required-Tools Preflight](https://docs.mcpproxy.app/features/tools-preflight/)
- [Agent Tokens](https://docs.mcpproxy.app/features/agent-tokens/)
- [Sensitive Data Detection](https://docs.mcpproxy.app/features/sensitive-data-detection/)

Expand Down
1 change: 1 addition & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -799,3 +799,4 @@ Legend: `shipped` ≥95% checked · `in-flight` 1–94% · `drafted` 0% · `—`
| [095-update-failure-ux](./specs/095-update-failure-ux/) | `shipped` | 28/28 (100%) |
| [096-batched-call-tools](./specs/096-batched-call-tools/) | `in-flight` | 15/16 (94%) |
| [097-stored-scripts](./specs/097-stored-scripts/) | `in-flight` | 13/14 (93%) |
| [098-tools-preflight](./specs/098-tools-preflight/) | `drafted` | 0/33 (0%) |
88 changes: 88 additions & 0 deletions cmd/generate-types/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,90 @@ export type RejectionReason = 'queue_full' | 'queue_timeout';
/** Limiter tier that shed the call (activity metadata rejection_scope). */
export type RejectionScope = 'server' | 'global';

`)

// Required-tools preflight (Spec 098) - generated from internal/contracts/types.go,
// which mirrors internal/preflight/reasons.go (the single source of truth for
// the taxonomy). A drift test in internal/preflight keeps the two identical.
sb.WriteString(`// Preflight (Spec 098) - generated from internal/contracts/types.go
export const PreflightStatusReady = 'ready' as const;
export const PreflightStatusUnavailable = 'unavailable' as const;
export type PreflightStatus = typeof PreflightStatusReady | typeof PreflightStatusUnavailable;

/**
* Closed 15-code failure enum. Additive-only: treat an unknown code as
* non-retryable. 'server_saturated' is reserved and not emitted.
*/
export type PreflightReason =
| 'server_initializing'
| 'server_unhealthy'
| 'server_disabled'
| 'server_quarantined'
| 'tool_pending_approval'
| 'tool_changed'
| 'tool_blocked_by_user'
| 'oauth_required'
| 'hash_mismatch'
| 'server_not_in_scope'
| 'tool_denied_by_config'
| 'missing_annotation'
| 'policy_filtered'
| 'not_found'
| 'server_not_configured';

/** Set-level aggregate (worst class present); drives the CLI exit code 0/10/11/12. */
export const PreflightVerdictReady = 'ready' as const;
export const PreflightVerdictDegradedRetryable = 'degraded_retryable' as const;
export const PreflightVerdictBlocked = 'blocked' as const;
export const PreflightVerdictUnknownIds = 'unknown_ids' as const;
export type PreflightVerdict =
| typeof PreflightVerdictReady
| typeof PreflightVerdictDegradedRetryable
| typeof PreflightVerdictBlocked
| typeof PreflightVerdictUnknownIds;

export interface PreflightToolRef {
id: string;
/** "sha256/v{N}:{hex}" - the schema version distinguishes a proxy hash bump from upstream drift. */
pin_hash?: string;
}

export interface PreflightPolicy {
read_only_only?: boolean;
exclude_destructive?: boolean;
exclude_open_world?: boolean;
}

export interface PreflightRequest {
tools: PreflightToolRef[];
profile?: string;
policy?: PreflightPolicy;
wait_ms?: number;
}

export interface PreflightToolResult {
id: string;
status: PreflightStatus;
/** Present only when status is 'unavailable'. */
reason?: PreflightReason;
retryable?: boolean;
/** Health-action vocabulary; omitted (not 'none') when the reason has no action. */
action?: HealthAction;
detail?: string;
remediation?: string;
/** Operator tier + ready results only; never disclosed to an agent token. */
hash?: string;
/** Up to 3 nearest caller-visible ids, on not_found only. */
did_you_mean?: string[];
}

export interface PreflightResponse {
verdict: PreflightVerdict;
checked_at: string; // RFC3339
waited_ms?: number;
tools: PreflightToolResult[];
}

`)

// Server types
Expand Down Expand Up @@ -241,6 +325,10 @@ export interface IsolationDefaults {
held_reason?: string;
held_verdict?: string;
held_signals?: string[];
// The tool's current hash in the preflight pin format "sha256/v{N}:{hex}"
// (spec 098 FR-011) — the value to paste into a preflight pin. Operator tier
// only: absent for agent-token callers and for tools with no stored hash.
hash?: string;
}

export interface SearchResult {
Expand Down
Loading
Loading