Skip to content

In your agent

Sluicer’s MCP server is one command, and every client that speaks MCP can start it:

uvx --with "sluicer[mcp]" sluicer mcp

It needs uv on the PATH; uvx fetches Sluicer and its mcp extra, which brings fetching and markdown, the first time. sluicer-mcp, after uv pip install "sluicer[mcp]", is the same server.

Every one of its twelve tools only reads – the page it is given, or the web – and says so in its MCP annotations (readOnlyHint, not destructiveHint), so a client that asks before a tool writes runs them without asking. Measured with Codex 0.144.4 on 2026-09-24: before the annotations, codex exec cancelled the call unless the server’s tools were approved in advance; with them, it runs the tool in its default mode, in writes and in auto.

Each tool a client registers costs its agent context, called or not. To offer only some, name them: sluicer mcp --tools extract_declared,page_markdown, or SLUICER_MCP_TOOLS=extract_declared,page_markdown for a client that sets variables rather than arguments. A name that is not a tool stops the server with the list of them, and exit code 2. Where the scripts directory is not on the PATH, python -m sluicer mcp starts the same server.

The server refuses to fetch localhost, a private network or a cloud’s metadata endpoint unless it is started with SLUICER_ALLOW_PRIVATE=1.

Claude Code

claude mcp add sluicer -- uvx --with "sluicer[mcp]" sluicer mcp

Or install the repository as a plugin, which brings the server and a skill that tells the agent when to reach for it:

claude plugin marketplace add Gi0tto/sluicer
claude plugin install sluicer@sluicer

Verified end to end on 2026-09-24 with Claude Code 2.1.281: given the published server, claude -p called extract_declared and answered a page’s price, its place and the conflict with the page’s second price; the plugin, loaded with --plugin-dir, brought the server and the skill; and claude plugin validate passes the repository’s manifests. Installing from the marketplace was not run.

Codex

codex mcp add sluicer -- uvx --with "sluicer[mcp]" sluicer mcp

This writes the server into ~/.codex/config.toml:

[mcp_servers.sluicer]
command = "uvx"
args = ["--with", "sluicer[mcp]", "sluicer", "mcp"]

Codex reads skills in the open Agent Skills format from ~/.agents/skills/ and from .agents/skills/ in a repository. Sluicer’s skill keeps to that format: the standard’s own validator passes it, and a test in the suite holds it to the fields the standard allows.

git clone --depth 1 https://github.com/Gi0tto/sluicer /tmp/sluicer
mkdir -p ~/.agents/skills && cp -r /tmp/sluicer/skills/sluicer ~/.agents/skills/

Each release from 0.8.0 also carries the skill as sluicer-skill-VERSION.zip, with the sluicer folder at its root: unzip it into ~/.agents/skills/, or upload it to claude.ai as a custom skill.

Verified end to end on 2026-09-24 with codex-cli 0.144.4: Codex called extract_declared and answered a page’s price with its source and place.

As an Agent Plugin

The repository is also a plugin in the open Agent Plugins 1.0 format: plugin.json and mcp.json at its root, and the skill in skills/. VS Code, GitHub Copilot, Cursor and Codex load that format, and each gets the server, started as uvx --with "sluicer[mcp]==VERSION" sluicer mcp at the plugin’s own version, and the skill. It needs uv on the PATH, as the commands above do.

  • Codex: codex plugin marketplace add Gi0tto/sluicer, then codex plugin add sluicer@sluicer.
  • VS Code: run Chat: Install Plugin From Source and give it https://github.com/Gi0tto/sluicer.
  • Cursor: copy the repository into ~/.cursor/plugins/local/sluicer and reload the window.

Run on 2026-09-25 from a local copy of the repository, not from GitHub: codex-cli 0.157.0 installed the plugin at its version and registered the server with that command, and Claude Code 2.1.282, given the same folder, still loaded its own plugin from .claude-plugin/, server and skill. The official schemas pass both files. VS Code and Cursor were not run.

Other clients

Gemini CLI was run here; the others are written from each client’s own documentation, as it read on 2026-09-24, and were not. Each starts the same command.

Cursor – .cursor/mcp.json in a project, or ~/.cursor/mcp.json:

{
  "mcpServers": {
    "sluicer": {
      "type": "stdio",
      "command": "uvx",
      "args": ["--with", "sluicer[mcp]", "sluicer", "mcp"]
    }
  }
}

VS Code – .vscode/mcp.json in a workspace, or the user profile’s mcp.json:

{
  "servers": {
    "sluicer": {
      "command": "uvx",
      "args": ["--with", "sluicer[mcp]", "sluicer", "mcp"]
    }
  }
}

or, from a terminal:

code --add-mcp '{"name":"sluicer","command":"uvx","args":["--with","sluicer[mcp]","sluicer","mcp"]}'

Gemini CLI – run on 2026-09-24:

gemini mcp add -s user sluicer uvx --with "sluicer[mcp]" sluicer mcp

writes this into ~/.gemini/settings.json (or .gemini/settings.json in a project, without -s user), and gemini mcp list then shows the server connected. Gemini CLI starts MCP servers only in a folder it trusts: in one it does not, the server is listed as disabled.

{
  "mcpServers": {
    "sluicer": {
      "command": "uvx",
      "args": ["--with", "sluicer[mcp]", "sluicer", "mcp"]
    }
  }
}

Claude Desktop – each release from 0.8.0 carries sluicer-VERSION.mcpb, a bundle in the MCPB format: open it with Claude Desktop to install the server. It is 134 KB and holds no Python; its manifest has the host install sluicer[mcp] at that version from PyPI with uv, and its two settings are the variables at the top of this page, SLUICER_MCP_TOOLS and SLUICER_ALLOW_PRIVATE. The release checks it with the format’s own validator, then unpacks it and lists its tools with uv, as the manifest starts it. Installing it in Claude Desktop was not run.

Or by hand: claude_desktop_config.json, in ~/Library/Application Support/Claude/ on macOS and %APPDATA%\Claude\ on Windows, with the same mcpServers entry as Gemini CLI’s. An application started from the Dock may not see your shell’s PATH; if uvx is not found, write its full path, which which uvx prints.

Zed – settings.json:

{
  "context_servers": {
    "sluicer": {
      "command": "uvx",
      "args": ["--with", "sluicer[mcp]", "sluicer", "mcp"],
      "env": {}
    }
  }
}

In Docker – the image’s default command is the MCP server over stdio, so a client starts it with -i:

{
  "mcpServers": {
    "sluicer": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "ghcr.io/gi0tto/sluicer"]
    }
  }
}

Each release from 0.8.0 publishes the image, for amd64 and arm64, after a client has listed its tools from it; the HTTP API page says what it carries.

Anything else that speaks MCP – over stdio, the command above.

Over HTTP: n8n and Dify

n8n and Dify do not start MCP servers over stdio; they connect to one over HTTP. sluicer serve (the api extra) is that server at /mcp, over MCP’s streamable HTTP transport, with the same tools:

uv tool install "sluicer[api]"
export SLUICER_API_TOKEN="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')"
sluicer serve --host 0.0.0.0     # beyond loopback, so it needs the token

On one machine with no container in between, sluicer serve alone listens on 127.0.0.1:8000 with no token. A container’s 127.0.0.1 is its own, so for n8n or Dify in Docker the server listens beyond loopback, with the token, and the address names the host as the container sees it (host.docker.internal with Docker Desktop), or Sluicer runs as a container on the same network. The HTTP API has the details and what the server refuses.

n8n – the MCP Client Tool node, on an AI Agent’s Tool input, or the MCP Client node for one call as a step:

Field Value
Endpoint URL http://host.docker.internal:8000/mcp
Server Transport HTTP Streamable
Authentication Bearer Auth, a credential whose Bearer Token is the token
Options > Timeout 130000, above the server’s 120-second budget; the node waits 60000 ms by default

Dify – Tools > MCP > Add MCP Server (HTTP):

Field Value
Server URL http://host.docker.internal:8000/mcp
Name, Server Identifier sluicer
Advanced Options > Custom Headers Authorization: Bearer <token>
Advanced Options > Timeouts above the server’s 120-second budget

Neither was run here; both are written from their documentation as it read on 2026-09-25. What was run, against sluicer serve, is the mcp Python SDK’s client and the TypeScript SDK’s, which n8n’s MCP nodes are built on: both listed the ten tools it had before select_values and extract_many and called them.

The documentation, for an agent

The site serves llms.txt in llmstxt.org’s format, one link a page with its opening paragraph, and every page’s markdown at the page’s path with .md (https://gi0tto.github.io/sluicer/agents.md for this one). Both are written from the nav and the pages at each build. context7.json at the repository’s root says which of these pages Context7 indexes, and the rules it gives an agent.

In your own agent’s code

Any framework that speaks MCP starts the same server. Run it as its own process, as below, rather than importing it into your application’s environment: measured on 2026-09-24, langchain-mcp-adapters 0.3.1 resolves mcp 1.30 and fails to import against mcp 2.2, which sluicer[mcp] needs, so the two in one environment break the application. As separate processes they speak MCP to each other, and every one of these listed the ten tools it had before select_values and answered a page’s price with its place:

LangChain (langchain-mcp-adapters 0.3.1, its client on mcp 1.30):

from langchain_mcp_adapters.client import MultiServerMCPClient

client = MultiServerMCPClient({"sluicer": {
    "command": "uvx", "args": ["--with", "sluicer[mcp]", "sluicer", "mcp"],
    "transport": "stdio",
}})
tools = await client.get_tools()   # hand them to any LangChain agent

OpenAI Agents SDK (openai-agents 0.22.3):

from agents import Agent
from agents.mcp import MCPServerStdio

async with MCPServerStdio(params={
    "command": "uvx", "args": ["--with", "sluicer[mcp]", "sluicer", "mcp"],
}) as sluicer:
    agent = Agent(name="reader", mcp_servers=[sluicer])

Pydantic AI (pydantic-ai-slim[mcp] 2.48, on FastMCP 4.0.7):

from pydantic_ai import Agent
from pydantic_ai.mcp import MCPToolset, StdioTransport

sluicer = MCPToolset(StdioTransport("uvx", ["--with", "sluicer[mcp]", "sluicer", "mcp"]))
agent = Agent("openai:gpt-5", toolsets=[sluicer])

The model is the framework’s to choose: Sluicer’s answers are the same whichever model reads them, since none is asked to produce them.

Without MCP

sluicer serve answers the same tools over HTTP, for any language and any model’s function calling: POST /v1/tools/<name> with the tool’s arguments as JSON, described at /openapi.json. See the HTTP API. And in Python, sluicer.extract(html, url=...) is the library the server calls.

With other tools

Sluicer reads HTML, whoever fetched it: extract never fetches. Hand it the page as the other tool brought it back – bytes are better than text, since the page’s own charset declaration is still in them – with the address it came from and, when the tool keeps them, the response’s headers: a Link header’s canonical, an X-Robots-Tag and the charset are read from those. Each of these was run on 2026-09-24 against a local page, and each answered its price with its place and its canonical from the Link header:

import sluicer

# httpx 0.28
r = httpx.get(url)
sluicer.extract(r.content, url=str(r.url), headers=dict(r.headers))

# Playwright 1.63: the rendered page, as text
response = page.goto(url)
sluicer.extract(page.content(), url=page.url, headers=response.all_headers())

# Scrapling 0.4
page = Fetcher.get(url)
sluicer.extract(page.body, url=page.url, headers=dict(page.headers))

# Crawl4AI 0.9: inside `async with AsyncWebCrawler() as crawler`
result = await crawler.arun(url=url)
sluicer.extract(result.html, url=result.url, headers=result.response_headers)

# Scrapy 2.19: in a spider's callback
headers = {k.decode(): v[0].decode() for k, v in response.headers.items()}
sluicer.extract(response.body, url=response.url, headers=headers)

Firecrawl’s raw HTML is the page as it was received, and firecrawl-py 4.44 holds it in raw_html; this one was not run here, since Firecrawl’s service needs a key:

doc = Firecrawl(api_key=key).scrape(url, formats=["rawHtml"])
sluicer.extract(doc.raw_html, url=doc.metadata.source_url)

All six install beside Sluicer’s base package in one environment; the base package needs only lxml, click, cssselect and protego.