Documentation source
External Agents
Connecting to external AI agents via OpenClaw, A2A protocol, and MCP -- connection lifecycle, delegation, and administration.
# External Agents
The Sprinter Platform supports connections to external AI agents and services through three protocols: OpenClaw (OpenAI-compatible API), A2A (Agent-to-Agent protocol), and MCP (Model Context Protocol). Additional source-type connections (RSS, API, Authenticated Web) enable data ingestion from external services.
## Connection types
| Type | Protocol | Test endpoint | Use case |
|------|----------|---------------|----------|
| `openclaw` | OpenAI-compatible API | `/v1/models` | External LLM agents via OpenClaw gateway |
| `a2a` | Agent-to-Agent | `/.well-known/agent-card.json` | Google A2A protocol agents |
| `mcp` | Model Context Protocol | `/tools/list` (POST, JSON-RPC) | MCP tool servers |
| `rss` | RSS/Atom feeds | Feed URL | Content ingestion from RSS feeds |
| `api` | REST API | Base URL | JSON API data sources |
| `authenticated_web` | HTTP with auth | Base URL | Authenticated website scraping |
The first three (`openclaw`, `a2a`, `mcp`) are agent gateway types -- they proxy to external agents that can receive delegated tasks. The remaining three are data source types used by the source sync system for content ingestion.
## Database schema
Connections are stored in the `agent_connections` table:
| Column | Type | Purpose |
|--------|------|---------|
| `id` | uuid | Primary key |
| `tenant_id` | uuid | Tenant scope |
| `name` | text | Display name |
| `connection_type` | text | One of the six connection types |
| `base_url` | text | Base URL for the external service |
| `encrypted_credentials` | text | AES-encrypted credentials blob (never returned via API) |
| `config` | jsonb | Additional configuration (custom headers, test path, user agent) |
| `status` | text | `active`, `inactive`, or `error` |
| `last_health_check` | timestamptz | Last successful health check |
| `last_error` | text | Last error message |
| `created_by` | uuid | User who created the connection |
Credentials are encrypted at rest using `encryptCredentials()` from `lib/crypto/credentials.ts`. The API-facing type (`AgentConnection`) redacts credentials to a boolean `hasCredentials` field via `toPublicConnection()`.
## Connection lifecycle
### 1. Create
Create a connection via Admin > Connections or the API:
```
POST /api/agent-connections
{
"name": "My OpenClaw Agent",
"connection_type": "openclaw",
"base_url": "https://agent.example.com",
"credentials": { "apiKey": "sk-..." },
"config": { "testPath": "/v1/models" }
}
```
Credentials are encrypted before storage. The `config` object supports:
- `testPath` -- custom endpoint for health checks (overrides the protocol default)
- `userAgent` -- custom User-Agent header
- `headers` -- additional HTTP headers as key-value pairs
- `oauth` -- optional provider metadata for generic OAuth 2 connections
- `oauthScopes` -- optional scope override for OAuth-backed connections
### 2. Test
Test connectivity and credentials:
```
POST /api/agent-connections/:id/test
```
The test endpoint sends a protocol-appropriate request:
- **OpenClaw:** GET to `/v1/models`
- **A2A:** GET to `/.well-known/agent-card.json`
- **MCP:** POST JSON-RPC `tools/list` to the base URL
- **RSS/API/Web:** GET to the base URL
On success, updates `status` to `active` and records `last_health_check`. On failure, updates `status` to `error` and stores the error message.
Requests have a 10-second timeout. Credentials (token, API key, cookie, or custom headers) are decrypted and applied to the request automatically.
### OAuth connect / refresh
Some provider presets now support an OAuth authorization-code flow directly from the Connections UI. The same `agent_connections` row stores:
- encrypted client credentials and tokens in `encrypted_credentials`
- public connection state in `config.oauthStatus`, `config.oauthConnectedAt`, `config.oauthExpiresAt`, and `config.oauthScope`
Routes:
```text
GET /api/agent-connections/:id/oauth/start
GET /api/agent-connections/:id/oauth/callback
POST /api/agent-connections/:id/oauth/refresh
```
The current preset-backed OAuth connectors are Canva, Dropbox, Google Analytics, and LinkedIn. Other providers can still use the same flow by supplying `config.oauth` in advanced JSON.
### 3. Discover
For A2A connections, discover available agents:
```
POST /api/agent-connections/:id/discover
```
Returns a list of `DiscoveredAgent` objects with `externalAgentId`, `name`, `description`, and whether the agent has already been imported.
### 4. Update and delete
```
PATCH /api/agent-connections/:id
DELETE /api/agent-connections/:id
```
Updates can change the name, URL, credentials, config, or status. Deleting a connection removes it permanently.
## Server actions
Connection CRUD is also available via server actions in `features/agents/server/connection-actions.ts`:
- `getConnections()` -- list all connections for the current tenant
- `getConnectionById(id)` -- fetch a single connection (API-safe, credentials redacted)
- `getConnectionByIdInternal(id)` -- fetch with encrypted credentials (server-only)
- `createConnection(input)` -- create with Zod-validated input
- `updateConnection(id, input)` -- partial update
- `deleteConnection(id)` -- remove
- `testConnection(id)` -- health check with status update
## MCP connections in chat agents
When an admin creates an MCP connection and sets its status to `active`, that connection is automatically available to chat agents the next time a message is sent. No code change or agent reconfiguration is needed.
Chat agents receive two gateway tools — `listMcpTools` and `callMcpTool` — that are assembled at request time from the tenant's active MCP connections. This is the **only** way MCP servers surface to internal agents; there is no separate "enable MCP" toggle on an agent.
**The flow from connection to agent use:**
1. Admin creates an MCP connection in **Admin > Connections** (connection type: MCP) and tests it.
2. On the next chat request, `getMcpServerConfigs(tenantId)` fetches active MCP connections (cached cross-request, invalidated after any mutation).
3. `createMcpGatewayTools(configs)` produces `listMcpTools` and `callMcpTool` with the server names embedded in the description prefix.
4. The agent's ToolSet contains exactly 2 MCP-related tools regardless of how many MCP servers are connected.
5. During the conversation the agent calls `listMcpTools` to discover available tools, then `callMcpTool` to execute them.
**Context cost is always 2 tool slots.** MCP tool schemas are fetched lazily only when the agent calls `listMcpTools`, not at ToolSet assembly time. This keeps the prompt prefix stable and preserves Anthropic prompt caching.
**Credential resolution.** The gateway resolves all auth headers from the connection's encrypted credentials using `resolveConnectionAuthHeaders()`. Any credential type supported by the shared resolver (Bearer, cookie, basic, custom headers) works out of the box — no special MCP-specific auth configuration is needed.
**Autonomous paths (heartbeat, extraction, workflows) are excluded by default.** These paths pass an empty config list, so no MCP gateway tools are injected. This is intentional: autonomous runs execute without human oversight, and MCP tool calls reach external services. Opting in requires an explicit code change to pass `mcpConfigs` in those paths.
## Agent delegation
Agents can delegate tasks to other agents (internal or external) via the delegation tool, which is automatically included in every agent's tool set.
The delegation factory at `features/agents/lib/delegate.ts` creates a `delegateToAgent` tool:
```typescript
const delegateTool = createDelegateToAgentTool();
// Agents call: delegateToAgent({ agentSlug: "researcher", task: "Find market data for..." })
```
**Internal delegation:** Resolves the target agent from the code registry, runs it inline within the same process.
**External delegation:** Looks up the target agent via `agent_connections`, routes the task through the connection-based provider (OpenClaw or A2A protocol). Supports configurable timeouts for external calls.
## Credential types
The encrypted credentials blob supports several authentication patterns:
| Pattern | Fields | Header applied |
|---------|--------|----------------|
| OAuth 2 | `{ scheme: "oauth2", oauth2: { clientId, clientSecret, accessToken, refreshToken } }` | `Authorization: Bearer <accessToken>` |
| Bearer token | `{ token: "..." }` | `Authorization: Bearer <token>` |
| API key | `{ apiKey: "..." }` | `Authorization: Bearer <apiKey>` |
| Cookie | `{ cookie: "..." }` | `Cookie: <cookie>` |
| Basic auth | `{ basicAuth: { username, password } }` | `Authorization: Basic <base64>` |
| Custom headers | `{ headers: { "X-Custom": "..." } }` | Applied directly |
Multiple credential types can be combined in a single connection.
## Zod validation schemas
All API inputs are validated with Zod schemas defined in `features/agents/connection-types.ts`:
- `CreateConnectionSchema` -- validates name (1-255 chars), connection_type (enum), base_url (valid URL), optional credentials and config
- `UpdateConnectionSchema` -- all fields optional for partial updates
## Administration
The Connections tab in Admin (`/admin` > Connections) provides:
- Table view of all connections with status indicators (green dot for active, gray for inactive, red for error)
- Badge indicating connection type (OpenClaw, A2A, MCP, RSS, API, Authenticated Web)
- Capability toggles so a single connection can be used for source monitoring, publishing, external agent runtime, or a combination of those roles
- Browser monitoring agent ID field for source-capable OpenClaw/A2A connections
- OAuth connect, reconnect, and token refresh actions for supported providers
- Test button for each connection with inline result display
- Add/edit dialog with credential input
- Last health check timestamp and error message display
- Delete with confirmation
## Source connections
RSS, API, and Authenticated Web connections are used by the source sync system (`features/source-sync/`) to ingest external content into the entity graph. These connections provide:
- Authentication for paywalled or private content sources
- Custom headers and user agent configuration
- Domain and keyword filtering
- Deduplication with configurable time windows
Source entities reference connections via their `content.connection_id` field. The source sync function resolves the connection, decrypts credentials, and uses them when fetching content.
### Browser-agent source monitoring
Some sites are too dynamic or hostile for plain HTTP scraping. In those cases a source can use the `browser` scrape strategy and point to a source-capable OpenClaw or A2A connection instead of an HTTP data-source connection.
Expected connection config:
- `config.capabilities` includes `source`
- `config.browserAgentId` (or `config.externalAgentId`) names the external browser-capable agent to invoke
The Admin UI exposes this directly: select a gateway connection, enable the `Source monitoring` capability, and enter the browser agent's external ID. That makes existing gateway agents such as Chippy reusable for monitored-source jobs without creating a second browser-only connection model.
This keeps browser monitoring on the same connection primitive as the rest of the platform instead of introducing a second browser-specific credential model.
## Connect your agents (quick start)
Three steps to connect any MCP-compatible agent to Amble:
1. **Create an API key** in Admin > API Keys with scopes: `tools:execute`, `skills:read`, `views:read`
2. **Point your agent** at `{AMBLE_URL}/api/mcp/server` with the key as a Bearer token
3. **Verify** with a curl test:
```bash
curl -X POST https://app.sprinter.ai/api/mcp/server \
-H "Authorization: Bearer sk_..." \
-H "Content-Type: application/json" \
-H "MCP-Protocol-Version: 2025-03-26" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.1.0"}}}'
```
A successful response returns `{"jsonrpc":"2.0","id":1,"result":{...}}` with server capabilities.
For production use at `app.sprinter.ai`, replace `localhost:3000` in all examples below with your deployment URL.
## Local interoperability endpoints
Amble exposes two complementary surfaces for external agent/app consumers:
### 1. Standard MCP server (preferred for Codex / Claude Code / OpenClaw)
- `POST /api/mcp/server`
- `GET /api/mcp/server`
- `DELETE /api/mcp/server`
This is a **real streamable HTTP MCP server** backed by Amble tools. Point local coding agents here.
API-key scopes control what the MCP server exposes:
- `tools:execute` — exposes tenant-accessible Amble tools
- `skills:read` — exposes helper tools:
- `ambleListSkills`
- `ambleGetSkill`
- `views:read` — exposes helper tools:
- `ambleListViews`
- `ambleGetView`
The old snake_case names (`amble_list_skills`, `amble_get_skill`, `amble_list_views`, `amble_get_view`) remain as deprecated aliases until 2026-09-30.
### 2. App/CLI-friendly REST + JSON-RPC primitives
- `POST /api/mcp/tools` — JSON-RPC `tools/list` / `tools/call`
- `GET /api/skills` — list skills (`skills:read`)
- `GET /api/skills/:id` — fetch skill by id (`skills:read`)
- `GET /api/skills/slug/:slug` — fetch skill by slug (`skills:read`)
- `GET /api/views` — list views (`views:read`)
- `GET /api/views/:id` — fetch view by id (`views:read`)
These are useful for simple scripts, app integrations, and CLI workflows where a full MCP client is unnecessary.
### Authentication
Authentication is API-key based:
- `Authorization: Bearer sk_...`
- `X-API-Key: sk_...`
Recommended local-agent key scopes:
- `tools:execute`
- `skills:read`
- `views:read`
Add `entities:read` and `entity-types:read` if the consuming app also needs direct record/schema access.
## Setup recipes
Set `AMBLE_BASE_URL` to your deployment (e.g. `https://app.sprinter.ai` for production, `http://localhost:3000` for local dev).
### Codex
```bash
export AMBLE_BASE_URL="https://app.sprinter.ai"
export AMBLE_API_KEY="sk_..."
codex mcp add amble --url "$AMBLE_BASE_URL/api/mcp/server" --bearer-token-env-var AMBLE_API_KEY
```
### Claude Code
```bash
export AMBLE_BASE_URL="https://app.sprinter.ai"
export AMBLE_API_KEY="sk_..."
claude mcp add --transport http amble "$AMBLE_BASE_URL/api/mcp/server" --header "Authorization: Bearer $AMBLE_API_KEY"
```
### OpenClaw ACP agents
Configure the ACP plugin's `mcpServers` to point at Amble's MCP server:
```json
{
"plugins": {
"entries": [
{
"type": "acpx",
"enabled": true,
"config": {
"mcpServers": {
"amble": {
"type": "http",
"url": "https://app.sprinter.ai/api/mcp/server",
"headers": {
"Authorization": "Bearer sk_..."
}
}
}
}
}
]
}
}
```
If you want secrets managed outside the raw config, use OpenClaw's secrets/env mechanisms and inject the bearer token there instead of hard-coding it.
## CLI bootstrap
Use the Amble CLI to validate and bootstrap local setups:
```bash
pnpm cli doctor --base-url http://localhost:3000 --api-key sk_...
pnpm cli config codex --base-url http://localhost:3000 --api-key sk_...
pnpm cli config claude --base-url http://localhost:3000 --api-key sk_...
pnpm cli mcp list --base-url http://localhost:3000 --api-key sk_...
pnpm cli mcp call my-tool --payload '{"foo":"bar"}' --api-key sk_...
pnpm cli skills --base-url http://localhost:3000 --api-key sk_...
pnpm cli skills get my-skill --base-url http://localhost:3000 --api-key sk_...
pnpm cli views --base-url http://localhost:3000 --api-key sk_...
```
## Chippy rollout checklist
When rolling this out to Chippy later:
1. Mint a dedicated API key for Chippy.
2. Scope it to the minimum required capabilities.
3. Point Chippy to `/api/mcp/server`.
4. Validate with `pnpm cli doctor` against the target environment.
5. Verify tool execution + skill fetch + view fetch before turning on autonomous runs.