agentr 0.1.2__py3-none-any.whl → 0.1.3__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 +15 -8
- agentr/applications/github/app.py +52 -0
- agentr/applications/google_calendar/app.py +74 -0
- agentr/applications/google_mail/app.py +68 -0
- agentr/applications/reddit/app.py +29 -0
- agentr/applications/resend/app.py +43 -0
- agentr/applications/tavily/app.py +57 -0
- agentr/cli.py +25 -2
- agentr/integration.py +91 -0
- agentr/server.py +87 -15
- agentr/store.py +37 -1
- agentr/test.py +38 -0
- agentr/utils/bridge.py +0 -0
- {agentr-0.1.2.dist-info → agentr-0.1.3.dist-info}/METADATA +2 -1
- agentr-0.1.3.dist-info/RECORD +21 -0
- agentr/mcp.py +0 -9
- agentr-0.1.2.dist-info/RECORD +0 -13
- {agentr-0.1.2.dist-info → agentr-0.1.3.dist-info}/WHEEL +0 -0
- {agentr-0.1.2.dist-info → agentr-0.1.3.dist-info}/entry_points.txt +0 -0
agentr/application.py
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
-
from agentr.
|
2
|
+
from agentr.integration import Integration
|
3
3
|
import httpx
|
4
4
|
|
5
5
|
class Application(ABC):
|
6
|
+
"""
|
7
|
+
Application is collection of tools that can be used by an agent.
|
8
|
+
"""
|
6
9
|
def __init__(self, name: str, **kwargs):
|
7
10
|
self.name = name
|
8
11
|
|
@@ -12,9 +15,12 @@ class Application(ABC):
|
|
12
15
|
|
13
16
|
|
14
17
|
class APIApplication(Application):
|
15
|
-
|
18
|
+
"""
|
19
|
+
APIApplication is an application that uses an API to interact with the world.
|
20
|
+
"""
|
21
|
+
def __init__(self, name: str, integration: Integration = None, **kwargs):
|
16
22
|
super().__init__(name, **kwargs)
|
17
|
-
self.
|
23
|
+
self.integration = integration
|
18
24
|
|
19
25
|
def _get_headers(self):
|
20
26
|
return {}
|
@@ -22,29 +28,30 @@ class APIApplication(Application):
|
|
22
28
|
def _get(self, url, params=None):
|
23
29
|
headers = self._get_headers()
|
24
30
|
response = httpx.get(url, headers=headers, params=params)
|
31
|
+
response.raise_for_status()
|
25
32
|
return response
|
26
33
|
|
27
34
|
def _post(self, url, data):
|
28
35
|
headers = self._get_headers()
|
29
|
-
response = httpx.post(url, headers=headers,
|
36
|
+
response = httpx.post(url, headers=headers, json=data)
|
37
|
+
response.raise_for_status()
|
30
38
|
return response
|
31
39
|
|
32
40
|
def _put(self, url, data):
|
33
41
|
headers = self._get_headers()
|
34
|
-
response = httpx.put(url, headers=headers,
|
42
|
+
response = httpx.put(url, headers=headers, json=data)
|
43
|
+
response.raise_for_status()
|
35
44
|
return response
|
36
45
|
|
37
46
|
def _delete(self, url):
|
38
47
|
headers = self._get_headers()
|
39
48
|
response = httpx.delete(url, headers=headers)
|
49
|
+
response.raise_for_status()
|
40
50
|
return response
|
41
51
|
|
42
52
|
def validate(self):
|
43
53
|
pass
|
44
54
|
|
45
|
-
def authorize(self):
|
46
|
-
pass
|
47
|
-
|
48
55
|
@abstractmethod
|
49
56
|
def list_tools(self):
|
50
57
|
pass
|
@@ -0,0 +1,52 @@
|
|
1
|
+
from agentr.integration import Integration
|
2
|
+
from agentr.application import APIApplication
|
3
|
+
from loguru import logger
|
4
|
+
|
5
|
+
|
6
|
+
class GithubApp(APIApplication):
|
7
|
+
def __init__(self, integration: Integration) -> None:
|
8
|
+
super().__init__(name="github", integration=integration)
|
9
|
+
|
10
|
+
def _get_headers(self):
|
11
|
+
if not self.integration:
|
12
|
+
raise ValueError("Integration not configured")
|
13
|
+
credentials = self.integration.get_credentials()
|
14
|
+
if not credentials:
|
15
|
+
logger.warning("No credentials found")
|
16
|
+
return self.integration.authorize()
|
17
|
+
if "headers" in credentials:
|
18
|
+
return credentials["headers"]
|
19
|
+
return {
|
20
|
+
"Authorization": f"Bearer {credentials['access_token']}",
|
21
|
+
"Accept": "application/vnd.github.v3+json"
|
22
|
+
}
|
23
|
+
|
24
|
+
|
25
|
+
def star_repository(self, repo_full_name: str) -> str:
|
26
|
+
"""Star a GitHub repository
|
27
|
+
|
28
|
+
Args:
|
29
|
+
repo_full_name: The full name of the repository (e.g. 'owner/repo')
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
|
33
|
+
A confirmation message
|
34
|
+
"""
|
35
|
+
try:
|
36
|
+
url = f"https://api.github.com/user/starred/{repo_full_name}"
|
37
|
+
response = self._put(url, data={})
|
38
|
+
|
39
|
+
if response.status_code == 204:
|
40
|
+
return f"Successfully starred repository {repo_full_name}"
|
41
|
+
elif response.status_code == 404:
|
42
|
+
return f"Repository {repo_full_name} not found"
|
43
|
+
else:
|
44
|
+
logger.error(response.text)
|
45
|
+
return f"Error starring repository: {response.text}"
|
46
|
+
except Exception as e:
|
47
|
+
logger.error(e)
|
48
|
+
return f"Error starring repository: {e}"
|
49
|
+
|
50
|
+
|
51
|
+
def list_tools(self):
|
52
|
+
return [self.star_repository]
|
@@ -0,0 +1,74 @@
|
|
1
|
+
from agentr.application import APIApplication
|
2
|
+
from agentr.integration import Integration
|
3
|
+
from loguru import logger
|
4
|
+
from datetime import datetime, timedelta
|
5
|
+
|
6
|
+
class GoogleCalendarApp(APIApplication):
|
7
|
+
def __init__(self, integration: Integration) -> None:
|
8
|
+
super().__init__(name="google-calendar", integration=integration)
|
9
|
+
|
10
|
+
def _get_headers(self):
|
11
|
+
credentials = self.integration.get_credentials()
|
12
|
+
if "headers" in credentials:
|
13
|
+
return credentials["headers"]
|
14
|
+
return {
|
15
|
+
"Authorization": f"Bearer {credentials['access_token']}",
|
16
|
+
"Accept": "application/json"
|
17
|
+
}
|
18
|
+
|
19
|
+
|
20
|
+
def get_today_events(self) -> str:
|
21
|
+
"""Get events from your Google Calendar for today
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
A formatted list of today's events or an error message
|
25
|
+
"""
|
26
|
+
if not self.validate():
|
27
|
+
logger.warning("Connection not configured correctly")
|
28
|
+
return self.authorize()
|
29
|
+
|
30
|
+
try:
|
31
|
+
# Get today's date in ISO format
|
32
|
+
today = datetime.now().date()
|
33
|
+
tomorrow = today + timedelta(days=1)
|
34
|
+
|
35
|
+
# Format dates for API
|
36
|
+
time_min = f"{today.isoformat()}T00:00:00Z"
|
37
|
+
time_max = f"{tomorrow.isoformat()}T00:00:00Z"
|
38
|
+
|
39
|
+
url = "https://www.googleapis.com/calendar/v3/calendars/primary/events"
|
40
|
+
params = {
|
41
|
+
"timeMin": time_min,
|
42
|
+
"timeMax": time_max,
|
43
|
+
"singleEvents": "true",
|
44
|
+
"orderBy": "startTime"
|
45
|
+
}
|
46
|
+
|
47
|
+
response = self._get(url, params=params)
|
48
|
+
|
49
|
+
if response.status_code == 200:
|
50
|
+
events = response.json().get("items", [])
|
51
|
+
if not events:
|
52
|
+
return "No events scheduled for today."
|
53
|
+
|
54
|
+
result = "Today's events:\n\n"
|
55
|
+
for event in events:
|
56
|
+
start = event.get("start", {})
|
57
|
+
start_time = start.get("dateTime", start.get("date", "All day"))
|
58
|
+
if "T" in start_time: # Format datetime
|
59
|
+
start_dt = datetime.fromisoformat(start_time.replace("Z", "+00:00"))
|
60
|
+
start_time = start_dt.strftime("%I:%M %p")
|
61
|
+
|
62
|
+
summary = event.get("summary", "Untitled event")
|
63
|
+
result += f"- {start_time}: {summary}\n"
|
64
|
+
|
65
|
+
return result
|
66
|
+
else:
|
67
|
+
logger.error(response.text)
|
68
|
+
return f"Error retrieving calendar events: {response.text}"
|
69
|
+
except Exception as e:
|
70
|
+
logger.error(e)
|
71
|
+
return f"Error retrieving calendar events: {e}"
|
72
|
+
|
73
|
+
def list_tools(self):
|
74
|
+
return [self.get_today_events]
|
@@ -0,0 +1,68 @@
|
|
1
|
+
from agentr.application import APIApplication
|
2
|
+
from agentr.integration import Integration
|
3
|
+
from loguru import logger
|
4
|
+
import base64
|
5
|
+
from email.message import EmailMessage
|
6
|
+
|
7
|
+
class GmailApp(APIApplication):
|
8
|
+
def __init__(self, integration: Integration) -> None:
|
9
|
+
super().__init__(name="gmail", integration=integration)
|
10
|
+
|
11
|
+
def _get_headers(self):
|
12
|
+
credentials = self.integration.get_credentials()
|
13
|
+
if "headers" in credentials:
|
14
|
+
return credentials["headers"]
|
15
|
+
return {
|
16
|
+
"Authorization": f"Bearer {credentials['access_token']}",
|
17
|
+
'Content-Type': 'application/json'
|
18
|
+
}
|
19
|
+
|
20
|
+
def send_email(self, to: str, subject: str, body: str) -> str:
|
21
|
+
"""Send an email
|
22
|
+
|
23
|
+
Args:
|
24
|
+
to: The email address of the recipient
|
25
|
+
subject: The subject of the email
|
26
|
+
body: The body of the email
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
|
30
|
+
A confirmation message
|
31
|
+
"""
|
32
|
+
if not self.validate():
|
33
|
+
logger.warning("Connection not configured correctly")
|
34
|
+
return self.authorize()
|
35
|
+
try:
|
36
|
+
url = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send"
|
37
|
+
|
38
|
+
# Create email in base64 encoded format
|
39
|
+
email_message = {
|
40
|
+
"raw": self._create_message(to, subject, body)
|
41
|
+
}
|
42
|
+
logger.info(email_message)
|
43
|
+
|
44
|
+
# Use json parameter instead of data to properly format JSON
|
45
|
+
response = self._post(url, email_message)
|
46
|
+
|
47
|
+
if response.status_code == 200:
|
48
|
+
return f"Successfully sent email to {to}"
|
49
|
+
else:
|
50
|
+
logger.error(response.text)
|
51
|
+
return f"Error sending email: {response.text}"
|
52
|
+
except Exception as e:
|
53
|
+
logger.error(e)
|
54
|
+
return f"Error sending email: {e}"
|
55
|
+
|
56
|
+
def _create_message(self, to, subject, body):
|
57
|
+
message = EmailMessage()
|
58
|
+
message['to'] = to
|
59
|
+
message['from'] = "manojbajaj95@gmail.com"
|
60
|
+
message['subject'] = subject
|
61
|
+
message.set_content(body)
|
62
|
+
|
63
|
+
# Encode as base64 string
|
64
|
+
raw = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
65
|
+
return raw
|
66
|
+
|
67
|
+
def list_tools(self):
|
68
|
+
return [self.send_email]
|
@@ -0,0 +1,29 @@
|
|
1
|
+
from agentr.application import APIApplication
|
2
|
+
from agentr.integration import Integration
|
3
|
+
|
4
|
+
class RedditApp(APIApplication):
|
5
|
+
def __init__(self, integration: Integration) -> None:
|
6
|
+
super().__init__(name="reddit", integration=integration)
|
7
|
+
|
8
|
+
def _get_headers(self):
|
9
|
+
credentials = self.integration.get_credentials()
|
10
|
+
if "headers" in credentials:
|
11
|
+
return credentials["headers"]
|
12
|
+
return {
|
13
|
+
"Authorization": f"Bearer {credentials['access_token']}",
|
14
|
+
}
|
15
|
+
|
16
|
+
def get_subreddit_posts(self, subreddit: str) -> str:
|
17
|
+
"""Get the latest posts from a subreddit
|
18
|
+
|
19
|
+
Args:
|
20
|
+
subreddit: The subreddit to get posts from
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
A list of posts from the subreddit
|
24
|
+
"""
|
25
|
+
|
26
|
+
|
27
|
+
def list_tools(self):
|
28
|
+
return []
|
29
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
from agentr.application import APIApplication
|
2
|
+
from agentr.integration import Integration
|
3
|
+
|
4
|
+
class ResendApp(APIApplication):
|
5
|
+
def __init__(self, integration: Integration) -> None:
|
6
|
+
super().__init__(name="resend", integration=integration)
|
7
|
+
|
8
|
+
def _get_headers(self):
|
9
|
+
credentials = self.integration.get_credentials()
|
10
|
+
if not credentials:
|
11
|
+
raise ValueError("No credentials found")
|
12
|
+
return {
|
13
|
+
"Authorization": f"Bearer {credentials['api_key']}",
|
14
|
+
}
|
15
|
+
|
16
|
+
def send_email(self, to: str, subject: str, content: str) -> str:
|
17
|
+
"""Send an email using the Resend API
|
18
|
+
|
19
|
+
Args:
|
20
|
+
to: The email address to send the email to
|
21
|
+
subject: The subject of the email
|
22
|
+
content: The content of the email
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
A message indicating that the email was sent successfully
|
26
|
+
"""
|
27
|
+
credentials = self.integration.get_credentials()
|
28
|
+
if not credentials:
|
29
|
+
raise ValueError("No credentials found")
|
30
|
+
from_email = credentials.get("from_email", "Manoj <manoj@agentr.dev>")
|
31
|
+
url = "https://api.resend.com/emails"
|
32
|
+
body = {
|
33
|
+
"from": from_email,
|
34
|
+
"to": [to],
|
35
|
+
"subject": subject,
|
36
|
+
"text": content
|
37
|
+
}
|
38
|
+
self._post(url, body)
|
39
|
+
return "Sent Successfully"
|
40
|
+
|
41
|
+
def list_tools(self):
|
42
|
+
return [self.send_email]
|
43
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
from agentr.application import APIApplication
|
2
|
+
from agentr.integration import Integration
|
3
|
+
|
4
|
+
class TavilyApp(APIApplication):
|
5
|
+
def __init__(self, integration: Integration) -> None:
|
6
|
+
name = "tavily"
|
7
|
+
self.base_url = "https://api.tavily.com"
|
8
|
+
super().__init__(name=name, integration=integration)
|
9
|
+
|
10
|
+
def _get_headers(self):
|
11
|
+
credentials = self.integration.get_credentials()
|
12
|
+
if not credentials:
|
13
|
+
raise ValueError("No credentials found")
|
14
|
+
return {
|
15
|
+
"Authorization": f"Bearer {credentials['api_key']}",
|
16
|
+
"Content-Type": "application/json"
|
17
|
+
}
|
18
|
+
|
19
|
+
def search(self, query: str) -> str:
|
20
|
+
"""Search the web using Tavily's search API
|
21
|
+
|
22
|
+
Args:
|
23
|
+
query: The search query
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
str: A summary of search results
|
27
|
+
"""
|
28
|
+
self.validate()
|
29
|
+
url = f"{self.base_url}/search"
|
30
|
+
payload = {
|
31
|
+
"query": query,
|
32
|
+
"topic": "general",
|
33
|
+
"search_depth": "basic",
|
34
|
+
"max_results": 3,
|
35
|
+
"include_answer": True,
|
36
|
+
"include_raw_content": False,
|
37
|
+
"include_images": False,
|
38
|
+
"include_image_descriptions": False,
|
39
|
+
"include_domains": [],
|
40
|
+
"exclude_domains": []
|
41
|
+
}
|
42
|
+
|
43
|
+
response = self._post(url, payload)
|
44
|
+
result = response.json()
|
45
|
+
|
46
|
+
if "answer" in result:
|
47
|
+
return result["answer"]
|
48
|
+
|
49
|
+
# Fallback to combining top results if no direct answer
|
50
|
+
summaries = []
|
51
|
+
for item in result.get("results", [])[:3]:
|
52
|
+
summaries.append(f"• {item['title']}: {item['snippet']}")
|
53
|
+
|
54
|
+
return "\n".join(summaries)
|
55
|
+
|
56
|
+
def list_tools(self):
|
57
|
+
return [self.search]
|
agentr/cli.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import typer
|
2
|
-
import yaml
|
3
2
|
from pathlib import Path
|
4
3
|
|
5
4
|
app = typer.Typer()
|
@@ -28,8 +27,32 @@ def generate(schema_path: Path = typer.Option(..., "--schema", "-s")):
|
|
28
27
|
@app.command()
|
29
28
|
def run():
|
30
29
|
"""Run the MCP server"""
|
31
|
-
from agentr.
|
30
|
+
from agentr.test import mcp
|
32
31
|
mcp.run()
|
33
32
|
|
33
|
+
@app.command()
|
34
|
+
def install(app_name: str):
|
35
|
+
"""Install an app"""
|
36
|
+
import json
|
37
|
+
api_key = typer.prompt("Enter your API key", hide_input=True)
|
38
|
+
if app_name == "claude":
|
39
|
+
typer.echo(f"Installing mcp server for: {app_name}")
|
40
|
+
config_path = '~/Library/Application Support/Claude/claude_desktop_config.json'
|
41
|
+
with open(config_path, 'r') as f:
|
42
|
+
config = json.load(f)
|
43
|
+
config['mcpServers']['agentr-r'] = {
|
44
|
+
"command": "uvx",
|
45
|
+
"args": ["agentr@latest", "run"],
|
46
|
+
"env": {
|
47
|
+
"AGENTR_API_KEY": api_key
|
48
|
+
}
|
49
|
+
}
|
50
|
+
with open(config_path, 'w') as f:
|
51
|
+
json.dump(config, f, indent=4)
|
52
|
+
typer.echo("App installed successfully")
|
53
|
+
else:
|
54
|
+
typer.echo(f"App {app_name} not supported")
|
55
|
+
|
56
|
+
|
34
57
|
if __name__ == "__main__":
|
35
58
|
app()
|
agentr/integration.py
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
import os
|
3
|
+
from agentr.store import Store
|
4
|
+
import httpx
|
5
|
+
|
6
|
+
"""
|
7
|
+
Integration defines how a Application needs to authorize.
|
8
|
+
It is responsible for authenticating application with the service provider.
|
9
|
+
Supported integrations:
|
10
|
+
- AgentR Integration
|
11
|
+
- API Key Integration
|
12
|
+
"""
|
13
|
+
|
14
|
+
class Integration(ABC):
|
15
|
+
def __init__(self, name: str, store: Store = None):
|
16
|
+
self.name = name
|
17
|
+
self.store = store
|
18
|
+
|
19
|
+
@abstractmethod
|
20
|
+
def get_credentials(self):
|
21
|
+
pass
|
22
|
+
|
23
|
+
@abstractmethod
|
24
|
+
def set_credentials(self, credentials: dict):
|
25
|
+
pass
|
26
|
+
|
27
|
+
class ApiKeyIntegration(Integration):
|
28
|
+
def __init__(self, name: str, store: Store = None, **kwargs):
|
29
|
+
super().__init__(name, store, **kwargs)
|
30
|
+
|
31
|
+
def get_credentials(self):
|
32
|
+
credentials = self.store.get(self.name)
|
33
|
+
return credentials
|
34
|
+
|
35
|
+
def set_credentials(self, credentials: dict):
|
36
|
+
self.store.set(self.name, credentials)
|
37
|
+
|
38
|
+
def authorize(self):
|
39
|
+
return {"text": "Please configure the environment variable {self.name}_API_KEY"}
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
class NangoIntegration(Integration):
|
44
|
+
def __init__(self, user_id, integration_id):
|
45
|
+
self.integration_id = integration_id
|
46
|
+
self.user_id = user_id
|
47
|
+
self.nango_secret_key = os.getenv("NANGO_SECRET_KEY")
|
48
|
+
|
49
|
+
def _create_session_token(self):
|
50
|
+
url = "https://api.nango.dev/connect/sessions"
|
51
|
+
body = {
|
52
|
+
"end_user": {
|
53
|
+
"id": self.user_id,
|
54
|
+
},
|
55
|
+
"allowed_integrations": [self.integration_id]
|
56
|
+
}
|
57
|
+
response = httpx.post(url, headers={"Authorization": f"Bearer {self.nango_secret_key}"}, json=body)
|
58
|
+
data = response.json()
|
59
|
+
return data["data"]["token"]
|
60
|
+
|
61
|
+
def get_authorize_url(self):
|
62
|
+
session_token = self._create_session_token()
|
63
|
+
return f"https://api.nango.dev/oauth/connect/{self.integration_id}?connect_session_token={session_token}"
|
64
|
+
|
65
|
+
def get_connection_by_owner(self, user_id):
|
66
|
+
url = f"https://api.nango.dev/connection?endUserId={user_id}"
|
67
|
+
response = httpx.get(url, headers={"Authorization": f"Bearer {self.nango_secret_key}"})
|
68
|
+
if response.status_code == 200:
|
69
|
+
connections = response.json()["connections"]
|
70
|
+
for connection in connections:
|
71
|
+
if connection["provider_config_key"] == self.integration_id:
|
72
|
+
return connection["connection_id"]
|
73
|
+
return None
|
74
|
+
|
75
|
+
class AgentRIntegration(Integration):
|
76
|
+
def __init__(self, name: str, api_key: str = None, **kwargs):
|
77
|
+
super().__init__(name, **kwargs)
|
78
|
+
self.api_key = api_key or os.getenv("AGENTR_API_KEY")
|
79
|
+
if not self.api_key:
|
80
|
+
raise ValueError("api_key is required")
|
81
|
+
self.base_url = "https://api.agentr.dev"
|
82
|
+
self.user_id = "default"
|
83
|
+
|
84
|
+
def get_credentials(self):
|
85
|
+
response = httpx.get(f"{self.base_url}/integrations/{self.name}/credentials", headers={"Authorization": f"Bearer {self.api_key}"})
|
86
|
+
return response.json()
|
87
|
+
|
88
|
+
def authorize(self):
|
89
|
+
response = httpx.post(f"{self.base_url}/integrations/{self.name}/authorize", headers={"Authorization": f"Bearer {self.api_key}"})
|
90
|
+
url = response.json()["url"]
|
91
|
+
return {"url": url, "text": "Please authorize the application by clicking the link {url}"}
|
agentr/server.py
CHANGED
@@ -1,8 +1,23 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
+
from typing import Literal
|
2
3
|
from mcp.server.fastmcp import FastMCP
|
4
|
+
from agentr.integration import AgentRIntegration, ApiKeyIntegration
|
5
|
+
from agentr.store import EnvironmentStore, MemoryStore
|
6
|
+
from pydantic import BaseModel
|
7
|
+
from loguru import logger
|
3
8
|
|
4
|
-
|
5
|
-
|
9
|
+
class StoreConfig(BaseModel):
|
10
|
+
type: Literal["memory", "environment"]
|
11
|
+
|
12
|
+
class IntegrationConfig(BaseModel):
|
13
|
+
name: str
|
14
|
+
type: Literal["api_key", "agentr"]
|
15
|
+
credentials: dict | None = None
|
16
|
+
store: StoreConfig | None = None
|
17
|
+
|
18
|
+
class AppConfig(BaseModel):
|
19
|
+
name: str
|
20
|
+
integration: IntegrationConfig | None = None
|
6
21
|
|
7
22
|
class Server(FastMCP, ABC):
|
8
23
|
"""
|
@@ -10,24 +25,81 @@ class Server(FastMCP, ABC):
|
|
10
25
|
It also acts as a router for the applications, and exposed to the client
|
11
26
|
|
12
27
|
"""
|
13
|
-
def __init__(self, name: str, description: str,
|
14
|
-
self.store = store
|
28
|
+
def __init__(self, name: str, description: str, **kwargs):
|
15
29
|
super().__init__(name, description, **kwargs)
|
16
30
|
|
31
|
+
@abstractmethod
|
32
|
+
def _load_apps(self):
|
33
|
+
pass
|
34
|
+
|
17
35
|
|
18
36
|
class TestServer(Server):
|
19
37
|
"""
|
20
38
|
Test server for development purposes
|
21
39
|
"""
|
22
|
-
def __init__(self, **kwargs):
|
23
|
-
super().__init__(**kwargs)
|
24
|
-
self.apps_list = [
|
25
|
-
self.
|
26
|
-
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
40
|
+
def __init__(self, name: str, description: str, apps_list: list[AppConfig] = [], **kwargs):
|
41
|
+
super().__init__(name, description=description, **kwargs)
|
42
|
+
self.apps_list = [AppConfig.model_validate(app) for app in apps_list]
|
43
|
+
self._load_apps()
|
44
|
+
|
45
|
+
def _get_store(self, store_config: StoreConfig):
|
46
|
+
if store_config.type == "memory":
|
47
|
+
return MemoryStore()
|
48
|
+
elif store_config.type == "environment":
|
49
|
+
return EnvironmentStore()
|
50
|
+
return None
|
51
|
+
|
52
|
+
def _get_integration(self, integration_config: IntegrationConfig):
|
53
|
+
if integration_config.type == "api_key":
|
54
|
+
store = self._get_store(integration_config.store)
|
55
|
+
integration = ApiKeyIntegration(integration_config.name, store=store)
|
56
|
+
if integration_config.credentials:
|
57
|
+
integration.set_credentials(integration_config.credentials)
|
58
|
+
return integration
|
59
|
+
elif integration_config.type == "agentr":
|
60
|
+
integration = AgentRIntegration(integration_config.name, api_key=integration_config.credentials.get("api_key") if integration_config.credentials else None)
|
61
|
+
return integration
|
62
|
+
return None
|
63
|
+
|
64
|
+
def _load_app(self, app_config: AppConfig):
|
65
|
+
name = app_config.name
|
66
|
+
if name == "zenquotes":
|
67
|
+
from agentr.applications.zenquotes.app import ZenQuoteApp
|
68
|
+
return ZenQuoteApp()
|
69
|
+
elif name == "tavily":
|
70
|
+
from agentr.applications.tavily.app import TavilyApp
|
71
|
+
integration = self._get_integration(app_config.integration)
|
72
|
+
return TavilyApp(integration=integration)
|
73
|
+
elif name == "github":
|
74
|
+
from agentr.applications.github.app import GithubApp
|
75
|
+
integration = self._get_integration(app_config.integration)
|
76
|
+
return GithubApp(integration=integration)
|
77
|
+
elif name == "google-calendar":
|
78
|
+
from agentr.applications.google_calendar.app import GoogleCalendarApp
|
79
|
+
integration = self._get_integration(app_config.integration)
|
80
|
+
return GoogleCalendarApp(integration=integration)
|
81
|
+
elif name == "gmail":
|
82
|
+
from agentr.applications.google_mail.app import GmailApp
|
83
|
+
integration = self._get_integration(app_config.integration)
|
84
|
+
return GmailApp(integration=integration)
|
85
|
+
elif name == "resend":
|
86
|
+
from agentr.applications.resend.app import ResendApp
|
87
|
+
integration = self._get_integration(app_config.integration)
|
88
|
+
return ResendApp(integration=integration)
|
89
|
+
elif name == "reddit":
|
90
|
+
from agentr.applications.reddit.app import RedditApp
|
91
|
+
integration = self._get_integration(app_config.integration)
|
92
|
+
return RedditApp(integration=integration)
|
93
|
+
else:
|
94
|
+
return None
|
95
|
+
|
96
|
+
def _load_apps(self):
|
97
|
+
logger.info(f"Loading apps: {self.apps_list}")
|
98
|
+
for app_config in self.apps_list:
|
99
|
+
app = self._load_app(app_config)
|
100
|
+
if app:
|
32
101
|
tools = app.list_tools()
|
33
|
-
|
102
|
+
for tool in tools:
|
103
|
+
self.add_tool(tool)
|
104
|
+
|
105
|
+
|
agentr/store.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
import os
|
2
2
|
from abc import ABC, abstractmethod
|
3
3
|
|
4
4
|
|
@@ -33,3 +33,39 @@ class MemoryStore:
|
|
33
33
|
def delete(self, key: str):
|
34
34
|
del self.data[key]
|
35
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
|
+
self.redis.delete(key)
|
agentr/test.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
from agentr.server import TestServer
|
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 = TestServer(name="Test Server", description="Test Server", apps_list=apps_list)
|
29
|
+
|
30
|
+
async def test():
|
31
|
+
tools = await mcp.list_tools()
|
32
|
+
print(tools)
|
33
|
+
result = await mcp.call_tool("search", {"query": "python"})
|
34
|
+
print(result)
|
35
|
+
|
36
|
+
if __name__ == "__main__":
|
37
|
+
import asyncio
|
38
|
+
asyncio.run(test())
|
agentr/utils/bridge.py
ADDED
File without changes
|
@@ -1,9 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: agentr
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.3
|
4
4
|
Summary: Add your description here
|
5
5
|
Author-email: Manoj Bajaj <manojbajaj95@gmail.com>
|
6
6
|
Requires-Python: >=3.11
|
7
|
+
Requires-Dist: loguru>=0.7.3
|
7
8
|
Requires-Dist: mcp>=1.5.0
|
8
9
|
Requires-Dist: pyyaml>=6.0.2
|
9
10
|
Requires-Dist: typer>=0.15.2
|
@@ -0,0 +1,21 @@
|
|
1
|
+
agentr/__init__.py,sha256=LOWhgQayrQV7f5ro4rlBJ_6WevhbWIbjAOHnqP7b_-4,30
|
2
|
+
agentr/application.py,sha256=2K7jL5QUDwlD_zRS7adN_R3_yPt-EJBYpAmr1abDew8,1551
|
3
|
+
agentr/cli.py,sha256=KcmYrnzoVzkh4SWB6UDjxGJboQbMcphj1juNbfqrIAE,1676
|
4
|
+
agentr/integration.py,sha256=jRwpp_bUXJWl8OkZsnRDqoScoRSxCMDBMrDMfRBhgN4,3298
|
5
|
+
agentr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
agentr/server.py,sha256=24cD8e3ks8ckftPMphI7K3SfGs8LZZyXpfiKU2daY-Q,4220
|
7
|
+
agentr/store.py,sha256=fB3uAaobnWf2ILcDBmg3ToDaqAIPYlLtmHBdpmkcGcI,1585
|
8
|
+
agentr/test.py,sha256=A0t1fdI9N89eF9xm903fKtvTzibnrHeMKkPwZnSBEc4,852
|
9
|
+
agentr/applications/github/app.py,sha256=_PW_xzkCzNB468lEOvFzdQk0m7Ol6y_PQ5OqCkVkDVE,1811
|
10
|
+
agentr/applications/google_calendar/app.py,sha256=WW-4MTqPl-NU2w5DKKyEvfTho4d6tS7t52jDRyq-tv0,2841
|
11
|
+
agentr/applications/google_mail/app.py,sha256=SqzWyYW0xF_fdZC8kf_8FZfQK6OOddI-EVpdgYAengg,2334
|
12
|
+
agentr/applications/reddit/app.py,sha256=iNkSRVGScj7Mae3e8N-Mkov_hvZrvH5NynbpO8mKToQ,842
|
13
|
+
agentr/applications/resend/app.py,sha256=ihRzP65bwoNIq3EzBqIghxgLZRxvy-LbHy-rER20ODo,1428
|
14
|
+
agentr/applications/tavily/app.py,sha256=D4FOhm2yxNbuTVHTo3T8ZsuE5AgwK874YutfYx2Njcw,1805
|
15
|
+
agentr/applications/zenquotes/app.py,sha256=4LjYeWdERI8ZMzkajOsDgy9IRTA9plUKem-rW4M03sA,607
|
16
|
+
agentr/utils/bridge.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
+
agentr/utils/openapi.py,sha256=yjiPYs-_JsYQ7T3aPh7oimHUCf8pMblIR8IPCaAeNCg,6279
|
18
|
+
agentr-0.1.3.dist-info/METADATA,sha256=YWWFhc2Z9sG48D4zr7lIDJ39JO_pK13RZ5mQO1jiWEY,273
|
19
|
+
agentr-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
20
|
+
agentr-0.1.3.dist-info/entry_points.txt,sha256=13fGFeVhgF6_8T-VFiIkNxYO7gDQaUwwTcUNWdvaLQg,42
|
21
|
+
agentr-0.1.3.dist-info/RECORD,,
|
agentr/mcp.py
DELETED
agentr-0.1.2.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
agentr/__init__.py,sha256=LOWhgQayrQV7f5ro4rlBJ_6WevhbWIbjAOHnqP7b_-4,30
|
2
|
-
agentr/application.py,sha256=rQ2vomCfZKigdXQLjwCvlTC9_UcnKyux_x9evmQqnjA,1220
|
3
|
-
agentr/cli.py,sha256=F16qdCnfgg9UJt7D7VBjZ2-nKvwb7dJ3zezbopNH2YQ,877
|
4
|
-
agentr/mcp.py,sha256=kGraScdBgDkCvrzbyg07TTUiIVMXIOF_um7v4g4-4Cs,216
|
5
|
-
agentr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
agentr/server.py,sha256=fweppEwF7L9cRow1zDPHtLYQE-qUdTlICeYUbxHGb2w,990
|
7
|
-
agentr/store.py,sha256=u18GL9nDQgkkGm8HFIMz6BiHmy04EDi3vugotbH87ss,689
|
8
|
-
agentr/applications/zenquotes/app.py,sha256=4LjYeWdERI8ZMzkajOsDgy9IRTA9plUKem-rW4M03sA,607
|
9
|
-
agentr/utils/openapi.py,sha256=yjiPYs-_JsYQ7T3aPh7oimHUCf8pMblIR8IPCaAeNCg,6279
|
10
|
-
agentr-0.1.2.dist-info/METADATA,sha256=n6pFbzT-1SjybZX5H81kEjRcpOpfo-HvOkxUMY48PbU,244
|
11
|
-
agentr-0.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
12
|
-
agentr-0.1.2.dist-info/entry_points.txt,sha256=13fGFeVhgF6_8T-VFiIkNxYO7gDQaUwwTcUNWdvaLQg,42
|
13
|
-
agentr-0.1.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|