objectstore-mcp 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.
- objectstore_mcp-0.1.0/LICENSE +21 -0
- objectstore_mcp-0.1.0/MANIFEST.in +4 -0
- objectstore_mcp-0.1.0/PKG-INFO +234 -0
- objectstore_mcp-0.1.0/README.md +198 -0
- objectstore_mcp-0.1.0/objectstore_mcp/__init__.py +63 -0
- objectstore_mcp-0.1.0/objectstore_mcp/__main__.py +4 -0
- objectstore_mcp-0.1.0/objectstore_mcp/agent_server.py +73 -0
- objectstore_mcp-0.1.0/objectstore_mcp/api/__init__.py +84 -0
- objectstore_mcp-0.1.0/objectstore_mcp/api/api_client_azure_blob.py +308 -0
- objectstore_mcp-0.1.0/objectstore_mcp/api/api_client_base.py +234 -0
- objectstore_mcp-0.1.0/objectstore_mcp/api/api_client_filesystem.py +285 -0
- objectstore_mcp-0.1.0/objectstore_mcp/api/api_client_gcs.py +268 -0
- objectstore_mcp-0.1.0/objectstore_mcp/api/api_client_s3.py +291 -0
- objectstore_mcp-0.1.0/objectstore_mcp/api_client.py +37 -0
- objectstore_mcp-0.1.0/objectstore_mcp/auth.py +64 -0
- objectstore_mcp-0.1.0/objectstore_mcp/config.py +170 -0
- objectstore_mcp-0.1.0/objectstore_mcp/main_agent.json +14 -0
- objectstore_mcp-0.1.0/objectstore_mcp/mcp/__init__.py +5 -0
- objectstore_mcp-0.1.0/objectstore_mcp/mcp/mcp_objectstore.py +471 -0
- objectstore_mcp-0.1.0/objectstore_mcp/mcp_config.json +1 -0
- objectstore_mcp-0.1.0/objectstore_mcp/mcp_server.py +61 -0
- objectstore_mcp-0.1.0/objectstore_mcp/objectstore_input_models.py +64 -0
- objectstore_mcp-0.1.0/objectstore_mcp/objectstore_response_models.py +52 -0
- objectstore_mcp-0.1.0/objectstore_mcp.egg-info/PKG-INFO +234 -0
- objectstore_mcp-0.1.0/objectstore_mcp.egg-info/SOURCES.txt +44 -0
- objectstore_mcp-0.1.0/objectstore_mcp.egg-info/dependency_links.txt +1 -0
- objectstore_mcp-0.1.0/objectstore_mcp.egg-info/entry_points.txt +3 -0
- objectstore_mcp-0.1.0/objectstore_mcp.egg-info/requires.txt +27 -0
- objectstore_mcp-0.1.0/objectstore_mcp.egg-info/top_level.txt +1 -0
- objectstore_mcp-0.1.0/pyproject.toml +60 -0
- objectstore_mcp-0.1.0/requirements.txt +2 -0
- objectstore_mcp-0.1.0/setup.cfg +4 -0
- objectstore_mcp-0.1.0/tests/test_api_wrapper.py +33 -0
- objectstore_mcp-0.1.0/tests/test_auth.py +22 -0
- objectstore_mcp-0.1.0/tests/test_azure_backend.py +169 -0
- objectstore_mcp-0.1.0/tests/test_backend_conformance.py +235 -0
- objectstore_mcp-0.1.0/tests/test_concept_parity.py +29 -0
- objectstore_mcp-0.1.0/tests/test_config.py +121 -0
- objectstore_mcp-0.1.0/tests/test_gcs_backend.py +172 -0
- objectstore_mcp-0.1.0/tests/test_init_dynamics.py +6 -0
- objectstore_mcp-0.1.0/tests/test_objectstore_mcp_validation.py +249 -0
- objectstore_mcp-0.1.0/tests/test_optional_dependencies.py +72 -0
- objectstore_mcp-0.1.0/tests/test_s3_backend.py +238 -0
- objectstore_mcp-0.1.0/tests/test_s3_moto.py +45 -0
- objectstore_mcp-0.1.0/tests/test_safety.py +217 -0
- objectstore_mcp-0.1.0/tests/test_startup.py +19 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Knuckles-Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: objectstore-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Object storage (S3/MinIO/R2, Google Cloud Storage, Azure Blob, local filesystem) API + MCP Server + A2A Server for Agentic AI!
|
|
5
|
+
Author-email: Audel Rouhi <knucklessg1@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: <3.15,>=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: agent-utilities>=0.47.0
|
|
16
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
17
|
+
Provides-Extra: mcp
|
|
18
|
+
Requires-Dist: agent-utilities[mcp]>=0.47.0; extra == "mcp"
|
|
19
|
+
Provides-Extra: agent
|
|
20
|
+
Requires-Dist: agent-utilities[agent,logfire]>=0.47.0; extra == "agent"
|
|
21
|
+
Provides-Extra: s3
|
|
22
|
+
Requires-Dist: boto3>=1.34.0; extra == "s3"
|
|
23
|
+
Provides-Extra: gcs
|
|
24
|
+
Requires-Dist: google-cloud-storage>=2.16.0; extra == "gcs"
|
|
25
|
+
Provides-Extra: azure
|
|
26
|
+
Requires-Dist: azure-storage-blob>=12.20.0; extra == "azure"
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: objectstore-mcp[agent,azure,gcs,logfire,mcp,s3]>=0.1.0; extra == "all"
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest-xdist>=3.6.0; extra == "test"
|
|
31
|
+
Requires-Dist: pytest; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
33
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
34
|
+
Requires-Dist: moto[s3]>=5.0.0; extra == "test"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# ObjectStore MCP
|
|
38
|
+
## Multi-Backend Object Storage | MCP Server
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+

|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
The object-storage connector for the agent-utilities fleet: **one MCP tool
|
|
45
|
+
surface over S3 and S3-compatible stores (MinIO, Cloudflare R2), Google Cloud
|
|
46
|
+
Storage, Azure Blob Storage, and a zero-infra local-filesystem backend**.
|
|
47
|
+
|
|
48
|
+
*Version: 0.1.0*
|
|
49
|
+
|
|
50
|
+
> **Documentation** — Installation, deployment, and usage across the API, CLI, and
|
|
51
|
+
> MCP interfaces are maintained in [`docs/`](docs/index.md).
|
|
52
|
+
|
|
53
|
+
## Table of Contents
|
|
54
|
+
|
|
55
|
+
- [Overview](#overview)
|
|
56
|
+
- [What it provides](#what-it-provides)
|
|
57
|
+
- [Backend × capability matrix](#backend--capability-matrix)
|
|
58
|
+
- [Installation](#installation)
|
|
59
|
+
- [Configuration (environment)](#configuration-environment)
|
|
60
|
+
- [Usage](#usage)
|
|
61
|
+
- [MCP config](#mcp-config)
|
|
62
|
+
- [Docker deployment](#docker-deployment)
|
|
63
|
+
- [Development](#development)
|
|
64
|
+
- [License](#license)
|
|
65
|
+
|
|
66
|
+
## Overview
|
|
67
|
+
|
|
68
|
+
`objectstore-mcp` wraps heterogeneous object stores behind one typed,
|
|
69
|
+
deterministic MCP tool surface, plus an optional Pydantic-AI A2A agent server
|
|
70
|
+
(`objectstore-agent`). Safety caps, explicit buckets, and dry-run-by-default
|
|
71
|
+
batch deletes are enforced uniformly in the tool layer, regardless of backend.
|
|
72
|
+
|
|
73
|
+
## What it provides
|
|
74
|
+
|
|
75
|
+
- **A multi-backend store abstraction** (`objectstore_mcp.api`,
|
|
76
|
+
CONCEPT:OBJ-1.0) — every provider implements the same
|
|
77
|
+
`ObjectStoreBackend` protocol, validated by a single conformance test
|
|
78
|
+
suite that runs for real against the filesystem backend.
|
|
79
|
+
- **Three consolidated, action-routed MCP tools** (`objectstore-mcp`
|
|
80
|
+
console script):
|
|
81
|
+
|
|
82
|
+
| Tool | Actions | Description |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `objects` | `list` (prefix/delimiter pagination), `head`, `get` (text/base64, size-capped), `put` (text/base64, size-capped), `copy`, `move`, `delete`, `delete_batch` (capped, dry-run by default), `presign`, `metadata_get`, `metadata_set` | Single-object lifecycle and listing on any store |
|
|
85
|
+
| `buckets` | `list`, `create`, `delete` (empty-only, opt-in), `exists`, `info`, `stores` | Bucket/container admin and store registry introspection |
|
|
86
|
+
| `transfer` | `upload`, `download`, `upload_dir`, `download_prefix` (all size/batch-capped) | Local-filesystem ⇄ object-store transfer, single or by prefix |
|
|
87
|
+
|
|
88
|
+
The whole tool set toggles with `OBJECTSTORETOOL`.
|
|
89
|
+
|
|
90
|
+
- **Named multi-store routing** — `OBJECTSTORE_STORES` JSON maps store names
|
|
91
|
+
to `{backend, bucket?, endpoint?, profile?, ...}`; every tool takes an
|
|
92
|
+
optional `store` argument. A zero-infra `local` filesystem store always
|
|
93
|
+
exists, so the server works with no cloud credentials at all.
|
|
94
|
+
- **A safety governor** — size caps on get/put/transfer, list/batch key caps,
|
|
95
|
+
deletes that demand an explicit bucket+key (no wildcards), batch deletes
|
|
96
|
+
that are prefix-scoped, capped, and dry-run by default, and bucket deletes
|
|
97
|
+
that are empty-only and disabled unless explicitly enabled.
|
|
98
|
+
|
|
99
|
+
## Backend × capability matrix
|
|
100
|
+
|
|
101
|
+
| Capability | filesystem | s3 / minio / r2 | gcs | azure |
|
|
102
|
+
|---|---|---|---|---|
|
|
103
|
+
| buckets (list/create/delete/exists/info) | yes | yes | yes | yes |
|
|
104
|
+
| objects (list/head/get/put/copy/move/delete) | yes | yes | yes | yes |
|
|
105
|
+
| prefix + delimiter listing, pagination | yes | yes | yes | yes |
|
|
106
|
+
| user metadata get/set | yes | yes | yes | yes |
|
|
107
|
+
| presigned URLs | no | yes | yes (needs service-account key) | yes (needs account key) |
|
|
108
|
+
| bucket location | no | yes | yes | no |
|
|
109
|
+
|
|
110
|
+
## Installation
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
pip install objectstore-mcp # core: local filesystem backend only
|
|
114
|
+
pip install objectstore-mcp[s3] # + boto3 (S3, MinIO, R2)
|
|
115
|
+
pip install objectstore-mcp[gcs] # + google-cloud-storage
|
|
116
|
+
pip install objectstore-mcp[azure] # + azure-storage-blob
|
|
117
|
+
pip install objectstore-mcp[all] # everything (incl. MCP + agent extras)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Or pull the container image:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
docker pull knucklessg1/objectstore-mcp:latest
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Configuration (environment)
|
|
127
|
+
|
|
128
|
+
| Var | Default | Meaning |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `OBJECTSTORE_STORES` | _(empty)_ | JSON: store name → `{backend, bucket?, endpoint?, profile?, region?, root?, project?, connection_string?}` |
|
|
131
|
+
| `OBJECTSTORE_DEFAULT_STORE` | first configured store, else `local` | Store used when a tool call omits `store` |
|
|
132
|
+
| `OBJECTSTORE_FS_ROOT` | `~/.local/share/objectstore-mcp` | Root of the implicit `local` filesystem store |
|
|
133
|
+
| `OBJECTSTORE_MAX_GET_BYTES` | `10485760` (10 MiB) | Cap on `objects get` |
|
|
134
|
+
| `OBJECTSTORE_MAX_PUT_BYTES` | `10485760` (10 MiB) | Cap on `objects put` |
|
|
135
|
+
| `OBJECTSTORE_MAX_TRANSFER_BYTES` | `104857600` (100 MiB) | Cap per transfer (single or batch total) |
|
|
136
|
+
| `OBJECTSTORE_MAX_BATCH_KEYS` | `100` | Cap on batch delete / dir transfer key counts |
|
|
137
|
+
| `OBJECTSTORE_MAX_LIST_KEYS` | `1000` | Cap on one listing page |
|
|
138
|
+
| `OBJECTSTORE_ALLOW_DELETE` | `true` | Object deletes (delete/delete_batch/move) |
|
|
139
|
+
| `OBJECTSTORE_ALLOW_BUCKET_DELETE` | `false` | Bucket deletes (empty buckets only) |
|
|
140
|
+
| `OBJECTSTORETOOL` | `True` | Register the objectstore tool set |
|
|
141
|
+
| `HOST` / `PORT` / `TRANSPORT` | `0.0.0.0` / `8000` / `stdio` | MCP server bind + transport (`stdio`, `streamable-http`, `sse`) |
|
|
142
|
+
| `AUTH_TYPE` | `none` | MCP auth mode (container image) |
|
|
143
|
+
| `ENABLE_OTEL` | `True` | OTEL/Langfuse telemetry export |
|
|
144
|
+
| `EUNOMIA_TYPE` / `EUNOMIA_POLICY_FILE` | `none` / `mcp_policies.json` | Eunomia access-governance middleware |
|
|
145
|
+
| `DEFAULT_AGENT_NAME` / `AGENT_DESCRIPTION` / `AGENT_SYSTEM_PROMPT` | identity defaults | A2A agent server identity overrides |
|
|
146
|
+
| `MCP_URL` | _(empty)_ | MCP endpoint the A2A agent connects to |
|
|
147
|
+
|
|
148
|
+
Provider credentials resolve through each SDK's own chain — boto3's
|
|
149
|
+
resolution order for S3 (env keys, `~/.aws` profiles via the store's
|
|
150
|
+
`profile`, SSO, instance metadata), `GOOGLE_APPLICATION_CREDENTIALS` /
|
|
151
|
+
Application Default Credentials for GCS, and
|
|
152
|
+
`AZURE_STORAGE_CONNECTION_STRING` for Azure Blob.
|
|
153
|
+
|
|
154
|
+
### Example store registry
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"media": {"backend": "s3", "bucket": "media-prod", "profile": "prod", "region": "us-east-1"},
|
|
159
|
+
"minio": {"backend": "s3", "endpoint": "http://minio.arpa:9000"},
|
|
160
|
+
"r2": {"backend": "s3", "endpoint": "https://<account>.r2.cloudflarestorage.com"},
|
|
161
|
+
"reports": {"backend": "gcs", "bucket": "acme-reports"},
|
|
162
|
+
"archive": {"backend": "azure", "bucket": "archive"},
|
|
163
|
+
"scratch": {"backend": "filesystem", "root": "~/scratch-store"}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Usage
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
objectstore-mcp # stdio
|
|
171
|
+
objectstore-mcp --transport streamable-http --host 0.0.0.0 --port 8000
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Example tool calls (any MCP client):
|
|
175
|
+
|
|
176
|
+
```jsonc
|
|
177
|
+
// Write then read a text object on the zero-infra local store
|
|
178
|
+
{"tool": "buckets", "arguments": {"action": "create", "params_json": "{\"bucket\": \"notes\"}"}}
|
|
179
|
+
{"tool": "objects", "arguments": {"action": "put", "params_json": "{\"bucket\": \"notes\", \"key\": \"todo.md\", \"text\": \"- ship it\"}"}}
|
|
180
|
+
{"tool": "objects", "arguments": {"action": "get", "params_json": "{\"bucket\": \"notes\", \"key\": \"todo.md\"}"}}
|
|
181
|
+
|
|
182
|
+
// Preview then execute a prefix-scoped batch delete on a named S3 store
|
|
183
|
+
{"tool": "objects", "arguments": {"store": "media", "action": "delete_batch", "params_json": "{\"bucket\": \"media-prod\", \"prefix\": \"tmp/\"}"}}
|
|
184
|
+
{"tool": "objects", "arguments": {"store": "media", "action": "delete_batch", "params_json": "{\"bucket\": \"media-prod\", \"prefix\": \"tmp/\", \"dry_run\": false}"}}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## MCP config
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"mcpServers": {
|
|
192
|
+
"objectstore-mcp": {
|
|
193
|
+
"command": "uv",
|
|
194
|
+
"args": ["run", "objectstore-mcp"],
|
|
195
|
+
"env": {
|
|
196
|
+
"OBJECTSTORE_STORES": "{\"minio\": {\"backend\": \"s3\", \"endpoint\": \"http://minio.arpa:9000\"}}",
|
|
197
|
+
"OBJECTSTORE_DEFAULT_STORE": "local"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Run the A2A agent server against a live MCP server:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
objectstore-agent --mcp-url http://localhost:8000/mcp --host 0.0.0.0 --port 9001
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Docker deployment
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
docker compose -f docker/mcp.compose.yml up -d # MCP server only
|
|
214
|
+
docker compose -f docker/agent.compose.yml up -d # MCP + A2A agent
|
|
215
|
+
curl -s http://localhost:8000/health # {"status":"OK"}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Both services read configuration from `../.env` (copy
|
|
219
|
+
[`.env.example`](.env.example)); see [`docs/deployment.md`](docs/deployment.md).
|
|
220
|
+
|
|
221
|
+
## Development
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
pip install -e .[test]
|
|
225
|
+
pytest # full suite (cloud SDKs not required)
|
|
226
|
+
pip install -e .[test-s3] # adds boto3+moto integration tests
|
|
227
|
+
pre-commit run --all-files
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
See [`docs/`](docs/) for architecture, concepts, and deployment details.
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# ObjectStore MCP
|
|
2
|
+
## Multi-Backend Object Storage | MCP Server
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
The object-storage connector for the agent-utilities fleet: **one MCP tool
|
|
9
|
+
surface over S3 and S3-compatible stores (MinIO, Cloudflare R2), Google Cloud
|
|
10
|
+
Storage, Azure Blob Storage, and a zero-infra local-filesystem backend**.
|
|
11
|
+
|
|
12
|
+
*Version: 0.1.0*
|
|
13
|
+
|
|
14
|
+
> **Documentation** — Installation, deployment, and usage across the API, CLI, and
|
|
15
|
+
> MCP interfaces are maintained in [`docs/`](docs/index.md).
|
|
16
|
+
|
|
17
|
+
## Table of Contents
|
|
18
|
+
|
|
19
|
+
- [Overview](#overview)
|
|
20
|
+
- [What it provides](#what-it-provides)
|
|
21
|
+
- [Backend × capability matrix](#backend--capability-matrix)
|
|
22
|
+
- [Installation](#installation)
|
|
23
|
+
- [Configuration (environment)](#configuration-environment)
|
|
24
|
+
- [Usage](#usage)
|
|
25
|
+
- [MCP config](#mcp-config)
|
|
26
|
+
- [Docker deployment](#docker-deployment)
|
|
27
|
+
- [Development](#development)
|
|
28
|
+
- [License](#license)
|
|
29
|
+
|
|
30
|
+
## Overview
|
|
31
|
+
|
|
32
|
+
`objectstore-mcp` wraps heterogeneous object stores behind one typed,
|
|
33
|
+
deterministic MCP tool surface, plus an optional Pydantic-AI A2A agent server
|
|
34
|
+
(`objectstore-agent`). Safety caps, explicit buckets, and dry-run-by-default
|
|
35
|
+
batch deletes are enforced uniformly in the tool layer, regardless of backend.
|
|
36
|
+
|
|
37
|
+
## What it provides
|
|
38
|
+
|
|
39
|
+
- **A multi-backend store abstraction** (`objectstore_mcp.api`,
|
|
40
|
+
CONCEPT:OBJ-1.0) — every provider implements the same
|
|
41
|
+
`ObjectStoreBackend` protocol, validated by a single conformance test
|
|
42
|
+
suite that runs for real against the filesystem backend.
|
|
43
|
+
- **Three consolidated, action-routed MCP tools** (`objectstore-mcp`
|
|
44
|
+
console script):
|
|
45
|
+
|
|
46
|
+
| Tool | Actions | Description |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `objects` | `list` (prefix/delimiter pagination), `head`, `get` (text/base64, size-capped), `put` (text/base64, size-capped), `copy`, `move`, `delete`, `delete_batch` (capped, dry-run by default), `presign`, `metadata_get`, `metadata_set` | Single-object lifecycle and listing on any store |
|
|
49
|
+
| `buckets` | `list`, `create`, `delete` (empty-only, opt-in), `exists`, `info`, `stores` | Bucket/container admin and store registry introspection |
|
|
50
|
+
| `transfer` | `upload`, `download`, `upload_dir`, `download_prefix` (all size/batch-capped) | Local-filesystem ⇄ object-store transfer, single or by prefix |
|
|
51
|
+
|
|
52
|
+
The whole tool set toggles with `OBJECTSTORETOOL`.
|
|
53
|
+
|
|
54
|
+
- **Named multi-store routing** — `OBJECTSTORE_STORES` JSON maps store names
|
|
55
|
+
to `{backend, bucket?, endpoint?, profile?, ...}`; every tool takes an
|
|
56
|
+
optional `store` argument. A zero-infra `local` filesystem store always
|
|
57
|
+
exists, so the server works with no cloud credentials at all.
|
|
58
|
+
- **A safety governor** — size caps on get/put/transfer, list/batch key caps,
|
|
59
|
+
deletes that demand an explicit bucket+key (no wildcards), batch deletes
|
|
60
|
+
that are prefix-scoped, capped, and dry-run by default, and bucket deletes
|
|
61
|
+
that are empty-only and disabled unless explicitly enabled.
|
|
62
|
+
|
|
63
|
+
## Backend × capability matrix
|
|
64
|
+
|
|
65
|
+
| Capability | filesystem | s3 / minio / r2 | gcs | azure |
|
|
66
|
+
|---|---|---|---|---|
|
|
67
|
+
| buckets (list/create/delete/exists/info) | yes | yes | yes | yes |
|
|
68
|
+
| objects (list/head/get/put/copy/move/delete) | yes | yes | yes | yes |
|
|
69
|
+
| prefix + delimiter listing, pagination | yes | yes | yes | yes |
|
|
70
|
+
| user metadata get/set | yes | yes | yes | yes |
|
|
71
|
+
| presigned URLs | no | yes | yes (needs service-account key) | yes (needs account key) |
|
|
72
|
+
| bucket location | no | yes | yes | no |
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install objectstore-mcp # core: local filesystem backend only
|
|
78
|
+
pip install objectstore-mcp[s3] # + boto3 (S3, MinIO, R2)
|
|
79
|
+
pip install objectstore-mcp[gcs] # + google-cloud-storage
|
|
80
|
+
pip install objectstore-mcp[azure] # + azure-storage-blob
|
|
81
|
+
pip install objectstore-mcp[all] # everything (incl. MCP + agent extras)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Or pull the container image:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
docker pull knucklessg1/objectstore-mcp:latest
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Configuration (environment)
|
|
91
|
+
|
|
92
|
+
| Var | Default | Meaning |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `OBJECTSTORE_STORES` | _(empty)_ | JSON: store name → `{backend, bucket?, endpoint?, profile?, region?, root?, project?, connection_string?}` |
|
|
95
|
+
| `OBJECTSTORE_DEFAULT_STORE` | first configured store, else `local` | Store used when a tool call omits `store` |
|
|
96
|
+
| `OBJECTSTORE_FS_ROOT` | `~/.local/share/objectstore-mcp` | Root of the implicit `local` filesystem store |
|
|
97
|
+
| `OBJECTSTORE_MAX_GET_BYTES` | `10485760` (10 MiB) | Cap on `objects get` |
|
|
98
|
+
| `OBJECTSTORE_MAX_PUT_BYTES` | `10485760` (10 MiB) | Cap on `objects put` |
|
|
99
|
+
| `OBJECTSTORE_MAX_TRANSFER_BYTES` | `104857600` (100 MiB) | Cap per transfer (single or batch total) |
|
|
100
|
+
| `OBJECTSTORE_MAX_BATCH_KEYS` | `100` | Cap on batch delete / dir transfer key counts |
|
|
101
|
+
| `OBJECTSTORE_MAX_LIST_KEYS` | `1000` | Cap on one listing page |
|
|
102
|
+
| `OBJECTSTORE_ALLOW_DELETE` | `true` | Object deletes (delete/delete_batch/move) |
|
|
103
|
+
| `OBJECTSTORE_ALLOW_BUCKET_DELETE` | `false` | Bucket deletes (empty buckets only) |
|
|
104
|
+
| `OBJECTSTORETOOL` | `True` | Register the objectstore tool set |
|
|
105
|
+
| `HOST` / `PORT` / `TRANSPORT` | `0.0.0.0` / `8000` / `stdio` | MCP server bind + transport (`stdio`, `streamable-http`, `sse`) |
|
|
106
|
+
| `AUTH_TYPE` | `none` | MCP auth mode (container image) |
|
|
107
|
+
| `ENABLE_OTEL` | `True` | OTEL/Langfuse telemetry export |
|
|
108
|
+
| `EUNOMIA_TYPE` / `EUNOMIA_POLICY_FILE` | `none` / `mcp_policies.json` | Eunomia access-governance middleware |
|
|
109
|
+
| `DEFAULT_AGENT_NAME` / `AGENT_DESCRIPTION` / `AGENT_SYSTEM_PROMPT` | identity defaults | A2A agent server identity overrides |
|
|
110
|
+
| `MCP_URL` | _(empty)_ | MCP endpoint the A2A agent connects to |
|
|
111
|
+
|
|
112
|
+
Provider credentials resolve through each SDK's own chain — boto3's
|
|
113
|
+
resolution order for S3 (env keys, `~/.aws` profiles via the store's
|
|
114
|
+
`profile`, SSO, instance metadata), `GOOGLE_APPLICATION_CREDENTIALS` /
|
|
115
|
+
Application Default Credentials for GCS, and
|
|
116
|
+
`AZURE_STORAGE_CONNECTION_STRING` for Azure Blob.
|
|
117
|
+
|
|
118
|
+
### Example store registry
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"media": {"backend": "s3", "bucket": "media-prod", "profile": "prod", "region": "us-east-1"},
|
|
123
|
+
"minio": {"backend": "s3", "endpoint": "http://minio.arpa:9000"},
|
|
124
|
+
"r2": {"backend": "s3", "endpoint": "https://<account>.r2.cloudflarestorage.com"},
|
|
125
|
+
"reports": {"backend": "gcs", "bucket": "acme-reports"},
|
|
126
|
+
"archive": {"backend": "azure", "bucket": "archive"},
|
|
127
|
+
"scratch": {"backend": "filesystem", "root": "~/scratch-store"}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Usage
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
objectstore-mcp # stdio
|
|
135
|
+
objectstore-mcp --transport streamable-http --host 0.0.0.0 --port 8000
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Example tool calls (any MCP client):
|
|
139
|
+
|
|
140
|
+
```jsonc
|
|
141
|
+
// Write then read a text object on the zero-infra local store
|
|
142
|
+
{"tool": "buckets", "arguments": {"action": "create", "params_json": "{\"bucket\": \"notes\"}"}}
|
|
143
|
+
{"tool": "objects", "arguments": {"action": "put", "params_json": "{\"bucket\": \"notes\", \"key\": \"todo.md\", \"text\": \"- ship it\"}"}}
|
|
144
|
+
{"tool": "objects", "arguments": {"action": "get", "params_json": "{\"bucket\": \"notes\", \"key\": \"todo.md\"}"}}
|
|
145
|
+
|
|
146
|
+
// Preview then execute a prefix-scoped batch delete on a named S3 store
|
|
147
|
+
{"tool": "objects", "arguments": {"store": "media", "action": "delete_batch", "params_json": "{\"bucket\": \"media-prod\", \"prefix\": \"tmp/\"}"}}
|
|
148
|
+
{"tool": "objects", "arguments": {"store": "media", "action": "delete_batch", "params_json": "{\"bucket\": \"media-prod\", \"prefix\": \"tmp/\", \"dry_run\": false}"}}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## MCP config
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"mcpServers": {
|
|
156
|
+
"objectstore-mcp": {
|
|
157
|
+
"command": "uv",
|
|
158
|
+
"args": ["run", "objectstore-mcp"],
|
|
159
|
+
"env": {
|
|
160
|
+
"OBJECTSTORE_STORES": "{\"minio\": {\"backend\": \"s3\", \"endpoint\": \"http://minio.arpa:9000\"}}",
|
|
161
|
+
"OBJECTSTORE_DEFAULT_STORE": "local"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Run the A2A agent server against a live MCP server:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
objectstore-agent --mcp-url http://localhost:8000/mcp --host 0.0.0.0 --port 9001
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Docker deployment
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
docker compose -f docker/mcp.compose.yml up -d # MCP server only
|
|
178
|
+
docker compose -f docker/agent.compose.yml up -d # MCP + A2A agent
|
|
179
|
+
curl -s http://localhost:8000/health # {"status":"OK"}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Both services read configuration from `../.env` (copy
|
|
183
|
+
[`.env.example`](.env.example)); see [`docs/deployment.md`](docs/deployment.md).
|
|
184
|
+
|
|
185
|
+
## Development
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
pip install -e .[test]
|
|
189
|
+
pytest # full suite (cloud SDKs not required)
|
|
190
|
+
pip install -e .[test-s3] # adds boto3+moto integration tests
|
|
191
|
+
pre-commit run --all-files
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
See [`docs/`](docs/) for architecture, concepts, and deployment details.
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""objectstore-mcp — multi-backend object-storage API + MCP Server + A2A Agent.
|
|
2
|
+
|
|
3
|
+
One MCP tool surface (objects/buckets/transfer) over S3 and S3-compatible
|
|
4
|
+
stores (MinIO, Cloudflare R2), Google Cloud Storage, Azure Blob Storage, and
|
|
5
|
+
a zero-infra local-filesystem backend (CONCEPT:OBJ-1.0).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import importlib
|
|
9
|
+
import inspect
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
__all__: list[str] = []
|
|
14
|
+
|
|
15
|
+
CORE_MODULES = ["objectstore_mcp.api_client"]
|
|
16
|
+
OPTIONAL_MODULES = {
|
|
17
|
+
"objectstore_mcp.agent_server": "agent",
|
|
18
|
+
"objectstore_mcp.mcp_server": "mcp",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _expose_members(module):
|
|
23
|
+
for name, obj in inspect.getmembers(module):
|
|
24
|
+
if (inspect.isclass(obj) or inspect.isfunction(obj)) and not name.startswith(
|
|
25
|
+
"_"
|
|
26
|
+
):
|
|
27
|
+
globals()[name] = obj
|
|
28
|
+
if name not in __all__:
|
|
29
|
+
__all__.append(name)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
for module_name in CORE_MODULES:
|
|
33
|
+
module = importlib.import_module(module_name)
|
|
34
|
+
_expose_members(module)
|
|
35
|
+
|
|
36
|
+
_loaded_optional_modules: dict = {}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _import_module_safely(module_name: str):
|
|
40
|
+
try:
|
|
41
|
+
return importlib.import_module(module_name)
|
|
42
|
+
except ImportError:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def __getattr__(name: str) -> Any:
|
|
47
|
+
if name == "_MCP_AVAILABLE":
|
|
48
|
+
mcp_key = next((k for k in OPTIONAL_MODULES if "mcp_server" in k), None)
|
|
49
|
+
return _import_module_safely(mcp_key) is not None if mcp_key else False
|
|
50
|
+
if name == "_AGENT_AVAILABLE":
|
|
51
|
+
agent_key = next((k for k in OPTIONAL_MODULES if "agent_server" in k), None)
|
|
52
|
+
return _import_module_safely(agent_key) is not None if agent_key else False
|
|
53
|
+
|
|
54
|
+
for module_name in OPTIONAL_MODULES:
|
|
55
|
+
if module_name not in _loaded_optional_modules:
|
|
56
|
+
module = _import_module_safely(module_name)
|
|
57
|
+
_loaded_optional_modules[module_name] = module
|
|
58
|
+
if module is not None:
|
|
59
|
+
_expose_members(module)
|
|
60
|
+
module = _loaded_optional_modules[module_name]
|
|
61
|
+
if module is not None and hasattr(module, name):
|
|
62
|
+
return getattr(module, name)
|
|
63
|
+
raise AttributeError(f"module 'objectstore_mcp' has no attribute {name!r}")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
logging.basicConfig(
|
|
10
|
+
level=logging.INFO,
|
|
11
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
12
|
+
handlers=[logging.StreamHandler()],
|
|
13
|
+
)
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def agent_server():
|
|
18
|
+
from agent_utilities import (
|
|
19
|
+
build_system_prompt_from_workspace,
|
|
20
|
+
create_agent_parser,
|
|
21
|
+
create_agent_server,
|
|
22
|
+
initialize_workspace,
|
|
23
|
+
load_identity,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
warnings.filterwarnings("ignore", message=".*urllib3.*or chardet.*")
|
|
27
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning, module="fastmcp")
|
|
28
|
+
|
|
29
|
+
initialize_workspace()
|
|
30
|
+
meta = load_identity()
|
|
31
|
+
agent_name = os.getenv("DEFAULT_AGENT_NAME", meta.get("name", "Objectstore Mcp"))
|
|
32
|
+
|
|
33
|
+
print(f"{agent_name} v{__version__}", file=sys.stderr)
|
|
34
|
+
parser = create_agent_parser()
|
|
35
|
+
args = parser.parse_args()
|
|
36
|
+
|
|
37
|
+
if args.debug:
|
|
38
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
|
39
|
+
logger.debug("Debug mode enabled")
|
|
40
|
+
|
|
41
|
+
create_agent_server(
|
|
42
|
+
mcp_url=args.mcp_url,
|
|
43
|
+
mcp_config=args.mcp_config or "mcp_config.json",
|
|
44
|
+
host=args.host,
|
|
45
|
+
port=args.port,
|
|
46
|
+
provider=args.provider,
|
|
47
|
+
model_id=args.model_id,
|
|
48
|
+
router_model=args.model_id,
|
|
49
|
+
agent_model=args.model_id,
|
|
50
|
+
base_url=args.base_url,
|
|
51
|
+
api_key=args.api_key,
|
|
52
|
+
agent_description=os.getenv(
|
|
53
|
+
"AGENT_DESCRIPTION",
|
|
54
|
+
meta.get("description", "Objectstore Mcp API + MCP Server + A2A Server"),
|
|
55
|
+
),
|
|
56
|
+
system_prompt=os.getenv(
|
|
57
|
+
"AGENT_SYSTEM_PROMPT",
|
|
58
|
+
meta.get("content") or build_system_prompt_from_workspace(),
|
|
59
|
+
),
|
|
60
|
+
custom_skills_directory=args.custom_skills_directory,
|
|
61
|
+
enable_web_ui=args.web,
|
|
62
|
+
enable_otel=args.otel,
|
|
63
|
+
otel_endpoint=args.otel_endpoint,
|
|
64
|
+
otel_headers=args.otel_headers,
|
|
65
|
+
otel_public_key=args.otel_public_key,
|
|
66
|
+
otel_secret_key=args.otel_secret_key,
|
|
67
|
+
otel_protocol=args.otel_protocol,
|
|
68
|
+
debug=args.debug,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
agent_server()
|