swarmv-mcp 1.2.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.
@@ -0,0 +1,26 @@
1
+ node_modules/
2
+ dist/
3
+ .env
4
+ .dev.vars
5
+ .DS_Store
6
+ .backup/
7
+ # Secrets and local copies of them
8
+ *.bak
9
+ .env.*
10
+ !.env.example
11
+ # Build output and local state
12
+ .wrangler/
13
+ target/
14
+ crates/edgevec-wasm/pkg/
15
+ .next/
16
+ .vercel/
17
+ __pycache__/
18
+ *.pyc
19
+ # Load test reports
20
+ load-test-*.json
21
+ .venv/
22
+ venv/
23
+ *.egg-info/
24
+ .pytest_cache/
25
+ # Separate repository (has its own .git)
26
+ edgevec/
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.5
2
+ Name: swarmv-mcp
3
+ Version: 1.2.0
4
+ Summary: MCP server for SwarmV: persistent vector memory for AI agents, limited to what the user may access.
5
+ Project-URL: Homepage, https://edgevec.pages.dev
6
+ Project-URL: Documentation, https://edgevec.pages.dev/docs
7
+ License-Expression: MIT
8
+ Keywords: agents,ai,mcp,memory,swarmv,vector
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: httpx>=0.27
11
+ Requires-Dist: mcp<2,>=1.1.2
12
+ Requires-Dist: swarmv>=1.2.0
13
+ Description-Content-Type: text/markdown
14
+
15
+ # swarmv-mcp
16
+
17
+ MCP server for [SwarmV](https://edgevec.pages.dev): gives an AI agent persistent vector memory, limited to what the signed-in user is allowed to see and edit.
18
+
19
+ Tools:
20
+
21
+ - `read_from_memory` returns the closest records (id, distance, metadata) from the containers the user can view.
22
+ - `write_to_memory` stores a note in a container the user can edit (put the text in `metadata.text`).
23
+
24
+ ## Setup
25
+
26
+ ```bash
27
+ pip install swarmv-mcp
28
+ ```
29
+
30
+ Add it to your MCP client's configuration (Claude Desktop, Cursor and others):
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "swarmv": {
36
+ "command": "swarmv-mcp",
37
+ "env": {
38
+ "SWARMV_TOKEN_URL": "https://your-backend.example.com/swarmv-token",
39
+ "SWARMV_TOKEN_URL_AUTH": "<the signed-in user's session credential>"
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ Or without installing first: `"command": "uvx", "args": ["swarmv-mcp"]`.
47
+
48
+ ## Credentials
49
+
50
+ Pick one:
51
+
52
+ | Variables | Use |
53
+ | --- | --- |
54
+ | `SWARMV_TOKEN_URL` (+ optional `SWARMV_TOKEN_URL_AUTH`) | Recommended. Your backend returns `{ "token": "..." }`, a short-lived user token it minted with your API key. |
55
+ | `SWARMV_USER_TOKEN` | A user token you already have (expires). |
56
+ | `SWARMV_API_KEY` + `SWARMV_USER_ID=user:<id>` | Local development only. Never ship your API key to end users. |
57
+
58
+ Optional: `SWARMV_DIM` (vector dimensions, default 768), `SWARMV_BASE_URL`.
59
+
60
+ Full documentation: https://edgevec.pages.dev/docs
61
+
62
+ ## License
63
+
64
+ MIT
@@ -0,0 +1,50 @@
1
+ # swarmv-mcp
2
+
3
+ MCP server for [SwarmV](https://edgevec.pages.dev): gives an AI agent persistent vector memory, limited to what the signed-in user is allowed to see and edit.
4
+
5
+ Tools:
6
+
7
+ - `read_from_memory` returns the closest records (id, distance, metadata) from the containers the user can view.
8
+ - `write_to_memory` stores a note in a container the user can edit (put the text in `metadata.text`).
9
+
10
+ ## Setup
11
+
12
+ ```bash
13
+ pip install swarmv-mcp
14
+ ```
15
+
16
+ Add it to your MCP client's configuration (Claude Desktop, Cursor and others):
17
+
18
+ ```json
19
+ {
20
+ "mcpServers": {
21
+ "swarmv": {
22
+ "command": "swarmv-mcp",
23
+ "env": {
24
+ "SWARMV_TOKEN_URL": "https://your-backend.example.com/swarmv-token",
25
+ "SWARMV_TOKEN_URL_AUTH": "<the signed-in user's session credential>"
26
+ }
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ Or without installing first: `"command": "uvx", "args": ["swarmv-mcp"]`.
33
+
34
+ ## Credentials
35
+
36
+ Pick one:
37
+
38
+ | Variables | Use |
39
+ | --- | --- |
40
+ | `SWARMV_TOKEN_URL` (+ optional `SWARMV_TOKEN_URL_AUTH`) | Recommended. Your backend returns `{ "token": "..." }`, a short-lived user token it minted with your API key. |
41
+ | `SWARMV_USER_TOKEN` | A user token you already have (expires). |
42
+ | `SWARMV_API_KEY` + `SWARMV_USER_ID=user:<id>` | Local development only. Never ship your API key to end users. |
43
+
44
+ Optional: `SWARMV_DIM` (vector dimensions, default 768), `SWARMV_BASE_URL`.
45
+
46
+ Full documentation: https://edgevec.pages.dev/docs
47
+
48
+ ## License
49
+
50
+ MIT
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "swarmv-mcp"
7
+ version = "1.2.0"
8
+ description = "MCP server for SwarmV: persistent vector memory for AI agents, limited to what the user may access."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ keywords = ["mcp", "ai", "agents", "memory", "vector", "swarmv"]
12
+ requires-python = ">=3.10"
13
+ dependencies = [
14
+ "mcp>=1.1.2,<2",
15
+ "swarmv>=1.2.0",
16
+ "httpx>=0.27"
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://edgevec.pages.dev"
21
+ Documentation = "https://edgevec.pages.dev/docs"
22
+
23
+ [tool.hatch.build.targets.sdist]
24
+ include = ["src/swarmv_mcp", "README.md", "pyproject.toml"]
25
+ exclude = ["**/__pycache__"]
26
+
27
+ [tool.hatch.build.targets.wheel]
28
+ packages = ["src/swarmv_mcp"]
29
+
30
+ [project.scripts]
31
+ swarmv-mcp = "swarmv_mcp.server:main"
@@ -0,0 +1 @@
1
+ """MCP Server implementation for SwarmV in Python."""
@@ -0,0 +1,161 @@
1
+ """SwarmV MCP server (Python).
2
+
3
+ The agent always acts as ONE user, fixed by the credentials below and never chosen by the model.
4
+
5
+ SWARMV_TOKEN_URL (recommended) Your backend endpoint returning {"token": "<user token>"} for the
6
+ signed-in user. Called with POST; SWARMV_TOKEN_URL_AUTH, if set, is sent as a Bearer
7
+ credential. Tokens are refreshed automatically when they expire.
8
+ SWARMV_USER_TOKEN A fixed user token from generate_user_token(). Expires within an hour; for testing.
9
+ SWARMV_API_KEY + Local development only: the server mints its own tokens for SWARMV_USER_ID.
10
+ SWARMV_USER_ID This keeps a backend key on the machine, so don't use it on shared or production hosts.
11
+ """
12
+
13
+ import asyncio
14
+ import json
15
+ import os
16
+ import re
17
+ import sys
18
+
19
+ import httpx
20
+ from mcp.server import Server
21
+ from mcp.server.stdio import stdio_server
22
+ from mcp.types import TextContent, Tool
23
+ from swarmv import AsyncSwarmV
24
+
25
+ server = Server("swarmv-mcp-python")
26
+
27
+ BASE_URL = os.environ.get("SWARMV_BASE_URL", "https://edgevec-edge.lorencogonzaga.workers.dev")
28
+ DIM = int(os.environ.get("SWARMV_DIM", "768"))
29
+
30
+
31
+ def _fail(msg: str) -> None:
32
+ print(msg, file=sys.stderr)
33
+ sys.exit(1)
34
+
35
+
36
+ def _build_client():
37
+ token_url = os.environ.get("SWARMV_TOKEN_URL")
38
+ user_token = os.environ.get("SWARMV_USER_TOKEN")
39
+ api_key = os.environ.get("SWARMV_API_KEY")
40
+ user_id = os.environ.get("SWARMV_USER_ID")
41
+
42
+ if token_url:
43
+ async def provider() -> str:
44
+ headers = {}
45
+ if os.environ.get("SWARMV_TOKEN_URL_AUTH"):
46
+ headers["Authorization"] = f"Bearer {os.environ['SWARMV_TOKEN_URL_AUTH']}"
47
+ async with httpx.AsyncClient(timeout=10) as http:
48
+ res = await http.post(token_url, headers=headers)
49
+ res.raise_for_status()
50
+ token = res.json().get("token")
51
+ if not token:
52
+ raise RuntimeError("Token endpoint response has no 'token' field")
53
+ return token
54
+
55
+ return AsyncSwarmV(token_provider=provider, base_url=BASE_URL), "token-url"
56
+
57
+ if user_token:
58
+ return AsyncSwarmV(token=user_token, base_url=BASE_URL), "user-token"
59
+
60
+ if api_key:
61
+ if not user_id or not user_id.startswith("user:"):
62
+ _fail(
63
+ "SWARMV_API_KEY alone is not allowed: the agent would act with backend-wide access.\n"
64
+ "Set SWARMV_TOKEN_URL (recommended), or SWARMV_USER_TOKEN, or add SWARMV_USER_ID=user:<id> for local development."
65
+ )
66
+ admin = AsyncSwarmV(api_key=api_key, base_url=BASE_URL)
67
+
68
+ async def provider() -> str:
69
+ return await admin.generate_user_token(user_id, 900, scopes=["vector:read", "vector:write"])
70
+
71
+ print(f"SwarmV MCP: minting tokens locally for {user_id}. Use SWARMV_TOKEN_URL outside local development.", file=sys.stderr)
72
+ return AsyncSwarmV(token_provider=provider, base_url=BASE_URL), "local-key"
73
+
74
+ _fail("No SwarmV credentials. Set SWARMV_TOKEN_URL (recommended), SWARMV_USER_TOKEN, or SWARMV_API_KEY with SWARMV_USER_ID.")
75
+
76
+
77
+ memory, MODE = _build_client()
78
+
79
+
80
+ def _pad(v):
81
+ out = [float(x) for x in v[:DIM]]
82
+ out.extend([0.0] * (DIM - len(out)))
83
+ return out
84
+
85
+
86
+ @server.list_tools()
87
+ async def handle_list_tools() -> list[Tool]:
88
+ return [
89
+ Tool(
90
+ name="read_from_memory",
91
+ description="Search SwarmV memory. Returns the closest records with their id, distance (lower is closer) and stored metadata, such as the note text. Includes notes written moments ago. Results are limited to the containers the connected user can view.",
92
+ inputSchema={
93
+ "type": "object",
94
+ "properties": {
95
+ "query_vector": {"type": "array", "items": {"type": "number"}, "description": f"Dense embedding; padded/truncated to {DIM} dimensions."},
96
+ "top_k": {"type": "number", "description": "Number of results (1-100, default 10)."},
97
+ },
98
+ "required": ["query_vector"],
99
+ },
100
+ ),
101
+ Tool(
102
+ name="write_to_memory",
103
+ description="Write a note to SwarmV memory in a container the connected user can edit. Put the note itself in metadata.text so it comes back from read_from_memory.",
104
+ inputSchema={
105
+ "type": "object",
106
+ "properties": {
107
+ "id": {"type": "string", "description": "Numeric string id (unsigned 32-bit integer)."},
108
+ "vector": {"type": "array", "items": {"type": "number"}, "description": f"Dense embedding; padded/truncated to {DIM} dimensions."},
109
+ "container_id": {"type": "string", "description": "Target container, e.g. 'container:apollo'. The user must be an editor of it."},
110
+ "metadata": {"type": "object", "description": "Flat metadata (string, number or boolean values; up to 8 KB), e.g. {text: 'the note'}. Values come back as strings."},
111
+ },
112
+ "required": ["id", "vector", "container_id"],
113
+ },
114
+ ),
115
+ ]
116
+
117
+
118
+ @server.call_tool()
119
+ async def handle_call_tool(name: str, arguments: dict) -> list[TextContent]:
120
+ try:
121
+ if name == "read_from_memory":
122
+ top_k = int(arguments.get("top_k", 10))
123
+ if not 1 <= top_k <= 100:
124
+ raise ValueError("top_k must be between 1 and 100")
125
+ # No auth_user_id: the user is fixed by the credential, so a prompt can't change who we search as.
126
+ # Agents often read what they (or another agent) just wrote: always include unmerged writes.
127
+ results = await memory.search(query=_pad(arguments["query_vector"]), top_k=top_k, fresh=True)
128
+ payload = [{"id": r.id, "distance": r.distance, "metadata": r.metadata} for r in results]
129
+ return [TextContent(type="text", text=json.dumps(payload, indent=2))]
130
+
131
+ if name == "write_to_memory":
132
+ id_ = str(arguments["id"])
133
+ if not re.fullmatch(r"\d+", id_):
134
+ raise ValueError("id must be a numeric string (unsigned 32-bit integer)")
135
+ container_id = arguments.get("container_id", "")
136
+ if not str(container_id).startswith("container:"):
137
+ raise ValueError("container_id must look like 'container:<name>'")
138
+ metadata = dict(arguments.get("metadata") or {})
139
+ metadata["container_id"] = container_id
140
+ # Write immediately (not buffered) so permission errors are reported back to the model.
141
+ await memory.insert_many([{"id": id_, "vector": _pad(arguments["vector"]), "metadata": metadata}])
142
+ return [TextContent(type="text", text=f"Stored {id_} in {container_id}.")]
143
+
144
+ raise ValueError(f"Unknown tool: {name}")
145
+
146
+ except Exception as e:
147
+ return [TextContent(type="text", text=f"Error executing {name}: {e}")]
148
+
149
+
150
+ async def run_server():
151
+ async with stdio_server() as (read_stream, write_stream):
152
+ print(f"SwarmV MCP Server (Python) running on stdio (credentials: {MODE})", file=sys.stderr)
153
+ await server.run(read_stream, write_stream, server.create_initialization_options())
154
+
155
+
156
+ def main():
157
+ asyncio.run(run_server())
158
+
159
+
160
+ if __name__ == "__main__":
161
+ main()