MCP Servers, RAG & Sub-Workflows
Modern agent architectures rarely operate in isolation: they query internal knowledge bases via retrieval-augmented generation (@agboss/core/rag), consume or expose tools over the Model Context Protocol (@agboss/core/mcp), and delegate subtasks to child workflows or A2A peers. AGBoss models all of these integrations as typed, testable TypeScript components.
How to consume and expose MCP tools (@agboss/core/mcp)
Section titled “How to consume and expose MCP tools (@agboss/core/mcp)”@agboss/core/mcp works in both directions: agents can call external MCP servers via @McpServer and @McpTool, and you can expose your own @Tool and @WorkflowStart components as an MCP server over stdio or HTTP using @McpExpose and createMcpService:
import { createMcpService, McpExpose } from "@agboss/core/mcp";
@McpExpose({ name: "job-scout-mcp", version: "1.0.0", exports: [SearchIssues, ChatWorkflowStart],})export class JobScoutMcp {}
export async function serveStdio() { const app = await createApp(JobScoutApplication); const service = createMcpService(app, JobScoutMcp); await service.connectStdio();}import { type ToolHandler } from "@agboss/core";import { McpServer, McpServerClient, McpTool, type ServerTools } from "@agboss/core/mcp";
const fsTools = { read_file: { input: ReadFileInputDto, output: ReadFileOutputDto },} as const satisfies ServerTools;
@McpServer({ name: "filesystem-mcp-server", transport: "stdio", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "./workspace"], tools: fsTools,})export class FilesystemMcpServer extends McpServerClient<typeof fsTools> {}
@McpTool({ name: "read_file", description: "Reads a file from the workspace via MCP", server: FilesystemMcpServer, input: ReadFileInputDto, output: ReadFileOutputDto, deps: [FilesystemMcpServer],})export class ReadFileMcpTool implements ToolHandler<ReadFileInputDto, ReadFileOutputDto> { constructor(private readonly fs: FilesystemMcpServer) {}
run(input: ReadFileInputDto): Promise<ReadFileOutputDto> { return this.fs.call("read_file", input); }}How @Rag knowledge bases work (@agboss/core/rag)
Section titled “How @Rag knowledge bases work (@agboss/core/rag)”In AGBoss, a knowledge base is any @Rag class implementing RagConnector (retrieve(query, { topK, signal }) and optional index()), or extending the built-in SqliteFtsConnector. The topK setting is required on every @Rag decorator, and reported retrieval spend (costUsd) is tracked automatically under rag:<name>:
import { Agent, RagMode, env } from "@agboss/core";import { Rag, SqliteFtsConnector } from "@agboss/core/rag";
export class AppEnvironment { handbookDbPath = env.optional("HANDBOOK_DB_PATH");}
@Rag({ name: "company_docs", description: "Product, onboarding, and engineering runbook documentation", topK: 4, deps: [AppEnvironment],})export class CompanyDocs extends SqliteFtsConnector { constructor(environment: AppEnvironment) { super({ folder: "./docs/handbook", dbFile: environment.handbookDbPath ?? ":memory:", }); }}
@Agent({ name: "onboarding-agent", description: "Answers questions from the handbook with citations", prompt: "Answer from company_docs and cite [source].", model: "moonshotai/kimi-k2.6", rag: [{ use: CompanyDocs, mode: RagMode.Tool }],})export class OnboardingAgent {}import { expect, it } from "vitest";import { testRag } from "@agboss/core/testing";import { AppEnvironment, CompanyDocs } from "../src/rag/company-docs.rag.js";
it("retrieves BM25 chunks with [source] IDs via testRag", async () => { const runRag = testRag(CompanyDocs); const { output, spend } = await runRag("refund timeframe"); expect(output).toBeDefined(); expect(spend.totalUsd).toBe(0);});How child workflows (.toWorkflow()), ThisWorkflow recursion, and A2A (@agboss/core/a2a) work
Section titled “How child workflows (.toWorkflow()), ThisWorkflow recursion, and A2A (@agboss/core/a2a) work”When a workflow grows beyond a single responsibility or crosses service boundaries, compose child @Workflow classes in-process / across instances or delegate over HTTP using Agent-to-Agent (@agboss/core/a2a):
- Workflow as a step (
.toWorkflow(ChildWorkflow, { placement? })): Runs an ordinary@Workflowlisted in the application as an isolated child execution with its ownmaxStepsand path scope, while sharing the parent’s checkpoint and budgets. Pass{ placement: ChildPlacement.AnyInstance }to queue the child inChildExecutionQueueRepository(SqliteChildExecutionQueueRepository) so any worker instance with free slots can steal it while the parent yields its tree lease. Branch on the child’s finishes using.returns(oneOf(ScoreDto, RejectedDto)).switch(caseOf(...)...). - Parallel child workflows (
.toParallelWorkflows(ChildWorkflow, { maxConcurrency, maxItems, placement? })): Fans out alistOf(ItemDto)across concurrent child workflow executions (in-process viaChildPlacement.ParentInstanceor distributed across worker pods viaChildPlacement.AnyInstancewith per-child 30s leases and per-child crash recovery) and joins their outputs with.join().returns(listOf(oneOf(...))). - Self-recursion (
ThisWorkflow): A workflow with@Workflow({ maxDepth: 3, maxConcurrentExecutions: 6 })can launch itself via.toWorkflow(ThisWorkflow)or.toParallelWorkflows(ThisWorkflow, { maxItems })to decompose recursive tasks safely. - Multi-tenant Agent-to-Agent (
@A2AExpose,a2aHttpListener,A2ACallerResolver, and@A2AAgent):- Exposing (
@A2AExpose({ name, version, exports: [Start] })in@Application({ exposes })): Declares the@WorkflowStartentry points served over A2A and exported inagboss describe --jsonforagboss check --federation. - Serving (
a2aHttpListener(app, { expose, callerResolver, meshVerifier?, base })): ServesPOST <base>/execute({ input, start?, threadId? }),POST <base>/resume({ pauseId, decision }), andPOST <base>/cancel({ threadId }).a2aHttpListenernever guesses a tenant from the request body — it resolves an@Injectable()A2ACallerResolver<typeof StartClass>from the DI container (providers: [ShopA2aCallerResolver],[a2a.caller-resolver-not-listed]if omitted).resolveCaller(request: A2ACallerRequest)inspectsrequest.http,request.route(A2ARoute.Execute/Resume/Cancel), andrequest.envelope(MeshEnvelope), returningA2ACallerResolution.resolved({ tenant, ownerId, execution, peer }),.unauthenticated({ reason, challenge? })(401), or.forbidden({ reason })(403). - Calling (
@A2AAgentextends A2AClient<InDto, OutDto>): Declare@A2AAgent({ name: "order-desk-a2a-agent", url, target: { application, start }, input: InDto, output: OutDto, price?, deps: [ExecutionContext] })inprovidersand inject it into a@Toolor@Actionto callexecute(input, { threadId, signal, onEvent })orsend(input, call). On401/403,A2AClientthrowsA2ACallerRejectedError(code: "a2a.caller-rejected", with the server’s rejection code inwireCode:"a2a.caller-unauthenticated"or"a2a.caller-forbidden",#457). - In-process testing (
a2aServer({ expose, callerResolver })): IntestWith(App), thea2aServerfixture spins up a loopback listener ({ url }) and assertsA2ACallerRejectedErroron401/403without external network sockets.
- Exposing (
Cross-Service Agent Mesh (MeshEnvelope, JWS Signing, --federation & agboss hub)
Section titled “Cross-Service Agent Mesh (MeshEnvelope, JWS Signing, --federation & agboss hub)”When multiple AGBoss applications call each other over A2A (A2AClient $\to$ a2aHttpListener) or MCP (@McpServer $\to$ createMcpService), AGBoss automatically propagates an Agent Mesh Envelope (agboss-mesh JWS EdDSA Ed25519 header over A2A, _meta["agboss.dev/mesh"] over MCP, plus W3C traceparent):
- Propagated context (
MeshEnvelope): Carriesorigin(executionId/rootExecutionId,parentExecutionId,workflow,step.idempotencyKey),budget.remaining(installing acaller-budgetcap on the downstream execution so a child hop can never exceed its parent’s remaining USD budget),deadline.remaining(DeadlineExceededErrorif exhausted), andcallChain(readonly MeshHop[]). - Cryptographic peer verification (
@Application({ mesh })&MeshVerifier): Applications withmesh: { signingKey: "meshSigningKey", peers: [{ application: "orders-application", verifyKey: "ordersPublicKey" }] }sign every outgoingMeshEnvelopeand every returnedMeshRunReport(spend,executionId,bodyHash). When bothcallerResolver(peer) and the Ed25519 signature (signedBy) match, the receiver deduplicates retries byorigin.step.idempotencyKey(MeshRequestRepository/SqliteMeshRequestRepository) and the caller records verified remote spend undera2a:<issuer>/mcp:<issuer>andToolCallRecord.remote. - Distributed cycle & hop protection: If
callChainalready contains the receiving(application, start)pair (Service A -> Service B -> Service A), the receiver rejects the call before the first step with409 limit.mesh-loop, whichA2AClientrethrows as typedMeshLoopDetectedError(catchable via.onError(MeshLoopDetectedError, ...)). IfcallChain.lengthreaches@Application({ maxMeshHops })(8by default), it rejects withHopsLimitExceededError. - Cross-repo contract verification (
--federation) & AGBoss Mesh Hub (agboss hub,#401): In CI and local operations, verify that all services in an Agent Mesh agree on input/output DTO schemas and visualize cross-service topology, stitched execution trees (ExecutionHistoryRepository+agboss records export), Progressive Crystallization Radar, and Blast Radius:
npx agboss check --federation snapshots/npx agboss records export --all-tenants --out .artifacts/records.jsonlnpx agboss hub show snapshots/ .artifacts/records.jsonl --html .artifacts/hub.htmlnpx agboss hub ui snapshots/ .artifacts/records.jsonlIf an upstream service changes an exposed DTO without updating consumers, npx agboss check --federation fails before deployment and prints the cross-service blast radius.
Standalone Executable Proofs: 06-enterprise-agent-mesh & 03-support-refund-desk
Section titled “Standalone Executable Proofs: 06-enterprise-agent-mesh & 03-support-refund-desk”06-enterprise-agent-mesh: Demonstrates a 3-service microservice mesh (apps/1-order-gateway,apps/2-billing-a2awith@A2AExpose, andapps/3-catalog-mcpwith@McpExpose), Ed25519agboss-meshJWS signatures,MeshLoopDetectedError/HopsLimitExceededErrorfallback routing, MCP_meta.callIddeduplication viaSqliteMeshRequestRepository, andagboss check --federationBlast Radius overfederation/snapshots.03-support-refund-desk: Demonstrates an in-memory BM25/TF-IDF section indexer (InMemoryKnowledgeIndexService+RefundPolicyRetriever) wired into@Rag({ mode: RagMode.Context })alongsidePolicyAwareCompaction(see 7 Verified Examples).