---
title: "MCP Plugin"
description: "Exposes the content as an MCP (Model Context Protocol) server for AI agents."
type: "docs"
category: "doc"
tags: []
authors: [Anonymous]
date: "2026-08-21"
last_update: "2026-08-21"
time_minutes: 4
draft: false
unlisted: false
url: "https://www.derafu.dev/docs/ui/content/mcp"
---

# MCP plugin

Exposes the content of the website as an [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server, so AI agents (Claude Code, Claude Desktop, Cursor, claude.ai connectors, etc.) can search and fetch it directly as tools during their own conversation — instead of relying on stale training data or scraping HTML.

This is a different consumption channel than the [API](./api) plugin: the API plugin is a one-shot bulk export meant for an indexing pipeline (Qdrant, etc.); the MCP plugin is a live, callable interface for any MCP-capable agent.

## Endpoint

A single route, `POST /api/mcp` (also accepts `DELETE` to end a session, and `OPTIONS` for CORS preflight). It deliberately has **no `.json` suffix**: unlike every other endpoint in this package, its response is not always JSON — it can also be `text/event-stream` (SSE) when the protocol needs to keep the connection open mid-call. The MCP protocol negotiates that per-request via the `Accept` header, not via the URL, and every real MCP client already expects a plain endpoint URL with no extension.

It speaks JSON-RPC 2.0 over the "Streamable HTTP" transport of the [official PHP MCP SDK](https://github.com/modelcontextprotocol/php-sdk) (`mcp/sdk`), synchronously — there is no event loop involved, it fits the same request/response model as every other controller in this package.

## Configuration (`services.yaml`)

Enabled under `derafu.content.config.plugins.mcp`:

```yaml
parameters:
    derafu.content.config:
        plugins:
            mcp:
              ask:
                enabled: true
```

| Option | Type | Default | Description |
|---|---|---|---|
| `server_name` | string | `derafu-content` | Name announced to MCP clients during the `initialize` handshake. |
| `server_version` | string | `1.0.0` | Version announced to MCP clients during the `initialize` handshake. |
| `session_path` | string | a subdirectory of `sys_get_temp_dir()` | Directory where MCP sessions are persisted on disk between requests (a session is created on `initialize` and referenced by later calls via the `Mcp-Session-Id` header; since each request builds a new server instance, this cannot live in memory). Point it at something under the website's `var/` directory for a more durable/cleanable location. |
| `session_ttl` | int | `3600` | Time to live, in seconds, of a persisted session. |
| `ask.enabled` | bool | `false` | Whether the `ask` tool (LLM-backed conversational answers) is registered. See [Tools](#content-tools) below. |

This plugin does not handle authentication nor rate limiting — that is not its responsibility. If the endpoint needs to be protected, do it at the HTTP stack level (e.g. with the middlewares of `derafu/http`), before this controller is reached.

### The `mcp/sdk` dependency

`mcp/sdk` is a `require-dev` dependency (with a `suggest` entry) of `derafu/content`, not a hard `require`. If you enable this plugin, add it explicitly to **your own** `composer.json`:

```json
{
    "require": {
        "mcp/sdk": "^0.7"
    }
}
```

If you don't enable the plugin, nothing needs it: hitting `/api/mcp` without it installed and without the plugin configured fails with a normal "plugin not found" error, exactly like hitting `/api/search.json` without the `search` plugin configured — it does not affect any other endpoint of the website.

## Tools

| Tool | Always registered? | Description |
|---|---|---|
| `search_content` | yes | Semantic search across the indexed content, delegating to the [Search](./search) plugin's engine (Qdrant, or whatever is configured there). Accepts an optional `source` (`academy`, `blog`, `docs`, `faq`, `pages`, or `all`) to scope the search; omitting it or passing `all` searches every source at once. |
| `get_content` | yes | Fetch a single item by source and URI: full Markdown body plus metadata (title, tags, dates, authors). |
| `list_content` | yes | Browse/filter the items of a source (academy, blog, docs, faq, pages), optionally by tag, category or free-text search. |
| `list_tags` | yes | List the tags used in a source, with how many items use each one. |
| `ask` | only if `ask.enabled: true` | Ask a natural-language question and get a conversational answer from the LLM configured in the [Search](./search) plugin, grounded on the indexed content. |

`ask` is opt-in because, unlike the other four tools (which only ever read from the indexed content), it depends entirely on the quality of whatever LLM backend answers it — a bad or failed answer there makes the whole MCP server look unreliable, even though the rest of the tools never touch an LLM at all.

## Content frontmatter

Not applicable — this plugin has no content items of its own, it reuses whichever content plugins (Academy, Blog, Docs, FAQ, Pages) are already enabled.



---
Last updated on 21/08/2026

