Skip to content

Extensibility & Plugin SDK

Floci exposes three extension points that let you react to emulator activity, shape outbound traffic, and ship custom MCP tools — all without touching the core.


A webhook is a URL you register once. Every time Floci fires a matching event, it POSTs a JSON payload to that URL within a 5-second timeout.

Any string is a valid event name. Floci currently fires events from the pipeline and marketplace actions (e.g. floci.resource.created). You can also fire your own events manually with the emit endpoint — useful for testing integrations or signalling other agents.

Events are matched against each webhook’s pattern using shell-style glob matching, so * catches everything and floci.resource.* catches every resource event.

POST /api/extensibility/webhooks
Content-Type: application/json
{
"event": "floci.resource.created",
"url": "https://hooks.slack.com/services/T.../B.../...",
"description": "Notify #dev-infra when a new resource appears"
}

Or via Claude / Cursor:

You: Notify https://hooks.example/floci whenever any Floci event fires
Claude: [calls register_lifecycle_webhook(event="*", url="https://hooks.example/floci")]

Every request Floci sends to your URL has the same shape:

{
"event": "floci.resource.created",
"payload": { ... },
"timestamp": 1748700000.123
}

The webhook record also tracks lastDelivery — the HTTP status (or error) from the most recent delivery — visible in the Extensibility panel.

POST /api/extensibility/webhooks/emit
Content-Type: application/json
{
"event": "floci.deploy.finished",
"payload": { "recipeId": "postgres", "env": "test" }
}

This fires the event immediately to all webhooks whose pattern matches floci.deploy.finished, and returns a delivery report:

{
"event": "floci.deploy.finished",
"delivered": [
{ "webhookId": "...", "url": "https://...", "status": 200 }
],
"count": 1
}
  • Slack / Teams alerts — fire a Slack webhook when a deployment finishes or a DLQ message lands.
  • AI agent handoffs — signal a second Claude session to start running tests after floci.deploy.finished.
  • Log to a file — point at a tiny local HTTP server (e.g. python -m http.server) to capture every emulator event during a debugging session.
  • Trigger CI — call a GitHub Actions workflow_dispatch endpoint so the CI pipeline starts as soon as the local stack is ready.

Interceptors are declarative rules that modify traffic flowing through the Studio proxy (/api/studio/client/proxy). They apply before a request leaves Floci (request phase) or before the response is returned to the caller (response phase).

They are intentionally declarative — no arbitrary code, no eval — so the local environment stays safe by default.

Each rule matches URLs by substring or glob against url_pattern:

FieldValuesDescription
url_patternany string or globSubstring check first, then fnmatch glob
phaserequest · responseWhen the rule fires
actionset_header · set_status · delay_msWhat to do
paramsdepends on actionAction parameters

set_header — inject or override a header on the matching request or response. Useful for adding auth tokens or CORS headers your app needs during local development.

POST /api/extensibility/interceptors
{
"url_pattern": "*/api/orders*",
"phase": "request",
"action": "set_header",
"params": { "X-Internal-Token": "dev-secret" }
}

set_status — force a specific HTTP status code on the response. Use this to simulate service failures without touching your application code.

POST /api/extensibility/interceptors
{
"url_pattern": "*/payments*",
"phase": "response",
"action": "set_status",
"params": { "status": 503 }
}

delay_ms — add artificial latency before the response is returned. Replicate high-latency network conditions or expose race conditions that only appear under load.

POST /api/extensibility/interceptors
{
"url_pattern": "*/search*",
"phase": "response",
"action": "delay_ms",
"params": { "ms": 3000 }
}
ScenarioRule
Add a JWT to every outbound requestset_header on request, pattern *
Test circuit-breaker logicset_status: 503 on response, pattern */downstream-service*
Verify timeout handlingdelay_ms: 5000 on response, pattern */slow-endpoint*
Block calls to a third-party APIset_status: 401 on response, pattern */stripe.com*
Inject a CORS header for a local UIset_header on response, Access-Control-Allow-Origin: *

Rules are stored in state/lifecycle.json and survive restarts. Delete a rule by its id when you’re done:

DELETE /api/extensibility/interceptors/{id}

Plugins let you ship new MCP tools for services Floci doesn’t cover — a proprietary internal API, a niche AWS service, or a wrapper around a CLI tool — without touching the Floci codebase.

Drop a folder inside mcp/plugins/:

mcp/plugins/
└── my_plugin/
├── plugin.json ← manifest
└── tools.py ← MCP tool registrations

The MCP server discovers every plugin.json at startup and loads the matching tools.py. The backend exposes the full catalog at GET /api/extensibility/plugins.

{
"name": "My Plugin",
"version": "1.0.0",
"description": "Adds tools for the Acme internal API.",
"author": "you@acme.com",
"tools": ["acme_list_services", "acme_deploy"]
}

The only requirement is a top-level register(mcp) function. Inside it you define tools exactly like the Floci core modules do:

def register(mcp):
@mcp.tool()
async def acme_list_services() -> dict:
"""List services registered in the Acme internal registry."""
import httpx
async with httpx.AsyncClient() as client:
r = await client.get("http://localhost:9999/services")
return r.json()
@mcp.tool()
async def acme_deploy(service: str, env: str = "staging") -> dict:
"""Deploy a service to the Acme internal cluster."""
import httpx
async with httpx.AsyncClient() as client:
r = await client.post(
"http://localhost:9999/deploy",
json={"service": service, "env": env},
)
return r.json()

Tools registered this way appear in Claude / Cursor exactly like native Floci tools.

Set FLOCI_PLUGINS_DIR to load plugins from a different path — useful for monorepos where plugins live outside the mcp/ tree:

Terminal window
FLOCI_PLUGINS_DIR=/workspace/floci-plugins uv run --project mcp python mcp/floci_mcp.py

mcp/plugins/example_hello/ is a minimal working plugin you can copy as a template. It registers one tool, floci_hello, that accepts a name and returns a greeting.

You: Say hello to Alice using the Floci plugin
Claude: [calls floci_hello(name="Alice")]
→ { "message": "Hello, Alice! — from the example_hello plugin." }

LayerEndpoint / Tool
GUIStudio → Extensibility
Webhooks RESTGET/POST /api/extensibility/webhooks · DELETE …/{id} · POST …/emit
Interceptors RESTGET/POST /api/extensibility/interceptors · DELETE …/{id}
Plugins RESTGET /api/extensibility/plugins
MCP — webhookslist_lifecycle_webhooks · register_lifecycle_webhook · delete_lifecycle_webhook · emit_lifecycle_event
MCP — interceptorslist_http_interceptors · register_http_interceptor · delete_http_interceptor
MCP — pluginslist_floci_plugins
State filestate/lifecycle.json
Plugins dirmcp/plugins/ (override with FLOCI_PLUGINS_DIR)