MCP & A2A Expose
Use endpoint adapters when you want external systems to call your agents through protocol-specific shapes.
Keep this boundary rule:
- inside PURISTA: native agent invoke + native AI protocol envelopes
- outside PURISTA: adapter command that maps request/response to the external protocol shape
Why adapter commands
Adapter commands give you:
- explicit auth/rate-limit boundaries
- stable external contracts without polluting agent handlers
- reuse of
context.invokeAgent...and existing allowlists/guards/transforms
Decision matrix: native vs MCP vs A2A
| Target consumer | Recommended contract | Why |
|---|---|---|
| PURISTA command/subscription/stream inside same app | native context.invokeAgent + AI envelopes | strongest typing, no extra mapping |
| Custom frontend/backend consuming your SSE stream | native AI envelopes (frame.kind) | keeps full tool/telemetry/error fidelity |
| External MCP ecosystem | MCP adapter command (toMcpReferenceToolResult) | protocol-compatible boundary without leaking internals |
| External A2A ecosystem | A2A adapter command (toAgent2AgentReferenceMessage) | message-oriented interoperability boundary |
MCP-style expose endpoint
Example route in examples/ai-basic:
GET /api/v1/support/mcp/toolsreturns tool descriptorsPOST /api/v1/support/mcp/callinvokes an allowlisted MCP tool (agent or command) and returns MCP-style tool result
Example MCP HTTP call
Request:
json
{
"name": "supportAgent",
"arguments": {
"prompt": "How can I request a refund for my order?",
"sessionId": "chat-123"
}
}Response shape:
json
{
"content": [
{ "type": "text", "text": "Final answer..." }
],
"metadata": {
"telemetry": {
"usage": { "promptTokens": 120, "completionTokens": 90, "totalTokens": 210 },
"durationMs": 4100
}
}
}Implementation pattern
ts
const tools = exposeToolsAsMCP({
agents: [supportAgent, triageAgent],
commands: [
{
serviceName: 'support',
serviceVersion: '1',
commandName: 'lookupFaq',
description: 'Looks up support FAQ entries by question',
payloadSchema: lookupFaqInputSchema,
},
],
})ts
export const runSupportMcpCommandBuilder = supportV1ServiceBuilder
.getCommandBuilder('runSupportMcp', 'Invokes MCP tools and returns MCP-style result payload')
.canInvoke('support', '1', 'lookupFaq', lookupFaqOutputSchema, lookupFaqInputSchema)
.canInvokeAgent('supportAgent', '1', {
payloadSchema: runSupportMcpInvokePayloadSchema,
parameterSchema: runSupportMcpInvokeParameterSchema,
})
.exposeAsHttpEndpoint('POST', 'support/mcp/call')
.setCommandFunction(async function (context, payload) {
if (payload.name === 'support.1.lookupFaq') {
const faq = await context.service.support['1'].lookupFaq(payload.arguments as { question: string })
return { content: [{ type: 'json', json: faq }] }
}
const prompt = String(payload.arguments?.prompt ?? '')
const envelopes = await context.invokeAgent.supportAgent['1']
.call({ message: prompt, prompt, history: [], attachments: [] }, { channel: 'command' })
.final()
return toMcpReferenceToolResult(agentProtocolEnvelopeSchema.array().parse(envelopes))
})Agent2Agent-style expose endpoint
Example route in examples/ai-basic:
POST /api/v1/support/a2a/callreturns reference Agent2Agent messages
Example A2A HTTP call
Request:
json
{
"prompt": "How can I request a refund for my order?",
"sessionId": "chat-123"
}Response shape:
json
{
"messages": [
{
"id": "msg-1",
"threadId": "conv-1",
"frameType": "message",
"sender": { "service": "support", "agent": "runSupportAgentStream" },
"payload": { "kind": "message", "content": "Checking FAQ..." }
}
]
}Implementation pattern
ts
export const runSupportA2aCommandBuilder = supportV1ServiceBuilder
.getCommandBuilder('runSupportA2a', 'Invokes support agent and returns Agent2Agent-style messages')
.canInvokeAgent('supportAgent', '1', {
payloadSchema: runSupportA2aInvokePayloadSchema,
parameterSchema: runSupportA2aInvokeParameterSchema,
})
.exposeAsHttpEndpoint('POST', 'support/a2a/call')
.setCommandFunction(async function (context, payload) {
const envelopes = await context.invokeAgent.supportAgent['1']
.call(
{
message: payload.prompt,
prompt: payload.prompt,
sessionId: payload.sessionId,
responseFormat: payload.responseFormat,
history: [],
attachments: [],
},
{ channel: 'command' },
)
.final()
return {
messages: agentProtocolEnvelopeSchema.array().parse(envelopes).map(toAgent2AgentReferenceMessage),
}
})Exposure checklist
Before exposing MCP/A2A endpoints:
- keep agent payload schema strict (
addPayloadSchema(...)) - restrict callable agents with
canInvokeAgent(...) - keep protocol mapping in command layer only
- return normalized errors (handled/unhandled) instead of raw exceptions
Important note on scope
toMcpReferenceToolResult and toAgent2AgentReferenceMessage are reference mappings. They are intended as deterministic adapter helpers, not full official protocol stacks.
For envelope semantics and nesting, see AI Protocol.
