vaultzero 0.1.2__tar.gz → 0.1.3__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.
- {vaultzero-0.1.2 → vaultzero-0.1.3}/PKG-INFO +9 -2
- {vaultzero-0.1.2 → vaultzero-0.1.3}/README.md +8 -1
- {vaultzero-0.1.2 → vaultzero-0.1.3}/pyproject.toml +1 -1
- {vaultzero-0.1.2 → vaultzero-0.1.3}/tests/unit/test_sdk_and_mcp.py +78 -1
- {vaultzero-0.1.2 → vaultzero-0.1.3}/vaultzero/client/sdk.py +14 -1
- {vaultzero-0.1.2 → vaultzero-0.1.3}/.gitignore +0 -0
- {vaultzero-0.1.2 → vaultzero-0.1.3}/LICENSE +0 -0
- {vaultzero-0.1.2 → vaultzero-0.1.3}/tests/__init__.py +0 -0
- {vaultzero-0.1.2 → vaultzero-0.1.3}/tests/unit/__init__.py +0 -0
- {vaultzero-0.1.2 → vaultzero-0.1.3}/tests/unit/golden_vectors.json +0 -0
- {vaultzero-0.1.2 → vaultzero-0.1.3}/tests/unit/test_crypto.py +0 -0
- {vaultzero-0.1.2 → vaultzero-0.1.3}/vaultzero/__init__.py +0 -0
- {vaultzero-0.1.2 → vaultzero-0.1.3}/vaultzero/client/__init__.py +0 -0
- {vaultzero-0.1.2 → vaultzero-0.1.3}/vaultzero/client/_net.py +0 -0
- {vaultzero-0.1.2 → vaultzero-0.1.3}/vaultzero/client/crypto.py +0 -0
- {vaultzero-0.1.2 → vaultzero-0.1.3}/vaultzero/client/embeddings.py +0 -0
- {vaultzero-0.1.2 → vaultzero-0.1.3}/vaultzero/client/mcp_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vaultzero
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: MCP server for a privacy-first agent memory vault: memories are encrypted on the client before they reach the server.
|
|
5
5
|
Project-URL: Homepage, https://vaultzero.cc
|
|
6
6
|
Project-URL: Documentation, https://github.com/xo0ps/VaultZero-Client#readme
|
|
@@ -39,6 +39,8 @@ Description-Content-Type: text/markdown
|
|
|
39
39
|
[](https://modelcontextprotocol.io)
|
|
40
40
|
[](https://www.python.org)
|
|
41
41
|
|
|
42
|
+
[](https://mcpservers.org/servers/xo0ps/vaultzero-client)
|
|
43
|
+
|
|
42
44
|
**An MCP server that gives an AI agent long-term memory it cannot read.** VaultZero is a
|
|
43
45
|
privacy-first memory vault: this package embeds and encrypts every memory on your machine
|
|
44
46
|
before it is sent, so the server stores ciphertext and vectors and never holds a key.
|
|
@@ -79,7 +81,7 @@ pip install vaultzero # or: uv tool install vaultzero
|
|
|
79
81
|
"mcpServers": {
|
|
80
82
|
"vaultzero": {
|
|
81
83
|
"command": "uvx",
|
|
82
|
-
"args": ["vaultzero-mcp"],
|
|
84
|
+
"args": ["--from", "vaultzero", "vaultzero-mcp"],
|
|
83
85
|
"env": {
|
|
84
86
|
"VZ_BASE_URL": "https://api.vaultzero.cc",
|
|
85
87
|
"VZ_CREDENTIAL": "<your 64-hex-character bearer credential>",
|
|
@@ -91,6 +93,11 @@ pip install vaultzero # or: uv tool install vaultzero
|
|
|
91
93
|
}
|
|
92
94
|
```
|
|
93
95
|
|
|
96
|
+
`--from vaultzero` is not optional. `vaultzero-mcp` is the console script *inside* the
|
|
97
|
+
`vaultzero` package, not a package name, so `uvx vaultzero-mcp` fails with "Because
|
|
98
|
+
vaultzero-mcp was not found in the package registry". The flag tells uvx which package to
|
|
99
|
+
install before running that script.
|
|
100
|
+
|
|
94
101
|
`VZ_BASE_URL` and `VZ_CREDENTIAL` are required. The other three are strongly recommended and
|
|
95
102
|
are described under [Configuration](#configuration).
|
|
96
103
|
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
[](https://modelcontextprotocol.io)
|
|
5
5
|
[](https://www.python.org)
|
|
6
6
|
|
|
7
|
+
[](https://mcpservers.org/servers/xo0ps/vaultzero-client)
|
|
8
|
+
|
|
7
9
|
**An MCP server that gives an AI agent long-term memory it cannot read.** VaultZero is a
|
|
8
10
|
privacy-first memory vault: this package embeds and encrypts every memory on your machine
|
|
9
11
|
before it is sent, so the server stores ciphertext and vectors and never holds a key.
|
|
@@ -44,7 +46,7 @@ pip install vaultzero # or: uv tool install vaultzero
|
|
|
44
46
|
"mcpServers": {
|
|
45
47
|
"vaultzero": {
|
|
46
48
|
"command": "uvx",
|
|
47
|
-
"args": ["vaultzero-mcp"],
|
|
49
|
+
"args": ["--from", "vaultzero", "vaultzero-mcp"],
|
|
48
50
|
"env": {
|
|
49
51
|
"VZ_BASE_URL": "https://api.vaultzero.cc",
|
|
50
52
|
"VZ_CREDENTIAL": "<your 64-hex-character bearer credential>",
|
|
@@ -56,6 +58,11 @@ pip install vaultzero # or: uv tool install vaultzero
|
|
|
56
58
|
}
|
|
57
59
|
```
|
|
58
60
|
|
|
61
|
+
`--from vaultzero` is not optional. `vaultzero-mcp` is the console script *inside* the
|
|
62
|
+
`vaultzero` package, not a package name, so `uvx vaultzero-mcp` fails with "Because
|
|
63
|
+
vaultzero-mcp was not found in the package registry". The flag tells uvx which package to
|
|
64
|
+
install before running that script.
|
|
65
|
+
|
|
59
66
|
`VZ_BASE_URL` and `VZ_CREDENTIAL` are required. The other three are strongly recommended and
|
|
60
67
|
are described under [Configuration](#configuration).
|
|
61
68
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vaultzero"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
# This string is what MCP directories show next to the listing, so it names the thing a
|
|
9
9
|
# prospective user is looking for (an MCP server) before the architecture behind it.
|
|
10
10
|
description = "MCP server for a privacy-first agent memory vault: memories are encrypted on the client before they reach the server."
|
|
@@ -9,7 +9,7 @@ from uuid import uuid4
|
|
|
9
9
|
import pytest
|
|
10
10
|
|
|
11
11
|
from vaultzero.client._net import require_secure_url
|
|
12
|
-
from vaultzero.client.embeddings import HashingTestEmbeddings
|
|
12
|
+
from vaultzero.client.embeddings import EmbeddingSpace, HashingTestEmbeddings
|
|
13
13
|
from vaultzero.client.mcp_server import UNTRUSTED_HEADER, create_mcp_server
|
|
14
14
|
from vaultzero.client.sdk import (
|
|
15
15
|
Context,
|
|
@@ -92,6 +92,83 @@ def test_recalled_envelope_must_match_requested_scope_and_space() -> None:
|
|
|
92
92
|
assert not match(ctx, {**item, "subject_epoch": str(uuid4())}, frozenset({"medical"}), "s1")
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
# -- embedding space bootstrap ------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _ctx_with_spaces(spaces: dict) -> Context:
|
|
99
|
+
return Context(
|
|
100
|
+
tenant_id=uuid4(),
|
|
101
|
+
user_id=uuid4(),
|
|
102
|
+
agent_id=uuid4(),
|
|
103
|
+
subject_epoch=uuid4(),
|
|
104
|
+
scopes=frozenset({"preferences"}),
|
|
105
|
+
embedding_spaces=spaces,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _client_for(space_id: str) -> VaultZeroClient:
|
|
110
|
+
return VaultZeroClient(
|
|
111
|
+
base_url="https://example.invalid",
|
|
112
|
+
credential="0" * 64,
|
|
113
|
+
key_store=InMemoryKeyStore(),
|
|
114
|
+
embedding_provider=HashingTestEmbeddings(
|
|
115
|
+
EmbeddingSpace(
|
|
116
|
+
space_id=space_id,
|
|
117
|
+
model="vaultzero-hashing",
|
|
118
|
+
revision="1",
|
|
119
|
+
preprocessing="nfc-strip-v1",
|
|
120
|
+
)
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_empty_embedding_spaces_allows_the_first_write() -> None:
|
|
126
|
+
"""Regression: a brand-new tenant could never store anything.
|
|
127
|
+
|
|
128
|
+
/v1/context lists only spaces the tenant already HAS MEMORIES IN, which is
|
|
129
|
+
deliberate: unfiltered, it leaked every other tenant's space ids. But the client
|
|
130
|
+
treated an empty list as "not registered" and refused to write, so a tenant with no
|
|
131
|
+
memories could never create its first one:
|
|
132
|
+
|
|
133
|
+
no memories -> empty list -> client refuses -> no memories
|
|
134
|
+
|
|
135
|
+
Found by an end-to-end run against production, not by any unit test.
|
|
136
|
+
"""
|
|
137
|
+
client = _client_for("test-hashing-v1")
|
|
138
|
+
ctx = _ctx_with_spaces({})
|
|
139
|
+
assert client._require_space(ctx) == "test-hashing-v1"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_unknown_space_is_still_refused_when_others_exist() -> None:
|
|
143
|
+
"""The leak fix must not be undone: a tenant that writes elsewhere still gets refused.
|
|
144
|
+
|
|
145
|
+
Only the ambiguous empty case is relaxed. If the tenant has memories in other spaces
|
|
146
|
+
and this one is absent, that is a genuine mismatch, and silently using it would store
|
|
147
|
+
vectors that can never be matched.
|
|
148
|
+
"""
|
|
149
|
+
client = _client_for("not-registered")
|
|
150
|
+
ctx = _ctx_with_spaces({"some-other-space": {"space_id": "some-other-space"}})
|
|
151
|
+
with pytest.raises(VaultZeroClientError, match="not registered"):
|
|
152
|
+
client._require_space(ctx)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_matching_space_with_wrong_configuration_is_refused() -> None:
|
|
156
|
+
client = _client_for("test-hashing-v1")
|
|
157
|
+
ctx = _ctx_with_spaces(
|
|
158
|
+
{
|
|
159
|
+
"test-hashing-v1": {
|
|
160
|
+
"space_id": "test-hashing-v1",
|
|
161
|
+
"model": "a-different-model",
|
|
162
|
+
"revision": "1",
|
|
163
|
+
"preprocessing": "nfc-strip-v1",
|
|
164
|
+
"dimensions": 1536,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
with pytest.raises(VaultZeroClientError, match="differs from the registry"):
|
|
169
|
+
client._require_space(ctx)
|
|
170
|
+
|
|
171
|
+
|
|
95
172
|
# -- MCP ----------------------------------------------------------------------------------
|
|
96
173
|
|
|
97
174
|
|
|
@@ -263,7 +263,20 @@ class VaultZeroClient:
|
|
|
263
263
|
space = self._embeddings.space
|
|
264
264
|
entry = ctx.embedding_spaces.get(space.space_id)
|
|
265
265
|
if entry is None:
|
|
266
|
-
|
|
266
|
+
# /v1/context lists only the spaces the tenant ALREADY HAS MEMORIES IN. That
|
|
267
|
+
# scoping is deliberate: unfiltered, it leaked every other tenant's space ids
|
|
268
|
+
# and model names. But it makes an empty list mean "nothing written yet", not
|
|
269
|
+
# "not registered", and treating the two the same deadlocked every new tenant:
|
|
270
|
+
# no memories -> empty list -> the client refused to write -> no memories.
|
|
271
|
+
#
|
|
272
|
+
# An empty list therefore passes. A NON-empty list that lacks this space still
|
|
273
|
+
# raises: the tenant writes to other spaces, so this one is genuinely unknown to
|
|
274
|
+
# it, and quietly using a space the server never agreed to risks storing vectors
|
|
275
|
+
# that can never be matched. The server independently checks the global registry
|
|
276
|
+
# (_require_space in memory_engine), so this is not the only line of defence.
|
|
277
|
+
if ctx.embedding_spaces:
|
|
278
|
+
raise VaultZeroClientError("embedding space not registered on the server")
|
|
279
|
+
return space.space_id
|
|
267
280
|
if not space.matches_registry(entry):
|
|
268
281
|
# Same identifier, different model/revision/preprocessing: vectors would be
|
|
269
282
|
# silently incomparable, so refuse.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|