marona-sdk 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.
- marona_sdk-0.1.0/LICENSE +21 -0
- marona_sdk-0.1.0/PKG-INFO +191 -0
- marona_sdk-0.1.0/README.md +159 -0
- marona_sdk-0.1.0/marona_sdk/__init__.py +25 -0
- marona_sdk-0.1.0/marona_sdk/cli.py +18 -0
- marona_sdk-0.1.0/marona_sdk/hub.py +43 -0
- marona_sdk-0.1.0/marona_sdk/models.py +202 -0
- marona_sdk-0.1.0/marona_sdk/py.typed +1 -0
- marona_sdk-0.1.0/marona_sdk/results.py +67 -0
- marona_sdk-0.1.0/marona_sdk/server.py +448 -0
- marona_sdk-0.1.0/marona_sdk.egg-info/PKG-INFO +191 -0
- marona_sdk-0.1.0/marona_sdk.egg-info/SOURCES.txt +17 -0
- marona_sdk-0.1.0/marona_sdk.egg-info/dependency_links.txt +1 -0
- marona_sdk-0.1.0/marona_sdk.egg-info/entry_points.txt +2 -0
- marona_sdk-0.1.0/marona_sdk.egg-info/requires.txt +10 -0
- marona_sdk-0.1.0/marona_sdk.egg-info/top_level.txt +1 -0
- marona_sdk-0.1.0/pyproject.toml +58 -0
- marona_sdk-0.1.0/setup.cfg +4 -0
- marona_sdk-0.1.0/tests/test_agent_app.py +302 -0
marona_sdk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Blessing Nyuwani
|
|
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,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: marona-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official developer SDK for building Marona-compatible apps, tool servers, skills, and workflows.
|
|
5
|
+
Author: Blessing Nyuwani
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://www.marona.ai
|
|
8
|
+
Project-URL: Repository, https://github.com/BlessingNyuwani/agentnet-mcp-sdk
|
|
9
|
+
Project-URL: Documentation, https://hub.marona.ai
|
|
10
|
+
Keywords: marona,sdk,mcp,agents,ai,tools,workflows
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: fastapi<1.0,>=0.115
|
|
23
|
+
Requires-Dist: httpx<1.0,>=0.28
|
|
24
|
+
Requires-Dist: mcp<2.0,>=1.28
|
|
25
|
+
Requires-Dist: pydantic<3.0,>=2.10
|
|
26
|
+
Requires-Dist: uvicorn[standard]<1.0,>=0.34
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build<2.0,>=1.2; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest<9.0,>=8.3; extra == "dev"
|
|
30
|
+
Requires-Dist: twine<7.0,>=6.0; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# Marona SDK
|
|
34
|
+
|
|
35
|
+
Marona SDK is the official developer SDK for building Marona-compatible apps,
|
|
36
|
+
MCP servers, skills, tool servers, and workflows.
|
|
37
|
+
|
|
38
|
+
It is not the runtime client SDK. Use `marona` when you want an application to
|
|
39
|
+
call a Marona-compatible runtime. Use `marona-sdk` when you are building
|
|
40
|
+
developer-side integrations that need the Marona tool contract.
|
|
41
|
+
|
|
42
|
+
The SDK helps projects produce the Marona standard automatically:
|
|
43
|
+
|
|
44
|
+
- `GET /health`
|
|
45
|
+
- `GET /manifest`
|
|
46
|
+
- `GET /hub-registration`
|
|
47
|
+
- `POST /mcp/` using the official MCP Python SDK `FastMCP`
|
|
48
|
+
- Tool outputs with `status`, `success`, `message`, `content`,
|
|
49
|
+
`content_type`, `presentation_hint`, and `context`
|
|
50
|
+
|
|
51
|
+
## Install For Local Development
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python3.11 -m venv .venv
|
|
55
|
+
.venv/bin/pip install -e ".[dev]"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Install In A Developer Integration Repo
|
|
59
|
+
|
|
60
|
+
Developer integration repos can depend on this SDK like any other pip package:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install marona-sdk
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
In `requirements.txt`:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
marona-sdk
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
It installs from the package index and imports as `marona_sdk`.
|
|
73
|
+
|
|
74
|
+
## Minimal Server
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from marona_sdk import AgentApp, success
|
|
78
|
+
|
|
79
|
+
agent_app = AgentApp(
|
|
80
|
+
name="Hello Tool Server",
|
|
81
|
+
server_name="hello-tools",
|
|
82
|
+
slug="hello",
|
|
83
|
+
description="Example Marona-compatible tool server.",
|
|
84
|
+
category="Tools",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@agent_app.tool(description="Say hello to a user.")
|
|
88
|
+
def say_hello(name: str) -> dict:
|
|
89
|
+
return success(f"Hello {name}.", greeting=f"Hello {name}.")
|
|
90
|
+
|
|
91
|
+
app = agent_app.create_fastapi_app()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Standard Tool Results
|
|
95
|
+
|
|
96
|
+
All Marona-compatible tools should return the SDK result shape. The required
|
|
97
|
+
fields are:
|
|
98
|
+
|
|
99
|
+
- `status`: machine-readable result status.
|
|
100
|
+
- `success`: boolean result success flag.
|
|
101
|
+
- `message`: short user-facing status line.
|
|
102
|
+
- `content`: primary user-facing text/content for the agent to use.
|
|
103
|
+
- `content_type`: provider-neutral semantic type, for example `text`, `document`, `message`, `list`, `media`, or `search_results`.
|
|
104
|
+
- `presentation_hint`: provider-neutral usage hint, for example `display_as_provided`.
|
|
105
|
+
- `context`: short provider-neutral guidance for interpreting the result.
|
|
106
|
+
|
|
107
|
+
Provider-specific payloads must live under generic standard fields:
|
|
108
|
+
|
|
109
|
+
- `data`: one structured provider-specific object.
|
|
110
|
+
- `items`: a list of structured provider-specific objects.
|
|
111
|
+
- `count`: item count when `items` is used.
|
|
112
|
+
- `artifacts`: generic artifact descriptors.
|
|
113
|
+
- `job`: generic async job metadata.
|
|
114
|
+
|
|
115
|
+
If a tool declares its own `output_schema`, the SDK merges these standard fields
|
|
116
|
+
into that schema before exposing `/manifest` and `/hub-registration`.
|
|
117
|
+
|
|
118
|
+
## Standard Tool Inputs For User Files
|
|
119
|
+
|
|
120
|
+
Tools that consume uploaded files should declare the SDK-standard
|
|
121
|
+
`attachments` input property. The Edge runtime injects the current conversation
|
|
122
|
+
files into that array automatically, so the tool does not need custom runtime
|
|
123
|
+
logic or a provider-specific input name.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from marona_sdk import standard_attachments_property
|
|
127
|
+
|
|
128
|
+
input_schema = {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"properties": {
|
|
131
|
+
"question": {"type": "string"},
|
|
132
|
+
"attachments": standard_attachments_property(),
|
|
133
|
+
},
|
|
134
|
+
"required": [],
|
|
135
|
+
"additionalProperties": False,
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Each attachment can include `artifact_id`, `filename`, `mime_type`, `kind`,
|
|
140
|
+
`url`, `file_url`, `content_base64`, `content`, and `metadata`.
|
|
141
|
+
|
|
142
|
+
Example:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
return success(
|
|
146
|
+
"Loaded email.",
|
|
147
|
+
content=email_text,
|
|
148
|
+
content_type="email_message",
|
|
149
|
+
presentation_hint="display_as_provided",
|
|
150
|
+
context="Return content as provided unless the user asks for a summary.",
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Run it:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
uvicorn examples.hello_server:app --host 127.0.0.1 --port 62900
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Then inspect:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
http://127.0.0.1:62900/health
|
|
164
|
+
http://127.0.0.1:62900/hub-registration
|
|
165
|
+
http://127.0.0.1:62900/mcp/
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Developer Hub Helper
|
|
169
|
+
|
|
170
|
+
`DeveloperHubClient` is only for developer workflow commands such as create,
|
|
171
|
+
sync, and submit. Apps, devices, bots, and user interfaces should use the
|
|
172
|
+
separate `marona` runtime client package.
|
|
173
|
+
|
|
174
|
+
## Release
|
|
175
|
+
|
|
176
|
+
Build and verify the package before uploading:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
rm -rf dist build marona_sdk.egg-info
|
|
180
|
+
python3.11 -m build
|
|
181
|
+
python3.11 -m twine check dist/*
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Upload with a PyPI token:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
TWINE_USERNAME=__token__ TWINE_PASSWORD='PYPI_TOKEN' python3.11 -m twine upload dist/*
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
For the first upload of a new package, PyPI requires an account-scoped upload
|
|
191
|
+
token. After the project exists, use a project-scoped token for normal releases.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Marona SDK
|
|
2
|
+
|
|
3
|
+
Marona SDK is the official developer SDK for building Marona-compatible apps,
|
|
4
|
+
MCP servers, skills, tool servers, and workflows.
|
|
5
|
+
|
|
6
|
+
It is not the runtime client SDK. Use `marona` when you want an application to
|
|
7
|
+
call a Marona-compatible runtime. Use `marona-sdk` when you are building
|
|
8
|
+
developer-side integrations that need the Marona tool contract.
|
|
9
|
+
|
|
10
|
+
The SDK helps projects produce the Marona standard automatically:
|
|
11
|
+
|
|
12
|
+
- `GET /health`
|
|
13
|
+
- `GET /manifest`
|
|
14
|
+
- `GET /hub-registration`
|
|
15
|
+
- `POST /mcp/` using the official MCP Python SDK `FastMCP`
|
|
16
|
+
- Tool outputs with `status`, `success`, `message`, `content`,
|
|
17
|
+
`content_type`, `presentation_hint`, and `context`
|
|
18
|
+
|
|
19
|
+
## Install For Local Development
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
python3.11 -m venv .venv
|
|
23
|
+
.venv/bin/pip install -e ".[dev]"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Install In A Developer Integration Repo
|
|
27
|
+
|
|
28
|
+
Developer integration repos can depend on this SDK like any other pip package:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install marona-sdk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
In `requirements.txt`:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
marona-sdk
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
It installs from the package index and imports as `marona_sdk`.
|
|
41
|
+
|
|
42
|
+
## Minimal Server
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from marona_sdk import AgentApp, success
|
|
46
|
+
|
|
47
|
+
agent_app = AgentApp(
|
|
48
|
+
name="Hello Tool Server",
|
|
49
|
+
server_name="hello-tools",
|
|
50
|
+
slug="hello",
|
|
51
|
+
description="Example Marona-compatible tool server.",
|
|
52
|
+
category="Tools",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
@agent_app.tool(description="Say hello to a user.")
|
|
56
|
+
def say_hello(name: str) -> dict:
|
|
57
|
+
return success(f"Hello {name}.", greeting=f"Hello {name}.")
|
|
58
|
+
|
|
59
|
+
app = agent_app.create_fastapi_app()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Standard Tool Results
|
|
63
|
+
|
|
64
|
+
All Marona-compatible tools should return the SDK result shape. The required
|
|
65
|
+
fields are:
|
|
66
|
+
|
|
67
|
+
- `status`: machine-readable result status.
|
|
68
|
+
- `success`: boolean result success flag.
|
|
69
|
+
- `message`: short user-facing status line.
|
|
70
|
+
- `content`: primary user-facing text/content for the agent to use.
|
|
71
|
+
- `content_type`: provider-neutral semantic type, for example `text`, `document`, `message`, `list`, `media`, or `search_results`.
|
|
72
|
+
- `presentation_hint`: provider-neutral usage hint, for example `display_as_provided`.
|
|
73
|
+
- `context`: short provider-neutral guidance for interpreting the result.
|
|
74
|
+
|
|
75
|
+
Provider-specific payloads must live under generic standard fields:
|
|
76
|
+
|
|
77
|
+
- `data`: one structured provider-specific object.
|
|
78
|
+
- `items`: a list of structured provider-specific objects.
|
|
79
|
+
- `count`: item count when `items` is used.
|
|
80
|
+
- `artifacts`: generic artifact descriptors.
|
|
81
|
+
- `job`: generic async job metadata.
|
|
82
|
+
|
|
83
|
+
If a tool declares its own `output_schema`, the SDK merges these standard fields
|
|
84
|
+
into that schema before exposing `/manifest` and `/hub-registration`.
|
|
85
|
+
|
|
86
|
+
## Standard Tool Inputs For User Files
|
|
87
|
+
|
|
88
|
+
Tools that consume uploaded files should declare the SDK-standard
|
|
89
|
+
`attachments` input property. The Edge runtime injects the current conversation
|
|
90
|
+
files into that array automatically, so the tool does not need custom runtime
|
|
91
|
+
logic or a provider-specific input name.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from marona_sdk import standard_attachments_property
|
|
95
|
+
|
|
96
|
+
input_schema = {
|
|
97
|
+
"type": "object",
|
|
98
|
+
"properties": {
|
|
99
|
+
"question": {"type": "string"},
|
|
100
|
+
"attachments": standard_attachments_property(),
|
|
101
|
+
},
|
|
102
|
+
"required": [],
|
|
103
|
+
"additionalProperties": False,
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Each attachment can include `artifact_id`, `filename`, `mime_type`, `kind`,
|
|
108
|
+
`url`, `file_url`, `content_base64`, `content`, and `metadata`.
|
|
109
|
+
|
|
110
|
+
Example:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
return success(
|
|
114
|
+
"Loaded email.",
|
|
115
|
+
content=email_text,
|
|
116
|
+
content_type="email_message",
|
|
117
|
+
presentation_hint="display_as_provided",
|
|
118
|
+
context="Return content as provided unless the user asks for a summary.",
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Run it:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
uvicorn examples.hello_server:app --host 127.0.0.1 --port 62900
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Then inspect:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
http://127.0.0.1:62900/health
|
|
132
|
+
http://127.0.0.1:62900/hub-registration
|
|
133
|
+
http://127.0.0.1:62900/mcp/
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Developer Hub Helper
|
|
137
|
+
|
|
138
|
+
`DeveloperHubClient` is only for developer workflow commands such as create,
|
|
139
|
+
sync, and submit. Apps, devices, bots, and user interfaces should use the
|
|
140
|
+
separate `marona` runtime client package.
|
|
141
|
+
|
|
142
|
+
## Release
|
|
143
|
+
|
|
144
|
+
Build and verify the package before uploading:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
rm -rf dist build marona_sdk.egg-info
|
|
148
|
+
python3.11 -m build
|
|
149
|
+
python3.11 -m twine check dist/*
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Upload with a PyPI token:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
TWINE_USERNAME=__token__ TWINE_PASSWORD='PYPI_TOKEN' python3.11 -m twine upload dist/*
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
For the first upload of a new package, PyPI requires an account-scoped upload
|
|
159
|
+
token. After the project exists, use a project-scoped token for normal releases.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from marona_sdk.hub import DeveloperHubClient
|
|
2
|
+
from marona_sdk.models import (
|
|
3
|
+
PermissionSpec,
|
|
4
|
+
ToolSpec,
|
|
5
|
+
standard_attachment_schema,
|
|
6
|
+
standard_attachments_property,
|
|
7
|
+
standard_output_schema,
|
|
8
|
+
)
|
|
9
|
+
from marona_sdk.results import failure, success
|
|
10
|
+
from marona_sdk.server import AgentApp
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AgentApp",
|
|
16
|
+
"DeveloperHubClient",
|
|
17
|
+
"PermissionSpec",
|
|
18
|
+
"ToolSpec",
|
|
19
|
+
"standard_attachment_schema",
|
|
20
|
+
"standard_attachments_property",
|
|
21
|
+
"standard_output_schema",
|
|
22
|
+
"failure",
|
|
23
|
+
"success",
|
|
24
|
+
"__version__",
|
|
25
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main() -> None:
|
|
7
|
+
parser = argparse.ArgumentParser(
|
|
8
|
+
prog="marona-sdk",
|
|
9
|
+
description="Marona SDK developer helpers.",
|
|
10
|
+
)
|
|
11
|
+
parser.add_argument(
|
|
12
|
+
"command",
|
|
13
|
+
choices=["version"],
|
|
14
|
+
help="Command to run. More commands will be added as the SDK grows.",
|
|
15
|
+
)
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
if args.command == "version":
|
|
18
|
+
print("marona-sdk 0.1.0")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DeveloperHubClient:
|
|
9
|
+
"""Small helper for developer workflows. Runtime edge sync belongs in Edge SDK."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, *, hub_url: str, token: str, timeout_seconds: float = 30) -> None:
|
|
12
|
+
self.hub_url = hub_url.rstrip("/")
|
|
13
|
+
self.token = token
|
|
14
|
+
self.timeout_seconds = timeout_seconds
|
|
15
|
+
|
|
16
|
+
async def create_agent_app(self, registration_payload: dict[str, Any]) -> dict[str, Any]:
|
|
17
|
+
return await self._request("POST", "/portal/developer/agent-apps", json=registration_payload)
|
|
18
|
+
|
|
19
|
+
async def sync_agent_app(self, agent_app_id: str) -> dict[str, Any]:
|
|
20
|
+
return await self._request("POST", f"/portal/developer/agent-apps/{agent_app_id}/sync-mcp-server")
|
|
21
|
+
|
|
22
|
+
async def submit_agent_app(self, agent_app_id: str, comments: str | None = None) -> dict[str, Any]:
|
|
23
|
+
return await self._request(
|
|
24
|
+
"POST",
|
|
25
|
+
f"/portal/developer/agent-apps/{agent_app_id}/submit",
|
|
26
|
+
json={"comments": comments} if comments else {},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
async def _request(
|
|
30
|
+
self,
|
|
31
|
+
method: str,
|
|
32
|
+
path: str,
|
|
33
|
+
*,
|
|
34
|
+
json: dict[str, Any] | None = None,
|
|
35
|
+
) -> dict[str, Any]:
|
|
36
|
+
async with httpx.AsyncClient(
|
|
37
|
+
base_url=self.hub_url,
|
|
38
|
+
timeout=self.timeout_seconds,
|
|
39
|
+
headers={"Authorization": f"Bearer {self.token}"},
|
|
40
|
+
) as client:
|
|
41
|
+
response = await client.request(method, path, json=json)
|
|
42
|
+
response.raise_for_status()
|
|
43
|
+
return response.json()
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
STANDARD_OUTPUT_SCHEMA: dict[str, Any] = {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"status": {"type": "string"},
|
|
13
|
+
"success": {"type": "boolean"},
|
|
14
|
+
"message": {"type": "string"},
|
|
15
|
+
"content": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Primary user-facing content the agent can display or reason over.",
|
|
18
|
+
},
|
|
19
|
+
"content_type": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Provider-neutral semantic type for content, such as text, document, message, list, media, or search_results.",
|
|
22
|
+
},
|
|
23
|
+
"presentation_hint": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Provider-neutral presentation hint, such as display_as_provided or summarize_if_requested.",
|
|
26
|
+
},
|
|
27
|
+
"context": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Provider-neutral guidance for interpreting this result.",
|
|
30
|
+
},
|
|
31
|
+
"data": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"description": "Provider-specific structured object payload. Keep provider fields inside this object.",
|
|
34
|
+
"additionalProperties": True,
|
|
35
|
+
},
|
|
36
|
+
"items": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"description": "Provider-specific structured list payload. Use for search/list results from any MCP.",
|
|
39
|
+
"items": {"type": "object", "additionalProperties": True},
|
|
40
|
+
},
|
|
41
|
+
"count": {
|
|
42
|
+
"type": "integer",
|
|
43
|
+
"description": "Number of items represented in items when applicable.",
|
|
44
|
+
},
|
|
45
|
+
"artifacts": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"description": "Generic artifacts produced by the tool.",
|
|
48
|
+
"items": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"type": {"type": "string"},
|
|
52
|
+
"label": {"type": "string"},
|
|
53
|
+
"mime_type": {"type": "string"},
|
|
54
|
+
"url": {"type": "string"},
|
|
55
|
+
"base64": {"type": "string"},
|
|
56
|
+
"text": {"type": "string"},
|
|
57
|
+
"external_id": {"type": "string"},
|
|
58
|
+
"filename": {"type": "string"},
|
|
59
|
+
"metadata": {"type": "object", "additionalProperties": True},
|
|
60
|
+
},
|
|
61
|
+
"additionalProperties": True,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
"job": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"description": "Generic async job payload with provider-specific details.",
|
|
67
|
+
"additionalProperties": True,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
"required": ["status", "success", "message", "content", "content_type", "presentation_hint", "context"],
|
|
71
|
+
"additionalProperties": True,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
STANDARD_ATTACHMENT_SCHEMA: dict[str, Any] = {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"description": "Provider-neutral user file/artifact passed by the runtime.",
|
|
78
|
+
"properties": {
|
|
79
|
+
"artifact_id": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"description": "Runtime artifact id for this attachment, when available.",
|
|
82
|
+
},
|
|
83
|
+
"filename": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"description": "Original or display filename.",
|
|
86
|
+
},
|
|
87
|
+
"mime_type": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"description": "MIME type, for example application/pdf or image/png.",
|
|
90
|
+
},
|
|
91
|
+
"kind": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"description": "Runtime artifact kind, for example document, image, audio, video, or file.",
|
|
94
|
+
},
|
|
95
|
+
"url": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"description": "Runtime-accessible URL for the attachment, when available.",
|
|
98
|
+
},
|
|
99
|
+
"file_url": {
|
|
100
|
+
"type": "string",
|
|
101
|
+
"description": "Runtime-accessible file URL for tools that consume file_url.",
|
|
102
|
+
},
|
|
103
|
+
"content_base64": {
|
|
104
|
+
"type": "string",
|
|
105
|
+
"description": "Base64-encoded attachment bytes when the runtime sends inline bytes.",
|
|
106
|
+
},
|
|
107
|
+
"content": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"description": "Inline text content when the attachment is text-like.",
|
|
110
|
+
},
|
|
111
|
+
"metadata": {
|
|
112
|
+
"type": "object",
|
|
113
|
+
"description": "Runtime or provider-specific attachment metadata.",
|
|
114
|
+
"additionalProperties": True,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
"additionalProperties": True,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
STANDARD_ATTACHMENTS_PROPERTY: dict[str, Any] = {
|
|
121
|
+
"type": "array",
|
|
122
|
+
"description": (
|
|
123
|
+
"User-provided files or runtime artifacts. Declare this property on any "
|
|
124
|
+
"tool that can consume uploaded files; the Edge runtime injects the "
|
|
125
|
+
"current conversation attachments automatically."
|
|
126
|
+
),
|
|
127
|
+
"items": STANDARD_ATTACHMENT_SCHEMA,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def standard_attachment_schema() -> dict[str, Any]:
|
|
132
|
+
return deepcopy(STANDARD_ATTACHMENT_SCHEMA)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def standard_attachments_property() -> dict[str, Any]:
|
|
136
|
+
return deepcopy(STANDARD_ATTACHMENTS_PROPERTY)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def standard_output_schema(schema: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
140
|
+
if schema is None:
|
|
141
|
+
return deepcopy(STANDARD_OUTPUT_SCHEMA)
|
|
142
|
+
|
|
143
|
+
merged = deepcopy(schema)
|
|
144
|
+
base = deepcopy(STANDARD_OUTPUT_SCHEMA)
|
|
145
|
+
merged["type"] = merged.get("type") or "object"
|
|
146
|
+
merged["properties"] = {
|
|
147
|
+
**base["properties"],
|
|
148
|
+
**(merged.get("properties") or {}),
|
|
149
|
+
}
|
|
150
|
+
required = list(dict.fromkeys([*base["required"], *(merged.get("required") or [])]))
|
|
151
|
+
merged["required"] = required
|
|
152
|
+
merged.setdefault("additionalProperties", True)
|
|
153
|
+
return merged
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class PermissionSpec(BaseModel):
|
|
157
|
+
key: str = Field(pattern=r"^[a-zA-Z0-9_.:-]+$")
|
|
158
|
+
name: str
|
|
159
|
+
description: str | None = None
|
|
160
|
+
required: bool = True
|
|
161
|
+
scope: str | None = None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class ToolSpec(BaseModel):
|
|
165
|
+
name: str
|
|
166
|
+
title: str | None = None
|
|
167
|
+
description: str
|
|
168
|
+
input_schema: dict[str, Any]
|
|
169
|
+
output_schema: dict[str, Any] = Field(default_factory=standard_output_schema)
|
|
170
|
+
is_destructive: bool = False
|
|
171
|
+
requires_user_confirmation: bool = False
|
|
172
|
+
|
|
173
|
+
def descriptor(self) -> dict[str, Any]:
|
|
174
|
+
return {
|
|
175
|
+
"name": self.name,
|
|
176
|
+
"title": self.title or self.name.replace("_", " ").title(),
|
|
177
|
+
"description": self.description,
|
|
178
|
+
"input_schema": self.input_schema,
|
|
179
|
+
"output_schema": standard_output_schema(self.output_schema),
|
|
180
|
+
"is_destructive": self.is_destructive,
|
|
181
|
+
"requires_user_confirmation": self.requires_user_confirmation,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
def mcp_descriptor(self) -> dict[str, Any]:
|
|
185
|
+
return {
|
|
186
|
+
"name": self.name,
|
|
187
|
+
"title": self.title or self.name.replace("_", " ").title(),
|
|
188
|
+
"description": self.description,
|
|
189
|
+
"inputSchema": self.input_schema,
|
|
190
|
+
"annotations": {
|
|
191
|
+
"readOnlyHint": not self.is_destructive,
|
|
192
|
+
"destructiveHint": self.is_destructive,
|
|
193
|
+
"openWorldHint": True,
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class AuthConfig(BaseModel):
|
|
199
|
+
is_auth_required: bool = False
|
|
200
|
+
auth_type: Literal["none", "oauth2", "api_key", "bearer_token", "custom"] = "none"
|
|
201
|
+
auth_instructions: str | None = None
|
|
202
|
+
connection_schema: dict[str, Any] | None = None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|