# How Capture Adapters Are Declared in Claude-Obsidian: The Complete Guide

> Discover how capture adapters are declared in claude-obsidian using the centralized adapters.json manifest. Learn about adapter states, input constraints, and execution requirements for seamless integration.

- Repository: [Agrici.Daniel/claude-obsidian](https://github.com/AgriciDaniel/claude-obsidian)
- Tags: how-to-guide
- Published: 2026-08-29

---

**Capture adapters in claude-obsidian are declared in a centralized JSON manifest at [`config/adapters.json`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/config/adapters.json) that follows the `claude-obsidian.capture-adapters.v1` schema, defining each adapter's maturity state, input constraints, and execution requirements.**

The claude-obsidian project uses a declarative configuration system to govern how content is ingested from diverse sources. By understanding how capture adapters are declared, developers can extend the tool to support new file formats and external resources without modifying the core runtime logic.

## The Adapter Manifest Structure

All capture adapter definitions reside in the single JSON file located at [`config/adapters.json`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/config/adapters.json). This manifest follows the schema **`claude-obsidian.capture-adapters.v1`** and establishes a **privacy default** alongside structured metadata that describes adapter capabilities and security constraints.

### Maturity States

The manifest classifies each adapter into one of three maturity states that indicate implementation completeness:

- **`implemented`** – Runs deterministically inside the portable standard-library core without external dependencies.
- **`metadata-only`** – The core recognizes the input type and can describe it, but does not extract full content.
- **`external-runner-required`** – Execution requires a separate, consent-gated runner (for example, network fetches).

### Required and Optional Fields

Each adapter entry in the manifest is a JSON object containing the following fields:

| Field | Description |
|-------|-------------|
| `id` | Symbolic identifier used on the CLI (e.g., `filesystem`, `url`). |
| `maturity` | One of the three maturity states defined above. |
| `input` | Type of input expected: `local-file` or `https-url`. |
| `execution` | Runtime model: `internal` or `planned-command`. |
| `network` | Boolean indicating if network access is required. |
| `llm` | Boolean indicating if the adapter calls an LLM directly. |
| `destructive` | Boolean indicating if the adapter can modify source data. |
| `extensions` *(optional)* | Array of accepted file extensions (e.g., `[".md", ".txt"]`). |
| `runner_capability` *(optional)* | Capability identifier required from external runners (e.g., `fetch-https-with-pinned-dns-and-redirect-policy`). |
| `approved_hosts` *(optional)* | Whitelist of hostnames for URL adapters (e.g., `["youtube.com", "youtu.be"]`). |

## Loading Adapters at Runtime

The core reads the manifest at startup using the project's generic JSON loader. In **[`claude_obsidian/hook_adapter.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/hook_adapter.py)**, these definitions are exposed to the Claude Code lifecycle, managing context feeding and runtime status.

When a user invokes a capture command, the CLI entry point in **[`scripts/claude-obsidian.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/scripts/claude-obsidian.py)** looks up the requested `id` in the manifest, validates the supplied input against the declared `extensions` or URL schema, and dispatches either built-in logic (`internal`) or schedules a planned external command using the declared `runner_capability`.

```python
import json
from pathlib import Path

ADAPTERS_PATH = Path(__file__).parent.parent / "config" / "adapters.json"

with ADAPTERS_PATH.open() as f:
    manifest = json.load(f)

# Build a dict keyed by adapter id for quick lookup

adapters = {a["id"]: a for a in manifest["adapters"]}

# Example: retrieve the filesystem adapter definition

fs_adapter = adapters["filesystem"]
print(fs_adapter["extensions"])

# → ['.md', '.markdown', '.txt', '.json', …]

```

## Declaring a New Capture Adapter

To add support for a new input type, append an adapter object to the `"adapters"` array in [`config/adapters.json`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/config/adapters.json). The CLI automatically recognizes new entries without requiring code changes.

For example, to add support for `.mdx` files:

```json
{
  "id": "mdx",
  "maturity": "implemented",
  "input": "local-file",
  "execution": "internal",
  "network": false,
  "llm": false,
  "destructive": false,
  "extensions": [".mdx"]
}

```

After adding this configuration, the command `claude-obsidian capture --adapter mdx ./file.mdx` will function immediately.

## CLI Usage and Execution Models

The manifest drives the capture pipeline by determining whether to execute logic internally or delegate to an external runner.

```bash

# Capture a local markdown file using the built-in filesystem adapter

claude-obsidian capture --adapter filesystem /path/to/notes.md

# Capture a YouTube video's metadata via the external runner

claude-obsidian capture --adapter youtube https://youtu.be/dQw4w9WgXcQ

```

Adapters with `"execution": "internal"` run within the standard library core, while those marked `"external-runner-required"` trigger the consent-gated runner system defined by their `runner_capability` field.

## Summary

- Capture adapters are declared centrally in [`config/adapters.json`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/config/adapters.json) using the schema `claude-obsidian.capture-adapters.v1`.
- Each adapter specifies a **maturity state** (`implemented`, `metadata-only`, or `external-runner-required`) that determines runtime behavior.
- Required fields include `id`, `input`, `execution`, and boolean flags for `network`, `llm`, and `destructive` capabilities.
- The core loads the manifest at runtime via [`claude_obsidian/hook_adapter.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/hook_adapter.py), while [`scripts/claude-obsidian.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/scripts/claude-obsidian.py) handles CLI argument parsing and dispatch.
- New adapters require only JSON configuration changes, not core code modifications, enabling rapid extension of supported input types.

## Frequently Asked Questions

### Where exactly are capture adapters declared in claude-obsidian?

Capture adapters are declared in the JSON manifest file located at [`config/adapters.json`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/config/adapters.json) in the repository root. This single file serves as the definitive registry for all adapter capabilities and constraints.

### What determines whether an adapter runs internally or externally?

The `execution` field in the adapter declaration controls this behavior. Value `internal` routes execution through the standard library core, while `planned-command` defers to an external runner identified by the optional `runner_capability` field.

### Can I add support for new file extensions without modifying Python code?

Yes. Append a new adapter object to the `"adapters"` array in [`config/adapters.json`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/config/adapters.json) with the appropriate `extensions` list and `maturity` state. The CLI in [`scripts/claude-obsidian.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/scripts/claude-obsidian.py) automatically detects and loads new adapters from the manifest without requiring code changes.

### What is the difference between `metadata-only` and `implemented` maturity states?

`metadata-only` adapters recognize input types and describe them but do not extract full content, functioning as placeholders or partial implementations. `implemented` adapters run deterministically within the core and perform complete content extraction.