MCP Tools Development
Build, test, and deploy MCP tools on STOA Platform.
Architectureβ
STOA exposes tools to AI agents through two mechanisms:
| Source | Use Case | How It Works |
|---|---|---|
| Tool CRD | Wrap any REST API as an MCP tool | Define endpoint + schema in YAML, gateway calls it |
| ToolSet CRD | Connect an existing MCP server | Gateway discovers tools via upstream SSE/HTTP |
| API Bridge | Expose Control Plane APIs as tools | Automatic conversion of registered APIs to MCP tools |
Tool CRD Referenceβ
Full Schemaβ
apiVersion: gostoa.dev/v1alpha1
kind: Tool
metadata:
name: <tool-name> # Lowercase, hyphens allowed
namespace: stoa-system # Must be in gateway namespace
spec:
displayName: <string> # Required β human-readable name
description: <string> # Required β description for LLM context
endpoint: <url> # Required β backend HTTP endpoint
method: <GET|POST|PUT|DELETE|PATCH> # Required
inputSchema: # Required β JSON Schema for arguments
type: object
properties: { ... }
required: [ ... ]
outputSchema: # Optional β JSON Schema for response
type: object
properties: { ... }
auth: <bearer|service_account|none> # Default: none
rateLimit: "<N>/<unit>" # Optional β e.g., "100/min", "1000/hour"
annotations: # Optional β MCP 2025-03-26 hints
readOnly: <boolean> # Tool only reads data
destructive: <boolean> # Tool deletes/modifies irreversibly
idempotent: <boolean> # Same call twice = same result
openWorld: <boolean> # Interacts with external systems
Status (read-only, set by gateway)β
status:
registered: true # Gateway has loaded this tool
lastSeen: "2026-02-13T10:30:00Z"
error: "" # Empty = no error
Annotations Best Practicesβ
Annotations help AI agents decide when and how to use tools safely:
| Action Type | readOnly | destructive | idempotent | openWorld |
|---|---|---|---|---|
| Read/Search | true | false | true | depends |
| Create | false | false | false | depends |
| Update | false | false | true | depends |
| Delete | false | true | false | depends |
| External call | depends | depends | depends | true |
tip
If unsure, set openWorld: true β it signals the tool has side effects beyond the local system. AI agents treat openWorld: false tools as safe to call speculatively.
Input Schema Patternsβ
Simple query parameterβ
inputSchema:
type: object
properties:
query:
type: string
description: "Search term"
required: [query]
Complex object with validationβ
inputSchema:
type: object
properties:
customer_id:
type: string
pattern: "^[A-Z]{2}-[0-9]{6}$"
description: "Customer ID (format: XX-000000)"
amount:
type: number
minimum: 0
maximum: 1000000
description: "Transaction amount in EUR"
currency:
type: string
enum: [EUR, USD, GBP]
default: EUR
required: [customer_id, amount]
Nested objectsβ
inputSchema:
type: object
properties:
filters:
type: object
properties:
status:
type: string
enum: [active, suspended, archived]
created_after:
type: string
format: date
description: "Filter criteria"
pagination:
type: object
properties:
page:
type: integer
minimum: 1
default: 1
per_page:
type: integer
minimum: 1
maximum: 100
default: 20
ToolSet CRD Referenceβ
Full Schemaβ
apiVersion: gostoa.dev/v1alpha1
kind: ToolSet
metadata:
name: <toolset-name>
namespace: stoa-system
spec:
upstream:
url: <string> # Required β MCP server URL
transport: <sse|streamable-http> # Default: sse
auth: # Optional
type: <bearer|header>
secretRef: <string> # K8s Secret name
headerName: <string> # For header type only
timeoutSeconds: <integer> # Connection timeout
tools: [] # Tool names to expose (empty = all)
prefix: <string> # Optional β prefix for tool names
Status (read-only)β
status:
connected: true
toolCount: 12
discoveredTools:
- search_customers
- create_order
- get_invoice
lastSync: "2026-02-13T10:30:00Z"
error: ""
Selective Tool Exposureβ
Expose only specific tools from an upstream server:
spec:
tools:
- search_customers
- get_customer_detail
# Only these 2 tools will be available, even if the server has 20
Prefix for Collision Avoidanceβ
When connecting multiple MCP servers that might have tools with the same name:
spec:
prefix: "crm"
# "search_customers" becomes "crm_search_customers"
Authentication Patternsβ
No Auth (Public APIs)β
spec:
auth: none
endpoint: https://api.open-meteo.com/v1/forecast
Bearer Token (K8s Secret)β
spec:
auth: bearer
---
# Create the secret first
apiVersion: v1
kind: Secret
metadata:
name: my-api-token
namespace: stoa-system
type: Opaque
stringData:
token: "sk-my-secret-token"
Service Account (OAuth2 M2M)β
spec:
auth: service_account
# Uses Keycloak service account credentials configured in gateway
Testing Toolsβ
Verify CRD Appliedβ
kubectl get tools.gostoa.dev -n stoa-system -o wide
Test via RESTβ
# List tools
curl -s -X POST "${STOA_GATEWAY_URL}/mcp/v1/tools" \
-H "Authorization: Bearer ${TOKEN}" | jq '.tools[] | {name, description}'
# Invoke
curl -s -X POST "${STOA_GATEWAY_URL}/mcp/v1/tools/invoke" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"name": "get-weather", "arguments": {"q": "Berlin"}}' | jq
Test via MCP Protocolβ
# Initialize session
curl -s -X POST "${STOA_GATEWAY_URL}/mcp/sse" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {"tools": {}},
"clientInfo": {"name": "test", "version": "1.0"}
},
"id": 1
}' | jq
# List tools
curl -s -X POST "${STOA_GATEWAY_URL}/mcp/sse" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "method": "tools/list", "id": 2}' | jq
# Call tool
curl -s -X POST "${STOA_GATEWAY_URL}/mcp/sse" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {"name": "get-weather", "arguments": {"q": "Berlin"}},
"id": 3
}' | jq
Check Gateway Adminβ
curl -s "${STOA_GATEWAY_URL}/admin/health" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" | jq
Deployment Patternsβ
Pattern 1: Wrap Existing REST APIβ
The most common pattern β expose a company's internal API to AI agents:
apiVersion: gostoa.dev/v1alpha1
kind: Tool
metadata:
name: search-orders
namespace: stoa-system
spec:
displayName: Search Orders
description: "Search customer orders by status, date range, or customer ID. Returns order summary with amounts."
endpoint: https://erp.internal.example.com/api/v2/orders/search
method: POST
inputSchema:
type: object
properties:
customer_id:
type: string
description: "Customer identifier"
status:
type: string
enum: [pending, shipped, delivered, cancelled]
date_from:
type: string
format: date
required: [customer_id]
auth: bearer
rateLimit: "100/min"
annotations:
readOnly: true
idempotent: true
openWorld: false
Pattern 2: Federation via ToolSetβ
Connect an entire MCP server and expose its tools through STOA's auth and rate limiting:
apiVersion: gostoa.dev/v1alpha1
kind: ToolSet
metadata:
name: data-science-tools
namespace: stoa-system
spec:
upstream:
url: https://ds-tools.internal.example.com/mcp
transport: sse
auth:
type: bearer
secretRef: ds-tools-secret
timeoutSeconds: 60
prefix: "ds"
tools:
- run_prediction
- feature_importance
- model_metrics
Pattern 3: Multi-Gateway Tool Distributionβ
Deploy the same tool across multiple gateway instances for high availability:
# Tool CRD is cluster-scoped β all gateways in the namespace see it
kubectl apply -f my-tool.yaml -n stoa-system
# Verify on each gateway
curl -s "${STOA_GATEWAY_URL}/mcp/v1/tools" \
-H "Authorization: Bearer ${TOKEN}" | jq '.tools | length'
What's Nextβ
| Topic | Link |
|---|---|
| MCP Getting Started | Quick tutorial |
| MCP API Reference | Full endpoint reference |
| Tool CRD spec | CRD Reference |
| ToolSet CRD spec | CRD Reference |
| Gateway modes | Architecture concepts |