agentr 0.1.7__py3-none-any.whl → 0.1.8__py3-none-any.whl
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.
- agentr/application.py +9 -8
- agentr/applications/github/app.py +168 -257
- agentr/applications/google_calendar/app.py +349 -408
- agentr/applications/reddit/app.py +288 -8
- agentr/integration.py +79 -3
- agentr/integrations/README.md +25 -0
- agentr/integrations/__init__.py +5 -0
- agentr/integrations/agentr.py +87 -0
- agentr/integrations/api_key.py +16 -0
- agentr/integrations/base.py +60 -0
- agentr/server.py +15 -0
- agentr/test.py +3 -28
- agentr/utils/openapi.py +113 -25
- {agentr-0.1.7.dist-info → agentr-0.1.8.dist-info}/METADATA +1 -1
- agentr-0.1.8.dist-info/RECORD +30 -0
- agentr-0.1.7.dist-info/RECORD +0 -25
- {agentr-0.1.7.dist-info → agentr-0.1.8.dist-info}/WHEEL +0 -0
- {agentr-0.1.7.dist-info → agentr-0.1.8.dist-info}/entry_points.txt +0 -0
- {agentr-0.1.7.dist-info → agentr-0.1.8.dist-info}/licenses/LICENSE +0 -0
agentr/server.py
CHANGED
@@ -2,11 +2,15 @@ from abc import ABC, abstractmethod
|
|
2
2
|
import httpx
|
3
3
|
from mcp.server.fastmcp import FastMCP
|
4
4
|
from agentr.applications import app_from_name
|
5
|
+
from agentr.exceptions import NotAuthorizedError
|
5
6
|
from agentr.integration import AgentRIntegration, ApiKeyIntegration
|
6
7
|
from agentr.store import EnvironmentStore, MemoryStore
|
7
8
|
from agentr.config import AppConfig, IntegrationConfig, StoreConfig
|
8
9
|
from loguru import logger
|
9
10
|
import os
|
11
|
+
from typing import Any
|
12
|
+
from mcp.types import TextContent
|
13
|
+
from mcp.server.fastmcp.exceptions import ToolError
|
10
14
|
|
11
15
|
class Server(FastMCP, ABC):
|
12
16
|
"""
|
@@ -21,6 +25,17 @@ class Server(FastMCP, ABC):
|
|
21
25
|
def _load_apps(self):
|
22
26
|
pass
|
23
27
|
|
28
|
+
async def call_tool(self, name: str, arguments: dict[str, Any]):
|
29
|
+
"""Call a tool by name with arguments."""
|
30
|
+
try:
|
31
|
+
result = await super().call_tool(name, arguments)
|
32
|
+
return result
|
33
|
+
except ToolError as e:
|
34
|
+
raised_error = e.__cause__
|
35
|
+
if isinstance(raised_error, NotAuthorizedError):
|
36
|
+
return [TextContent(type="text", text=raised_error.message)]
|
37
|
+
else:
|
38
|
+
raise e
|
24
39
|
|
25
40
|
class LocalServer(Server):
|
26
41
|
"""
|
agentr/test.py
CHANGED
@@ -1,38 +1,13 @@
|
|
1
|
-
from agentr.server import
|
2
|
-
from agentr.store import MemoryStore
|
1
|
+
from agentr.server import AgentRServer
|
3
2
|
|
4
|
-
store = MemoryStore()
|
5
|
-
apps_list = [
|
6
|
-
{
|
7
|
-
"name": "tavily",
|
8
|
-
"integration": {
|
9
|
-
"name": "tavily_api_key",
|
10
|
-
"type": "api_key",
|
11
|
-
"store": {
|
12
|
-
"type": "environment",
|
13
|
-
}
|
14
|
-
},
|
15
|
-
},
|
16
|
-
{
|
17
|
-
"name": "zenquotes",
|
18
|
-
"integration": None
|
19
|
-
},
|
20
|
-
{
|
21
|
-
"name": "github",
|
22
|
-
"integration": {
|
23
|
-
"name": "github",
|
24
|
-
"type": "agentr",
|
25
|
-
}
|
26
|
-
}
|
27
|
-
]
|
28
|
-
mcp = LocalServer(name="Test Server", description="Test Server", apps_list=apps_list)
|
29
3
|
|
4
|
+
mcp = AgentRServer(name="Test Server", description="Test Server")
|
30
5
|
|
31
6
|
async def test():
|
32
7
|
tools = await mcp.list_tools()
|
33
8
|
from pprint import pprint
|
34
9
|
pprint(tools)
|
35
|
-
result = await mcp.call_tool("
|
10
|
+
result = await mcp.call_tool("get_today_events", {})
|
36
11
|
print(result)
|
37
12
|
|
38
13
|
if __name__ == "__main__":
|
agentr/utils/openapi.py
CHANGED
@@ -1,6 +1,26 @@
|
|
1
1
|
import json
|
2
2
|
import yaml
|
3
|
+
import re
|
3
4
|
from pathlib import Path
|
5
|
+
from typing import Dict, Any
|
6
|
+
|
7
|
+
|
8
|
+
def convert_to_snake_case(identifier: str) -> str:
|
9
|
+
"""
|
10
|
+
Convert a camelCase or PascalCase identifier to snake_case.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
identifier (str): The string to convert
|
14
|
+
|
15
|
+
Returns:
|
16
|
+
str: The converted snake_case string
|
17
|
+
"""
|
18
|
+
if not identifier:
|
19
|
+
return identifier
|
20
|
+
# Add underscore between lowercase and uppercase letters
|
21
|
+
result = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', identifier)
|
22
|
+
# Convert to lowercase
|
23
|
+
return result.lower()
|
4
24
|
|
5
25
|
|
6
26
|
def load_schema(path: Path):
|
@@ -14,6 +34,7 @@ def load_schema(path: Path):
|
|
14
34
|
else:
|
15
35
|
return json.load(f)
|
16
36
|
|
37
|
+
|
17
38
|
def generate_api_client(schema):
|
18
39
|
"""
|
19
40
|
Generate a Python API client class from an OpenAPI schema.
|
@@ -25,25 +46,62 @@ def generate_api_client(schema):
|
|
25
46
|
str: A string containing the Python code for the API client class.
|
26
47
|
"""
|
27
48
|
methods = []
|
49
|
+
method_names = []
|
50
|
+
|
51
|
+
# Extract API info for naming and base URL
|
52
|
+
info = schema.get('info', {})
|
53
|
+
api_title = info.get('title', 'API')
|
54
|
+
|
55
|
+
# Get base URL from servers array if available
|
56
|
+
base_url = ""
|
57
|
+
servers = schema.get('servers', [])
|
58
|
+
if servers and isinstance(servers, list) and 'url' in servers[0]:
|
59
|
+
base_url = servers[0]['url'].rstrip('/')
|
60
|
+
|
61
|
+
# Create a clean class name from API title
|
62
|
+
if api_title:
|
63
|
+
# Convert API title to a clean class name
|
64
|
+
base_name = "".join(word.capitalize() for word in api_title.split())
|
65
|
+
clean_name = ''.join(c for c in base_name if c.isalnum())
|
66
|
+
class_name = f"{clean_name}App"
|
67
|
+
else:
|
68
|
+
class_name = "APIClient"
|
28
69
|
|
29
70
|
# Iterate over paths and their operations
|
30
71
|
for path, path_info in schema.get('paths', {}).items():
|
31
72
|
for method in path_info:
|
32
73
|
if method in ['get', 'post', 'put', 'delete', 'patch', 'options', 'head']:
|
33
74
|
operation = path_info[method]
|
34
|
-
method_code = generate_method_code(path, method, operation)
|
75
|
+
method_code, func_name = generate_method_code(path, method, operation)
|
35
76
|
methods.append(method_code)
|
77
|
+
method_names.append(func_name)
|
78
|
+
|
79
|
+
# Generate list_tools method with all the function names
|
80
|
+
tools_list = ",\n ".join([f"self.{name}" for name in method_names])
|
81
|
+
list_tools_method = f""" def list_tools(self):
|
82
|
+
return [
|
83
|
+
{tools_list}
|
84
|
+
]"""
|
85
|
+
|
86
|
+
# Generate class imports
|
87
|
+
imports = [
|
88
|
+
"from agentr.application import APIApplication",
|
89
|
+
"from agentr.integration import Integration"
|
90
|
+
]
|
36
91
|
|
37
92
|
# Construct the class code
|
38
93
|
class_code = (
|
39
|
-
"
|
40
|
-
"class
|
41
|
-
" def __init__(self,
|
42
|
-
"
|
43
|
-
|
94
|
+
"\n".join(imports) + "\n\n"
|
95
|
+
f"class {class_name}(APIApplication):\n"
|
96
|
+
f" def __init__(self, integration: Integration = None, **kwargs) -> None:\n"
|
97
|
+
f" super().__init__(name='{class_name.lower()}', integration=integration, **kwargs)\n"
|
98
|
+
f" self.base_url = \"{base_url}\"\n\n" +
|
99
|
+
'\n\n'.join(methods) + "\n\n" +
|
100
|
+
list_tools_method + "\n"
|
44
101
|
)
|
45
102
|
return class_code
|
46
103
|
|
104
|
+
|
47
105
|
def generate_method_code(path, method, operation):
|
48
106
|
"""
|
49
107
|
Generate the code for a single API method.
|
@@ -54,11 +112,13 @@ def generate_method_code(path, method, operation):
|
|
54
112
|
operation (dict): The operation details from the schema.
|
55
113
|
|
56
114
|
Returns:
|
57
|
-
|
115
|
+
tuple: (method_code, func_name) - The Python code for the method and its name.
|
58
116
|
"""
|
59
117
|
# Determine function name
|
60
118
|
if 'operationId' in operation:
|
61
|
-
|
119
|
+
raw_name = operation['operationId']
|
120
|
+
cleaned_name = raw_name.replace('.', '_').replace('-', '_')
|
121
|
+
func_name = convert_to_snake_case(cleaned_name)
|
62
122
|
else:
|
63
123
|
# Generate name from path and method
|
64
124
|
path_parts = path.strip('/').split('/')
|
@@ -82,43 +142,71 @@ def generate_method_code(path, method, operation):
|
|
82
142
|
args.append(param['name'])
|
83
143
|
else:
|
84
144
|
args.append(f"{param['name']}=None")
|
145
|
+
|
85
146
|
if has_body:
|
86
|
-
args.append('
|
87
|
-
|
147
|
+
args.append('request_body' if body_required else 'request_body=None')
|
148
|
+
|
149
|
+
signature = f" def {func_name}(self, {', '.join(args)}) -> Dict[str, Any]:"
|
88
150
|
|
89
151
|
# Build method body
|
90
152
|
body_lines = []
|
91
153
|
|
154
|
+
# Validate required parameters
|
155
|
+
for param in parameters:
|
156
|
+
if param.get('required', False):
|
157
|
+
body_lines.append(f" if {param['name']} is None:")
|
158
|
+
body_lines.append(f" raise ValueError(\"Missing required parameter '{param['name']}'\")")
|
159
|
+
|
160
|
+
# Validate required body
|
161
|
+
if has_body and body_required:
|
162
|
+
body_lines.append(" if request_body is None:")
|
163
|
+
body_lines.append(" raise ValueError(\"Missing required request body\")")
|
164
|
+
|
92
165
|
# Path parameters
|
93
166
|
path_params = [p for p in parameters if p['in'] == 'path']
|
94
167
|
path_params_dict = ', '.join([f"'{p['name']}': {p['name']}" for p in path_params])
|
95
|
-
body_lines.append(f"
|
168
|
+
body_lines.append(f" path_params = {{{path_params_dict}}}")
|
169
|
+
|
170
|
+
# Format URL
|
171
|
+
body_lines.append(f" url = f\"{{self.base_url}}{path}\".format_map(path_params)")
|
96
172
|
|
97
173
|
# Query parameters
|
98
174
|
query_params = [p for p in parameters if p['in'] == 'query']
|
99
175
|
query_params_items = ', '.join([f"('{p['name']}', {p['name']})" for p in query_params])
|
100
176
|
body_lines.append(
|
101
|
-
f"
|
177
|
+
f" query_params = {{k: v for k, v in [{query_params_items}] if v is not None}}"
|
102
178
|
)
|
103
179
|
|
104
|
-
#
|
105
|
-
body_lines.append(f" url = f\"{{self.base_url}}{path}\".format_map(path_params)")
|
106
|
-
|
107
|
-
# Make HTTP request
|
108
|
-
method_func = method.lower()
|
180
|
+
# Request body handling for JSON
|
109
181
|
if has_body:
|
110
|
-
body_lines.append("
|
111
|
-
|
112
|
-
|
113
|
-
|
182
|
+
body_lines.append(" json_body = request_body if request_body is not None else None")
|
183
|
+
|
184
|
+
# Make HTTP request using the proper method
|
185
|
+
method_lower = method.lower()
|
186
|
+
if method_lower == 'get':
|
187
|
+
body_lines.append(" response = self._get(url, params=query_params)")
|
188
|
+
elif method_lower == 'post':
|
189
|
+
if has_body:
|
190
|
+
body_lines.append(" response = self._post(url, data=json_body)")
|
191
|
+
else:
|
192
|
+
body_lines.append(" response = self._post(url, data={})")
|
193
|
+
elif method_lower == 'put':
|
194
|
+
if has_body:
|
195
|
+
body_lines.append(" response = self._put(url, data=json_body)")
|
196
|
+
else:
|
197
|
+
body_lines.append(" response = self._put(url, data={})")
|
198
|
+
elif method_lower == 'delete':
|
199
|
+
body_lines.append(" response = self._delete(url)")
|
114
200
|
else:
|
115
|
-
body_lines.append(f"
|
201
|
+
body_lines.append(f" response = self._{method_lower}(url, data={{}})")
|
116
202
|
|
117
203
|
# Handle response
|
118
|
-
body_lines.append("
|
119
|
-
body_lines.append("
|
204
|
+
body_lines.append(" response.raise_for_status()")
|
205
|
+
body_lines.append(" return response.json()")
|
120
206
|
|
121
|
-
|
207
|
+
method_code = signature + '\n' + '\n'.join(body_lines)
|
208
|
+
return method_code, func_name
|
209
|
+
|
122
210
|
|
123
211
|
# Example usage
|
124
212
|
if __name__ == "__main__":
|
@@ -0,0 +1,30 @@
|
|
1
|
+
agentr/__init__.py,sha256=LOWhgQayrQV7f5ro4rlBJ_6WevhbWIbjAOHnqP7b_-4,30
|
2
|
+
agentr/application.py,sha256=DdDF55_44Bj2vE_9JDzv3i9rYm8LK2r8E5mBd7xQdR0,2884
|
3
|
+
agentr/cli.py,sha256=z0QOdyiBcypNE_npN2lYIkEcYZXG8Ji0027IcEuhDTs,2880
|
4
|
+
agentr/config.py,sha256=YTygfFJPUuL-epRuILvt5tc5ACzByWIFFNhpFwHlDCE,387
|
5
|
+
agentr/exceptions.py,sha256=hHlyXUZBjG4DfUurvqd0ZiruHC67gbpT6EHKxifwUhg,271
|
6
|
+
agentr/integration.py,sha256=hr2Edd2H-JG_CNe6xgYvfjy2BgKgFrBN2Grn8n4vfGs,5433
|
7
|
+
agentr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
+
agentr/server.py,sha256=PNtg8BkVST4fOgNI4tWoBX2ue1EfGdgEf3__MjOVYmc,4907
|
9
|
+
agentr/store.py,sha256=fB3uAaobnWf2ILcDBmg3ToDaqAIPYlLtmHBdpmkcGcI,1585
|
10
|
+
agentr/test.py,sha256=crKkiygUhXBaJPfcaS35bLbTd_-hc9jdaONQkT79i3M,354
|
11
|
+
agentr/applications/__init__.py,sha256=huqhhfMkSMjcc3eqVAprW03Drr98OHbH2Rh0GwGQHjs,942
|
12
|
+
agentr/applications/github/app.py,sha256=qN2O3F9bfRLTxklTPzs67scjZ2KxtrqS6nOGh3ZDmR8,12407
|
13
|
+
agentr/applications/google_calendar/app.py,sha256=BlOBmdoEqifuOrG194BgjAvUkBhwvVeGM1HBYKv0bDU,19953
|
14
|
+
agentr/applications/google_mail/app.py,sha256=GWmJwgdpBKyNufsrHp2PkYzXNzyCb7XOHxZPYw6XaBA,23710
|
15
|
+
agentr/applications/reddit/app.py,sha256=W4rGSDE3ooZiMPWUsgM2URCjaI7lYC56r5pfdlrP4Ss,12548
|
16
|
+
agentr/applications/resend/app.py,sha256=ihRzP65bwoNIq3EzBqIghxgLZRxvy-LbHy-rER20ODo,1428
|
17
|
+
agentr/applications/tavily/app.py,sha256=D4FOhm2yxNbuTVHTo3T8ZsuE5AgwK874YutfYx2Njcw,1805
|
18
|
+
agentr/applications/zenquotes/app.py,sha256=4LjYeWdERI8ZMzkajOsDgy9IRTA9plUKem-rW4M03sA,607
|
19
|
+
agentr/integrations/README.md,sha256=lTAPXO2nivcBe1q7JT6PRa6v9Ns_ZersQMIdw-nmwEA,996
|
20
|
+
agentr/integrations/__init__.py,sha256=_jGeykaUjY97rklbUIGbY7oWQXhtYGRUdYszNRw_NOg,232
|
21
|
+
agentr/integrations/agentr.py,sha256=MWUHItiRvs-9PNi8jcPHXgJ-cxCGVpzIpW7VNWWIoAs,3364
|
22
|
+
agentr/integrations/api_key.py,sha256=cbX_8Je7nX3alIK1g0EqQcyNsNylQRQExGzsUARfytU,552
|
23
|
+
agentr/integrations/base.py,sha256=jZPWwzEHPJxS7onfscp8wwXt0bT66SC9tr75XQaueR4,1536
|
24
|
+
agentr/utils/bridge.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
|
+
agentr/utils/openapi.py,sha256=AEBMAuuDpjmoNjC3KWri6m2Pje0H_h397xE17deLLQg,9563
|
26
|
+
agentr-0.1.8.dist-info/METADATA,sha256=TyG5rgrVd_19Si0Y0ORwiE9LWVVRjGTCs82FOpUxoL0,4388
|
27
|
+
agentr-0.1.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
28
|
+
agentr-0.1.8.dist-info/entry_points.txt,sha256=13fGFeVhgF6_8T-VFiIkNxYO7gDQaUwwTcUNWdvaLQg,42
|
29
|
+
agentr-0.1.8.dist-info/licenses/LICENSE,sha256=CPslwL9mT3MH-lEljRJQHKe646096G-szURVmOD18Lc,1063
|
30
|
+
agentr-0.1.8.dist-info/RECORD,,
|
agentr-0.1.7.dist-info/RECORD
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
agentr/__init__.py,sha256=LOWhgQayrQV7f5ro4rlBJ_6WevhbWIbjAOHnqP7b_-4,30
|
2
|
-
agentr/application.py,sha256=r2o4PP0uDBFTkziseuOh7AobcVqnEWDoYJxN_UA7UVo,2932
|
3
|
-
agentr/cli.py,sha256=z0QOdyiBcypNE_npN2lYIkEcYZXG8Ji0027IcEuhDTs,2880
|
4
|
-
agentr/config.py,sha256=YTygfFJPUuL-epRuILvt5tc5ACzByWIFFNhpFwHlDCE,387
|
5
|
-
agentr/exceptions.py,sha256=hHlyXUZBjG4DfUurvqd0ZiruHC67gbpT6EHKxifwUhg,271
|
6
|
-
agentr/integration.py,sha256=BmtPpn2lXV7i6k6Uqu5g-LWpD9wRiMz7R3SgztLAktc,2663
|
7
|
-
agentr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
agentr/server.py,sha256=vg9YlApxlLvxcIn9h_Rz2IgatXv7aBj0pTTb6XWMRN4,4279
|
9
|
-
agentr/store.py,sha256=fB3uAaobnWf2ILcDBmg3ToDaqAIPYlLtmHBdpmkcGcI,1585
|
10
|
-
agentr/test.py,sha256=wtupsEMrn-Q9Ocjlhelk4YrVSrWMM5vwMBhnvepEaHo,917
|
11
|
-
agentr/applications/__init__.py,sha256=huqhhfMkSMjcc3eqVAprW03Drr98OHbH2Rh0GwGQHjs,942
|
12
|
-
agentr/applications/github/app.py,sha256=N-f9TX5btcUDr19H-eTG7NlFlb25fGENLZ07fFfQBg4,17301
|
13
|
-
agentr/applications/google_calendar/app.py,sha256=3mpqP0MsTYsJyZDF2Skgs1VLfLRr6E7OSOhbcAQ_L3E,25710
|
14
|
-
agentr/applications/google_mail/app.py,sha256=GWmJwgdpBKyNufsrHp2PkYzXNzyCb7XOHxZPYw6XaBA,23710
|
15
|
-
agentr/applications/reddit/app.py,sha256=iNkSRVGScj7Mae3e8N-Mkov_hvZrvH5NynbpO8mKToQ,842
|
16
|
-
agentr/applications/resend/app.py,sha256=ihRzP65bwoNIq3EzBqIghxgLZRxvy-LbHy-rER20ODo,1428
|
17
|
-
agentr/applications/tavily/app.py,sha256=D4FOhm2yxNbuTVHTo3T8ZsuE5AgwK874YutfYx2Njcw,1805
|
18
|
-
agentr/applications/zenquotes/app.py,sha256=4LjYeWdERI8ZMzkajOsDgy9IRTA9plUKem-rW4M03sA,607
|
19
|
-
agentr/utils/bridge.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
-
agentr/utils/openapi.py,sha256=DT4jcWue_Fx0J-wg7T53w3uMb5CIzn4u2QswmtpSFVU,6280
|
21
|
-
agentr-0.1.7.dist-info/METADATA,sha256=BUoooDf1WgcRVE-rVBD3W78W-FrhSGnvAmhbYbuV-TY,4388
|
22
|
-
agentr-0.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
23
|
-
agentr-0.1.7.dist-info/entry_points.txt,sha256=13fGFeVhgF6_8T-VFiIkNxYO7gDQaUwwTcUNWdvaLQg,42
|
24
|
-
agentr-0.1.7.dist-info/licenses/LICENSE,sha256=CPslwL9mT3MH-lEljRJQHKe646096G-szURVmOD18Lc,1063
|
25
|
-
agentr-0.1.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|