mcp-sluice 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_sluice-0.1.0/.github/workflows/ci.yml +62 -0
- mcp_sluice-0.1.0/.github/workflows/release.yml +103 -0
- mcp_sluice-0.1.0/.gitignore +11 -0
- mcp_sluice-0.1.0/.hypothesis/.gitignore +9 -0
- mcp_sluice-0.1.0/.hypothesis/constants/03a92850ec05bf96 +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/28ed97e6b732c25b +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/2ce49e5fccb99c6e +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/2d25ca6f46e04685 +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/36d619bad1bddc4e +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/43910582fbcf1b5d +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/44302d81fc4ea7ad +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/5b63440f1497986b +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/7483b75bcf1c815e +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/75de9f9bfb03fafb +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/9ead00bae9fc6e82 +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/a5359a1d63075933 +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/a88b472d44e25d12 +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/aba7595b54e25bc8 +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/b930103efb1873de +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/cc1232942d7e2bcc +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/e55a5401d33ff5d1 +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/ec9ac0b980d68864 +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/f4184698165767bc +4 -0
- mcp_sluice-0.1.0/.hypothesis/constants/f6384783b6a5db4f +4 -0
- mcp_sluice-0.1.0/CHANGELOG.md +69 -0
- mcp_sluice-0.1.0/CLAUDE.md +128 -0
- mcp_sluice-0.1.0/LICENSE +202 -0
- mcp_sluice-0.1.0/PKG-INFO +352 -0
- mcp_sluice-0.1.0/README.md +334 -0
- mcp_sluice-0.1.0/benchmarks/memory_materialization.py +352 -0
- mcp_sluice-0.1.0/benchmarks/results/memory-2026-08-30.md +166 -0
- mcp_sluice-0.1.0/demo/README.md +118 -0
- mcp_sluice-0.1.0/demo/__init__.py +1 -0
- mcp_sluice-0.1.0/demo/median.py +525 -0
- mcp_sluice-0.1.0/demo/transcripts/20260831T040623Z/baseline.txt +252 -0
- mcp_sluice-0.1.0/demo/transcripts/20260831T040623Z/report.json +83 -0
- mcp_sluice-0.1.0/demo/transcripts/20260831T040623Z/treatment.txt +61 -0
- mcp_sluice-0.1.0/intent/001-scratch-db.md +188 -0
- mcp_sluice-0.1.0/plan/001-notes-m0.md +1195 -0
- mcp_sluice-0.1.0/plan/001-scratch-db.md +385 -0
- mcp_sluice-0.1.0/pyproject.toml +69 -0
- mcp_sluice-0.1.0/review/canary-v0.1.0.md +53 -0
- mcp_sluice-0.1.0/review/codex-prompt.md +152 -0
- mcp_sluice-0.1.0/sluice.example.toml +30 -0
- mcp_sluice-0.1.0/spec/001-scratch-db.md +894 -0
- mcp_sluice-0.1.0/src/sluice/__init__.py +5 -0
- mcp_sluice-0.1.0/src/sluice/__main__.py +86 -0
- mcp_sluice-0.1.0/src/sluice/config.py +232 -0
- mcp_sluice-0.1.0/src/sluice/errors.py +55 -0
- mcp_sluice-0.1.0/src/sluice/gate.py +258 -0
- mcp_sluice-0.1.0/src/sluice/handle.py +115 -0
- mcp_sluice-0.1.0/src/sluice/infer.py +128 -0
- mcp_sluice-0.1.0/src/sluice/intercept.py +326 -0
- mcp_sluice-0.1.0/src/sluice/models.py +112 -0
- mcp_sluice-0.1.0/src/sluice/naming.py +100 -0
- mcp_sluice-0.1.0/src/sluice/payload.py +228 -0
- mcp_sluice-0.1.0/src/sluice/proxy.py +254 -0
- mcp_sluice-0.1.0/src/sluice/py.typed +0 -0
- mcp_sluice-0.1.0/src/sluice/query.py +267 -0
- mcp_sluice-0.1.0/src/sluice/scope.py +95 -0
- mcp_sluice-0.1.0/src/sluice/server.py +162 -0
- mcp_sluice-0.1.0/src/sluice/shape.py +172 -0
- mcp_sluice-0.1.0/src/sluice/store.py +581 -0
- mcp_sluice-0.1.0/tests/__init__.py +0 -0
- mcp_sluice-0.1.0/tests/conftest.py +77 -0
- mcp_sluice-0.1.0/tests/fake_server/__init__.py +11 -0
- mcp_sluice-0.1.0/tests/fake_server/__main__.py +18 -0
- mcp_sluice-0.1.0/tests/fake_server/server.py +264 -0
- mcp_sluice-0.1.0/tests/test_cli.py +133 -0
- mcp_sluice-0.1.0/tests/test_concurrency.py +93 -0
- mcp_sluice-0.1.0/tests/test_config.py +183 -0
- mcp_sluice-0.1.0/tests/test_demo_median.py +25 -0
- mcp_sluice-0.1.0/tests/test_engine_contract.py +344 -0
- mcp_sluice-0.1.0/tests/test_infer.py +111 -0
- mcp_sluice-0.1.0/tests/test_intercept.py +503 -0
- mcp_sluice-0.1.0/tests/test_memory_bounds.py +444 -0
- mcp_sluice-0.1.0/tests/test_naming.py +96 -0
- mcp_sluice-0.1.0/tests/test_passthrough.py +175 -0
- mcp_sluice-0.1.0/tests/test_property_aggregates.py +363 -0
- mcp_sluice-0.1.0/tests/test_proxy.py +166 -0
- mcp_sluice-0.1.0/tests/test_query_limits.py +334 -0
- mcp_sluice-0.1.0/tests/test_query_safety.py +222 -0
- mcp_sluice-0.1.0/tests/test_query_tool.py +250 -0
- mcp_sluice-0.1.0/tests/test_scope.py +107 -0
- mcp_sluice-0.1.0/tests/test_shape.py +110 -0
- mcp_sluice-0.1.0/tests/test_store.py +242 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
check:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
timeout-minutes: 15
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.14"
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v4
|
|
28
|
+
|
|
29
|
+
# Dependencies deliberately have lower bounds rather than a committed
|
|
30
|
+
# lock file. The engine contract tests make CI the forward-compatibility
|
|
31
|
+
# tripwire for new MCP and DuckDB releases.
|
|
32
|
+
- name: Install current dependencies
|
|
33
|
+
run: uv sync --upgrade
|
|
34
|
+
|
|
35
|
+
- name: Run tests
|
|
36
|
+
run: uv run pytest
|
|
37
|
+
|
|
38
|
+
- name: Run lint
|
|
39
|
+
run: uv run ruff check .
|
|
40
|
+
|
|
41
|
+
- name: Check formatting
|
|
42
|
+
run: uv run ruff format --check .
|
|
43
|
+
|
|
44
|
+
- name: Run type checks
|
|
45
|
+
run: uv run mypy src tests
|
|
46
|
+
|
|
47
|
+
- name: Build distributions
|
|
48
|
+
run: uv build
|
|
49
|
+
|
|
50
|
+
- name: Smoke-test wheel
|
|
51
|
+
run: |
|
|
52
|
+
uv venv --python 3.14 /tmp/sluice-wheel-smoke
|
|
53
|
+
uv pip install --python /tmp/sluice-wheel-smoke/bin/python dist/*.whl
|
|
54
|
+
/tmp/sluice-wheel-smoke/bin/python -c 'import importlib.metadata, importlib.resources, sluice; assert importlib.metadata.version("mcp-sluice") == sluice.__version__; assert importlib.resources.files("sluice").joinpath("py.typed").is_file()'
|
|
55
|
+
/tmp/sluice-wheel-smoke/bin/sluice --help
|
|
56
|
+
|
|
57
|
+
- name: Upload distributions
|
|
58
|
+
uses: actions/upload-artifact@v4
|
|
59
|
+
with:
|
|
60
|
+
name: mcp-sluice-dist
|
|
61
|
+
path: dist/
|
|
62
|
+
if-no-files-found: error
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v[0-9]+.[0-9]+.[0-9]+"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: release-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
timeout-minutes: 15
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Set up Python
|
|
24
|
+
uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.14"
|
|
27
|
+
|
|
28
|
+
- name: Install uv
|
|
29
|
+
uses: astral-sh/setup-uv@v4
|
|
30
|
+
|
|
31
|
+
- name: Install current dependencies
|
|
32
|
+
run: uv sync --upgrade
|
|
33
|
+
|
|
34
|
+
- name: Verify tag matches package version
|
|
35
|
+
run: |
|
|
36
|
+
test "${GITHUB_REF_NAME#v}" = "$(uv run python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
|
|
37
|
+
test "${GITHUB_REF_NAME#v}" = "$(uv run python -c 'import sluice; print(sluice.__version__)')"
|
|
38
|
+
|
|
39
|
+
- name: Run release checks
|
|
40
|
+
run: |
|
|
41
|
+
uv run pytest
|
|
42
|
+
uv run ruff check .
|
|
43
|
+
uv run ruff format --check .
|
|
44
|
+
uv run mypy src tests
|
|
45
|
+
|
|
46
|
+
- name: Build distributions
|
|
47
|
+
run: uv build
|
|
48
|
+
|
|
49
|
+
- name: Verify distribution metadata
|
|
50
|
+
run: |
|
|
51
|
+
test -f "dist/mcp_sluice-${GITHUB_REF_NAME#v}.tar.gz"
|
|
52
|
+
test -f "dist/mcp_sluice-${GITHUB_REF_NAME#v}-py3-none-any.whl"
|
|
53
|
+
uvx twine check dist/*
|
|
54
|
+
unzip -p dist/*.whl 'mcp_sluice-*.dist-info/METADATA' | grep -Fx 'Name: mcp-sluice'
|
|
55
|
+
unzip -p dist/*.whl 'mcp_sluice-*.dist-info/METADATA' | grep -Fx 'License-Expression: Apache-2.0'
|
|
56
|
+
unzip -l dist/*.whl | grep -F 'licenses/LICENSE'
|
|
57
|
+
|
|
58
|
+
- name: Upload distributions
|
|
59
|
+
uses: actions/upload-artifact@v4
|
|
60
|
+
with:
|
|
61
|
+
name: mcp-sluice-dist
|
|
62
|
+
path: dist/
|
|
63
|
+
if-no-files-found: error
|
|
64
|
+
|
|
65
|
+
github-release:
|
|
66
|
+
needs:
|
|
67
|
+
- build
|
|
68
|
+
- pypi-publish
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
timeout-minutes: 10
|
|
71
|
+
permissions:
|
|
72
|
+
contents: write
|
|
73
|
+
|
|
74
|
+
steps:
|
|
75
|
+
- name: Download distributions
|
|
76
|
+
uses: actions/download-artifact@v4
|
|
77
|
+
with:
|
|
78
|
+
name: mcp-sluice-dist
|
|
79
|
+
path: dist
|
|
80
|
+
|
|
81
|
+
- name: Create GitHub release
|
|
82
|
+
env:
|
|
83
|
+
GH_TOKEN: ${{ github.token }}
|
|
84
|
+
GH_REPO: ${{ github.repository }}
|
|
85
|
+
run: gh release create "$GITHUB_REF_NAME" dist/* --verify-tag --generate-notes --title "$GITHUB_REF_NAME"
|
|
86
|
+
|
|
87
|
+
pypi-publish:
|
|
88
|
+
needs: build
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
timeout-minutes: 10
|
|
91
|
+
environment: pypi
|
|
92
|
+
permissions:
|
|
93
|
+
id-token: write
|
|
94
|
+
|
|
95
|
+
steps:
|
|
96
|
+
- name: Download distributions
|
|
97
|
+
uses: actions/download-artifact@v4
|
|
98
|
+
with:
|
|
99
|
+
name: mcp-sluice-dist
|
|
100
|
+
path: dist
|
|
101
|
+
|
|
102
|
+
- name: Publish package distributions to PyPI
|
|
103
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# This .gitignore file was automatically created by Hypothesis. Hypothesis gitignores
|
|
2
|
+
# .hypothesis by default, because we generally recommend that .hypothesis not be checked
|
|
3
|
+
# into version control.
|
|
4
|
+
#
|
|
5
|
+
# If you *would* like to check .hypothesis into version control, you should delete this
|
|
6
|
+
# file. Hypothesis will not re-create this .gitignore unless .hypothesis is deleted (and
|
|
7
|
+
# if it does, that's a bug - please report it!)
|
|
8
|
+
|
|
9
|
+
*
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/sluice/sluice/src/sluice/gate.py
|
|
2
|
+
# hypothesis_version: 6.167.1
|
|
3
|
+
|
|
4
|
+
[', ', '?', 'BASE_TABLE', 'FUNCTION', 'RECURSIVE_CTE_NODE', 'SELECT', 'SHOW_REF', 'TABLE_FUNCTION', '\\A[0-9a-f]{32}\\Z', 'array_resize', 'bitstring', 'cte_map', 'error', 'format', 'function', 'function_name', 'key', 'left', 'list_resize', 'lpad', 'map', 'nextval', 'no statement found', 'node', 'printf', 'query', 'right', 'rpad', 'schema_name', 'setseed', 'sleep_ms', 'table_name', 'type', 'value', 'write_log']
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/sluice/sluice/src/sluice/handle.py
|
|
2
|
+
# hypothesis_version: 6.167.1
|
|
3
|
+
|
|
4
|
+
[' (inexact)', '*', ', ', 'JSON', 'byte_size', 'call_id', 'channel_conflict', 'columns', 'columns: ', 'envelope_table', 'exact', 'flat_reason', 'name', 'preview', 'preview_complete', 'renamed: ', 'renamed_from', 'row_count', 'scope_id', 'source_channel', 'source_path', 'tables', 'text', 'type']
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/sluice/sluice/src/sluice/config.py
|
|
2
|
+
# hypothesis_version: 6.167.1
|
|
3
|
+
|
|
4
|
+
[10.0, 100, 256, 384, 512, 1000, 1024, 2048, 65536, 1048576, ', ', '1GB', 'SLUICE_CONFIG', 'args', 'command', 'cwd', 'env', 'limits', 'max_cell_bytes', 'max_columns', 'max_payload_bytes', 'max_session_bytes', 'max_session_calls', 'preview_bytes', 'preview_rows', 'query_max_bytes', 'query_max_rows', 'servers', 'sluice.toml', 'url', 'utf-8']
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/sluice/sluice/src/sluice/query.py
|
|
2
|
+
# hypothesis_version: 6.167.1
|
|
3
|
+
|
|
4
|
+
[' |', ' | ', "''", '(no columns)', '---', 'NULL', '\\', '\\\\', '\\n', '\\|', 'additionalProperties', 'description', 'integer', 'max_rows', 'minimum', 'object', 'properties', 'query', 'required', 'sql', 'string', 'type', 'utf-8', '|', '| ', '…']
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/sluice/sluice/demo/median.py
|
|
2
|
+
# hypothesis_version: 6.167.1
|
|
3
|
+
|
|
4
|
+
[1e-06, 0.005, 180.0, 400, '\n--- stderr ---\n', '%Y%m%dT%H%M%SZ', ',', '--allowedTools', '--config', '--dry-run', '--max-turns', '--mcp-config', '--model', '--out-dir', '--output-format', '--permission-mode', '--rows', '--strict-mcp-config', '--timeout', '--tolerance-abs', '--tolerance-rel', '--tools', '--verbose', '-?\\d+(?:\\.\\d+)?', '-m', '-p', 'AKIA[0-9A-Z]{16}', 'ToolSearch', '[REDACTED]', '__main__', 'abs_tol', 'args', 'baseline', 'baseline.mcp.json', 'claude', 'command', 'conditions', 'cwd', 'demo', 'dontAsk', 'expected', 'expected_median', 'fake', 'generated_at_utc', 'max_turns', 'mcpServers', 'mcp__fake__rows', 'mcp__sluice__query', 'message', 'model', 'model_requested', 'non_determinism_note', 'ok', 'out_dir', 'pending', 'prompt', 'rel_tol', 'replace', 'report.json', 'result', 'rows', 'score', 'sk-[A-Za-z0-9]{20,}', 'sluice', 'sluice-demo-', 'sluice.toml', 'sonnet', 'status', 'store_true', 'stream-json', 'tests.fake_server', 'timeout', 'tolerance', 'tolerance_abs', 'tolerance_rel', 'transcripts', 'treatment', 'treatment.mcp.json', 'type', 'utf-8']
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
This tracks what has landed against the milestones in
|
|
4
|
+
`plan/001-scratch-db.md`.
|
|
5
|
+
|
|
6
|
+
## 0.1.0 — 2026-09-01
|
|
7
|
+
|
|
8
|
+
- **M1 — proxy.** Sluice starts, connects to one configured downstream MCP
|
|
9
|
+
server over stdio, mounts its tools under injective names, and forwards
|
|
10
|
+
calls unmodified. Paginated `tools/list`, whole-object tool cloning,
|
|
11
|
+
multi-round-trip relay, and the four-class failure taxonomy (§8).
|
|
12
|
+
- **M2 — envelope, scope, handle.** Every proxied call gets one `sluice_calls`
|
|
13
|
+
row in an in-memory DuckDB database, under the §6.1 engine lockdown. Scope
|
|
14
|
+
is derived from a client-supplied conversation id when present, minted
|
|
15
|
+
per call otherwise. Eligible results are replaced with a handle; channel
|
|
16
|
+
selection and conflict detection between `structuredContent` and text
|
|
17
|
+
content are implemented per §5.1.
|
|
18
|
+
- **M3 — flattening and inference.** Depth-1 projection extracts every
|
|
19
|
+
candidate row set from a result (§5.2), Sluice's own type inference assigns
|
|
20
|
+
a column type and an `exact` flag per §5.5 without going through DuckDB's
|
|
21
|
+
JSON inference, and tables are created file-free via explicit DDL plus
|
|
22
|
+
`executemany`.
|
|
23
|
+
- **M4 — the `query` tool.** The three-layer read-only gate (statement type,
|
|
24
|
+
engine lockdown, AST object allowlist), a per-query DuckDB connection with a
|
|
25
|
+
timer-based interrupt, and defined result shaping (row cap, byte cap,
|
|
26
|
+
per-cell truncation, markdown escaping) are implemented and covered by
|
|
27
|
+
`test_query_safety.py` and `test_query_limits.py`. Two review passes closed
|
|
28
|
+
a gate bypass and several other failures after initial implementation (see
|
|
29
|
+
commit history).
|
|
30
|
+
- Full test suite passes: proxy, passthrough, envelope, scope, shape,
|
|
31
|
+
inference, store, handle, config, CLI, engine-contract, query safety,
|
|
32
|
+
query limits, and concurrency.
|
|
33
|
+
|
|
34
|
+
### M5 — correctness property
|
|
35
|
+
|
|
36
|
+
- The end-to-end Hypothesis suite covers normalized aggregate correctness,
|
|
37
|
+
1–500-row boundaries, bounded floating-point regression evidence, and the
|
|
38
|
+
mandatory counterexamples. Adversarial review found cancellation inside the
|
|
39
|
+
former floating-point guarantee, so every `DOUBLE` column is now marked
|
|
40
|
+
inexact and per-aggregate exactness is left for future design.
|
|
41
|
+
- The reproducible live-model median demo is implemented outside CI. Its
|
|
42
|
+
committed sampled run recorded a wrong baseline answer (71.5) and the exact
|
|
43
|
+
Sluice-backed answer (72.5), with transcripts and a non-determinism warning.
|
|
44
|
+
|
|
45
|
+
### Runtime bounds and adversarial review
|
|
46
|
+
|
|
47
|
+
- Whole-pipeline admission, deterministic logical session retention, and
|
|
48
|
+
metadata/catalog cardinality limits bound Sluice's own continued work.
|
|
49
|
+
- Post-fix calibration across flat, nested, wide, and mixed 1 MiB dual-channel
|
|
50
|
+
payloads at concurrency 2 supports the conservative 1 MiB v0 admission
|
|
51
|
+
default; a 15-call run records long-session behavior.
|
|
52
|
+
- Final review closed CTE binding-order isolation, scalar memory amplification,
|
|
53
|
+
queued-worker timeout, truncation-reporting, dual-channel sizing, failure
|
|
54
|
+
envelope, and dead-transport reuse defects.
|
|
55
|
+
|
|
56
|
+
### M6 — documentation and packaging
|
|
57
|
+
|
|
58
|
+
- Installation, client configuration, architecture, usage, exactness,
|
|
59
|
+
isolation, resource bounds, limitations, troubleshooting, and release notes
|
|
60
|
+
are documented. The wheel includes `py.typed` and is validated by CI.
|
|
61
|
+
|
|
62
|
+
### Release verification
|
|
63
|
+
|
|
64
|
+
- The end-to-end canary against a real configured GitHub MCP server passed; see
|
|
65
|
+
`review/canary-v0.1.0.md` for the observed handle, query, envelope, and
|
|
66
|
+
isolation checks.
|
|
67
|
+
- The distribution name is `mcp-sluice`; the import package and console command
|
|
68
|
+
remain `sluice`.
|
|
69
|
+
- Source and distributions are licensed under Apache-2.0.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# CLAUDE.md - Sluice
|
|
2
|
+
|
|
3
|
+
Passthrough MCP server. Proxies **exactly one** downstream MCP server in v0
|
|
4
|
+
(namespacing is built for fan-out, deferred), materializes every tool result into
|
|
5
|
+
a DuckDB scratch DB, and returns a preview plus a table handle instead of the
|
|
6
|
+
payload. Exposes one tool of its own, `query`, for read-only SQL over those
|
|
7
|
+
tables.
|
|
8
|
+
|
|
9
|
+
Read `spec/001-scratch-db.md` before changing behavior; `intent/` records why the
|
|
10
|
+
non-goals are non-goals; `plan/001-notes-m0.md` is the measured evidence behind
|
|
11
|
+
the rules below.
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv sync # install
|
|
17
|
+
uv run sluice --config sluice.toml # run as an MCP stdio server
|
|
18
|
+
uv run pytest # full suite
|
|
19
|
+
uv run pytest -m "not slow" # skip timing-sensitive tests (concurrency, timeouts)
|
|
20
|
+
uv run ruff check . && uv run ruff format --check .
|
|
21
|
+
uv run mypy src tests
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The M5 Hypothesis correctness property is `tests/test_property_aggregates.py`.
|
|
25
|
+
The non-deterministic model-eval harness is `uv run python -m demo.median`; use
|
|
26
|
+
`--dry-run` to validate its setup without spending a model call. It is evidence,
|
|
27
|
+
never a CI gate.
|
|
28
|
+
|
|
29
|
+
## Python version rules
|
|
30
|
+
|
|
31
|
+
- `requires-python = ">=3.14"`. **Do not write compatibility shims for older
|
|
32
|
+
versions.** No `sys.version_info` branches, no backport imports.
|
|
33
|
+
- PEP 604 unions: `X | None`, never `typing.Optional[X]`.
|
|
34
|
+
- PEP 585 builtin generics: `list[str]`, `dict[str, int]`, never `typing.List`.
|
|
35
|
+
- `type` statements for aliases, never `TypeAlias`.
|
|
36
|
+
- Type hints on every function signature. `mypy` is not optional.
|
|
37
|
+
- Python 3.15 releases 2026-10-01. Moving the floor to 3.15 is an **open
|
|
38
|
+
question, not a decision**. The gating factor is DuckDB cp315 wheel
|
|
39
|
+
availability, since DuckDB is a compiled extension and a source build is not an
|
|
40
|
+
acceptable install path.
|
|
41
|
+
|
|
42
|
+
## Conventions
|
|
43
|
+
|
|
44
|
+
- Idiomatic modern Python of the FastAPI and Postgres kind. Dataclasses for value
|
|
45
|
+
objects, `pathlib`, structured logging **to stderr** (stdout is the MCP
|
|
46
|
+
transport and must never be written to).
|
|
47
|
+
- `shape.py`, `infer.py`, `naming.py` are pure: no DuckDB, no MCP, no IO. They
|
|
48
|
+
hold the logic most likely to be wrong and are cheapest to test. Keep them pure.
|
|
49
|
+
- DuckDB calls are blocking: dispatch through `anyio.to_thread.run_sync`,
|
|
50
|
+
serialize writes behind an `anyio.Lock`, one connection per in-flight query.
|
|
51
|
+
- pytest, function-style tests, and Hypothesis for the M5 correctness property.
|
|
52
|
+
|
|
53
|
+
## Architecture
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
client --stdio--> server.py tools/list = downstream union + query
|
|
57
|
+
proxy.py downstream session, paginated list, round-trip relay
|
|
58
|
+
gate.py the query tool's three-layer read-only gate
|
|
59
|
+
shape.py extract rows -> depth-1 projection (pure)
|
|
60
|
+
infer.py column types + the `exact` flag (pure)
|
|
61
|
+
naming.py injective names, quoting, collisions (pure)
|
|
62
|
+
scope.py scope ids; stale handles fail loudly
|
|
63
|
+
store.py envelope row + typed tables
|
|
64
|
+
handle.py preview + tables + columns -> the agent
|
|
65
|
+
query.py per-query connection, timeout, result shaping
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Data model: one `sluice_calls` envelope row per call. Plus one typed table per
|
|
69
|
+
candidate array, never appended to, named
|
|
70
|
+
`<server>__<tool>__<hash>__<scope>__<seq>`. There is no `__latest` view: with
|
|
71
|
+
scope minted per call it would name one table, and reaching a table whose name
|
|
72
|
+
you lost is discovery, which isolation blocks. Tables and the envelope row are
|
|
73
|
+
written in one transaction.
|
|
74
|
+
|
|
75
|
+
Verified baseline: MCP protocol `2026-07-28`, `mcp` 2.1.1, DuckDB 1.5.5. Every
|
|
76
|
+
normative claim in the spec is against those; dependencies have lower bounds
|
|
77
|
+
rather than exact pins, so do not generalize across revisions.
|
|
78
|
+
|
|
79
|
+
## Rules that are load-bearing
|
|
80
|
+
|
|
81
|
+
Each of these was a bug before it was a rule. Spec section in parentheses.
|
|
82
|
+
|
|
83
|
+
- **Sluice never turns a working tool call into a failed one** (§8). Failures
|
|
84
|
+
degrade to an envelope-only handle. Two boundaries: OOM, and a connection-wide
|
|
85
|
+
interrupt.
|
|
86
|
+
- **The handle rides in `content`** (§4.1); `structuredContent` mirrors it.
|
|
87
|
+
**On the way in, `structuredContent` wins** (§5.1): a tool may put data there
|
|
88
|
+
and prose in `content`, and flattening the prose discards the data.
|
|
89
|
+
- **Exactness is domain-bounded** (§5.6). Integer columns within ±2^53 can claim
|
|
90
|
+
exact aggregate results; larger integers and every `DOUBLE` column are
|
|
91
|
+
marked inexact. `avg` and float `sum` have only bounded regression evidence.
|
|
92
|
+
Never state the claim without its domain.
|
|
93
|
+
- **Materialization is file-free** (§5.4). The lockdown is database-global and
|
|
94
|
+
blocks DuckDB's own readers. Do not "fix" a load failure by relaxing it.
|
|
95
|
+
- **Sluice owns type inference** (§5.5), because of the above. Never infer
|
|
96
|
+
`TIMESTAMP` from a string; mixed scalars become `VARCHAR`, never `JSON`
|
|
97
|
+
(`median()` on `JSON` returns a lexicographic answer).
|
|
98
|
+
- **Read-only means three layers** (§6.1): statement gate, engine lockdown, and
|
|
99
|
+
an **allowlist** over the parsed AST. Not a denylist: an allowlist is closed by
|
|
100
|
+
construction. The statement gate does NOT stop `SHOW`, `DESCRIBE`, or
|
|
101
|
+
`PRAGMA` — DuckDB types all of them as SELECT. CTE names are honoured by
|
|
102
|
+
lexical scope; collecting them globally is a complete bypass.
|
|
103
|
+
- **The physical `sluice_calls` is never queryable** (§3.3). It lists every
|
|
104
|
+
scope's tables. Each scope gets a filtered view instead.
|
|
105
|
+
- **No table discovery** (§12). Enumeration is what isolation blocks. Do not add
|
|
106
|
+
a `sluice_schema` view back.
|
|
107
|
+
- **Sluice never answers an elicitation** (§11). Round trips are relayed
|
|
108
|
+
untouched; `request_state` is opaque.
|
|
109
|
+
- **Clone the whole downstream tool object** (FR-3), mutating only name,
|
|
110
|
+
description, and `outputSchema`. Rebuilding it field by field drops
|
|
111
|
+
`annotations.destructiveHint`.
|
|
112
|
+
- **Every truncation is reported** (§6.3). Silent truncation is a correctness bug
|
|
113
|
+
in a tool that sells determinism.
|
|
114
|
+
- **Agent-sized allocation functions are blocked** (§6.1). Table functions and
|
|
115
|
+
scalar constructors such as `range`, `lpad`, `list_resize`, and `bitstring`
|
|
116
|
+
can allocate before output rendering applies its cap.
|
|
117
|
+
- **Admission covers the whole interception pipeline** (§7). Selection, parse,
|
|
118
|
+
projection, commit, and handle rendering all run under the same semaphore.
|
|
119
|
+
- **Session retention is bounded** (§7). `max_session_bytes` evicts oldest calls
|
|
120
|
+
deterministically, dropping their tables and payload columns while preserving
|
|
121
|
+
envelope metadata; handles never advertise a table after its call is evicted.
|
|
122
|
+
`max_session_calls` separately bounds metadata rows and scope views.
|
|
123
|
+
|
|
124
|
+
## Out of scope for v0
|
|
125
|
+
|
|
126
|
+
No cross-session persistence, cross-server joins, entity resolution, auth,
|
|
127
|
+
policy, redaction, audit layer, hosted service, UI, or table discovery. Recorded
|
|
128
|
+
as choices in `intent/` §Non-goals, not as backlog.
|