Saltar al contenido principal

Channel Tools

Channels let a tenant expose custom MCP tools that other tenants and agents can discover and execute. Think of it as publishing an API built from your entity data, without writing backend code.

What are Channels?

A channel = one tenant publishing a set of tools that operate on its entities. Other agents call these tools through the execution endpoint.

Key properties:

  • One channel per tenant. Each tenant can publish exactly one channel.
  • Tool isolation. Tools belong to the channel owner's tenant. Cross-tenant tool CRUD is blocked -- tenant A cannot modify tenant B's tools.
  • Triple authorization. Every tool execution checks three layers: (1) channel is active, (2) tool permissions allow the operation, (3) record-level permission check.
  • Data stays with the owner. When a consumer executes a channel tool, the query runs against the channel owner's data, not the consumer's.

Publishing a Channel

REST:

curl -X POST https://api.fyso.dev/api/channels \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Product Catalog",
"description": "Search and browse our product inventory",
"tags": ["ecommerce", "products"]
}'

Response:

{
"success": true,
"data": {
"id": "uuid",
"tenant_id": "uuid",
"name": "Product Catalog",
"slug": "product-catalog",
"description": "Search and browse our product inventory",
"tags": ["ecommerce", "products"],
"is_active": true,
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-01T10:00:00Z"
}
}

The slug is auto-generated from the name. Publishing a second channel for the same tenant returns an error with the existing slug -- use PATCH to update instead.

Channel Permissions

Channels default to read access on publish. Set permissions explicitly:

curl -X PUT https://api.fyso.dev/api/channels/product-catalog/permissions \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"permissions": {
"defaultAccess": "readwrite",
"entityRules": [
{"entityName": "orders", "access": "read"}
]
}
}'
Access LevelWhat consumers can do
noneNo access
readQuery tools only
readwriteQuery + create/update/delete tools

Entity rules override the default for specific entities.

Defining Tools

Tools are defined against the channel owner's entities using a DSL (domain-specific language). Each tool maps to an operation on an entity.

Creating a Tool

curl -X POST https://api.fyso.dev/api/tools \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "search-products",
"description": "Search products by name",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search term"
}
},
"required": ["query"]
},
"tool_dsl": {
"operation": "query",
"entity": "products",
"fieldMapping": {
"query": "name"
},
"filters": [
{
"field": "name",
"operator": "contains",
"valueFrom": "query"
}
]
}
}'

Response:

{
"success": true,
"data": {
"id": "uuid",
"channel_id": "uuid",
"name": "search-products",
"slug": "search-products",
"description": "Search products by name",
"input_schema": { "..." : "..." },
"version": 1,
"is_active": true
}
}

Tool DSL Structure

{
"operation": "query | create | update | delete",
"entity": "entity-name",
"fieldMapping": {
"<input-param>": "<entity-field>"
},
"filters": [
{
"field": "<entity-field>",
"operator": "contains | equals | gt | gte | lt | lte",
"valueFrom": "<input-param>"
}
],
"defaults": {
"<entity-field>": "static-value | {{uuid}} | {{timestamp}}"
}
}
FieldRequiredDescription
operationYesWhat the tool does: query, create, update, delete
entityYesWhich entity the tool operates on. Must exist in the owner's tenant.
fieldMappingYesMaps input parameters to entity fields
filtersNoQuery filters. Only used with query operation.
defaultsNoDefault values for fields not provided by the caller

Dynamic Defaults

Defaults support special expressions that resolve at execution time:

ExpressionResolves to
{{uuid}}A new random UUID (v4). Unique per execution.
{{timestamp}}Current ISO 8601 timestamp
Any other stringUsed as a static literal value

Example: a "create order" tool that auto-generates an order ID and timestamp:

{
"operation": "create",
"entity": "orders",
"fieldMapping": {
"customer_name": "customer_name",
"total": "total"
},
"defaults": {
"order_id": "{{uuid}}",
"created_at": "{{timestamp}}",
"status": "pending"
}
}

The caller only provides customer_name and total. The order_id, created_at, and status fields are filled automatically.

Input Schema

Standard JSON Schema for validating caller parameters:

{
"type": "object",
"properties": {
"customer_name": {
"type": "string",
"description": "Customer full name"
},
"total": {
"type": "number",
"description": "Order total in cents"
}
},
"required": ["customer_name", "total"]
}

Required parameters are validated before execution. Missing required params return 400 with a clear error.

Executing Tools

Consumers call tools through the channel execution endpoint.

REST:

curl -X POST https://api.fyso.dev/api/channels/product-catalog/tools/search-products/execute \
-H "Authorization: Bearer <consumer-token>" \
-H "Content-Type: application/json" \
-d '{"query": "wireless"}'

Response:

{
"success": true,
"data": {
"records": [
{
"id": "rec-uuid",
"name": "Wireless Mouse",
"price": 2999,
"category": "peripherals"
}
],
"total": 1
},
"meta": {
"executionTimeMs": 42
}
}

For create operations, the response includes the new record's ID:

{
"success": true,
"data": {
"id": "new-record-uuid",
"data": {
"order_id": "generated-uuid",
"customer_name": "Acme Corp",
"status": "pending"
}
},
"meta": {
"executionTimeMs": 38
}
}

Cross-Tenant Consumption

Any authenticated tenant can execute public channel tools. The data returned comes from the channel owner's schema, not the caller's. This is the core feature -- expose your data through controlled tools without giving direct database access.

Tool Lifecycle

CRUD Operations

OperationEndpointMethod
Create/api/toolsPOST
Update/api/tools/:slugPATCH
Delete/api/tools/:slugDELETE
List own/api/tools/mineGET
List by channel (public)/api/channels/:slug/toolsGET

Create, Delete, Re-create

You can delete a tool and re-create one with the same slug. The old tool is hard-deleted, so the slug is immediately available. This works across multiple rounds -- create, delete, re-create as many times as you need.

Unpublishing a Channel

DELETE /api/channels/:slug performs a soft delete -- sets is_active = false.

  • Unpublished channels are not discoverable via GET /api/channels/:slug
  • The owner can still see their channel via GET /api/channels/mine
  • Re-publishing (calling POST /api/channels again) re-activates the channel with the new data
  • Tools remain associated with the channel after unpublish

Updating a Channel

curl -X PATCH https://api.fyso.dev/api/channels/product-catalog \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"description": "Updated description",
"tags": ["ecommerce", "products", "v2"]
}'

Updatable fields: name, description, tags, metadata.

Searching Channels

curl "https://api.fyso.dev/api/channels?query=product&tags=ecommerce&limit=10&offset=0"

No auth required. Only returns active channels. Supports text search on name/description and tag filtering.

Bot Identity for Channels

Bots are service accounts that can authenticate and operate on a tenant without a human user. Useful for automated channel operations.

Register a Bot

Requires admin auth:

curl -X POST https://api.fyso.dev/api/auth/bots/register \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"name": "catalog-sync-bot", "tenantSlug": "my-store"}'

Response (201):

{
"success": true,
"data": {
"id": "uuid",
"name": "catalog-sync-bot",
"secret": "a3f1b2c4d5e6...long-random-string",
"tenantSlug": "my-store"
}
}

The secret is shown once. Store it securely. If you lose it, you must reset it.

Bot name rules: minimum 3 characters, unique across the platform.

Identify (Authenticate)

curl -X POST https://api.fyso.dev/api/auth/bots/identify \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"name": "catalog-sync-bot", "secret": "a3f1b2c4d5e6..."}'

Response:

{
"success": true,
"data": {
"id": "uuid",
"name": "catalog-sync-bot",
"tenantSlug": "my-store",
"tenantId": "uuid"
}
}

The bot context (tenant association) persists for channel operations.

Secret Management

Reset secret (invalidates the old one immediately):

curl -X POST https://api.fyso.dev/api/auth/bots/<bot-id>/reset-secret \
-H "Authorization: Bearer <admin-token>"

Returns a new secret. The old secret stops working instantly.

Revoke a bot (permanent deactivation):

curl -X POST https://api.fyso.dev/api/auth/bots/<bot-id>/revoke \
-H "Authorization: Bearer <admin-token>"

Revoked bots cannot identify. Returns 401 on any subsequent identify attempt.

List all bots:

curl https://api.fyso.dev/api/auth/bots \
-H "Authorization: Bearer <admin-token>"

Returns bot metadata (id, name, tenantSlug, isActive). Never returns the secret hash.

Error Handling

StatusCodeWhen
400VALIDATION_ERRORMissing required params, invalid DSL structure, invalid JSON
401-No auth token, invalid token, revoked bot
403PERMISSION_DENIEDNo tenant context, channel permissions deny the operation
404CHANNEL_NOT_FOUNDChannel slug doesn't exist or is unpublished
404TOOL_NOT_FOUNDTool slug doesn't exist in the channel, or cross-tenant tool access attempted
409-Tool slug already exists in the channel, duplicate channel publish

Tenant Isolation

Cross-tenant tool CRUD is enforced at every level:

  • Tenant A cannot create tools in tenant B's channel
  • Tenant A cannot update or delete tenant B's tools (returns 404, not 403 -- no information leak)
  • Creating a tool that references another tenant's entity fails validation
  • Tools with the same slug can coexist in different channels without conflict