agentr 0.1.8__py3-none-any.whl → 0.1.9__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 CHANGED
@@ -1,5 +1,4 @@
1
- from abc import ABC, abstractmethod
2
-
1
+ from abc import ABC
3
2
  from loguru import logger
4
3
  from agentr.exceptions import NotAuthorizedError
5
4
  from agentr.integration import Integration
@@ -11,11 +10,25 @@ class Application(ABC):
11
10
  """
12
11
  def __init__(self, name: str, **kwargs):
13
12
  self.name = name
13
+ self.tools = []
14
14
 
15
- @abstractmethod
15
+ def tool(self):
16
+ """Decorator to register a method as a tool.
17
+
18
+ This decorator adds the decorated method to the application's tools list
19
+ and returns the original method unchanged.
20
+
21
+ Returns:
22
+ The decorated method
23
+ """
24
+ def decorator(func):
25
+ if func not in self.tools:
26
+ self.tools.append(func)
27
+ return func
28
+ return decorator
29
+
16
30
  def list_tools(self):
17
- pass
18
-
31
+ return self.tools
19
32
 
20
33
  class APIApplication(Application):
21
34
  """
@@ -86,9 +99,18 @@ class APIApplication(Application):
86
99
  logger.error(f"Error posting {url}: {e}")
87
100
  raise e
88
101
 
102
+ def _patch(self, url, data):
103
+ try:
104
+ headers = self._get_headers()
105
+ response = httpx.patch(url, headers=headers, json=data)
106
+ response.raise_for_status()
107
+ return response
108
+ except NotAuthorizedError as e:
109
+ logger.warning(f"Authorization needed: {e.message}")
110
+ raise e
111
+ except Exception as e:
112
+ logger.error(f"Error patching {url}: {e}")
113
+ raise e
114
+
89
115
  def validate(self):
90
- pass
91
-
92
- @abstractmethod
93
- def list_tools(self):
94
116
  pass
@@ -1,6 +1,7 @@
1
1
  from agentr.integration import Integration
2
2
  from agentr.application import APIApplication
3
3
  from loguru import logger
4
+ from typing import List, Dict, Any
4
5
 
5
6
  class GithubApp(APIApplication):
6
7
  def __init__(self, integration: Integration) -> None:
@@ -125,40 +126,38 @@ class GithubApp(APIApplication):
125
126
 
126
127
  return result
127
128
 
128
- def list_issues(self, repo_full_name: str, per_page: int = 30, page: int = 1) -> str:
129
+ def list_issues(self, repo_full_name: str, state: str = "open", assignee: str = None,
130
+ labels: str = None, per_page: int = 30, page: int = 1) -> List[Dict[str, Any]]:
129
131
  """List issues for a GitHub repository
130
132
 
131
133
  Args:
132
134
  repo_full_name: The full name of the repository (e.g. 'owner/repo')
135
+ state: State of issues to return (open, closed, all). Default: open
136
+ assignee: Filter by assignee. Use 'none' for no assignee, '*' for any assignee
137
+ labels: Comma-separated list of label names (e.g. "bug,ui,@high")
133
138
  per_page: The number of results per page (max 100)
134
139
  page: The page number of the results to fetch
135
140
 
136
141
  Returns:
137
- A formatted list of issues
142
+ The complete GitHub API response
138
143
  """
139
144
  repo_full_name = repo_full_name.strip()
140
- url = f"{self.base_api_url}/{repo_full_name}/issues/events"
145
+ url = f"{self.base_api_url}/{repo_full_name}/issues"
146
+
141
147
  params = {
148
+ "state": state,
142
149
  "per_page": per_page,
143
150
  "page": page
144
151
  }
145
- response = self._get(url, params=params)
146
- response.raise_for_status()
147
152
 
148
- issues = response.json()
149
- if not issues:
150
- return f"No issues found for repository {repo_full_name}"
151
-
152
- result = f"Issues for {repo_full_name} (Page {page}):\n\n"
153
- for issue in issues:
154
- issue_title = issue.get("issue", {}).get("title", "No Title")
155
- issue_number = issue.get("issue", {}).get("number", "Unknown")
156
- issue_state = issue.get("issue", {}).get("state", "Unknown")
157
- issue_user = issue.get("issue", {}).get("user", {}).get("login", "Unknown")
158
-
159
- result += f"- Issue #{issue_number}: {issue_title} (by {issue_user}, Status: {issue_state})\n"
153
+ if assignee:
154
+ params["assignee"] = assignee
155
+ if labels:
156
+ params["labels"] = labels
160
157
 
161
- return result
158
+ response = self._get(url, params=params)
159
+ response.raise_for_status()
160
+ return response.json()
162
161
 
163
162
  def get_pull_request(self, repo_full_name: str, pull_number: int) -> str:
164
163
  """Get a specific pull request for a GitHub repository
@@ -191,45 +190,47 @@ class GithubApp(APIApplication):
191
190
 
192
191
  return result
193
192
 
194
- def create_pull_request(self, repo_full_name: str, title: str, head: str, base: str, body: str = "",
195
- maintainer_can_modify: bool = True, draft: bool = False) -> str:
193
+ def create_pull_request(self, repo_full_name: str, head: str, base: str, title: str = None,
194
+ body: str = None, issue: int = None, maintainer_can_modify: bool = True,
195
+ draft: bool = False) -> Dict[str, Any]:
196
196
  """Create a new pull request for a GitHub repository
197
197
 
198
198
  Args:
199
199
  repo_full_name: The full name of the repository (e.g. 'owner/repo')
200
- title: The title of the new pull request
201
200
  head: The name of the branch where your changes are implemented
202
201
  base: The name of the branch you want the changes pulled into
202
+ title: The title of the new pull request (required if issue is not specified)
203
203
  body: The contents of the pull request
204
+ issue: An issue number to convert to a pull request. If specified, the issue's
205
+ title, body, and comments will be used for the pull request
204
206
  maintainer_can_modify: Indicates whether maintainers can modify the pull request
205
207
  draft: Indicates whether the pull request is a draft
206
208
 
207
209
  Returns:
208
- A confirmation message with the new pull request details
210
+ The complete GitHub API response
209
211
  """
210
212
  repo_full_name = repo_full_name.strip()
211
213
  url = f"{self.base_api_url}/{repo_full_name}/pulls"
212
214
 
213
215
  pull_request_data = {
214
- "title": title,
215
216
  "head": head,
216
217
  "base": base,
217
- "body": body,
218
218
  "maintainer_can_modify": maintainer_can_modify,
219
219
  "draft": draft
220
220
  }
221
221
 
222
+ if issue is not None:
223
+ pull_request_data["issue"] = issue
224
+ else:
225
+ if title is None:
226
+ raise ValueError("Either 'title' or 'issue' must be specified")
227
+ pull_request_data["title"] = title
228
+ if body is not None:
229
+ pull_request_data["body"] = body
230
+
222
231
  response = self._post(url, pull_request_data)
223
232
  response.raise_for_status()
224
-
225
- pr = response.json()
226
- pr_number = pr.get("number", "Unknown")
227
- pr_url = pr.get("html_url", "")
228
-
229
- return f"Successfully created pull request #{pr_number}:\n" \
230
- f"Title: {title}\n" \
231
- f"From: {head} → To: {base}\n" \
232
- f"URL: {pr_url}"
233
+ return response.json()
233
234
 
234
235
  def create_issue(self, repo_full_name: str, title: str, body: str = "", labels = None) -> str:
235
236
  """Create a new issue in a GitHub repository
@@ -313,7 +314,43 @@ class GithubApp(APIApplication):
313
314
 
314
315
  return result
315
316
 
317
+ def update_issue(self, repo_full_name: str, issue_number: int, title: str = None,
318
+ body: str = None, assignee: str = None, state: str = None,
319
+ state_reason: str = None) -> Dict[str, Any]:
320
+ """Update an issue in a GitHub repository
321
+
322
+ Args:
323
+ repo_full_name: The full name of the repository (e.g. 'owner/repo')
324
+ issue_number: The number that identifies the issue
325
+ title: The title of the issue
326
+ body: The contents of the issue
327
+ assignee: Username to assign to this issue
328
+ state: State of the issue (open or closed)
329
+ state_reason: Reason for state change (completed, not_planned, reopened, null)
330
+
331
+ Returns:
332
+ The complete GitHub API response
333
+ """
334
+ url = f"{self.base_api_url}/{repo_full_name}/issues/{issue_number}"
335
+
336
+ update_data = {}
337
+ if title is not None:
338
+ update_data["title"] = title
339
+ if body is not None:
340
+ update_data["body"] = body
341
+ if assignee is not None:
342
+ update_data["assignee"] = assignee
343
+ if state is not None:
344
+ update_data["state"] = state
345
+ if state_reason is not None:
346
+ update_data["state_reason"] = state_reason
347
+
348
+ response = self._patch(url, update_data)
349
+ response.raise_for_status()
350
+ return response.json()
351
+
316
352
  def list_tools(self):
317
353
  return [self.star_repository, self.list_commits, self.list_branches,
318
354
  self.list_pull_requests, self.list_issues, self.get_pull_request,
319
- self.create_pull_request, self.create_issue, self.list_repo_activities]
355
+ self.create_pull_request, self.create_issue, self.update_issue,
356
+ self.list_repo_activities]
@@ -1,11 +1,31 @@
1
+ import httpx
1
2
  from agentr.application import APIApplication
2
3
  from agentr.integration import Integration
4
+ from agentr.exceptions import NotAuthorizedError
3
5
  from loguru import logger
4
6
 
5
7
  class RedditApp(APIApplication):
6
8
  def __init__(self, integration: Integration) -> None:
7
9
  super().__init__(name="reddit", integration=integration)
8
- self.base_api_url = "https://oauth.reddit.com"
10
+ self.base_api_url = "https://oauth.reddit.com"
11
+
12
+ def _post(self, url, data):
13
+ try:
14
+ headers = self._get_headers()
15
+ response = httpx.post(url, headers=headers, data=data)
16
+ response.raise_for_status()
17
+ return response
18
+ except NotAuthorizedError as e:
19
+ logger.warning(f"Authorization needed: {e.message}")
20
+ raise e
21
+ except httpx.HTTPStatusError as e:
22
+ if e.response.status_code == 429:
23
+ return e.response.text or "Rate limit exceeded. Please try again later."
24
+ else:
25
+ raise e
26
+ except Exception as e:
27
+ logger.error(f"Error posting {url}: {e}")
28
+ raise e
9
29
 
10
30
  def _get_headers(self):
11
31
  if not self.integration:
agentr/cli.py CHANGED
@@ -59,6 +59,8 @@ def install(app_name: str):
59
59
 
60
60
  with open(config_path, 'r') as f:
61
61
  config = json.load(f)
62
+ if 'mcpServers' not in config:
63
+ config['mcpServers'] = {}
62
64
  config['mcpServers']['agentr'] = {
63
65
  "command": "uvx",
64
66
  "args": ["agentr@latest", "run"],
@@ -66,6 +68,35 @@ def install(app_name: str):
66
68
  "AGENTR_API_KEY": api_key
67
69
  }
68
70
  }
71
+ with open(config_path, 'w') as f:
72
+ json.dump(config, f, indent=4)
73
+ typer.echo("App installed successfully")
74
+ elif app_name == "cursor":
75
+ typer.echo(f"Installing mcp server for: {app_name}")
76
+
77
+ # Set up Cursor config path
78
+ config_path = Path.home() / ".cursor/mcp.json"
79
+
80
+ # Create config directory if it doesn't exist
81
+ config_path.parent.mkdir(parents=True, exist_ok=True)
82
+
83
+ # Create or load existing config
84
+ if config_path.exists():
85
+ with open(config_path, 'r') as f:
86
+ config = json.load(f)
87
+ else:
88
+ config = {}
89
+
90
+ if 'mcpServers' not in config:
91
+ config['mcpServers'] = {}
92
+ config['mcpServers']['agentr'] = {
93
+ "command": "uvx",
94
+ "args": ["agentr@latest", "run"],
95
+ "env": {
96
+ "AGENTR_API_KEY": api_key
97
+ }
98
+ }
99
+
69
100
  with open(config_path, 'w') as f:
70
101
  json.dump(config, f, indent=4)
71
102
  typer.echo("App installed successfully")
agentr/server.py CHANGED
@@ -80,7 +80,9 @@ class LocalServer(Server):
80
80
  if app:
81
81
  tools = app.list_tools()
82
82
  for tool in tools:
83
- self.add_tool(tool)
83
+ name = app.name + "_" + tool.__name__
84
+ description = tool.__doc__
85
+ self.add_tool(tool, name=name, description=description)
84
86
 
85
87
 
86
88
 
@@ -114,7 +116,9 @@ class AgentRServer(Server):
114
116
  "X-API-KEY": self.api_key
115
117
  }
116
118
  )
119
+ response.raise_for_status()
117
120
  apps = response.json()
121
+
118
122
  logger.info(f"Apps: {apps}")
119
123
  return [AppConfig.model_validate(app) for app in apps]
120
124
 
@@ -125,4 +129,6 @@ class AgentRServer(Server):
125
129
  if app:
126
130
  tools = app.list_tools()
127
131
  for tool in tools:
128
- self.add_tool(tool)
132
+ name = app.name + "_" + tool.__name__
133
+ description = tool.__doc__
134
+ self.add_tool(tool, name=name, description=description)
agentr/test.py CHANGED
@@ -7,8 +7,8 @@ async def test():
7
7
  tools = await mcp.list_tools()
8
8
  from pprint import pprint
9
9
  pprint(tools)
10
- result = await mcp.call_tool("get_today_events", {})
11
- print(result)
10
+ # result = await mcp.call_tool("get_today_events", {})
11
+ # print(result)
12
12
 
13
13
  if __name__ == "__main__":
14
14
  import asyncio
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentr
3
- Version: 0.1.8
4
- Summary: A python framework to build MCP servers
5
- Author-email: Manoj Bajaj <manojbajaj95@gmail.com>
3
+ Version: 0.1.9
4
+ Summary: An MCP middleware to connect to 400+ apps
5
+ Author-email: Manoj Bajaj <manoj@agentr.dev>
6
6
  License-File: LICENSE
7
7
  Requires-Python: >=3.11
8
8
  Requires-Dist: loguru>=0.7.3
@@ -1,18 +1,18 @@
1
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
2
+ agentr/application.py,sha256=1y3591Smesk8SOha7fa6aWFey7KNCqPWOP2uutwrk0w,3729
3
+ agentr/cli.py,sha256=SaJO502Eb-egiu6rXpxlmvDNkXeyYEGNirwSliOsbVI,3906
4
4
  agentr/config.py,sha256=YTygfFJPUuL-epRuILvt5tc5ACzByWIFFNhpFwHlDCE,387
5
5
  agentr/exceptions.py,sha256=hHlyXUZBjG4DfUurvqd0ZiruHC67gbpT6EHKxifwUhg,271
6
6
  agentr/integration.py,sha256=hr2Edd2H-JG_CNe6xgYvfjy2BgKgFrBN2Grn8n4vfGs,5433
7
7
  agentr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- agentr/server.py,sha256=PNtg8BkVST4fOgNI4tWoBX2ue1EfGdgEf3__MjOVYmc,4907
8
+ agentr/server.py,sha256=NgqUkTf0h8ot4DEWRQ0nH9winuZLF7E8F-3MPyKa1cs,5233
9
9
  agentr/store.py,sha256=fB3uAaobnWf2ILcDBmg3ToDaqAIPYlLtmHBdpmkcGcI,1585
10
- agentr/test.py,sha256=crKkiygUhXBaJPfcaS35bLbTd_-hc9jdaONQkT79i3M,354
10
+ agentr/test.py,sha256=yhTzUUvdqUXug7Og-cOAP2CQo9NPQ2HezLhIaSBcc7s,358
11
11
  agentr/applications/__init__.py,sha256=huqhhfMkSMjcc3eqVAprW03Drr98OHbH2Rh0GwGQHjs,942
12
- agentr/applications/github/app.py,sha256=qN2O3F9bfRLTxklTPzs67scjZ2KxtrqS6nOGh3ZDmR8,12407
12
+ agentr/applications/github/app.py,sha256=ysW1gzP_PjR9sE5TciECziEkYZq2h-I0R6soRhngF-8,14014
13
13
  agentr/applications/google_calendar/app.py,sha256=BlOBmdoEqifuOrG194BgjAvUkBhwvVeGM1HBYKv0bDU,19953
14
14
  agentr/applications/google_mail/app.py,sha256=GWmJwgdpBKyNufsrHp2PkYzXNzyCb7XOHxZPYw6XaBA,23710
15
- agentr/applications/reddit/app.py,sha256=W4rGSDE3ooZiMPWUsgM2URCjaI7lYC56r5pfdlrP4Ss,12548
15
+ agentr/applications/reddit/app.py,sha256=LNkT5lV18lylvghABh_1FFSBcXGo4GwGeH6AiHojXaA,13282
16
16
  agentr/applications/resend/app.py,sha256=ihRzP65bwoNIq3EzBqIghxgLZRxvy-LbHy-rER20ODo,1428
17
17
  agentr/applications/tavily/app.py,sha256=D4FOhm2yxNbuTVHTo3T8ZsuE5AgwK874YutfYx2Njcw,1805
18
18
  agentr/applications/zenquotes/app.py,sha256=4LjYeWdERI8ZMzkajOsDgy9IRTA9plUKem-rW4M03sA,607
@@ -23,8 +23,8 @@ agentr/integrations/api_key.py,sha256=cbX_8Je7nX3alIK1g0EqQcyNsNylQRQExGzsUARfyt
23
23
  agentr/integrations/base.py,sha256=jZPWwzEHPJxS7onfscp8wwXt0bT66SC9tr75XQaueR4,1536
24
24
  agentr/utils/bridge.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
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,,
26
+ agentr-0.1.9.dist-info/METADATA,sha256=zk7QncR1lOritziohTCOJrPhZFm62ubrAztkdmeNxO8,4384
27
+ agentr-0.1.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
+ agentr-0.1.9.dist-info/entry_points.txt,sha256=13fGFeVhgF6_8T-VFiIkNxYO7gDQaUwwTcUNWdvaLQg,42
29
+ agentr-0.1.9.dist-info/licenses/LICENSE,sha256=CPslwL9mT3MH-lEljRJQHKe646096G-szURVmOD18Lc,1063
30
+ agentr-0.1.9.dist-info/RECORD,,
File without changes