# Diaphora Python SDK

Python client library for the [Diaphora](/content/site-root.html) API.

**Supported Python versions**: 3.11, 3.12, 3.13

## Installation

```
pip install diaphora-python
```

## Authentication

The Diaphora client needs your Diaphora credentials. Pass them directly, or set the `DIAPHORA_USERNAME` and `DIAPHORA_PASSWORD` environment variables and call the constructor with no arguments.

**Direct credentials:**

```
from diaphora.auth.basic_authenticator import BasicAuthenticator

auth = BasicAuthenticator("user@example.com", "your-password")
```

**Environment variables:**

```
# export DIAPHORA_USERNAME=user@example.com
# export DIAPHORA_PASSWORD=your-password
auth = BasicAuthenticator()
```

To use a custom auth scheme, implement `DiaphoraAuthenticator`:

```
from diaphora.auth.authenticator_interface import DiaphoraAuthenticator

class MyAuthenticator(DiaphoraAuthenticator):
    def token(self) -> str:
        return "my-session-token"
```

## Diaphora

```
from diaphora import Diaphora

sdk = Diaphora(authenticator)
```

| Attribute      | Description                           |
| ---------------| ------------------------------------- |
| `sdk.store`    | Plan and result management            |
| `sdk.router`   | Plan execution and MCP tools         |

## Quickstart

### Search for plans

```
from diaphora.models import SearchPlansNamespace

# Namespace is one of ALL, DIAPHORA, BARNDOOR, NONE
plans = sdk.store.search_plans(namespace=SearchPlansNamespace.DIAPHORA)
```

### Showing Plan

```
plan = sdk.store.show_plan(str(plans[0].id))
print(plan.name, plan.description)
```

### Create a plan

```
from diaphora.models import PlanRequest, PlanRequestVisibility

new_plan = sdk.store.create_plan(PlanRequest(
    name="Daily Summary",
    description="Summarises activity from the past 24 hours",
    visibility=PlanRequestVisibility.ORGANIZATION,
    labels=["summary", "daily"],
    text="<plan definition>",
))
```

### Search results for a plan

```
from diaphora.models import SearchResultsStatus

results = sdk.store.search_results(
    plan_id=str(plan.id),
    status=SearchResultsStatus.SUCCESS,
)
```

### Run a plan

```
response = sdk.router.run_plan(
    plan_id=str(plan.id),
    parameters={"animal_type": "feline"},
)
```

### Stream a plan execution

```
from diaphora.events import START_PHASE, END_PHASE, RESULT_PHASE, StreamEvent

def handle_event(event: StreamEvent):
    if event.event == START_PHASE:
        print(f"[{event.component}] starting {event.session}...")
    elif event.event == END_PHASE:
        print(f"[{event.component}] done")
    elif event.event == RESULT_PHASE:
        print(event.content["document"])

sdk.router.stream_plan(
    plan_id=str(plan.id),
    on_event=handle_event,
    parameters={"animal_type": "feline"},
)
```

## sdk.store

### Plans

| Method                                                                          | Description                                                                                                              |
| ---------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
| [`search_plans`](/content/docs/sdk/python#search_plans/index.html)`(label, search, namespace, limit, offset)`      | Search plans. `namespace` is one of `ALL`, `DIAPHORA`, `BARNDOOR`, `NONE`                                                |
| [`show_plan`](/content/docs/sdk/python#show_plan/index.html)`(plan_id)`          | Get plan details                                                                                                       |
| [`create_plan`](/content/docs/sdk/python#create_plan/index.html)`(body: PlanRequest)`                  | Create a plan                                                                                                         |
| [`update_plan`](/content/docs/sdk/python#update_plan/index.html)`(plan_id, body: PlanRequest)`                   | Update a plan                                                                                                        |
| [`delete_plan`](/content/docs/sdk/python#delete_plan/index.html)`(plan_id)`    | Delete a plan                                                                                                        |
| [`list_plan_labels`](/content/docs/sdk/python#list_plan_labels/index.html)`()` | List all labels used across plans                                                                                     |

#### `search_plans`

Search plans visible to your organisation. All filters are optional and combinable.

**Parameters**

| Parameter  | Type               | Required | Description                                                     |
| -----------| ------------------| -------- | ---------------------------------------------------------------|
| `label`    | `list[str]`       | No      | Filter to plans that have all of the given labels               |
| `search`   | `str`             | No      | Full-text search across plan names and descriptions             |
| `namespace`| `SearchPlansNamespace` | No   | Filter by namespace — `ALL`, `DIAPHORA`, `BARNDOOR`, or `NONE`. Defaults to `ALL` |
| `limit`    | `int`             | No      | Maximum number of results to return                             |
| `offset`   | `int`             | No      | Number of results to skip (for pagination)                     |

**Returns** `list[PlanSummary] | None` — each item has `.id`, `.name`, `.description`, `.namespace`, `.labels`, `.visibility`, `.parameters`, `.created_at`, `.updated_at`.

**Example**

```
from diaphora.models import SearchPlansNamespace

# All plans
plans = sdk.store.search_plans()

# Filter by namespace and label
plans = sdk.store.search_plans(
    namespace=SearchPlansNamespace.DIAPHORA,
    label=["daily"],
    limit=10,
)
for plan in plans:
    print(plan.name, plan.namespace)
```

#### `show_plan`

Retrieve full details for a single plan, including its definition text and parameter schema.

**Parameters**

| Parameter  | Type      | Required | Description                              |
|------------|-----------|----------|------------------------------------------|
| `plan_id`  | `str`     | Yes      | ID of the plan to retrieve               |

**Returns** `PlanDetails | None` — all fields from `PlanSummary` plus `.text` (plan definition), `.document_template`, and `.resources`.

**Example**

```
plan = sdk.store.show_plan(str(plans[0].id))
print(plan.name, plan.description)
print("Parameters:", [p.name for p in plan.parameters])
```

#### `create_plan`

Create a new plan.

**Parameters**

| Parameter | Type               | Required | Description                         |
|-----------|--------------------|----------|-------------------------------------|
| `body`    | `PlanRequest`      | Yes      | Plan definition — see fields below |

**`PlanRequest` fields**

| Field               | Type      | Required | Description                           |
|---------------------|-----------|----------|---------------------------------------|
| `name`              | `str`     | Yes      | Display name for the plan            |
| `description`       | `str`     | Yes      | Human-readable summary                |
| `visibility`        | `PlanRequestVisibility` | Yes | `NAMESPACE`, `ORGANIZATION`, or `USER` |
| `labels`            | `list[str]`        | Yes      | Tags for grouping and filtering      |
| `text`              | `str`     | Yes      | Plan definition body                 |
| `namespace`         | `str`     | No       | Namespace to assign the plan to      |
| `document_template` | `str`     | No       | Template string for rendering the result document |

**Returns** `PlanDetails | None` — the newly created plan.

**Example**

```
from diaphora.models import PlanRequest, PlanRequestVisibility

new_plan = sdk.store.create_plan(PlanRequest(
    name="Daily Summary",
    description="Summarises activity from the past 24 hours",
    visibility=PlanRequestVisibility.ORGANIZATION,
    labels=["summary", "daily"],
    text="<plan definition>",
))
print(new_plan.id)
```

#### `update_plan`

Overwrite an existing plan's metadata and definition text.

**Parameters**

| Parameter | Type               | Required | Description                                         |
|-----------|--------------------|----------|-----------------------------------------------------|
| `plan_id` | `str`             | Yes      | ID of the plan to update                           |
| `body`    | `PlanRequest`      | Yes      | Updated plan definition — same fields as [`create_plan`](/content/docs/sdk/python#create_plan/index.html) |

**Returns** `PlanDetails | None` — the updated plan.

**Example**

```
from diaphora.models import PlanRequest, PlanRequestVisibility

updated = sdk.store.update_plan(
    plan_id=str(plan.id),
    body=PlanRequest(
        name="Daily Summary v2",
        description="Updated summary plan",
        visibility=PlanRequestVisibility.ORGANIZATION,
        labels=["summary", "daily", "v2"],
        text="<updated plan definition>",
    ),
)
print(updated.name)
```

#### `delete_plan`

Permanently delete a plan. This action cannot be undone.

**Parameters**

| Parameter | Type      | Required | Description                 |
|-----------|-----------|----------|-----------------------------|
| `plan_id` | `str`     | Yes      | ID of the plan to delete     |

**Returns** `Response` — HTTP 204 on success.

**Example**

```
sdk.store.delete_plan(str(plan.id))
```

#### `list_plan_labels`

Return every unique label used across plans in the organisation.

**Returns** `list[str] | None` — alphabetically sorted list of label strings.

**Example**

```
labels = sdk.store.list_plan_labels()
print(labels)  # ["daily", "reports", "summary"]
```

### Results

| Method                                                                          | Description                                                                                                              |
| ---------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
| [`search_results`](/content/docs/sdk/python#search_results/index.html)`(search, status, limit, offset, start, end, plan_id)`      | Search results. `status` is `SUCCESS` or `ERROR`                                                                         |
| [`show_results`](/content/docs/sdk/python#show_results/index.html)`(results_id)`          | Get result details                                                                                                       |
| [`delete_results`](/content/docs/sdk/python#delete_results/index.html)`(results_id)`    | Delete a result                                                                                                        |
| [`list_results_stats`](/content/docs/sdk/python#list_results_stats/index.html)`(start, end)` | Get result statistics                                                                                                      |

#### `search_results`

Search execution results with optional filters. All parameters are optional and combinable.

**Parameters**

| Parameter | Type                 | Required | Description                                 |
|-----------|----------------------|----------|---------------------------------------------|
| `search`  | `str`                | No       | Full-text search across result data         |
| `status`  | `SearchResultsStatus` | No       | Filter by outcome — `SUCCESS` or `ERROR`   |
| `limit`   | `int`                | No       | Maximum number of results to return         |
| `offset`  | `int`                | No       | Number of results to skip (for pagination) |
| `start`   | `datetime`           | No       | Return only results created at or after this time |
| `end`     | `datetime`           | No       | Return only results created at or before this time |
| `plan_id` | `str`                | No       | Filter to results from a specific plan     |

**Returns** `list[ResultSummary] | None` — each item has `.id`, `.plan_id`, `.plan_name`, `.created_at`.

**Example**

```
from diaphora.models import SearchResultsStatus

# All results for a plan
results = sdk.store.search_results(plan_id=str(plan.id))

# Successful results only, newest 20
recent = sdk.store.search_results(
    plan_id=str(plan.id),
    status=SearchResultsStatus.SUCCESS,
    limit=20,
)
```

#### `show_results`

Retrieve full details for a single result, including the execution output.

**Parameters**

| Parameter     | Type      | Required | Description                               |
|---------------|-----------|----------|-------------------------------------------|
| `results_id`  | `str`     | Yes      | ID of the result to retrieve               |

**Returns** `Response[ResultDetails]` — access the parsed object via `.parsed`. `ResultDetails` has `.id`, `.plan_id`, `.plan_name`, `.created_at`, `.data` (`ExecuteResponseBody`), and `.error`.

**Example**

```
response = sdk.store.show_results(str(results[0].id))
result = response.parsed
print(result.plan_name, result.created_at)
print(result.data.document)
```

#### `delete_results`

Permanently delete a result record. This action cannot be undone.

**Parameters**

| Parameter     | Type      | Required | Description               |
|---------------|-----------|----------|---------------------------|
| `results_id`  | `str`     | Yes      | ID of the result to delete |

**Returns** `Response` — HTTP 204 on success.

**Example**

```
sdk.store.delete_results(str(result.id))
```

#### `list_results_stats`

Return a time-ordered list of result metadata records, optionally bounded by a date range. Useful for building audit logs or activity dashboards.

**Parameters**

| Parameter | Type      | Required | Description                               |
|-----------|-----------|----------|-------------------------------------------|
| `start`   | `datetime` | No       | Return only stats at or after this time  |
| `end`     | `datetime` | No       | Return only stats at or before this time |

**Returns** `list[Stat] | None` — each item has `.result_id`, `.plan_id`, `.plan_name`, `.created_at`.

**Example**

```
from datetime import datetime, timezone

stats = sdk.store.list_results_stats()
for stat in stats:
    print(f"{stat.plan_name} — {stat.created_at}")

# Last 7 days only
from datetime import timedelta
week_ago = datetime.now(timezone.utc) - timedelta(days=7)
recent_stats = sdk.store.list_results_stats(start=week_ago)
```

### Public Links

| Method                                                                          | Description                                                                                                              |
| ---------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
| [`list_public_links`](/content/docs/sdk/python#list_public_links/index.html)`(results_id)` | List public links for a result                                                                                         |
| [`create_public_link`](/content/docs/sdk/python#create_public_link/index.html)`(results_id, body: PublicLinkRequest)` | Create a public link. `expires_in` accepts values like "7d" or "24h"                                              |
| [`delete_public_link`](/content/docs/sdk/python#delete_public_link/index.html)`(results_id, public_link_id)` | Delete a public link                                                                                                   |
| [`show_result_public_link`](/content/docs/sdk/python#show_result_public_link/index.html)`(public_link_id)` | Fetch a result via public link (no auth required)                                                                    |

#### `list_public_links`

List all public share links associated with a result.

**Parameters**

| Parameter      | Type    | Required | Description                                      |
|----------------|---------|----------|--------------------------------------------------|
| `results_id`   | `str`   | Yes      | ID of the result whose links to list              |

**Returns** `list[PublicLinkSummary] | None` — each item has `.id`, `.label`, `.created_at`, `.expires_at`, `.ref_id`, `.ref_type`.

**Example**

```
links = sdk.store.list_public_links(str(result.id))
for link in links:
    print(link.label, "expires:", link.expires_at)
```

#### `create_public_link`

Create a time-limited, unauthenticated share link for a result. Anyone with the link ID can fetch the result via [`show_result_public_link`](/content/docs/sdk/python#show_result_public_link/index.html) without credentials.

**Parameters**

| Parameter      | Type                       | Required | Description                                                       |
|----------------|----------------------------|----------|-------------------------------------------------------------------|
| `results_id`   | `str`                       | Yes      | ID of the result to share                                          |
| `body`         | `PublicLinkRequest`         | Yes      | Link configuration — see fields below                            |

**`PublicLinkRequest` fields**

| Field         | Type      | Required | Description                                   |
|---------------|-----------|----------|-----------------------------------------------|
| `expires_in`  | `str`     | Yes      | How long until the link expires, e.g. "7d", "24h", "30m" |
| `label`       | `str`     | Yes      | Human-readable name for the link             |

**Returns** `PublicLinkSummary | None` — the created link, including its `.id` and `.expires_at`.

**Example**

```
from diaphora.models import PublicLinkRequest

link = sdk.store.create_public_link(
    results_id=str(result.id),
    body=PublicLinkRequest(expires_in="7d", label="Weekly Report"),
)
print(f"Share link ID: {link.id}, expires: {link.expires_at}")
```

#### `delete_public_link`

Revoke a public share link immediately. Any subsequent requests using this link ID will fail.

**Parameters**

| Parameter      | Type    | Required | Description                                   |
|----------------|---------|----------|-----------------------------------------------|
| `results_id`   | `str`   | Yes      | ID of the result the link belongs to          |
| `public_link_id` | `str`   | Yes      | ID of the link to revoke                       |

**Returns** `Response` — HTTP 204 on success.

**Example**

```
sdk.store.delete_public_link(str(result.id), str(link.id))
```

#### `show_result_public_link`

Fetch a result via its public share link. Does not require authentication — useful for embedding results in external applications.

**Parameters**

| Parameter      | Type    | Required | Description                                 |
|----------------|---------|----------|---------------------------------------------|
| `public_link_id` | `str`   | Yes      | ID of the public link                       |

**Returns** `Response[ResultDetails]` — access the parsed object via `.parsed`.

**Example**

```
response = sdk.store.show_result_public_link(str(link.id))
result = response.parsed
print(result.data.document)
```

### Tools & Schema

| Method                                                                          | Description                                                                                                              |
| ---------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
| [`list_tools`](/content/docs/sdk/python#list_tools/index.html)`()`                  | List available tools                                                                                                    |
| [`show_tools`](/content/docs/sdk/python#show_tools/index.html)`(tool_id)`          | Get tool details                                                                                                       |
| [`show_default_tools`](/content/docs/sdk/python#show_default_tools/index.html)`()` | Get default tools                                                                                                      |
| [`get_frags_schema`](/content/docs/sdk/python#get_frags_schema/index.html)`()`    | Get the Frags JSON schema                                                                                              |

#### `list_tools`

List all tool sets available in the organisation.

**Returns** `list[ToolsSummary] | None` — each item has `.id`, `.name`, `.default`.

**Example**

```
tools = sdk.store.list_tools()
for t in tools:
    print(t.name, "(default)" if t.default else "")
```

#### `show_tools`

Get full details of a tool set, including its MCP servers, API CP servers, and collections.

**Parameters**

| Parameter     | Type      | Required | Description                              |
|---------------|-----------|----------|------------------------------------------|
| `tool_id`     | `str`     | Yes      | ID of the tool set to retrieve           |

**Returns** `Response[ToolsDetails]` — access via `.parsed`. `ToolsDetails` has `.id`, `.name`, `.default`, `.mcp_servers`, `.api_cps`, `.collections`.

**Example**

```
response = sdk.store.show_tools(str(tools[0].id))
details = response.parsed
print(f"{details.name} — {len(details.mcp_servers)} MCP servers")
```

#### `show_default_tools`

Get the organisation's default tool set.

**Returns** `Response[ToolsDetails]` — access via `.parsed`.

**Example**

```
response = sdk.store.show_default_tools()
default = response.parsed
if default:
    print(f"Default tool set: {default.name}")
```

#### `get_frags_schema`

Retrieve the Frags JSON schema that describes the plan definition format. Useful for validating plan text before submission.

**Returns** `Response` — the raw JSON schema is in `.content`.

**Example**

```
import json

response = sdk.store.get_frags_schema()
schema = json.loads(response.content)
```

## sdk.router

### Plan Execution

| Method                                                                          | Description                                                                                                              |
| ---------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
| [`run_plan`](/content/docs/sdk/python#run_plan/index.html)`(plan_id, parameters)` | Execute a plan synchronously                                                                                             |
| [`stream_plan`](/content/docs/sdk/python#stream_plan/index.html)`(plan_id, on_event, parameters)` | Execute a plan and receive SSE events via callback                                                                      |

`stream_plan` delivers `StreamEvent` objects to the callback:

| `event.event` | Additional fields                     | Description                             |
|---------------|--------------------------------------|-----------------------------------------|
| `START_PHASE` | `component`, `session`              | A component started                      |
| `END_PHASE`   | `component`                          | A component finished                     |
| `RESULT_PHASE`| `content`                            | Result payload — `content["document"]` holds the output text |

#### `run_plan`

Execute a plan synchronously and block until the result is ready.

**Parameters**

| Parameter     | Type      | Required | Description                                   |
|---------------|-----------|----------|-----------------------------------------------|
| `plan_id`     | `str`     | Yes      | ID of the plan to execute                    |
| `parameters`  | `dict`    | No       | Key/value pairs matching the plan's parameter definitions |

**Returns** `ExecuteResponseBody` with fields:

| Field         | Type      | Description                                      |
|---------------|-----------|--------------------------------------------------|
| `.result`     | `ExecuteResponseBodyResult` | Structured output produced by the plan — call `.to_dict()` for a plain dict |
| `.document`   | `str | None` | Rendered document string, if the plan has a document template |
| `.warnings`   | `list[str]` | Non-fatal warnings emitted during execution     |

**Example**

```
response = sdk.router.run_plan(
    plan_id=str(plan.id),
    parameters={"animal_type": "feline"},
)
print(response.result)
if response.document:
    print(response.document)
if response.warnings:
    print("Warnings:", response.warnings)
```

#### `stream_plan`

Execute a plan and receive progress events in real time via Server-Sent Events. The optional `on_event` callback is invoked for each event as it arrives. Returns the final result content when the stream ends.

**Parameters**

| Parameter     | Type      | Required | Description                                   |
|---------------|-----------|----------|-----------------------------------------------|
| `plan_id`     | `str`     | Yes      | ID of the plan to execute                    |
| `on_event`    | `Callable[[StreamEvent], None]` | No       | Callback invoked for each SSE event        |
| `parameters`  | `dict`    | No       | Key/value pairs matching the plan's parameter definitions |

**Returns** `dict | None` — the `content` dict from the `RESULT_PHASE` event, or `None` if the stream ended without a result. `content["document"]` holds the rendered output text.

**Example**

```
from diaphora.events import START_PHASE, END_PHASE, RESULT_PHASE, StreamEvent

content = sdk.router.stream_plan(
    plan_id=str(plan.id),
    on_event=handle_event,
    parameters={"animal_type": "feline"},
)
```

### MCP Tools

| Method                                                                          | Description                                                                                                              |
| ---------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
| [`check_plan_mcp_requirements`](/content/docs/sdk/python#check_plan_mcp_requirements/index.html)`(plan_id)` | Check which MCP servers a plan needs and their auth status                                                             |
| [`refresh_plan_mcp_requirements`](/content/docs/sdk/python#refresh_plan_mcp_requirements/index.html)`(plan_id)` | Force-refresh MCP requirement status                                                                                  |
| [`check_tool_mcp_requirements`](/content/docs/sdk/python#check_tool_mcp_requirements/index.html)`()` | Check global MCP requirements                                                                                          |
| [`list_tool_commands`](/content/docs/sdk/python#list_tool_commands/index.html)`(tools_id, server_id)` | List commands on an MCP server                                                                                         |
| [`call_tool_command`](/content/docs/sdk/python#call_tool_command/index.html)`(tools_id, server_id, command_name)` | Execute an MCP command                                                                                                  |
| [`list_mcp_auth_cache`](/content/docs/sdk/python#list_mcp_auth_cache/index.html)`()` | List cached MCP OAuth tokens                                                                                            |
| [`delete_mcp_auth_cache`](/content/docs/sdk/python#delete_mcp_auth_cache/index.html)`(cache_id)` | Revoke a cached MCP token                                                                                               |
| [`mcp_callback`](/content/docs/sdk/python#mcp_callback/index.html)`(state, code)` | Handle an OAuth redirect callback                                                                                       |
| [`render_template`](/content/docs/sdk/python#render_template/index.html)`(body: RenderTemplate)` | Render a template                                                                                                      |

#### `check_plan_mcp_requirements`

List the MCP servers required by a plan and whether each one is currently authenticated.

**Parameters**

| Parameter   | Type      | Required | Description                                   |
|-------------|-----------|----------|-----------------------------------------------|
| `plan_id`   | `str`     | Yes      | ID of the plan to inspect                     |

**Returns** `list[McpRequirement] | None` — each item has `.name`, `.status` (`ready` or `not_ready`), `.url` (the authentication URL when `not_ready`), `.authentication_method`.

**Example**

```
requirements = sdk.router.check_plan_mcp_requirements(str(plan.id))
for req in requirements:
    print(f"{req.name}: {req.status}")
    if req.status == "not_ready":
        print(f"  Authenticate at: {req.url}")
```

#### `refresh_plan_mcp_requirements`

Force re-evaluation of which MCP servers a plan needs and refresh their authentication status. Call this after completing an OAuth flow to confirm that requirements are now satisfied.

**Parameters**

| Parameter   | Type      | Required | Description                                   |
|-------------|-----------|----------|-----------------------------------------------|
| `plan_id`   | `str`     | Yes      | ID of the plan whose requirements to refresh |

**Returns** `list[McpServer] | None` — the updated list of MCP servers for the plan, each with `.id`, `.name`, `.url`, `.authentication_method`.

**Example**

```
servers = sdk.router.refresh_plan_mcp_requirements(str(plan.id))
for server in servers:
    print(f"{server.name}: {server.url}")
```

#### `check_tool_mcp_requirements`

List MCP authentication requirements across all tool sets in the organisation. Equivalent to [`check_plan_mcp_requirements`](/content/docs/sdk/python#check_plan_mcp_requirements/index.html) but not scoped to a single plan.

**Returns** `list[McpRequirement] | None`

**Example**

```
reqs = sdk.router.check_tool_mcp_requirements()
if reqs:
    not_ready = [r for r in reqs if r.status == "not_ready"]
    print(f"{len(not_ready)} servers need authentication")
```

#### `list_tool_commands`

List the commands exposed by a specific MCP server within a tool set.

**Parameters**

| Parameter     | Type      | Required | Description                               |
|---------------|-----------|----------|-------------------------------------------|
| `tools_id`    | `str`     | Yes      | ID of the tool set                        |
| `server_id`   | `str`     | Yes      | ID of the MCP server within that tool set |

**Returns** `list[McpCommand] | None` — each item describes a callable command on the MCP server.

**Example**

```
commands = sdk.router.list_tool_commands(
    tools_id=str(tools[0].id),
    server_id=str(server.id),
)
for cmd in commands:
    print(cmd.name)
```

#### `call_tool_command`

Execute a single command on an MCP server.

**Parameters**

| Parameter      | Type    | Required | Description                                  |
|----------------|---------|----------|----------------------------------------------|
| `tools_id`     | `str`   | Yes      | ID of the tool set                          |
| `server_id`    | `str`   | Yes      | ID of the MCP server                        |
| `command_name` | `str`   | Yes      | Name of the command to execute              |

**Returns** `CallToolCommandResponse200 | None`

**Example**

```
result = sdk.router.call_tool_command(
    tools_id=str(tools[0].id),
    server_id=str(server.id),
    command_name="list_files",
)
```

#### `list_mcp_auth_cache`

List all cached MCP OAuth tokens for the current user.

**Returns** `list[AuthCache] | None` — each item has `.id`, `.domain`, `.created_at`, `.expiry`, and optionally `.client_id`.

**Example**

```
cache = sdk.router.list_mcp_auth_cache()
for entry in cache:
    print(f"{entry.domain} — expires {entry.expiry}")
```

#### `delete_mcp_auth_cache`

Revoke a cached MCP OAuth token. The next request to that MCP server will require re-authentication.

**Parameters**

| Parameter   | Type    | Required | Description                                |
|-------------|---------|----------|--------------------------------------------|
| `cache_id`  | `str`   | Yes      | ID of the cache entry to revoke            |

**Returns** `None`

**Example**

```
cache = sdk.router.list_mcp_auth_cache()
sdk.router.delete_mcp_auth_cache(str(cache[0].id))
```

#### `mcp_callback`

Handle an OAuth redirect callback from an MCP server after the user completes authentication. Typically called from a redirect endpoint in your web application.

**Parameters**

| Parameter | Type      | Required | Description                                   |
|-----------|-----------|----------|-----------------------------------------------|
| `state`   | `str`     | Yes      | The `state` query parameter from the OAuth redirect |
| `code`    | `str`     | Yes      | The `code` query parameter from the OAuth redirect  |

**Returns** `Response`

**Example**

```
# In a web framework route handler:
# GET /mcp/callback?state=...&code=...
sdk.router.mcp_callback(state=request.args["state"], code=request.args["code"])  
```

#### `render_template`

Render a Diaphora template string against a given scope.

**Parameters**

| Parameter       | Type                | Required | Description                             |
|------------------|--------------------|----------|-----------------------------------------|
| `body`           | `RenderTemplate`    | Yes      | Template and scope — see fields below |

**`RenderTemplate` fields**

| Field        | Type      | Required | Description                                       |
|--------------|-----------|----------|-------------------------------------------------|
| `template`   | `str`     | Yes      | The template string to render                    |
| `scope`      | `RenderTemplateScope` | Yes      | Scope context — construct with `RenderTemplateScope()` and set attributes via dictionary access |

**Returns** `Response` — the rendered output is in `.content`.

**Example**

```
from diaphora.models import RenderTemplate
from diaphora.services.frags_router_open_api_client.models import RenderTemplateScope

scope = RenderTemplateScope()
scope["name"] = "Rayna"
response = sdk.router.render_template(
    RenderTemplate(template="Hello {{name}}", scope=scope)
)
print(response.content)
```
