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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vaultzero
3
- Version: 0.1.2
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
  [![MCP](https://img.shields.io/badge/MCP-server-blueviolet)](https://modelcontextprotocol.io)
40
40
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org)
41
41
 
42
+ [![Listed on mcpservers.org](https://mcpservers.org/badge.svg)](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
  [![MCP](https://img.shields.io/badge/MCP-server-blueviolet)](https://modelcontextprotocol.io)
5
5
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org)
6
6
 
7
+ [![Listed on mcpservers.org](https://mcpservers.org/badge.svg)](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.2"
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
- raise VaultZeroClientError("embedding space not registered on the server")
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