agentr 0.1.6__py3-none-any.whl → 0.1.7__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/__init__.py +1 -1
- agentr/application.py +92 -56
- agentr/applications/__init__.py +27 -0
- agentr/applications/github/app.py +408 -56
- agentr/applications/google_calendar/app.py +548 -74
- agentr/applications/google_mail/app.py +565 -68
- agentr/applications/reddit/app.py +29 -29
- agentr/applications/resend/app.py +43 -43
- agentr/applications/tavily/app.py +57 -57
- agentr/applications/zenquotes/app.py +20 -20
- agentr/cli.py +76 -75
- agentr/config.py +15 -0
- agentr/exceptions.py +6 -5
- agentr/integration.py +85 -98
- agentr/server.py +113 -105
- agentr/store.py +70 -70
- agentr/test.py +39 -37
- agentr/utils/openapi.py +185 -184
- {agentr-0.1.6.dist-info → agentr-0.1.7.dist-info}/METADATA +4 -1
- agentr-0.1.7.dist-info/RECORD +25 -0
- {agentr-0.1.6.dist-info → agentr-0.1.7.dist-info}/licenses/LICENSE +21 -21
- agentr-0.1.6.dist-info/RECORD +0 -23
- {agentr-0.1.6.dist-info → agentr-0.1.7.dist-info}/WHEEL +0 -0
- {agentr-0.1.6.dist-info → agentr-0.1.7.dist-info}/entry_points.txt +0 -0
agentr/integration.py
CHANGED
@@ -1,98 +1,85 @@
|
|
1
|
-
from abc import ABC, abstractmethod
|
2
|
-
import os
|
3
|
-
import sys
|
4
|
-
|
5
|
-
from loguru import logger
|
6
|
-
from agentr.store import Store
|
7
|
-
import httpx
|
8
|
-
|
9
|
-
"""
|
10
|
-
Integration defines how a Application needs to authorize.
|
11
|
-
It is responsible for authenticating application with the service provider.
|
12
|
-
Supported integrations:
|
13
|
-
- AgentR Integration
|
14
|
-
- API Key Integration
|
15
|
-
"""
|
16
|
-
|
17
|
-
class Integration(ABC):
|
18
|
-
def __init__(self, name: str, store: Store = None):
|
19
|
-
self.name = name
|
20
|
-
self.store = store
|
21
|
-
|
22
|
-
@abstractmethod
|
23
|
-
def
|
24
|
-
pass
|
25
|
-
|
26
|
-
@abstractmethod
|
27
|
-
def
|
28
|
-
pass
|
29
|
-
|
30
|
-
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
def
|
39
|
-
self.store.
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
response = httpx.
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
def get_credentials(self):
|
88
|
-
connection_id = self.get_connection_by_owner()
|
89
|
-
logger.info(f"Connection ID: {connection_id}")
|
90
|
-
if connection_id:
|
91
|
-
response = httpx.get(f"{self.base_url}/connection/{connection_id}?provider_config_key={self.name}", headers={"Authorization": f"Bearer {self.api_key}"})
|
92
|
-
data = response.json()
|
93
|
-
return data.get("credentials")
|
94
|
-
return None
|
95
|
-
|
96
|
-
def authorize(self):
|
97
|
-
url = self._get_authorize_url()
|
98
|
-
return f"Please authorize the application by clicking the link {url}"
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
|
5
|
+
from loguru import logger
|
6
|
+
from agentr.store import Store
|
7
|
+
import httpx
|
8
|
+
|
9
|
+
"""
|
10
|
+
Integration defines how a Application needs to authorize.
|
11
|
+
It is responsible for authenticating application with the service provider.
|
12
|
+
Supported integrations:
|
13
|
+
- AgentR Integration
|
14
|
+
- API Key Integration
|
15
|
+
"""
|
16
|
+
|
17
|
+
class Integration(ABC):
|
18
|
+
def __init__(self, name: str, store: Store = None):
|
19
|
+
self.name = name
|
20
|
+
self.store = store
|
21
|
+
|
22
|
+
@abstractmethod
|
23
|
+
def authorize(self):
|
24
|
+
pass
|
25
|
+
|
26
|
+
@abstractmethod
|
27
|
+
def get_credentials(self):
|
28
|
+
pass
|
29
|
+
|
30
|
+
@abstractmethod
|
31
|
+
def set_credentials(self, credentials: dict):
|
32
|
+
pass
|
33
|
+
|
34
|
+
class ApiKeyIntegration(Integration):
|
35
|
+
def __init__(self, name: str, store: Store = None, **kwargs):
|
36
|
+
super().__init__(name, store, **kwargs)
|
37
|
+
|
38
|
+
def get_credentials(self):
|
39
|
+
credentials = self.store.get(self.name)
|
40
|
+
return credentials
|
41
|
+
|
42
|
+
def set_credentials(self, credentials: dict):
|
43
|
+
self.store.set(self.name, credentials)
|
44
|
+
|
45
|
+
def authorize(self):
|
46
|
+
return {"text": "Please configure the environment variable {self.name}_API_KEY"}
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
class AgentRIntegration(Integration):
|
51
|
+
def __init__(self, name: str, api_key: str = None, **kwargs):
|
52
|
+
super().__init__(name, **kwargs)
|
53
|
+
self.api_key = api_key or os.getenv("AGENTR_API_KEY")
|
54
|
+
if not self.api_key:
|
55
|
+
logger.error("API key for AgentR is missing. Please visit https://agentr.dev to create an API key, then set it as AGENTR_API_KEY environment variable.")
|
56
|
+
raise ValueError("AgentR API key required - get one at https://agentr.dev")
|
57
|
+
self.base_url = os.getenv("AGENTR_BASE_URL", "https://api.agentr.dev")
|
58
|
+
|
59
|
+
|
60
|
+
def set_credentials(self, credentials: dict| None = None):
|
61
|
+
return self.authorize()
|
62
|
+
# raise NotImplementedError("AgentR Integration does not support setting credentials. Visit the authorize url to set credentials.")
|
63
|
+
|
64
|
+
def get_credentials(self):
|
65
|
+
response = httpx.get(
|
66
|
+
f"{self.base_url}/api/{self.name}/credentials/",
|
67
|
+
headers={
|
68
|
+
"accept": "application/json",
|
69
|
+
"X-API-KEY": self.api_key
|
70
|
+
}
|
71
|
+
)
|
72
|
+
response.raise_for_status()
|
73
|
+
data = response.json()
|
74
|
+
return data
|
75
|
+
|
76
|
+
def authorize(self):
|
77
|
+
response = httpx.get(
|
78
|
+
f"{self.base_url}/api/{self.name}/authorize/",
|
79
|
+
headers={
|
80
|
+
"X-API-KEY": self.api_key
|
81
|
+
}
|
82
|
+
)
|
83
|
+
response.raise_for_status()
|
84
|
+
url = response.json()
|
85
|
+
return f"Please authorize the application by clicking the link {url}"
|
agentr/server.py
CHANGED
@@ -1,105 +1,113 @@
|
|
1
|
-
from abc import ABC, abstractmethod
|
2
|
-
|
3
|
-
from mcp.server.fastmcp import FastMCP
|
4
|
-
from agentr.
|
5
|
-
from agentr.
|
6
|
-
from
|
7
|
-
from
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
return
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
import httpx
|
3
|
+
from mcp.server.fastmcp import FastMCP
|
4
|
+
from agentr.applications import app_from_name
|
5
|
+
from agentr.integration import AgentRIntegration, ApiKeyIntegration
|
6
|
+
from agentr.store import EnvironmentStore, MemoryStore
|
7
|
+
from agentr.config import AppConfig, IntegrationConfig, StoreConfig
|
8
|
+
from loguru import logger
|
9
|
+
import os
|
10
|
+
|
11
|
+
class Server(FastMCP, ABC):
|
12
|
+
"""
|
13
|
+
Server is responsible for managing the applications and the store
|
14
|
+
It also acts as a router for the applications, and exposed to the client
|
15
|
+
|
16
|
+
"""
|
17
|
+
def __init__(self, name: str, description: str, **kwargs):
|
18
|
+
super().__init__(name, description, **kwargs)
|
19
|
+
|
20
|
+
@abstractmethod
|
21
|
+
def _load_apps(self):
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
class LocalServer(Server):
|
26
|
+
"""
|
27
|
+
Local server for development purposes
|
28
|
+
"""
|
29
|
+
def __init__(self, name: str, description: str, apps_list: list[AppConfig] = [], **kwargs):
|
30
|
+
super().__init__(name, description=description, **kwargs)
|
31
|
+
self.apps_list = [AppConfig.model_validate(app) for app in apps_list]
|
32
|
+
self._load_apps()
|
33
|
+
|
34
|
+
def _get_store(self, store_config: StoreConfig):
|
35
|
+
if store_config.type == "memory":
|
36
|
+
return MemoryStore()
|
37
|
+
elif store_config.type == "environment":
|
38
|
+
return EnvironmentStore()
|
39
|
+
return None
|
40
|
+
|
41
|
+
def _get_integration(self, integration_config: IntegrationConfig):
|
42
|
+
if not integration_config:
|
43
|
+
return None
|
44
|
+
if integration_config.type == "api_key":
|
45
|
+
store = self._get_store(integration_config.store)
|
46
|
+
integration = ApiKeyIntegration(integration_config.name, store=store)
|
47
|
+
if integration_config.credentials:
|
48
|
+
integration.set_credentials(integration_config.credentials)
|
49
|
+
return integration
|
50
|
+
elif integration_config.type == "agentr":
|
51
|
+
integration = AgentRIntegration(integration_config.name, api_key=integration_config.credentials.get("api_key") if integration_config.credentials else None)
|
52
|
+
return integration
|
53
|
+
return None
|
54
|
+
|
55
|
+
def _load_app(self, app_config: AppConfig):
|
56
|
+
name = app_config.name
|
57
|
+
integration = self._get_integration(app_config.integration)
|
58
|
+
app = app_from_name(name)(integration=integration)
|
59
|
+
return app
|
60
|
+
|
61
|
+
def _load_apps(self):
|
62
|
+
logger.info(f"Loading apps: {self.apps_list}")
|
63
|
+
for app_config in self.apps_list:
|
64
|
+
app = self._load_app(app_config)
|
65
|
+
if app:
|
66
|
+
tools = app.list_tools()
|
67
|
+
for tool in tools:
|
68
|
+
self.add_tool(tool)
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
class AgentRServer(Server):
|
73
|
+
"""
|
74
|
+
AgentR server. Connects to the AgentR API to get the apps and tools. Only supports agentr integrations.
|
75
|
+
"""
|
76
|
+
def __init__(self, name: str, description: str, api_key: str | None = None, **kwargs):
|
77
|
+
super().__init__(name, description=description, **kwargs)
|
78
|
+
self.api_key = api_key or os.getenv("AGENTR_API_KEY")
|
79
|
+
self.base_url = os.getenv("AGENTR_BASE_URL", "https://api.agentr.dev")
|
80
|
+
if not self.api_key:
|
81
|
+
raise ValueError("API key required - get one at https://agentr.dev")
|
82
|
+
self._load_apps()
|
83
|
+
|
84
|
+
def _load_app(self, app_config: AppConfig):
|
85
|
+
name = app_config.name
|
86
|
+
if app_config.integration:
|
87
|
+
integration_name = app_config.integration.name
|
88
|
+
integration = AgentRIntegration(integration_name, api_key=self.api_key)
|
89
|
+
else:
|
90
|
+
integration = None
|
91
|
+
app = app_from_name(name)(integration=integration)
|
92
|
+
return app
|
93
|
+
|
94
|
+
def _list_apps_with_integrations(self):
|
95
|
+
# TODO: get this from the API
|
96
|
+
response = httpx.get(
|
97
|
+
f"{self.base_url}/api/apps/",
|
98
|
+
headers={
|
99
|
+
"X-API-KEY": self.api_key
|
100
|
+
}
|
101
|
+
)
|
102
|
+
apps = response.json()
|
103
|
+
logger.info(f"Apps: {apps}")
|
104
|
+
return [AppConfig.model_validate(app) for app in apps]
|
105
|
+
|
106
|
+
def _load_apps(self):
|
107
|
+
apps = self._list_apps_with_integrations()
|
108
|
+
for app in apps:
|
109
|
+
app = self._load_app(app)
|
110
|
+
if app:
|
111
|
+
tools = app.list_tools()
|
112
|
+
for tool in tools:
|
113
|
+
self.add_tool(tool)
|
agentr/store.py
CHANGED
@@ -1,71 +1,71 @@
|
|
1
|
-
import os
|
2
|
-
from abc import ABC, abstractmethod
|
3
|
-
|
4
|
-
|
5
|
-
class Store(ABC):
|
6
|
-
@abstractmethod
|
7
|
-
def get(self, key: str):
|
8
|
-
pass
|
9
|
-
|
10
|
-
@abstractmethod
|
11
|
-
def set(self, key: str, value: str):
|
12
|
-
pass
|
13
|
-
|
14
|
-
@abstractmethod
|
15
|
-
def delete(self, key: str):
|
16
|
-
pass
|
17
|
-
|
18
|
-
class MemoryStore:
|
19
|
-
"""
|
20
|
-
Acts as credential store for the applications.
|
21
|
-
Responsible for storing and retrieving credentials.
|
22
|
-
Ideally should be a key value store
|
23
|
-
"""
|
24
|
-
def __init__(self):
|
25
|
-
self.data = {}
|
26
|
-
|
27
|
-
def get(self, key: str):
|
28
|
-
return self.data.get(key)
|
29
|
-
|
30
|
-
def set(self, key: str, value: str):
|
31
|
-
self.data[key] = value
|
32
|
-
|
33
|
-
def delete(self, key: str):
|
34
|
-
del self.data[key]
|
35
|
-
|
36
|
-
|
37
|
-
class EnvironmentStore(Store):
|
38
|
-
"""
|
39
|
-
Store that uses environment variables to store credentials.
|
40
|
-
"""
|
41
|
-
def __init__(self):
|
42
|
-
pass
|
43
|
-
|
44
|
-
def get(self, key: str):
|
45
|
-
return {"api_key": os.getenv(key)}
|
46
|
-
|
47
|
-
def set(self, key: str, value: str):
|
48
|
-
os.environ[key] = value
|
49
|
-
|
50
|
-
def delete(self, key: str):
|
51
|
-
del os.environ[key]
|
52
|
-
|
53
|
-
class RedisStore(Store):
|
54
|
-
"""
|
55
|
-
Store that uses a redis database to store credentials.
|
56
|
-
"""
|
57
|
-
def __init__(self, host: str, port: int, db: int):
|
58
|
-
import redis
|
59
|
-
self.host = host
|
60
|
-
self.port = port
|
61
|
-
self.db = db
|
62
|
-
self.redis = redis.Redis(host=self.host, port=self.port, db=self.db)
|
63
|
-
|
64
|
-
def get(self, key: str):
|
65
|
-
return self.redis.get(key)
|
66
|
-
|
67
|
-
def set(self, key: str, value: str):
|
68
|
-
self.redis.set(key, value)
|
69
|
-
|
70
|
-
def delete(self, key: str):
|
1
|
+
import os
|
2
|
+
from abc import ABC, abstractmethod
|
3
|
+
|
4
|
+
|
5
|
+
class Store(ABC):
|
6
|
+
@abstractmethod
|
7
|
+
def get(self, key: str):
|
8
|
+
pass
|
9
|
+
|
10
|
+
@abstractmethod
|
11
|
+
def set(self, key: str, value: str):
|
12
|
+
pass
|
13
|
+
|
14
|
+
@abstractmethod
|
15
|
+
def delete(self, key: str):
|
16
|
+
pass
|
17
|
+
|
18
|
+
class MemoryStore:
|
19
|
+
"""
|
20
|
+
Acts as credential store for the applications.
|
21
|
+
Responsible for storing and retrieving credentials.
|
22
|
+
Ideally should be a key value store
|
23
|
+
"""
|
24
|
+
def __init__(self):
|
25
|
+
self.data = {}
|
26
|
+
|
27
|
+
def get(self, key: str):
|
28
|
+
return self.data.get(key)
|
29
|
+
|
30
|
+
def set(self, key: str, value: str):
|
31
|
+
self.data[key] = value
|
32
|
+
|
33
|
+
def delete(self, key: str):
|
34
|
+
del self.data[key]
|
35
|
+
|
36
|
+
|
37
|
+
class EnvironmentStore(Store):
|
38
|
+
"""
|
39
|
+
Store that uses environment variables to store credentials.
|
40
|
+
"""
|
41
|
+
def __init__(self):
|
42
|
+
pass
|
43
|
+
|
44
|
+
def get(self, key: str):
|
45
|
+
return {"api_key": os.getenv(key)}
|
46
|
+
|
47
|
+
def set(self, key: str, value: str):
|
48
|
+
os.environ[key] = value
|
49
|
+
|
50
|
+
def delete(self, key: str):
|
51
|
+
del os.environ[key]
|
52
|
+
|
53
|
+
class RedisStore(Store):
|
54
|
+
"""
|
55
|
+
Store that uses a redis database to store credentials.
|
56
|
+
"""
|
57
|
+
def __init__(self, host: str, port: int, db: int):
|
58
|
+
import redis
|
59
|
+
self.host = host
|
60
|
+
self.port = port
|
61
|
+
self.db = db
|
62
|
+
self.redis = redis.Redis(host=self.host, port=self.port, db=self.db)
|
63
|
+
|
64
|
+
def get(self, key: str):
|
65
|
+
return self.redis.get(key)
|
66
|
+
|
67
|
+
def set(self, key: str, value: str):
|
68
|
+
self.redis.set(key, value)
|
69
|
+
|
70
|
+
def delete(self, key: str):
|
71
71
|
self.redis.delete(key)
|
agentr/test.py
CHANGED
@@ -1,38 +1,40 @@
|
|
1
|
-
from agentr.server import
|
2
|
-
from agentr.store import MemoryStore
|
3
|
-
|
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 =
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
1
|
+
from agentr.server import LocalServer
|
2
|
+
from agentr.store import MemoryStore
|
3
|
+
|
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
|
+
|
30
|
+
|
31
|
+
async def test():
|
32
|
+
tools = await mcp.list_tools()
|
33
|
+
from pprint import pprint
|
34
|
+
pprint(tools)
|
35
|
+
result = await mcp.call_tool("star_repository", {"repo_full_name": "manojbajaj95/config"})
|
36
|
+
print(result)
|
37
|
+
|
38
|
+
if __name__ == "__main__":
|
39
|
+
import asyncio
|
38
40
|
asyncio.run(test())
|