vaultkit 0.1.1__tar.gz → 0.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.
- {vaultkit-0.1.1 → vaultkit-0.2.0}/PKG-INFO +7 -5
- {vaultkit-0.1.1 → vaultkit-0.2.0}/pyproject.toml +11 -4
- vaultkit-0.2.0/vaultkit/mcp/__init__.py +32 -0
- vaultkit-0.2.0/vaultkit/mcp/__main__.py +116 -0
- vaultkit-0.2.0/vaultkit/mcp/identity.py +118 -0
- vaultkit-0.2.0/vaultkit/mcp/server.py +130 -0
- vaultkit-0.2.0/vaultkit/tools/adapters/mcp.py +41 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit.egg-info/PKG-INFO +7 -5
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit.egg-info/SOURCES.txt +6 -0
- vaultkit-0.2.0/vaultkit.egg-info/entry_points.txt +2 -0
- vaultkit-0.2.0/vaultkit.egg-info/requires.txt +4 -0
- vaultkit-0.1.1/vaultkit.egg-info/requires.txt +0 -1
- {vaultkit-0.1.1 → vaultkit-0.2.0}/README.md +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/setup.cfg +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/tests/test_fetch.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/tests/test_query.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/__init__.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/client.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/core/__init__.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/core/http.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/core/polling.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/errors/__init__.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/errors/base.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/errors/exceptions.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/models/__init__.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/models/dataset_info.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/models/dataset_schema.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/models/fetch_result.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/models/query_result.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/tools/__init__.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/tools/adapters/__init__.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/tools/adapters/anthropic.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/tools/adapters/openai.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/tools/builder.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/tools/definitions.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/tools/executor.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/tools/schemas.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/utils/__init__.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/utils/retry.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit/utils/validation.py +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit.egg-info/dependency_links.txt +0 -0
- {vaultkit-0.1.1 → vaultkit-0.2.0}/vaultkit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vaultkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: VaultKit Python SDK for policy-driven, runtime governed data access
|
|
5
5
|
Author-email: VaultKit <founders@vaultkit.io>
|
|
6
6
|
License: MIT
|
|
@@ -8,19 +8,21 @@ Project-URL: Homepage, https://github.com/vaultkit-inc/vaultkit-sdk-python
|
|
|
8
8
|
Project-URL: Documentation, https://docs.vaultkit.io
|
|
9
9
|
Project-URL: Source, https://github.com/vaultkit-inc/vaultkit-sdk-python
|
|
10
10
|
Project-URL: Issues, https://github.com/vaultkit-inc/vaultkit-sdk-python/issues
|
|
11
|
-
Keywords: ai,data,sdk,governance,security,agents,runtime
|
|
11
|
+
Keywords: ai,data,sdk,governance,security,agents,runtime,mcp
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
20
|
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
-
Requires-Python: >=3.
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
Requires-Dist: httpx>=0.24.0
|
|
24
|
+
Provides-Extra: mcp
|
|
25
|
+
Requires-Dist: mcp<2,>=1.27; extra == "mcp"
|
|
24
26
|
|
|
25
27
|
# VaultKit Python SDK
|
|
26
28
|
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vaultkit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "VaultKit Python SDK for policy-driven, runtime governed data access"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "VaultKit", email = "founders@vaultkit.io" }
|
|
11
11
|
]
|
|
12
12
|
readme = "README.md"
|
|
13
|
-
requires-python = ">=3.
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
14
|
license = { text = "MIT" }
|
|
15
15
|
|
|
16
16
|
dependencies = [
|
|
@@ -25,6 +25,7 @@ keywords = [
|
|
|
25
25
|
"security",
|
|
26
26
|
"agents",
|
|
27
27
|
"runtime",
|
|
28
|
+
"mcp",
|
|
28
29
|
]
|
|
29
30
|
|
|
30
31
|
classifiers = [
|
|
@@ -32,13 +33,19 @@ classifiers = [
|
|
|
32
33
|
"Intended Audience :: Developers",
|
|
33
34
|
"License :: OSI Approved :: MIT License",
|
|
34
35
|
"Programming Language :: Python :: 3",
|
|
35
|
-
"Programming Language :: Python :: 3.8",
|
|
36
|
-
"Programming Language :: Python :: 3.9",
|
|
37
36
|
"Programming Language :: Python :: 3.10",
|
|
38
37
|
"Programming Language :: Python :: 3.11",
|
|
38
|
+
"Programming Language :: Python :: 3.12",
|
|
39
|
+
"Programming Language :: Python :: 3.13",
|
|
39
40
|
"Topic :: Software Development :: Libraries",
|
|
40
41
|
]
|
|
41
42
|
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
mcp = ["mcp>=1.27,<2"]
|
|
45
|
+
|
|
46
|
+
[project.scripts]
|
|
47
|
+
vaultkit-mcp = "vaultkit.mcp.__main__:main"
|
|
48
|
+
|
|
42
49
|
[project.urls]
|
|
43
50
|
Homepage = "https://github.com/vaultkit-inc/vaultkit-sdk-python"
|
|
44
51
|
Documentation = "https://docs.vaultkit.io"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
VaultKit MCP server (the ``[mcp]`` extra).
|
|
5
|
+
|
|
6
|
+
This subpackage exposes VaultKit's governed tools over the Model Context
|
|
7
|
+
Protocol so any MCP host (Claude Desktop, Cursor, custom agents) can query
|
|
8
|
+
governed data.
|
|
9
|
+
|
|
10
|
+
No policy evaluation happens here. This layer advertises tools by reusing
|
|
11
|
+
``ToolBuilder`` and dispatches calls by reusing ``ToolExecutor``. Every policy
|
|
12
|
+
decision, mask, grant and audit entry is performed by the control plane.
|
|
13
|
+
|
|
14
|
+
Two deployments share one codebase:
|
|
15
|
+
- stdio : one principal from environment variables (local use).
|
|
16
|
+
- http : one principal per authenticated session (not yet implemented;
|
|
17
|
+
see identity.HttpClientResolver).
|
|
18
|
+
|
|
19
|
+
The only difference between them is how the caller's identity is resolved into
|
|
20
|
+
a VaultKitClient; everything else is shared.
|
|
21
|
+
|
|
22
|
+
Install:
|
|
23
|
+
pip install vaultkit[mcp]
|
|
24
|
+
|
|
25
|
+
Run:
|
|
26
|
+
vaultkit-mcp # stdio (default)
|
|
27
|
+
vaultkit-mcp --transport http --host 0.0.0.0 --port 8000 # not yet implemented
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from .server import McpServerConfig, build_server
|
|
31
|
+
|
|
32
|
+
__all__ = ["McpServerConfig", "build_server"]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
``vaultkit-mcp`` entrypoint.
|
|
5
|
+
|
|
6
|
+
vaultkit-mcp # stdio (default), local use
|
|
7
|
+
vaultkit-mcp --transport http --port 8000 # hosted (not yet implemented)
|
|
8
|
+
|
|
9
|
+
stdio is the local transport: an MCP host (Claude Desktop, Cursor, ...) launches
|
|
10
|
+
this as a subprocess and pipes JSON-RPC over stdin/stdout. Credentials come from
|
|
11
|
+
the environment (see StdioClientResolver).
|
|
12
|
+
|
|
13
|
+
http (streamable HTTP) is the hosted transport. It is not wired up yet: it
|
|
14
|
+
requires the per-principal OAuth exchange in HttpClientResolver first.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
from typing import Optional
|
|
21
|
+
|
|
22
|
+
from .identity import HttpClientResolver, StdioClientResolver
|
|
23
|
+
from .server import McpServerConfig, build_server
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _config_from_env() -> McpServerConfig:
|
|
27
|
+
def _flag(name: str, default: bool) -> bool:
|
|
28
|
+
raw = os.environ.get(name)
|
|
29
|
+
if raw is None:
|
|
30
|
+
return default
|
|
31
|
+
return raw.strip().lower() in ("1", "true", "yes", "on")
|
|
32
|
+
|
|
33
|
+
return McpServerConfig(
|
|
34
|
+
environment=os.environ.get("VAULTKIT_ENVIRONMENT", "production"),
|
|
35
|
+
include_discover=_flag("VAULTKIT_MCP_INCLUDE_DISCOVER", True),
|
|
36
|
+
include_query=_flag("VAULTKIT_MCP_INCLUDE_QUERY", True),
|
|
37
|
+
include_check_approval=_flag("VAULTKIT_MCP_INCLUDE_CHECK_APPROVAL", True),
|
|
38
|
+
fetch_schema_hints=_flag("VAULTKIT_MCP_SCHEMA_HINTS", True),
|
|
39
|
+
default_purpose=os.environ.get("VAULTKIT_MCP_DEFAULT_PURPOSE"),
|
|
40
|
+
default_requester_region=os.environ.get("VAULTKIT_MCP_DEFAULT_REGION"),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def _run_stdio(server) -> None:
|
|
45
|
+
from mcp.server.stdio import stdio_server
|
|
46
|
+
|
|
47
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
48
|
+
await server.run(
|
|
49
|
+
read_stream,
|
|
50
|
+
write_stream,
|
|
51
|
+
server.create_initialization_options(),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _run_http(server, host: str, port: int) -> None:
|
|
56
|
+
# Not implemented. Streamable HTTP requires a session manager and an ASGI
|
|
57
|
+
# app, and — before that — the per-principal OAuth exchange in
|
|
58
|
+
# HttpClientResolver. Reference for the intended wiring:
|
|
59
|
+
#
|
|
60
|
+
# from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
|
61
|
+
# import uvicorn
|
|
62
|
+
# from starlette.applications import Starlette
|
|
63
|
+
# ... mount the session manager, add OAuth token validation middleware,
|
|
64
|
+
# then uvicorn.run(app, host=host, port=port)
|
|
65
|
+
raise NotImplementedError(
|
|
66
|
+
"Hosted HTTP transport is not implemented yet. Implement "
|
|
67
|
+
"HttpClientResolver (per-principal identity) first, then wire the "
|
|
68
|
+
"streamable-HTTP ASGI app here. Use --transport stdio for now."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def main(argv: Optional[list] = None) -> int:
|
|
73
|
+
parser = argparse.ArgumentParser(
|
|
74
|
+
prog="vaultkit-mcp",
|
|
75
|
+
description="Expose VaultKit's governed tools over the Model Context Protocol.",
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument(
|
|
78
|
+
"--transport",
|
|
79
|
+
choices=("stdio", "http"),
|
|
80
|
+
default=os.environ.get("VAULTKIT_MCP_TRANSPORT", "stdio"),
|
|
81
|
+
help="Transport to serve on (default: stdio).",
|
|
82
|
+
)
|
|
83
|
+
parser.add_argument("--host", default=os.environ.get("VAULTKIT_MCP_HOST", "127.0.0.1"))
|
|
84
|
+
parser.add_argument("--port", type=int, default=int(os.environ.get("VAULTKIT_MCP_PORT", "8000")))
|
|
85
|
+
args = parser.parse_args(argv)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
import anyio
|
|
89
|
+
except ImportError:
|
|
90
|
+
print(
|
|
91
|
+
"The MCP extra is not installed. Install it with:\n"
|
|
92
|
+
" pip install vaultkit[mcp]",
|
|
93
|
+
file=sys.stderr,
|
|
94
|
+
)
|
|
95
|
+
return 1
|
|
96
|
+
|
|
97
|
+
config = _config_from_env()
|
|
98
|
+
|
|
99
|
+
if args.transport == "stdio":
|
|
100
|
+
resolver = StdioClientResolver()
|
|
101
|
+
server = build_server(resolver, config)
|
|
102
|
+
try:
|
|
103
|
+
anyio.run(_run_stdio, server)
|
|
104
|
+
finally:
|
|
105
|
+
resolver.close()
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
# http
|
|
109
|
+
resolver = HttpClientResolver()
|
|
110
|
+
server = build_server(resolver, config)
|
|
111
|
+
_run_http(server, args.host, args.port)
|
|
112
|
+
return 0
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Identity resolution for the MCP server.
|
|
5
|
+
|
|
6
|
+
A single interface, ``ClientResolver``, turns an incoming request into a
|
|
7
|
+
``VaultKitClient``. This is the only part of the MCP layer that differs between
|
|
8
|
+
deployments, which lets ``server.py`` be written once and run in either mode:
|
|
9
|
+
|
|
10
|
+
stdio -> StdioClientResolver : one principal, read from env at startup.
|
|
11
|
+
http -> HttpClientResolver : one principal per authenticated request.
|
|
12
|
+
|
|
13
|
+
The request handlers depend only on the ``ClientResolver`` interface and never
|
|
14
|
+
know which implementation they received.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
from typing import Any, Optional
|
|
19
|
+
|
|
20
|
+
from vaultkit.client import VaultKitClient
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ClientResolver:
|
|
24
|
+
"""Resolve an incoming request context into a VaultKitClient.
|
|
25
|
+
|
|
26
|
+
``ctx`` is whatever the transport provides: ``None`` for stdio, or an
|
|
27
|
+
authenticated request context for http. Implementations use as much of it
|
|
28
|
+
as they need.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def resolve(self, ctx: Optional[Any] = None) -> VaultKitClient: # pragma: no cover
|
|
32
|
+
raise NotImplementedError
|
|
33
|
+
|
|
34
|
+
def close(self) -> None: # pragma: no cover
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class StdioClientResolver(ClientResolver):
|
|
39
|
+
"""Single-principal resolver for local (stdio) use.
|
|
40
|
+
|
|
41
|
+
Reads credentials from the environment once and returns the same client for
|
|
42
|
+
every call. Over stdio there is a single caller (the host process that
|
|
43
|
+
launched the server), so there is a single principal, and ``ctx`` is
|
|
44
|
+
ignored.
|
|
45
|
+
|
|
46
|
+
Required environment variables:
|
|
47
|
+
VAULTKIT_BASE_URL
|
|
48
|
+
VAULTKIT_TOKEN
|
|
49
|
+
VAULTKIT_ORG
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
*,
|
|
55
|
+
base_url: Optional[str] = None,
|
|
56
|
+
token: Optional[str] = None,
|
|
57
|
+
org: Optional[str] = None,
|
|
58
|
+
) -> None:
|
|
59
|
+
self._base_url = base_url or os.environ.get("VAULTKIT_BASE_URL")
|
|
60
|
+
self._token = token or os.environ.get("VAULTKIT_TOKEN")
|
|
61
|
+
self._org = org or os.environ.get("VAULTKIT_ORG")
|
|
62
|
+
|
|
63
|
+
missing = [
|
|
64
|
+
name
|
|
65
|
+
for name, val in (
|
|
66
|
+
("VAULTKIT_BASE_URL", self._base_url),
|
|
67
|
+
("VAULTKIT_TOKEN", self._token),
|
|
68
|
+
("VAULTKIT_ORG", self._org),
|
|
69
|
+
)
|
|
70
|
+
if not val
|
|
71
|
+
]
|
|
72
|
+
if missing:
|
|
73
|
+
raise RuntimeError(
|
|
74
|
+
"Missing required VaultKit credentials for the MCP server: "
|
|
75
|
+
+ ", ".join(missing)
|
|
76
|
+
+ ". Set them in the environment (or your MCP host's server "
|
|
77
|
+
"config 'env' block) before starting vaultkit-mcp."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
self._client: Optional[VaultKitClient] = None
|
|
81
|
+
|
|
82
|
+
def resolve(self, ctx: Optional[Any] = None) -> VaultKitClient:
|
|
83
|
+
if self._client is None:
|
|
84
|
+
self._client = VaultKitClient(
|
|
85
|
+
base_url=self._base_url, # type: ignore[arg-type]
|
|
86
|
+
token=self._token, # type: ignore[arg-type]
|
|
87
|
+
org=self._org, # type: ignore[arg-type]
|
|
88
|
+
)
|
|
89
|
+
return self._client
|
|
90
|
+
|
|
91
|
+
def close(self) -> None:
|
|
92
|
+
if self._client is not None:
|
|
93
|
+
self._client.close()
|
|
94
|
+
self._client = None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class HttpClientResolver(ClientResolver):
|
|
98
|
+
"""Per-principal resolver for hosted (streamable-HTTP) use.
|
|
99
|
+
|
|
100
|
+
Not yet implemented. Over http each request may belong to a different
|
|
101
|
+
principal, so a client cannot be built once at startup; it must be resolved
|
|
102
|
+
per request from the authenticated identity.
|
|
103
|
+
|
|
104
|
+
Intended implementation:
|
|
105
|
+
1. Read the bearer token from ``ctx`` (the request's auth context).
|
|
106
|
+
2. Resolve it to a (token, org) principal, delegating to the control
|
|
107
|
+
plane rather than trusting client-supplied claims.
|
|
108
|
+
3. Construct and return a VaultKitClient for that principal.
|
|
109
|
+
4. Optionally cache clients per principal and close idle ones.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def resolve(self, ctx: Optional[Any] = None) -> VaultKitClient:
|
|
113
|
+
raise NotImplementedError(
|
|
114
|
+
"Hosted HTTP identity resolution is not implemented yet. "
|
|
115
|
+
"Run the server with --transport stdio, or implement "
|
|
116
|
+
"HttpClientResolver.resolve to exchange the request's OAuth token "
|
|
117
|
+
"for a VaultKit principal via the control plane."
|
|
118
|
+
)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
The VaultKit MCP server.
|
|
5
|
+
|
|
6
|
+
Two handlers, both thin:
|
|
7
|
+
|
|
8
|
+
list_tools -> reuse ToolBuilder to advertise the caller's authorized tools.
|
|
9
|
+
call_tool -> reuse ToolExecutor to run a call and return its result.
|
|
10
|
+
|
|
11
|
+
This uses the low-level ``mcp.server.lowlevel.Server`` rather than the
|
|
12
|
+
``@mcp.tool`` decorator API because the decorator registers a static tool list
|
|
13
|
+
at startup, whereas VaultKit's tool list is per-principal: different callers are
|
|
14
|
+
authorized for different datasets. ``list_tools`` therefore has to be computed
|
|
15
|
+
per session, which the low-level API allows.
|
|
16
|
+
|
|
17
|
+
The VaultKit SDK is synchronous (blocking HTTP and polling), while MCP handlers
|
|
18
|
+
are async. Blocking work is offloaded to a worker thread via
|
|
19
|
+
``anyio.to_thread.run_sync`` so it does not stall the event loop. This is
|
|
20
|
+
required once multiple callers can be served concurrently, and harmless for the
|
|
21
|
+
single-caller stdio case.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from typing import List, Optional
|
|
27
|
+
|
|
28
|
+
import anyio
|
|
29
|
+
import mcp.types as types
|
|
30
|
+
from mcp.server.lowlevel import Server
|
|
31
|
+
|
|
32
|
+
from vaultkit.tools.adapters import ToolProvider
|
|
33
|
+
from vaultkit.tools.adapters.mcp import to_mcp_tool
|
|
34
|
+
from vaultkit.tools.builder import ToolBuilder
|
|
35
|
+
from vaultkit.tools.executor import ToolExecutor
|
|
36
|
+
|
|
37
|
+
from .identity import ClientResolver
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class McpServerConfig:
|
|
42
|
+
"""Server-wide configuration (not per-call; per-call args come from the agent)."""
|
|
43
|
+
|
|
44
|
+
environment: str = "production"
|
|
45
|
+
include_discover: bool = True
|
|
46
|
+
include_query: bool = True
|
|
47
|
+
# check_approval is exposed by default: the approval flow is asynchronous,
|
|
48
|
+
# so the agent needs a way to poll a pending request to completion.
|
|
49
|
+
include_check_approval: bool = True
|
|
50
|
+
# Schema hints feed the agent the exact valid field names per dataset, which
|
|
51
|
+
# guards against field-name hallucination. Keep on unless a very large
|
|
52
|
+
# catalog makes startup slow.
|
|
53
|
+
fetch_schema_hints: bool = True
|
|
54
|
+
# Optional fallbacks if an agent omits them (agents normally supply purpose).
|
|
55
|
+
default_purpose: Optional[str] = None
|
|
56
|
+
default_requester_region: Optional[str] = None
|
|
57
|
+
server_name: str = "vaultkit"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build_server(resolver: ClientResolver, config: Optional[McpServerConfig] = None) -> Server:
|
|
61
|
+
"""Wire a low-level MCP Server around the given identity resolver.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
resolver: Turns a request context into a VaultKitClient, and thereby
|
|
65
|
+
determines whether this is a single-principal (stdio) or
|
|
66
|
+
per-principal (http) server.
|
|
67
|
+
config: Server-wide options. Defaults are suitable for local (stdio) use.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
A configured ``mcp.server.lowlevel.Server`` ready to run over any
|
|
71
|
+
transport.
|
|
72
|
+
"""
|
|
73
|
+
cfg = config or McpServerConfig()
|
|
74
|
+
server: Server = Server(cfg.server_name)
|
|
75
|
+
|
|
76
|
+
@server.list_tools()
|
|
77
|
+
async def _list_tools() -> List[types.Tool]:
|
|
78
|
+
# Resolve the caller -> client, then build their authorized tool list.
|
|
79
|
+
# RAW gives canonical defs; those are mapped to MCP Tools by to_mcp_tool.
|
|
80
|
+
client = resolver.resolve(_current_ctx(server))
|
|
81
|
+
|
|
82
|
+
def _build():
|
|
83
|
+
builder = ToolBuilder(client)
|
|
84
|
+
return builder.build(
|
|
85
|
+
provider=ToolProvider.RAW,
|
|
86
|
+
environment=cfg.environment,
|
|
87
|
+
include_discover=cfg.include_discover,
|
|
88
|
+
include_query=cfg.include_query,
|
|
89
|
+
include_check_approval=cfg.include_check_approval,
|
|
90
|
+
fetch_schema_hints=cfg.fetch_schema_hints,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
canonical = await anyio.to_thread.run_sync(_build)
|
|
94
|
+
return [to_mcp_tool(t) for t in canonical]
|
|
95
|
+
|
|
96
|
+
@server.call_tool()
|
|
97
|
+
async def _call_tool(name: str, arguments: dict) -> List[types.ContentBlock]:
|
|
98
|
+
# Resolve the caller -> client, then dispatch through the executor.
|
|
99
|
+
# The executor never raises: denials, pending-approval and masking all
|
|
100
|
+
# come back as a structured dict with a `status` field, which is
|
|
101
|
+
# forwarded verbatim. A policy denial is a valid governed answer, not a
|
|
102
|
+
# protocol error, so it is not raised or marked isError.
|
|
103
|
+
client = resolver.resolve(_current_ctx(server))
|
|
104
|
+
|
|
105
|
+
def _run():
|
|
106
|
+
executor = ToolExecutor(
|
|
107
|
+
client,
|
|
108
|
+
default_purpose=cfg.default_purpose,
|
|
109
|
+
default_requester_region=cfg.default_requester_region,
|
|
110
|
+
)
|
|
111
|
+
return executor.execute(name, arguments or {})
|
|
112
|
+
|
|
113
|
+
result = await anyio.to_thread.run_sync(_run)
|
|
114
|
+
text = json.dumps(result, default=str, ensure_ascii=False, indent=2)
|
|
115
|
+
return [types.TextContent(type="text", text=text)]
|
|
116
|
+
|
|
117
|
+
return server
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _current_ctx(server: Server):
|
|
121
|
+
"""Return the active request context, or None if there is none.
|
|
122
|
+
|
|
123
|
+
stdio resolvers ignore this. The http resolver uses it to reach the
|
|
124
|
+
authenticated identity. Accessing ``request_context`` outside a request
|
|
125
|
+
raises, so it is guarded to keep the single-principal case working.
|
|
126
|
+
"""
|
|
127
|
+
try:
|
|
128
|
+
return server.request_context
|
|
129
|
+
except Exception:
|
|
130
|
+
return None
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
MCP tool adapter.
|
|
5
|
+
|
|
6
|
+
Converts a canonical VaultKit tool definition into an ``mcp.types.Tool``,
|
|
7
|
+
exactly the way ``to_anthropic_tool`` and ``to_openai_tool`` convert into
|
|
8
|
+
their provider shapes.
|
|
9
|
+
|
|
10
|
+
Canonical shape (from vaultkit.tools.definitions):
|
|
11
|
+
{"name": str, "description": str, "input_schema": {...}}
|
|
12
|
+
|
|
13
|
+
MCP shape (mcp.types.Tool):
|
|
14
|
+
Tool(name=..., description=..., inputSchema={...})
|
|
15
|
+
|
|
16
|
+
The only real work is the ``input_schema`` -> ``inputSchema`` rename and
|
|
17
|
+
wrapping the dict in the MCP type. No governance logic lives here — this is
|
|
18
|
+
pure translation, the same as every other adapter in this package.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import Any, Dict
|
|
22
|
+
|
|
23
|
+
import mcp.types as types
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def to_mcp_tool(canonical: Dict[str, Any]) -> types.Tool:
|
|
27
|
+
"""Convert one canonical tool def into an MCP Tool.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
canonical: A dict with ``name``, ``description`` and ``input_schema``,
|
|
31
|
+
as produced by ``vaultkit.tools.definitions``.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
An ``mcp.types.Tool`` suitable for returning from a ``list_tools``
|
|
35
|
+
handler.
|
|
36
|
+
"""
|
|
37
|
+
return types.Tool(
|
|
38
|
+
name=canonical["name"],
|
|
39
|
+
description=canonical.get("description"),
|
|
40
|
+
inputSchema=canonical["input_schema"],
|
|
41
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vaultkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: VaultKit Python SDK for policy-driven, runtime governed data access
|
|
5
5
|
Author-email: VaultKit <founders@vaultkit.io>
|
|
6
6
|
License: MIT
|
|
@@ -8,19 +8,21 @@ Project-URL: Homepage, https://github.com/vaultkit-inc/vaultkit-sdk-python
|
|
|
8
8
|
Project-URL: Documentation, https://docs.vaultkit.io
|
|
9
9
|
Project-URL: Source, https://github.com/vaultkit-inc/vaultkit-sdk-python
|
|
10
10
|
Project-URL: Issues, https://github.com/vaultkit-inc/vaultkit-sdk-python/issues
|
|
11
|
-
Keywords: ai,data,sdk,governance,security,agents,runtime
|
|
11
|
+
Keywords: ai,data,sdk,governance,security,agents,runtime,mcp
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
20
|
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
-
Requires-Python: >=3.
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
Requires-Dist: httpx>=0.24.0
|
|
24
|
+
Provides-Extra: mcp
|
|
25
|
+
Requires-Dist: mcp<2,>=1.27; extra == "mcp"
|
|
24
26
|
|
|
25
27
|
# VaultKit Python SDK
|
|
26
28
|
|
|
@@ -7,6 +7,7 @@ vaultkit/client.py
|
|
|
7
7
|
vaultkit.egg-info/PKG-INFO
|
|
8
8
|
vaultkit.egg-info/SOURCES.txt
|
|
9
9
|
vaultkit.egg-info/dependency_links.txt
|
|
10
|
+
vaultkit.egg-info/entry_points.txt
|
|
10
11
|
vaultkit.egg-info/requires.txt
|
|
11
12
|
vaultkit.egg-info/top_level.txt
|
|
12
13
|
vaultkit/core/__init__.py
|
|
@@ -15,6 +16,10 @@ vaultkit/core/polling.py
|
|
|
15
16
|
vaultkit/errors/__init__.py
|
|
16
17
|
vaultkit/errors/base.py
|
|
17
18
|
vaultkit/errors/exceptions.py
|
|
19
|
+
vaultkit/mcp/__init__.py
|
|
20
|
+
vaultkit/mcp/__main__.py
|
|
21
|
+
vaultkit/mcp/identity.py
|
|
22
|
+
vaultkit/mcp/server.py
|
|
18
23
|
vaultkit/models/__init__.py
|
|
19
24
|
vaultkit/models/dataset_info.py
|
|
20
25
|
vaultkit/models/dataset_schema.py
|
|
@@ -27,6 +32,7 @@ vaultkit/tools/executor.py
|
|
|
27
32
|
vaultkit/tools/schemas.py
|
|
28
33
|
vaultkit/tools/adapters/__init__.py
|
|
29
34
|
vaultkit/tools/adapters/anthropic.py
|
|
35
|
+
vaultkit/tools/adapters/mcp.py
|
|
30
36
|
vaultkit/tools/adapters/openai.py
|
|
31
37
|
vaultkit/utils/__init__.py
|
|
32
38
|
vaultkit/utils/retry.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
httpx>=0.24.0
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|