Provider
A Provider gives an agent access to an external service — GitHub, Telegram, Slack, AWS, GCP, Azure, MCP servers, or any HTTP API. The agent's requests to that service are transparently intercepted by the Pai sidecar, which injects credentials and enforces policy.
The agent never holds real credentials. It makes requests normally, and Pai handles authentication behind the scenes.
pai apply -f provider.yaml
pai get services
pai describe provider <name>
pai delete service <name>
How it works
When a provider is attached to an agent:
- DNS for the provider's hostname resolves to
127.0.0.1(the sidecar) inside the agent container - The sidecar intercepts the HTTPS request
- Policy is checked — if denied, the request is blocked with HTTP 403
- Credentials are injected (Bearer token, SigV4 signature, OAuth2 token, etc.)
- The real request is forwarded to the external service
Field reference
| Field | Required | Description |
|---|---|---|
type | Yes | Provider type (see below) |
host | No | Hostname to intercept. Defaults to the provider's standard host |
auth.type | Yes (most types) | Authentication method |
auth.secretRef | Yes (most types) | Name of the Secret holding credentials |
auth.secretKey | No | Key within the Secret (default: token) |
auth.agentEnvVar | No | Env var injected into the agent with a dummy value so libraries initialize correctly |
config.region | No | Cloud region (AWS, Azure) |
config.project | No | GCP project ID |
config.tenantId | No | Azure AD tenant ID |
config.services | No | Restrict to specific cloud services (e.g. [s3, sqs]) |
mcp.transport | Yes (MCP) | sse (default) or stdio |
mcp.url | Yes (MCP SSE) | SSE endpoint URL |
mcp.command | Yes (MCP stdio) | Command and args to launch the stdio server |
policy.allow | No | Allowed actions. ["*"] = allow all |
policy.deny | No | Denied actions (takes precedence over allow) |
policy.httpRules | No | Raw HTTP method + path rules (see below) |
policy.mcp.allowedTools | No | MCP only — tool names callable by the agent (empty = all) |
policy.mcp.deniedTools | No | MCP only — tools explicitly blocked |
scope.repositories | No | Allowed GitHub repositories |
scope.channels | No | Allowed Slack channels |
scope.resources | No | Allowed cloud resource patterns (ARNs, paths) |
audit.logRequests | No | Log every API call (default: true) |
audit.enforcement | No | enforce (default) blocks violations · audit logs but allows — useful for testing a new policy |
Provider types
type | auth.type | Description |
|---|---|---|
github | pat | GitHub Personal Access Token |
telegram | bot-token | Telegram Bot API |
slack | bot-token | Slack Bot token |
aws | aws-sigv4 | AWS SigV4 request signing |
azure | azure-client-credentials | Azure OAuth2 client credentials |
gcp | gcp-service-account | GCP service account JSON key |
mcp | api-key (SSE) / none (stdio) | Model Context Protocol server — exposes a tool catalogue to the agent |
http-token | bearer | Generic HTTP API with a bearer token (internal services, webhooks, anything OpenAPI-shaped) |
Examples
GitHub
Personal Access Token auth, narrow action allowlist, scoped to specific repositories.
apiVersion: pai.io/v1
kind: Provider
metadata:
name: github-writer
spec:
type: github
auth:
type: pat
secretRef: github-pat
policy:
allow:
- pulls:create
- pulls:comment
- issues:read
- contents:read
deny:
- admin:*
scope:
repositories:
- "myorg/repo-a"
- "myorg/repo-b"
Secret required:
pai add secret github-pat --from-literal token=ghp_YOUR_TOKEN
Telegram
Bot-token auth. agentEnvVar sets a placeholder env var inside the agent so Telegram SDKs initialize correctly without ever seeing the real token.
apiVersion: pai.io/v1
kind: Provider
metadata:
name: telegram-bot
spec:
type: telegram
host: api.telegram.org
auth:
type: bot-token
secretRef: telegram-token
secretKey: token
agentEnvVar: TELEGRAM_BOT_TOKEN
policy:
allow: ["*"]
Secret required:
pai add secret telegram-token --from-literal token=YOUR_BOT_TOKEN
AWS S3
SigV4 request signing from access keys. Scoped to one service and one bucket; read-only via explicit deny on mutation actions.
apiVersion: pai.io/v1
kind: Provider
metadata:
name: s3-reader
spec:
type: aws
auth:
type: aws-sigv4
secretRef: aws-creds
config:
region: us-east-1
services: [s3]
policy:
allow:
- s3:GetObject
- s3:ListBucket
deny:
- s3:DeleteObject
- s3:PutObject
scope:
resources:
- "arn:aws:s3:::my-bucket/*"
Secret required:
pai add secret aws-creds \
--from-literal access_key_id=AKIAIOSFODNN7EXAMPLE \
--from-literal secret_access_key=wJalrXUtnFEMI...
GCP
Service account JSON key exchanged for short-lived OAuth2 tokens (refreshed in the background). Scoped to one project and service.
apiVersion: pai.io/v1
kind: Provider
metadata:
name: gcp-storage
spec:
type: gcp
auth:
type: gcp-service-account
secretRef: gcp-sa-key
config:
project: my-gcp-project
services: [storage]
policy:
allow:
- storage.objects.get
- storage.objects.list
deny:
- storage.objects.delete
Secret required:
pai add secret gcp-sa-key --from-literal key.json="$(cat service-account.json)"
Azure
Service principal client credentials exchanged for OAuth2 tokens against Azure AD. Scoped to one tenant and service.
apiVersion: pai.io/v1
kind: Provider
metadata:
name: azure-storage
spec:
type: azure
auth:
type: azure-client-credentials
secretRef: azure-sp-creds
config:
tenantId: "your-tenant-id"
services: [storage]
policy:
allow:
- "Microsoft.Storage/storageAccounts/read"
deny:
- "Microsoft.Storage/storageAccounts/delete"
Secret required:
pai add secret azure-sp-creds \
--from-literal client_id=YOUR_CLIENT_ID \
--from-literal client_secret=YOUR_CLIENT_SECRET
MCP server (SSE)
Connect the agent to a hosted Model Context Protocol server — Notion, Linear, Sentry, GitHub's MCP, anything that speaks SSE. Pai injects the bearer token on outbound requests; the agent never sees it.
apiVersion: pai.io/v1
kind: Provider
metadata:
name: linear
spec:
type: mcp
mcp:
url: https://mcp.linear.app/sse
transport: sse
auth:
type: api-key
secretRef: linear-mcp-token
policy:
mcp:
allowedTools: [list_issues, create_issue, search_issues]
deniedTools: [delete_issue]
Secret required:
pai add secret linear-mcp-token --from-literal token=lin_api_YOUR_TOKEN
How the agent actually uses it depends on its mode:
-
Harness-backed agents (task / template,
spec.type: taskor no type). Just list the Provider inspec.providers— nothing else needed. At startup the harness callstools/liston the MCP server (Pai has a built-in MCP client), applies theallowedTools/deniedToolsfilter, and registers each remaining tool with the LLM aslinear__<tool_name>. The model can invoke tools directly. -
Service agents (custom container image,
spec.type: service). Your runtime (OpenClaw, LangChain, a hand-rolled loop, etc.) is its own MCP client — Pai doesn't reach inside your process to register tools. Point your runtime at the upstream URL (https://mcp.linear.app/sse); the sidecar intercepts the HTTPS request, enforces the policy, and injects the real token. Configure the connection in your agent's own config, typically viaconfigFiles:# In your service Agent:
providers: [linear] # so Pai sets up sidecar interception + policy
configFiles:
- path: /home/node/.openclaw/openclaw.json
content: |
{
"mcpServers": [
{"name": "linear", "url": "https://mcp.linear.app/sse"}
]
}The
.mcp.json/openclaw.json/ whatever format your runtime reads is opaque to Pai — only the HTTPS calls going out matter.
See the MCP Server provider for the full flow and a Linear worked example.
MCP server (stdio)
For self-hosted MCP servers that run as a subprocess (e.g. the official reference servers):
apiVersion: pai.io/v1
kind: Provider
metadata:
name: filesystem
spec:
type: mcp
mcp:
transport: stdio
command: ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
# no auth: stdio servers don't need one
For stdio servers, list the package on the Agent's packages so the binary is installed before the harness starts:
spec:
packages:
npm: ["@modelcontextprotocol/server-filesystem"]
- MCP Server provider — connecting Pai agents to MCP servers, transport options, and a Linear worked example.
- MCP Gateway — exposing a Pai-hosted MCP Provider to tools running on developer laptops (Claude Code, Cline, etc.).
Attaching a provider to an agent
spec:
providers:
- github-writer
- telegram-bot
Policy
spec.policy controls which operations an agent can perform through this provider. Pai supports three flavors:
- Action-based — list the high-level operations (e.g.
pulls:create,s3:GetObject) to allow or deny. Works for providers with a built-in action catalogue: GitHub, AWS, Azure, GCP, Telegram. - HTTP-level — list raw method + path rules. Works for any HTTP provider, and is the right choice for generic services or when action-based isn't granular enough.
- MCP tool-list — allow or deny specific tools on an MCP server. Pai filters
tools/callframes before they reach the upstream server.
All three live on spec.policy and can be combined.
Action-based policy
spec:
policy:
allow:
- pulls:create
- pulls:comment
- issues:read
deny:
- admin:* # deny always wins
| Field | Description |
|---|---|
allow | Actions permitted. Use ["*"] to allow everything the Provider supports. |
deny | Actions blocked. Evaluated before allow — a matching deny always wins. |
Action names follow the upstream service's convention:
| Provider | Format | Examples |
|---|---|---|
| GitHub | resource:action | pulls:create, issues:read, admin:* |
| AWS | service:Action | s3:GetObject, sqs:SendMessage |
| Azure | Provider/resource/action | Microsoft.Storage/storageAccounts/read |
| GCP | service.resource.action | storage.objects.get |
| Telegram | method name | sendMessage, getUpdates |
GitHub example — read-only access to issues and pull requests:
spec:
type: github
auth: {type: pat, secretRef: github-pat}
policy:
allow:
- issues:read
- pulls:read
- contents:read
AWS example — read-only S3, never write or delete:
spec:
type: aws
auth: {type: aws-sigv4, secretRef: aws-creds}
config: {region: us-east-1, services: [s3]}
policy:
allow:
- s3:GetObject
- s3:ListBucket
deny:
- s3:DeleteObject
- s3:PutObject
Telegram example — can send messages, cannot delete chats:
spec:
type: telegram
auth: {type: bot-token, secretRef: telegram-token}
policy:
allow: ["*"]
deny:
- deleteChat
- banChatMember
HTTP-level rules
spec:
policy:
allow: ["*"]
httpRules:
- methods: [GET]
paths: ["/repos/*/issues", "/repos/*/pulls"]
effect: allow
- methods: [POST, PUT, PATCH, DELETE]
paths: ["*"]
effect: deny
| Field | Description |
|---|---|
methods | HTTP methods this rule matches (case-insensitive) |
paths | Path patterns. Uses fnmatch globs (* = any chars in a segment, ** = across slashes) |
effect | allow or deny. Deny rules are evaluated first; the first match wins. |
If httpRules is defined but nothing matches the incoming request, the request is denied by default. Combine with policy.allow/deny to layer action-based and HTTP-based rules.
Example — read-only GitHub via raw paths:
spec:
type: github
auth: {type: pat, secretRef: github-pat}
policy:
httpRules:
- methods: [GET]
paths: ["/repos/*/*/issues", "/repos/*/*/pulls", "/repos/*/*/contents/**"]
effect: allow
- methods: [POST, PUT, PATCH, DELETE]
paths: ["*"]
effect: deny
Example — generic HTTP service (type: http-token) scoped to one path prefix:
spec:
type: http-token
host: api.internal.example.com
auth: {type: bearer, secretRef: internal-api-token}
policy:
httpRules:
- methods: [GET, POST]
paths: ["/v2/customers/**"]
effect: allow
MCP tool policy
For type: mcp Providers, filter which tools the agent can call on the upstream server. Pai intercepts every tools/call JSON-RPC frame and checks it against the allow/deny lists before forwarding.
spec:
type: mcp
mcp: {url: https://mcp.notion.com/sse, transport: sse}
auth: {type: api-key, secretRef: notion-mcp-token}
policy:
mcp:
allowedTools: [search_pages, fetch_document, fetch_database]
deniedTools: [delete_page, delete_database]
| Field | Description |
|---|---|
allowedTools | Tool names the agent may call. Empty = all tools permitted. |
deniedTools | Tool names explicitly blocked. Evaluated before allowedTools — a matching deny always wins. |
Tool names come from the MCP server's tools/list response. Pai filters that catalogue at registration time, so the agent only sees tools the policy allows.
Audit mode
Roll a new policy out safely without breaking live traffic. In audit mode, policy violations are logged with an AUDIT (not blocked): prefix and the request is still forwarded — use it to validate a tightening change against real traffic before flipping to enforce.
spec:
audit:
logRequests: true
enforcement: audit # switch to "enforce" once you're confident
| Field | Description |
|---|---|
logRequests | Log every API call (default true) |
enforcement | enforce (default) blocks violations · audit logs them but allows the request |
Policy changes (policy, httpRules, audit, scope) take effect on running agents within ~1 minute — no restart required.