mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-09-12 19:50:57 +05:00
Run the complete core and MCP documentation suites
This commit is contained in:
parent
c36d3c1575
commit
bcab0bfa6e
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -206,7 +206,7 @@ jobs:
|
||||
uv run --directory "$DATA_DIR" --no-sync --no-sources archivebox status
|
||||
|
||||
- name: Run consolidated core suite
|
||||
if: matrix.os_name == 'macOS' || matrix.python == '3.14.6'
|
||||
if: matrix.os_name == 'macOS' || matrix.python == '3.14'
|
||||
run: |
|
||||
mkdir -p tests/out
|
||||
uv run --no-sync --no-sources pytest -q archivebox/tests --basetemp="tests/out/${{ matrix.os_name }}-python-${{ matrix.python }}"
|
||||
|
||||
@ -19,7 +19,9 @@ This is a lightweight, stateless MCP server that dynamically introspects Archive
|
||||
### Start the MCP Server
|
||||
|
||||
```bash
|
||||
archivebox mcp
|
||||
request='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
|
||||
response="$(printf '%s\n' "$request" | "$UV_BINARY" run --project "$ARCHIVEBOX_PROJECT_DIR" --no-sync archivebox mcp)"
|
||||
"$JQ_BINARY" -e '.id == 1 and .result.serverInfo.name == "archivebox-mcp"' <<< "$response"
|
||||
```
|
||||
|
||||
The server runs in stdio mode, reading JSON-RPC 2.0 requests from stdin and writing responses to stdout.
|
||||
@ -27,25 +29,30 @@ The server runs in stdio mode, reading JSON-RPC 2.0 requests from stdin and writ
|
||||
### Example Client
|
||||
|
||||
```python
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
# Start MCP server
|
||||
proc = subprocess.Popen(
|
||||
['archivebox', 'mcp'],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
# Send initialize request
|
||||
request = {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}
|
||||
proc.stdin.write(json.dumps(request) + '\n')
|
||||
proc.stdin.flush()
|
||||
|
||||
# Read response
|
||||
response = json.loads(proc.stdout.readline())
|
||||
print(response)
|
||||
completed = subprocess.run(
|
||||
[
|
||||
os.environ["UV_BINARY"],
|
||||
"run",
|
||||
"--project",
|
||||
os.environ["ARCHIVEBOX_PROJECT_DIR"],
|
||||
"--no-sync",
|
||||
"archivebox",
|
||||
"mcp",
|
||||
],
|
||||
input=json.dumps(request) + "\n",
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
timeout=30,
|
||||
)
|
||||
response = json.loads(completed.stdout)
|
||||
assert response["id"] == 1
|
||||
assert response["result"]["serverInfo"]["name"] == "archivebox-mcp"
|
||||
```
|
||||
|
||||
### Example Requests
|
||||
@ -98,16 +105,21 @@ The server exposes all ArchiveBox CLI commands:
|
||||
Instead of manually defining schemas, the server uses Click's introspection API to automatically generate MCP tool definitions:
|
||||
|
||||
```python
|
||||
# Auto-discover commands
|
||||
from archivebox.cli import ArchiveBoxGroup
|
||||
cli_group = ArchiveBoxGroup()
|
||||
all_commands = cli_group.all_subcommands
|
||||
import click
|
||||
|
||||
# Auto-generate schemas from Click metadata
|
||||
for cmd_name in all_commands:
|
||||
click_cmd = cli_group.get_command(None, cmd_name)
|
||||
# Extract params, types, help text, etc.
|
||||
tool_schema = click_command_to_mcp_tool(cmd_name, click_cmd)
|
||||
from archivebox.cli import ArchiveBoxGroup
|
||||
from archivebox.mcp.server import click_command_to_mcp_tool
|
||||
|
||||
cli_group = ArchiveBoxGroup()
|
||||
context = click.Context(cli_group)
|
||||
|
||||
tools = []
|
||||
for command_name in cli_group.all_subcommands:
|
||||
command = cli_group.get_command(context, command_name)
|
||||
assert command is not None
|
||||
tools.append(click_command_to_mcp_tool(command_name, command))
|
||||
|
||||
assert {tool["name"] for tool in tools} == set(cli_group.all_subcommands)
|
||||
```
|
||||
|
||||
### Tool Execution
|
||||
@ -115,10 +127,14 @@ for cmd_name in all_commands:
|
||||
Commands are executed using Click's `CliRunner`:
|
||||
|
||||
```python
|
||||
from click.testing import CliRunner
|
||||
from archivebox.mcp.server import MCPServer
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(click_command, args)
|
||||
response = MCPServer().handle_request(
|
||||
{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}},
|
||||
)
|
||||
assert response["id"] == 2
|
||||
assert response["result"]["tools"]
|
||||
assert all("name" in tool and "inputSchema" in tool for tool in response["result"]["tools"])
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
@ -4,7 +4,7 @@ environments = ["core", "merge", "publishing", "docker", "root", "macos", "freeb
|
||||
[syntax]
|
||||
executed = ["bash", "sh", "python"]
|
||||
shell_syntax_only = ["console"]
|
||||
structured = ["yaml", "ini", "sql", "nginx", "mermaid"]
|
||||
structured = ["json", "yaml", "ini", "sql", "nginx", "mermaid"]
|
||||
prose = ["text"]
|
||||
directive_prefixes = ["{"]
|
||||
console_blocks = [
|
||||
@ -18,9 +18,6 @@ console_blocks = [
|
||||
"README.md::line 1524",
|
||||
"README.md::line 1537",
|
||||
"README.md::line 1601",
|
||||
"README.md::line 1617",
|
||||
"AGENTS.md::line 84",
|
||||
"skills/archivebox/SKILL.md::line 78",
|
||||
]
|
||||
|
||||
[ci.standard]
|
||||
@ -34,6 +31,7 @@ macos = "macos-15"
|
||||
[ci.core_shards]
|
||||
metadata = [
|
||||
"README.md",
|
||||
"archivebox/mcp/README.md",
|
||||
"docs/Changelog.md",
|
||||
"docs/Chromium-Install.md",
|
||||
]
|
||||
@ -64,6 +62,7 @@ openbsd = "vmactions/openbsd-vm@v1.4.5"
|
||||
[files]
|
||||
"README.md" = "core"
|
||||
"AGENTS.md" = "core"
|
||||
"archivebox/mcp/README.md" = "core"
|
||||
"skills/archivebox/SKILL.md" = "core"
|
||||
"docs/Changelog.md" = "core"
|
||||
"docs/Chromium-Install.md" = "core"
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import configparser
|
||||
from collections import Counter
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
import subprocess
|
||||
@ -15,7 +16,11 @@ WORKFLOW_PATH = REPO_ROOT / ".github" / "workflows" / "docs.yml"
|
||||
|
||||
|
||||
def markdown_paths() -> tuple[Path, ...]:
|
||||
candidates = [REPO_ROOT / "README.md", REPO_ROOT / "AGENTS.md"]
|
||||
candidates = [
|
||||
REPO_ROOT / "README.md",
|
||||
REPO_ROOT / "AGENTS.md",
|
||||
REPO_ROOT / "archivebox" / "mcp" / "README.md",
|
||||
]
|
||||
candidates.extend(sorted((REPO_ROOT / "skills").rglob("*.md")))
|
||||
candidates.extend(sorted((REPO_ROOT / "docs").rglob("*.md")))
|
||||
|
||||
@ -106,7 +111,9 @@ def test_structured_data_fences_parse() -> None:
|
||||
)
|
||||
|
||||
for nodeid, block in blocks.items():
|
||||
if block.syntax == "yaml":
|
||||
if block.syntax == "json":
|
||||
json.loads(block.code)
|
||||
elif block.syntax == "yaml":
|
||||
list(yaml.safe_load_all(block.code))
|
||||
elif block.syntax == "ini":
|
||||
parser = configparser.ConfigParser()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user