> Canonical guide: https://developers.foxlight.ai/build/first-capability/
> Contract snapshots: Skulk 2.0.0 (b0af39c79b6b7102b2478062904f1d7cc8619975); SDK 0.4.0 (31bb090b8f64689f87514e48385e22b6ad94a6c2). Check the installed runtime when versions differ.

# Serving a _capability_

Start with a small operation that has no external effects. The public Skulk extension API lets a provider declare its input and output, join discovery, and handle calls inside a Skulk process.

:::note Choose your integration
This guide uses `skulk.extensions`. A separately supervised plugin uses the [managed capability SDK](https://developers.foxlight.ai/build/sdk/), with isolated dependencies and a different lifecycle. SDK 0.4.0 and child protocol 4 support unary and all three streaming modes. See [managed streaming](https://developers.foxlight.ai/build/sdk/streams/) for that path and its release requirements.
:::

<div className="io-mode-grid">
<div><code>unary</code><p>One request, one result</p></div>
<div><code>server_streaming</code><p>Request, progressive output</p></div>
<div><code>client_streaming</code><p>Progressive input, final result</p></div>
<div><code>bidirectional</code><p>Input and output together</p></div>
</div>

## 1. Describe the operation

The descriptor is the contract a caller discovers. Use an exact semantic version and JSON Schemas. Inputs are validated before your handler runs, outputs after it returns.

```python
from skulk.extensions import CapabilityDescriptor

ECHO_DESCRIPTOR = CapabilityDescriptor(
    id="echo", version="1.0.0", title="Echo",
    description="Return the supplied text unchanged.",
    io_mode="unary",
    input_schema={
        "type": "object",
        "properties": {"text": {"type": "string"}},
        "required": ["text"], "additionalProperties": False,
    },
    output_schema={
        "type": "object",
        "properties": {"text": {"type": "string"}},
        "required": ["text"],
    },
)
```

## 2. Implement the provider

The extension has a name, a Skulk compatibility constraint, and a list of descriptors. A unary call returns a JSON object.

```python
from skulk.extensions import CapabilityCall, ExtensionContext

class EchoProvider:
    name = "echo-provider"
    skulk_requires = ">=2.0,<3"

    def chat_middleware(self) -> None:
        return None

    def capabilities(self) -> list[CapabilityDescriptor]:
        return [ECHO_DESCRIPTOR]

    async def handle_call(
        self, context: ExtensionContext, call: CapabilityCall,
    ) -> dict[str, object]:
        return {"text": call.payload["text"]}
```

## 3. Register the extension

Add a Python entry point to your package. Install the package into a dedicated Skulk development environment and restart that node; entry points are discovered at startup.

```toml
[project.entry-points."skulk.extensions"]
echo-provider = "your_package:EchoProvider"
```

The complete [reference echo package](https://github.com/Foxlight-Foundation/Skulk/tree/84adfad48eec95f9042ce46bd9deb37959d80752/examples/extensions/echo-provider) is available in the public Skulk repository. It includes the package layout and provider implementation. Its recorded example compatibility constraint is broader than the 2.0 constraint used here; test your own declared range.

## 4. Discover before calling

The tag says where to look. The descriptor says what can be called. Fetch the exact descriptor and revision on each negotiation; never invent a revision or use an old cached descriptor as proof of readiness.

```bash
curl http://localhost:52415/v1/capabilities
```

The result has `node_id`, `capabilities`, and `revisions`. `revisions` maps an exact `id@version` to its content digest. A peer can be described with `?node_id=...`.

Use the [descriptor explorer](https://developers.foxlight.ai/build/reference/capabilities/) to inspect the schema, then [call the pinned contract](https://docs.foxlight.ai/skulk/next/api/skulk-api/). Extension callers normally use `context.describe_node` and `context.call_capability`, rather than handling transport themselves.

## 5. Make failure part of the contract

Test invalid input, output validation, deadline expiry, unavailable providers, and restart. A valid unary envelope returns HTTP 200 with `ok` and a typed result or error. Switch on `error.code`; malformed envelopes receive HTTP 422.

For all four I/O modes and the complete handler lifecycle, read the [normative extension guide](https://docs.foxlight.ai/skulk/next/extensions/#serving-a-capability-providers).
