Skip to main content

Dativo Talon — MCP Proxy Architecture

This document describes how the MCP proxy fits Talon's architecture, and the vendor integration patterns it enables


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: companies can adopt AI vendors (Zendesk, Intercom, HubSpot) while keeping the same control plane they use for their own AI use cases — shared policy, cost attribution, and signed evidence for vendor traffic too. Vendor "compliance claims" become independently verifiable supporting evidence for GDPR/NIS2/EU AI Act reviews.


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 effective pii_action of the requesting agent. 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 an agent key; Talon enforces the agent's effective 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.