managed-research 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.
- managed_research-0.1.0/.gitignore +7 -0
- managed_research-0.1.0/PKG-INFO +73 -0
- managed_research-0.1.0/README.md +59 -0
- managed_research-0.1.0/managed_research/__init__.py +53 -0
- managed_research-0.1.0/managed_research/auth.py +75 -0
- managed_research-0.1.0/managed_research/client.py +3 -0
- managed_research-0.1.0/managed_research/errors.py +8 -0
- managed_research-0.1.0/managed_research/mcp/__init__.py +5 -0
- managed_research-0.1.0/managed_research/mcp/__main__.py +5 -0
- managed_research-0.1.0/managed_research/mcp/registry.py +47 -0
- managed_research-0.1.0/managed_research/mcp/server.py +1165 -0
- managed_research-0.1.0/managed_research/mcp/tools/__init__.py +19 -0
- managed_research-0.1.0/managed_research/mcp/tools/approvals.py +93 -0
- managed_research-0.1.0/managed_research/mcp/tools/artifacts.py +155 -0
- managed_research-0.1.0/managed_research/mcp/tools/integrations.py +211 -0
- managed_research-0.1.0/managed_research/mcp/tools/logs.py +97 -0
- managed_research-0.1.0/managed_research/mcp/tools/projects.py +399 -0
- managed_research-0.1.0/managed_research/mcp/tools/runs.py +575 -0
- managed_research-0.1.0/managed_research/mcp/tools/usage.py +107 -0
- managed_research-0.1.0/managed_research/models/__init__.py +37 -0
- managed_research-0.1.0/managed_research/models/generated/__init__.py +1 -0
- managed_research-0.1.0/managed_research/models/generated/v1/__init__.py +39 -0
- managed_research-0.1.0/managed_research/models/generated/v1/approvals.py +42 -0
- managed_research-0.1.0/managed_research/models/generated/v1/artifacts.py +40 -0
- managed_research-0.1.0/managed_research/models/generated/v1/common.py +60 -0
- managed_research-0.1.0/managed_research/models/generated/v1/integrations.py +125 -0
- managed_research-0.1.0/managed_research/models/generated/v1/logs.py +38 -0
- managed_research-0.1.0/managed_research/models/generated/v1/projects.py +71 -0
- managed_research-0.1.0/managed_research/models/generated/v1/questions.py +40 -0
- managed_research-0.1.0/managed_research/models/generated/v1/runs.py +75 -0
- managed_research-0.1.0/managed_research/models/generated/v1/usage.py +37 -0
- managed_research-0.1.0/managed_research/py.typed +1 -0
- managed_research-0.1.0/managed_research/schema_sync.py +110 -0
- managed_research-0.1.0/managed_research/sdk/__init__.py +65 -0
- managed_research-0.1.0/managed_research/sdk/_base.py +18 -0
- managed_research-0.1.0/managed_research/sdk/approvals.py +52 -0
- managed_research-0.1.0/managed_research/sdk/artifacts.py +46 -0
- managed_research-0.1.0/managed_research/sdk/client.py +2371 -0
- managed_research-0.1.0/managed_research/sdk/integrations.py +93 -0
- managed_research-0.1.0/managed_research/sdk/logs.py +52 -0
- managed_research-0.1.0/managed_research/sdk/projects.py +91 -0
- managed_research-0.1.0/managed_research/sdk/runs.py +77 -0
- managed_research-0.1.0/managed_research/sdk/usage.py +44 -0
- managed_research-0.1.0/managed_research/transport/__init__.py +25 -0
- managed_research-0.1.0/managed_research/transport/http.py +175 -0
- managed_research-0.1.0/managed_research/transport/pagination.py +49 -0
- managed_research-0.1.0/managed_research/transport/retries.py +67 -0
- managed_research-0.1.0/managed_research/transport/streaming.py +43 -0
- managed_research-0.1.0/managed_research/version.py +10 -0
- managed_research-0.1.0/pyproject.toml +40 -0
- managed_research-0.1.0/schemas/generated/README.md +14 -0
- managed_research-0.1.0/scripts/sync_public_schemas.py +7 -0
- managed_research-0.1.0/tests/contract/test_public_surface.py +55 -0
- managed_research-0.1.0/tests/contract/test_schema_sync.py +59 -0
- managed_research-0.1.0/tests/mcp/test_mcp_integrations.py +194 -0
- managed_research-0.1.0/tests/mcp/test_mcp_stdio_transport.py +75 -0
- managed_research-0.1.0/tests/mcp/test_registry.py +16 -0
- managed_research-0.1.0/tests/models/test_generated_models.py +113 -0
- managed_research-0.1.0/tests/sdk/test_client_integrations.py +217 -0
- managed_research-0.1.0/tests/sdk/test_data_factory.py +1128 -0
- managed_research-0.1.0/tests/sdk/test_namespaces.py +81 -0
- managed_research-0.1.0/tests/sdk/test_package_exports.py +8 -0
- managed_research-0.1.0/tests/transport/test_helpers.py +62 -0
- managed_research-0.1.0/tests/transport/test_http.py +124 -0
- managed_research-0.1.0/uv.lock +304 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: managed-research
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pure-Python SMR API client and MCP server.
|
|
5
|
+
Author: Synth Laboratories
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: httpx>=0.28.1
|
|
9
|
+
Requires-Dist: pynacl>=1.5.0
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest>=8.3.3; extra == 'dev'
|
|
12
|
+
Requires-Dist: ruff>=0.11.0; extra == 'dev'
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# managed-research
|
|
16
|
+
|
|
17
|
+
Pure-Python public SMR surfaces for Managed Research.
|
|
18
|
+
|
|
19
|
+
Current scope:
|
|
20
|
+
|
|
21
|
+
- SMR API client
|
|
22
|
+
- SMR MCP stdio server
|
|
23
|
+
- generated-schema sync path for public SMR contracts
|
|
24
|
+
|
|
25
|
+
Out of scope for this slice:
|
|
26
|
+
|
|
27
|
+
- runtime internals
|
|
28
|
+
- sandbox/session control
|
|
29
|
+
- private backend models
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install -U managed-research
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
For local development:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv sync --extra dev
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Run the MCP server
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv run managed-research-mcp
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The package reads `SYNTH_API_KEY` and `SYNTH_BACKEND_URL` from the environment.
|
|
50
|
+
|
|
51
|
+
Python API surface:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from managed_research import ManagedResearchClient
|
|
55
|
+
|
|
56
|
+
client = ManagedResearchClient(api_key="sk_...")
|
|
57
|
+
projects = client.list_projects()
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Sync exported public schemas
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv run python scripts/sync_public_schemas.py --source /path/to/exported/schemas
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If you prefer environment configuration, set `MANAGED_RESEARCH_SCHEMA_SOURCE`
|
|
67
|
+
instead of passing `--source`.
|
|
68
|
+
|
|
69
|
+
There is also a console entrypoint:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uv run managed-research-sync-schemas --source /path/to/exported/schemas
|
|
73
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# managed-research
|
|
2
|
+
|
|
3
|
+
Pure-Python public SMR surfaces for Managed Research.
|
|
4
|
+
|
|
5
|
+
Current scope:
|
|
6
|
+
|
|
7
|
+
- SMR API client
|
|
8
|
+
- SMR MCP stdio server
|
|
9
|
+
- generated-schema sync path for public SMR contracts
|
|
10
|
+
|
|
11
|
+
Out of scope for this slice:
|
|
12
|
+
|
|
13
|
+
- runtime internals
|
|
14
|
+
- sandbox/session control
|
|
15
|
+
- private backend models
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install -U managed-research
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For local development:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv sync --extra dev
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Run the MCP server
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv run managed-research-mcp
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The package reads `SYNTH_API_KEY` and `SYNTH_BACKEND_URL` from the environment.
|
|
36
|
+
|
|
37
|
+
Python API surface:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from managed_research import ManagedResearchClient
|
|
41
|
+
|
|
42
|
+
client = ManagedResearchClient(api_key="sk_...")
|
|
43
|
+
projects = client.list_projects()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Sync exported public schemas
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv run python scripts/sync_public_schemas.py --source /path/to/exported/schemas
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
If you prefer environment configuration, set `MANAGED_RESEARCH_SCHEMA_SOURCE`
|
|
53
|
+
instead of passing `--source`.
|
|
54
|
+
|
|
55
|
+
There is also a console entrypoint:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv run managed-research-sync-schemas --source /path/to/exported/schemas
|
|
59
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Public Managed Research package."""
|
|
2
|
+
|
|
3
|
+
from managed_research.errors import SmrApiError
|
|
4
|
+
from managed_research.models import (
|
|
5
|
+
SmrActorStatus,
|
|
6
|
+
SmrApproval,
|
|
7
|
+
SmrArtifact,
|
|
8
|
+
SmrCapabilities,
|
|
9
|
+
SmrIntegrationStatus,
|
|
10
|
+
SmrLinearTeam,
|
|
11
|
+
SmrLinearTeamListing,
|
|
12
|
+
SmrOAuthStart,
|
|
13
|
+
SmrProject,
|
|
14
|
+
SmrProjectStatusSnapshot,
|
|
15
|
+
SmrProviderKeyStatus,
|
|
16
|
+
SmrQuestion,
|
|
17
|
+
SmrRun,
|
|
18
|
+
SmrRunEconomics,
|
|
19
|
+
SmrRunLogArchive,
|
|
20
|
+
)
|
|
21
|
+
from managed_research.sdk.client import (
|
|
22
|
+
ACTIVE_RUN_STATES,
|
|
23
|
+
DEFAULT_TIMEOUT_SECONDS,
|
|
24
|
+
ManagedResearchClient,
|
|
25
|
+
SmrControlClient,
|
|
26
|
+
first_id,
|
|
27
|
+
)
|
|
28
|
+
from managed_research.version import __version__
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"ACTIVE_RUN_STATES",
|
|
32
|
+
"DEFAULT_TIMEOUT_SECONDS",
|
|
33
|
+
"ManagedResearchClient",
|
|
34
|
+
"SmrActorStatus",
|
|
35
|
+
"SmrApiError",
|
|
36
|
+
"SmrApproval",
|
|
37
|
+
"SmrArtifact",
|
|
38
|
+
"SmrCapabilities",
|
|
39
|
+
"SmrControlClient",
|
|
40
|
+
"SmrIntegrationStatus",
|
|
41
|
+
"SmrLinearTeam",
|
|
42
|
+
"SmrLinearTeamListing",
|
|
43
|
+
"SmrOAuthStart",
|
|
44
|
+
"SmrProject",
|
|
45
|
+
"SmrProjectStatusSnapshot",
|
|
46
|
+
"SmrProviderKeyStatus",
|
|
47
|
+
"SmrQuestion",
|
|
48
|
+
"SmrRun",
|
|
49
|
+
"SmrRunEconomics",
|
|
50
|
+
"SmrRunLogArchive",
|
|
51
|
+
"__version__",
|
|
52
|
+
"first_id",
|
|
53
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Auth and transport helpers for the public Managed Research package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from urllib.parse import urlparse, urlunparse
|
|
10
|
+
|
|
11
|
+
from nacl.public import PublicKey, SealedBox
|
|
12
|
+
|
|
13
|
+
BACKEND_URL_BASE = (os.getenv("SYNTH_BACKEND_URL") or "https://api.usesynth.ai").strip()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _read_json_object(path: Path) -> dict[str, object]:
|
|
17
|
+
try:
|
|
18
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
19
|
+
except Exception:
|
|
20
|
+
return {}
|
|
21
|
+
return raw if isinstance(raw, dict) else {}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _config_search_paths() -> list[Path]:
|
|
25
|
+
home = Path.home()
|
|
26
|
+
return [
|
|
27
|
+
home / ".synth_ai" / "config.json",
|
|
28
|
+
home / ".config" / "synth" / "config.json",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_api_key(env_key: str = "SYNTH_API_KEY", required: bool = True) -> str | None:
|
|
33
|
+
"""Resolve the Synth API key from environment or local config."""
|
|
34
|
+
value = (os.getenv(env_key) or "").strip()
|
|
35
|
+
if value:
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
for path in _config_search_paths():
|
|
39
|
+
payload = _read_json_object(path)
|
|
40
|
+
config_value = payload.get(env_key)
|
|
41
|
+
if isinstance(config_value, str) and config_value.strip():
|
|
42
|
+
return config_value.strip()
|
|
43
|
+
|
|
44
|
+
if required:
|
|
45
|
+
raise ValueError(f"{env_key} is required (set {env_key} or store it in local Synth config)")
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _strip_terminal_segment(path: str, segment: str) -> str:
|
|
50
|
+
trimmed = path.rstrip("/")
|
|
51
|
+
if trimmed.endswith(segment):
|
|
52
|
+
return trimmed[: -len(segment)].rstrip("/")
|
|
53
|
+
return trimmed
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def normalize_backend_base(url: str) -> str:
|
|
57
|
+
"""Normalize a backend URL down to the service base."""
|
|
58
|
+
parsed = urlparse(url.strip())
|
|
59
|
+
path = _strip_terminal_segment(parsed.path, "/v1")
|
|
60
|
+
path = _strip_terminal_segment(path, "/api")
|
|
61
|
+
normalized = parsed._replace(path=path.rstrip("/"), query="", fragment="")
|
|
62
|
+
return urlunparse(normalized)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def encrypt_for_backend(pubkey_b64: str, secret: str | bytes) -> str:
|
|
66
|
+
"""Encrypt a provider key using the backend's sealed-box public key."""
|
|
67
|
+
if isinstance(secret, bytes):
|
|
68
|
+
secret = secret.decode("utf-8")
|
|
69
|
+
try:
|
|
70
|
+
pubkey_raw = base64.b64decode(pubkey_b64, validate=True)
|
|
71
|
+
except Exception as exc:
|
|
72
|
+
raise RuntimeError("Invalid backend public key (not base64)") from exc
|
|
73
|
+
box = SealedBox(PublicKey(pubkey_raw))
|
|
74
|
+
ciphertext = box.encrypt(secret.encode("utf-8"))
|
|
75
|
+
return base64.b64encode(ciphertext).decode("utf-8")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Shared MCP registry types and schema helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
JSONDict = dict[str, Any]
|
|
10
|
+
ToolHandler = Callable[[JSONDict], Any]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class ToolDefinition:
|
|
15
|
+
"""MCP tool metadata and handler."""
|
|
16
|
+
|
|
17
|
+
name: str
|
|
18
|
+
description: str
|
|
19
|
+
input_schema: JSONDict
|
|
20
|
+
handler: ToolHandler
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_CONNECTION_PROPERTIES: JSONDict = {
|
|
24
|
+
"api_key": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Optional API key override (defaults to SYNTH_API_KEY).",
|
|
27
|
+
},
|
|
28
|
+
"backend_base": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Optional backend URL override (defaults to SYNTH_BACKEND_URL).",
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def tool_schema(properties: JSONDict, required: list[str]) -> JSONDict:
|
|
36
|
+
"""Attach shared connection properties to a tool schema."""
|
|
37
|
+
merged: JSONDict = dict(properties)
|
|
38
|
+
merged.update(_CONNECTION_PROPERTIES)
|
|
39
|
+
return {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": merged,
|
|
42
|
+
"required": required,
|
|
43
|
+
"additionalProperties": False,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
__all__ = ["JSONDict", "ToolDefinition", "ToolHandler", "tool_schema"]
|