Skip to main content

Dativo Talon — MCP Proxy Architecture (Addition to architecture.md)

This document extends architecture.md with vendor integration patterns


MCP Proxy Pattern

Overview

Talon's MCP server can operate in three modes:

  1. Native — Expose Talon-governed tools to agents (default)
  2. Proxy — Sit between third-party vendors and your data sources
  3. Hybrid — Mix of native tools and proxied vendor access

The proxy mode enables compliance for third-party AI vendors (Zendesk, Intercom, HubSpot) without vendor rewrites.


Architecture: Proxy Mode

Third-Party AI Vendor Talon MCP Proxy Your Data Sources
(Zendesk AI Agent, │ │ (Zendesk API, CRM, DB)
Intercom, HubSpot) │ │
│ │
│ │ │
▼ │ │
┌─────────────────┐ │ ┌──────────────────┐ │ ┌──────────────────┐
│ Vendor calls │───────────│ MCP Proxy Server │────────│ Your Zendesk API │
│ MCP endpoint │ │ │ │ │ │ │
│ │ │ │ ┌──────────────┐ │ │ │ │
│ POST /tools/call│ │ │ │Policy Engine │ │ │ │ │
│ { │ │ │ │(Check ACL) │ │ │ │ │
│ "name": │ │ │ └──────────────┘ │ │ │ │
│ "zendesk_ │ │ │ ┌──────────────┐ │ │ │ │
│ ticket_search"│ │ │ │PII Redaction │ │ │ │ │
│ } │ │ │ │(Mask fields) │ │ │ │ │
└─────────────────┘ │ │ └──────────────┘ │ │ │ │
│ │ ┌──────────────┐ │ │ │ │
│ │ │Evidence Log │ │ │ │ │
│ │ │(Audit trail) │ │ │ │ │
│ │ └──────────────┘ │ │ │ │
│ └──────────────────┘ │ └──────────────────┘
│ │ │ │
│ │ │ │
│ ┌────────▼──────────┐ │ ┌────────▼─────────┐
│ │ Evidence Store │ │ │ API Response │
│ │ (SQLite/PG) │ │ │ (redacted) │
│ │ - What accessed │ │ └──────────────────┘
│ │ - What redacted │ │ │
│ │ - Policy decision │ │ │
│ └───────────────────┘ │ │
│ │ │
│◄───────────────────────────┴─────────────┘
│ Redacted response returned
│ (vendor unaware of governance)

Key insight: Vendor believes it's calling your Zendesk API directly, but Talon intercepts, logs, redacts, and enforces policies transparently.


Implementation

Directory Structure

internal/mcp/
├── server.go # JSON-RPC 2.0 server (existing)
├── proxy.go # NEW: Proxy mode implementation
├── proxy_config.go # NEW: Proxy configuration loader
├── tools.go # Native tool bridge (existing)
└── transport.go # HTTP transport (existing)

Proxy Configuration

# agents/zendesk-vendor-proxy.talon.yaml
agent:
name: "zendesk-vendor-proxy"
type: "mcp_proxy" # NEW: Activates proxy mode

proxy:
mode: "intercept" # intercept | passthrough | shadow

upstream:
vendor: "zendesk-ai-agent"
url: "https://zendesk-ai-vendor.com"
auth:
type: "bearer"
header: "Authorization"
# Vendor's auth token passed through

allowed_tools:
- name: "zendesk_ticket_search"
upstream_name: "ticket_search" # Map to vendor's naming
- name: "zendesk_ticket_read"
upstream_name: "get_ticket"

forbidden_tools:
- "zendesk_user_delete" # Block destructive ops
- "zendesk_export_all" # Block mass exports
- "zendesk_admin_*" # Wildcard block

pii_handling:
redaction_rules:
- field: "requester.email"
method: "hash"
- field: "requester.phone"
method: "mask_middle"
- field: "description"
patterns:
- "(\\+?\\d{1,3}[-.\\s]?)?\\d{9,15}" # Phone regex
method: "mask"

compliance:
frameworks: ["gdpr", "nis2"]
audit_retention: 365
human_oversight:
required_for:
- tool: "zendesk_ticket_update"
conditions:
- "priority == 'urgent'"
- "impact > 500" # Financial threshold

evidence:
capture_requests: true
capture_responses: true
capture_redactions: true

Proxy Modes

proxy:
mode: "intercept"

Behavior:

  • Every MCP tool call goes through Talon
  • Policy checks BEFORE upstream call
  • Can block forbidden tools
  • PII redacted BEFORE vendor sees it
  • Full evidence trail

Flow:

Vendor → Talon (policy check) → Upstream API → Talon (redact response) → Vendor

Use when: You need real-time enforcement.


2. Passthrough Mode

proxy:
mode: "passthrough"

Behavior:

  • Talon logs calls but doesn't block
  • PII redaction optional (warn only)
  • Policy violations logged but allowed
  • Evidence trail generated

Flow:

Vendor → Talon (log only) → Upstream API → Talon (log response) → Vendor

Use when: Testing Talon policies without impacting vendor.


3. Shadow Mode

proxy:
mode: "shadow"

Behavior:

  • Vendor calls upstream directly
  • Talon independently audits logs
  • Compares vendor claims vs. actual access
  • Alerts on discrepancies

Flow:

Vendor → Upstream API (direct)
Talon → Polls audit logs → Compares → Alerts if mismatch

Use when: Vendor doesn't support custom MCP, but you need visibility.


Code: Proxy Implementation

internal/mcp/proxy.go

package mcp

import (
"context"
"encoding/json"
"fmt"
"net/http"

"github.com/dativo-io/talon/internal/policy"
"github.com/dativo-io/talon/internal/evidence"
"github.com/dativo-io/talon/internal/classifier"
)

type ProxyServer struct {
config *ProxyConfig
policyEngine *policy.Engine
evidenceStore *evidence.Store
classifier *classifier.Classifier
httpClient *http.Client
}

type ProxyConfig struct {
Mode string `yaml:"mode"` // intercept, passthrough, shadow
Upstream UpstreamConfig `yaml:"upstream"`
AllowedTools []ToolMapping `yaml:"allowed_tools"`
ForbiddenTools []string `yaml:"forbidden_tools"`
PIIRules []RedactionRule `yaml:"pii_handling"`
}

type UpstreamConfig struct {
Vendor string `yaml:"vendor"`
URL string `yaml:"url"`
Auth Auth `yaml:"auth"`
}

type ToolMapping struct {
Name string `yaml:"name"` // Talon's tool name
UpstreamName string `yaml:"upstream_name"` // Vendor's tool name
}

func (p *ProxyServer) HandleToolCall(ctx context.Context, req *JSONRPCRequest) (*JSONRPCResponse, error) {
var params struct {
Name string `json:"name"`
Arguments map[string]interface{} `json:"arguments"`
}

if err := json.Unmarshal(req.Params, &params); err != nil {
return nil, fmt.Errorf("invalid params: %w", err)
}

// 1. Check if tool is allowed
if p.isForbidden(params.Name) {
return p.blockRequest(req.ID, "Tool not in allowed_tools")
}

// 2. Classify PII in arguments
piiFields, err := p.classifier.DetectPII(params.Arguments)
if err != nil {
return nil, err
}

// 3. Evaluate policy
decision, err := p.policyEngine.Evaluate(ctx, &policy.Input{
ToolName: params.Name,
Arguments: params.Arguments,
PIIFields: piiFields,
})

if err != nil || !decision.Allowed {
return p.blockRequest(req.ID, decision.Reason)
}

// 4. Redact PII
redactedArgs := p.redactPII(params.Arguments, p.config.PIIRules)

// 5. Map to upstream tool name
upstreamName := p.mapToolName(params.Name)

// 6. Call upstream API
upstreamReq := &JSONRPCRequest{
JSONRPC: "2.0",
Method: "tools/call",
Params: json.RawMessage(mustMarshal(map[string]interface{}{
"name": upstreamName,
"arguments": redactedArgs,
})),
ID: req.ID,
}

upstreamResp, err := p.callUpstream(ctx, upstreamReq)
if err != nil {
return nil, err
}

// 7. Redact PII in response
redactedResp := p.redactPII(upstreamResp.Result, p.config.PIIRules)

// 8. Generate evidence
_ = p.evidenceStore.Record(ctx, &evidence.Record{
Type: "proxy_tool_call",
ToolName: params.Name,
UpstreamToolName: upstreamName,
PIIRedacted: piiFields,
PolicyDecision: decision,
Timestamp: time.Now(),
})

// 9. Return redacted response to vendor
return &JSONRPCResponse{
JSONRPC: "2.0",
Result: redactedResp,
ID: req.ID,
}, nil
}

func (p *ProxyServer) isForbidden(toolName string) bool {
for _, forbidden := range p.config.ForbiddenTools {
if matched, _ := filepath.Match(forbidden, toolName); matched {
return true
}
}

// Also check if it's in allowed_tools
for _, allowed := range p.config.AllowedTools {
if allowed.Name == toolName {
return false
}
}

// Not in allowed list = forbidden by default
return true
}

func (p *ProxyServer) redactPII(data interface{}, rules []RedactionRule) interface{} {
// Implementation:
// - Walk JSON structure
// - Match field paths against rules
// - Apply redaction method (hash, mask, redact_full)
// - Return redacted copy

// See internal/classifier/redactor.go for full implementation
return data
}

func (p *ProxyServer) callUpstream(ctx context.Context, req *JSONRPCRequest) (*JSONRPCResponse, error) {
body, err := json.Marshal(req)
if err != nil {
return nil, err
}

httpReq, err := http.NewRequestWithContext(ctx, "POST", p.config.Upstream.URL, bytes.NewReader(body))
if err != nil {
return nil, err
}

// Add vendor auth
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set(p.config.Upstream.Auth.Header, p.config.Upstream.Auth.Value)

resp, err := p.httpClient.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()

var mcpResp JSONRPCResponse
if err := json.NewDecoder(resp.Body).Decode(&mcpResp); err != nil {
return nil, err
}

return &mcpResp, nil
}

Testing Proxy Mode

1. Local Test Setup

# Terminal 1: Start Talon in proxy mode
talon serve \
--port 8080 \
--mcp-proxy \
--config agents/zendesk-vendor-proxy.talon.yaml

# Output:
# → MCP proxy listening on http://localhost:8080
# → Proxying to: https://zendesk-ai-vendor.com
# → Audit trail: /opt/talon/evidence.db

2. Test Tool Call

# Terminal 2: Simulate vendor calling Talon
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "zendesk_ticket_search",
"arguments": {
"query": "eSIM activation issue",
"requester_email": "customer@example.com"
}
},
"id": 1
}'

# Expected output:
# {
# "jsonrpc": "2.0",
# "result": {
# "tickets": [
# {
# "id": 45231,
# "subject": "eSIM activation issue",
# "requester": {
# "email": "sha256:a1b2c3...", # Redacted
# "phone": "+34 6XX XXX 789" # Masked
# }
# }
# ]
# },
# "id": 1
# }

3. Verify Evidence

# Check audit trail
talon audit list --agent zendesk-vendor-proxy --last 5m

# Output:
# [16:42:13] proxy_tool_call
# Tool: zendesk_ticket_search
# Upstream: ticket_search (zendesk-ai-vendor.com)
# PII redacted: 1 email, 1 phone
# Policy: ALLOWED (zendesk_ticket_search in allowed_tools)
# Evidence: evt_abc123

Deployment Patterns

┌─────────────────────────────────────────┐
│ Your Infrastructure (VPC) │
│ │
│ ┌────────────┐ ┌──────────────┐ │
│ │ Talon MCP │◄──────│ Zendesk API │ │
│ │ Proxy │ │ │ │
│ └────────────┘ └──────────────┘ │
│ ▲ │
└────────│────────────────────────────────┘
│ MCP endpoint exposed

┌────▼──────────────┐
│ Third-Party Vendor│
│ (Zendesk AI Agent)│
└───────────────────┘

Setup:

  • Deploy Talon on same VPC as your data sources
  • Expose MCP endpoint via HTTPS (with TLS cert)
  • Point vendor to https://talon.your-company.com

Pattern 2: Gateway Proxy (High-Scale)

┌───────────────────────────────────────────────┐
│ Edge (Cloudflare, AWS ALB) │
│ ├─ /mcp → Talon MCP Proxy (2+ replicas) │
│ └─ Rate limiting, DDoS protection │
└───────────────────────────────────────────────┘

┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Talon 1 │ │ Talon 2 │ │ Talon 3 │
│ (MCP │ │ (MCP │ │ (MCP │
│ proxy) │ │ proxy) │ │ proxy) │
└─────────┘ └─────────┘ └─────────┘

Shared PostgreSQL
(evidence + state)

Use when: High request volume (>100 req/sec), need HA.


Security Considerations

1. Vendor Authentication

Problem: How does vendor authenticate to Talon?

Solution: Bearer token with vendor-specific scope

proxy:
auth:
required: true
tokens:
- vendor: "zendesk-ai-agent"
token: "${ZENDESK_AI_TOKEN}" # From secrets vault
allowed_tools:
- "zendesk_*" # Wildcard
- vendor: "intercom"
token: "${INTERCOM_TOKEN}"
allowed_tools:
- "intercom_*"

2. Mutual TLS (mTLS)

For enterprise deployments:

proxy:
tls:
enabled: true
cert: "/etc/talon/tls/server.crt"
key: "/etc/talon/tls/server.key"
client_ca: "/etc/talon/tls/vendor-ca.crt" # Verify vendor cert
verify_client: true

3. Rate Limiting

Prevent vendor abuse:

proxy:
rate_limits:
- vendor: "zendesk-ai-agent"
requests_per_minute: 100
burst: 20
- vendor: "intercom"
requests_per_minute: 50

Monitoring & Alerts

Key Metrics (OTel)

// Expose these metrics via Prometheus
proxy_requests_total{vendor="zendesk", tool="ticket_search", status="allowed"}
proxy_requests_total{vendor="zendesk", tool="user_delete", status="blocked"}
proxy_pii_redactions_total{vendor="zendesk", field="email"}
proxy_upstream_latency_seconds{vendor="zendesk", tool="ticket_search"}
proxy_policy_evaluation_duration_seconds{vendor="zendesk"}

Alerting Rules

# Alert on forbidden tool attempts
alerts:
- name: "ForbiddenToolAttempt"
condition: "proxy_requests_total{status='blocked'} > 0"
notify: "security@company.com"

- name: "HighPIIRedactionRate"
condition: "proxy_pii_redactions_total / proxy_requests_total > 0.5"
notify: "compliance@company.com"

- name: "VendorDataExfiltration"
condition: "proxy_requests_total{tool=~'.*export.*'} > 0"
notify: "security@company.com"

Cost Analysis

Latency Overhead

Measured on 4 CPU, 8GB RAM VM:

  • Policy evaluation: 5-10ms
  • PII detection: 10-20ms (regex-based)
  • Redaction: 5-10ms
  • Evidence logging: 5ms (async)
  • Total: ~25-45ms

Compared to:

  • Upstream API latency: 200-500ms
  • Talon overhead: ~5-10% of total request time

Acceptable for most use cases.

Cost Savings

Without Talon (custom build):

  • Engineering: 4 weeks × €5,000/week = €20,000
  • Maintenance: €2,000/month = €24,000/year
  • Total Year 1: €44,000

With Talon:

  • Setup: 2 days × €1,000/day = €2,000
  • Hosting: €200/month = €2,400/year
  • Total Year 1: €4,400

ROI: €39,600 saved (90% cost reduction)


Roadmap

Phase 1 (MVP - Q1 2025)

  • ✅ MCP proxy intercept mode
  • ✅ PII redaction
  • ✅ Policy enforcement
  • ✅ Evidence logging

Phase 2 (Q2 2025)

  • Shadow mode implementation
  • Tool usage analytics
  • Multi-vendor config templates
  • Plan review UI for proxy calls

Phase 3 (Q3 2025)

  • mTLS support
  • Advanced rate limiting
  • Vendor-specific compliance overlays
  • A2A protocol proxy

Summary

Talon's MCP proxy pattern enables:

  1. Vendor transparency - See what third-party AI accesses
  2. Policy enforcement - Block forbidden operations
  3. PII protection - Redact before vendor sees data
  4. Compliance proof - Generate audit trails automatically
  5. No vendor lock-in - Switch vendors without rewriting governance

Key insight: European companies can NOW adopt AI vendors (Zendesk, Intercom, HubSpot) while maintaining GDPR/NIS2/EU AI Act compliance. Talon makes vendor "compliance claims" verifiable.

This is a massive competitive advantage - no other platform offers this.


Implementation Notes

Response-path PII scanning

Both the MCP proxy and the LLM API gateway now scan responses from upstream for PII before returning them to the caller. This is bidirectional: request arguments are scanned on the way in, and upstream responses are scanned on the way out. The gateway applies redact, block, or warn modes per the caller's pii_action config. Evidence is recorded for every redaction.

tools/list filtering

When a vendor calls tools/list via the MCP proxy, Talon filters the response so the caller only sees tools listed in allowed_tools. Forbidden tools and unlisted tools are stripped from the response before it reaches the vendor. This reduces the attack surface — agents cannot discover or attempt to call tools they are not authorized to use.


Talon also provides an LLM API Gateway at POST /v1/proxy/{provider}/v1/chat/completions. Unlike the MCP proxy (which intercepts tool-level MCP calls from vendors), the LLM gateway intercepts request-level LLM API calls from any application: desktop apps, Slack bots, scripts. Clients send OpenAI/Anthropic/Ollama requests to Talon with a caller API key; Talon enforces per-caller model and cost policy and records evidence. Enable with talon serve --gateway --gateway-config <path>. See OpenClaw integration, Slack bot integration, and Desktop app governance.