kite-agent 0.1.0__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.
Files changed (61) hide show
  1. kite/__init__.py +46 -0
  2. kite/ab_testing.py +384 -0
  3. kite/agent.py +556 -0
  4. kite/agents/__init__.py +3 -0
  5. kite/agents/plan_execute.py +191 -0
  6. kite/agents/react_agent.py +509 -0
  7. kite/agents/reflective_agent.py +90 -0
  8. kite/agents/rewoo.py +119 -0
  9. kite/agents/tot.py +151 -0
  10. kite/conversation.py +125 -0
  11. kite/core.py +974 -0
  12. kite/data_loaders.py +111 -0
  13. kite/embedding_providers.py +372 -0
  14. kite/llm_providers.py +1278 -0
  15. kite/memory/__init__.py +6 -0
  16. kite/memory/advanced_rag.py +333 -0
  17. kite/memory/graph_rag.py +719 -0
  18. kite/memory/session_memory.py +423 -0
  19. kite/memory/vector_memory.py +579 -0
  20. kite/monitoring.py +611 -0
  21. kite/observers.py +107 -0
  22. kite/optimization/__init__.py +9 -0
  23. kite/optimization/resource_router.py +80 -0
  24. kite/persistence.py +42 -0
  25. kite/pipeline/__init__.py +5 -0
  26. kite/pipeline/deterministic_pipeline.py +323 -0
  27. kite/pipeline/reactive_pipeline.py +171 -0
  28. kite/pipeline_manager.py +15 -0
  29. kite/routing/__init__.py +6 -0
  30. kite/routing/aggregator_router.py +325 -0
  31. kite/routing/llm_router.py +149 -0
  32. kite/routing/semantic_router.py +228 -0
  33. kite/safety/__init__.py +6 -0
  34. kite/safety/circuit_breaker.py +360 -0
  35. kite/safety/guardrails.py +82 -0
  36. kite/safety/idempotency_manager.py +304 -0
  37. kite/safety/kill_switch.py +75 -0
  38. kite/tool.py +183 -0
  39. kite/tool_registry.py +87 -0
  40. kite/tools/__init__.py +21 -0
  41. kite/tools/code_execution.py +53 -0
  42. kite/tools/contrib/__init__.py +19 -0
  43. kite/tools/contrib/calculator.py +26 -0
  44. kite/tools/contrib/datetime_utils.py +20 -0
  45. kite/tools/contrib/linkedin.py +428 -0
  46. kite/tools/contrib/web_search.py +30 -0
  47. kite/tools/mcp/__init__.py +31 -0
  48. kite/tools/mcp/database_mcp.py +267 -0
  49. kite/tools/mcp/gdrive_mcp_server.py +503 -0
  50. kite/tools/mcp/gmail_mcp_server.py +601 -0
  51. kite/tools/mcp/postgres_mcp_server.py +490 -0
  52. kite/tools/mcp/slack_mcp_server.py +538 -0
  53. kite/tools/mcp/stripe_mcp_server.py +219 -0
  54. kite/tools/search.py +90 -0
  55. kite/tools/system_tools.py +54 -0
  56. kite/tools_manager.py +27 -0
  57. kite_agent-0.1.0.dist-info/METADATA +621 -0
  58. kite_agent-0.1.0.dist-info/RECORD +61 -0
  59. kite_agent-0.1.0.dist-info/WHEEL +5 -0
  60. kite_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
  61. kite_agent-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,601 @@
1
+ """
2
+ Gmail MCP Server Implementation
3
+ Based on Chapter 4: MCP (Model Context Protocol)
4
+
5
+ Allows AI agents to interact with Gmail.
6
+
7
+ Tools provided:
8
+ - search_emails: Search emails by query
9
+ - read_email: Read specific email
10
+ - send_email: Send new email
11
+ - list_labels: Get Gmail labels
12
+
13
+ Run: python gmail_mcp_server.py
14
+ """
15
+
16
+ import os
17
+ import json
18
+ from typing import Dict, List, Optional, Any
19
+ from dataclasses import dataclass, field
20
+ from datetime import datetime, timedelta
21
+ from dotenv import load_dotenv
22
+
23
+ load_dotenv()
24
+
25
+
26
+ # ============================================================================
27
+ # CONFIGURATION
28
+ # ============================================================================
29
+
30
+ @dataclass
31
+ class GmailConfig:
32
+ """Configuration for Gmail MCP server."""
33
+ credentials_path: str = ""
34
+
35
+ # Safety limits
36
+ max_emails_per_search: int = 50
37
+ max_email_size_kb: int = 1024
38
+ rate_limit_per_minute: int = 60
39
+
40
+ # Allowed operations
41
+ enable_read: bool = True
42
+ enable_send: bool = False # Disabled by default for safety
43
+ enable_search: bool = True
44
+
45
+ # Content filters
46
+ max_attachment_size_mb: int = 10
47
+ blocked_domains: List[str] = field(default_factory=list)
48
+
49
+
50
+ # ============================================================================
51
+ # GMAIL MCP SERVER
52
+ # ============================================================================
53
+
54
+ class GmailMCPServer:
55
+ """
56
+ MCP Server for Gmail integration.
57
+
58
+ Provides tools for AI agents to search and manage emails.
59
+
60
+ Requires: google-api-python-client, google-auth-httplib2, google-auth-oauthlib
61
+ Install: pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib
62
+
63
+ Setup:
64
+ 1. Enable Gmail API in Google Cloud Console
65
+ 2. Download credentials.json
66
+ 3. Run authentication flow to get token
67
+
68
+ Example:
69
+ from googleapiclient.discovery import build
70
+ from google.oauth2.credentials import Credentials
71
+
72
+ config = GmailConfig(credentials_path='~/.gmail_credentials.json')
73
+ server = GmailMCPServer(config)
74
+
75
+ # Search emails
76
+ results = server.search_emails("Project Zeus")
77
+
78
+ # Read email
79
+ email = server.read_email("email001")
80
+ """
81
+
82
+ def __init__(self, config: GmailConfig = None, credentials_path: str = "", **kwargs):
83
+ self.config = config or GmailConfig()
84
+ if credentials_path:
85
+ self.config.credentials_path = credentials_path
86
+
87
+ # Initialize Gmail client (requires Google API client)
88
+ try:
89
+ from googleapiclient.discovery import build
90
+ from google.oauth2.credentials import Credentials
91
+ from google.auth.transport.requests import Request
92
+ from google_auth_oauthlib.flow import InstalledAppFlow
93
+ import os
94
+ import pickle
95
+
96
+ SCOPES = ['https://www.googleapis.com/auth/gmail.readonly',
97
+ 'https://www.googleapis.com/auth/gmail.send']
98
+
99
+ creds = None
100
+ token_path = os.path.expanduser('~/.gmail_token.pickle')
101
+
102
+ # Load existing token
103
+ if os.path.exists(token_path):
104
+ with open(token_path, 'rb') as token:
105
+ creds = pickle.load(token)
106
+
107
+ # Refresh or get new credentials
108
+ if not creds or not creds.valid:
109
+ if creds and creds.expired and creds.refresh_token:
110
+ creds.refresh(Request())
111
+ elif self.config.credentials_path and os.path.exists(self.config.credentials_path):
112
+ flow = InstalledAppFlow.from_client_secrets_file(
113
+ self.config.credentials_path, SCOPES)
114
+ creds = flow.run_local_server(port=0)
115
+ else:
116
+ raise Exception("Gmail credentials not found. Please provide credentials_path")
117
+
118
+ # Save token
119
+ with open(token_path, 'wb') as token:
120
+ pickle.dump(creds, token)
121
+
122
+ self.gmail = build('gmail', 'v1', credentials=creds)
123
+
124
+ except ImportError:
125
+ raise ImportError(
126
+ "Google API client is required for GmailMCPServer. "
127
+ "Install with: pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib"
128
+ )
129
+ except Exception as e:
130
+ raise Exception(f"Failed to initialize Gmail client: {e}")
131
+
132
+
133
+ # Rate limiting
134
+ self.request_count = 0
135
+ self.window_start = datetime.now()
136
+
137
+ print(f"[OK] Gmail MCP Server initialized")
138
+ print(f" Rate limit: {self.config.rate_limit_per_minute}/min")
139
+ print(f" Send enabled: {self.config.enable_send}")
140
+
141
+ def _check_rate_limit(self) -> bool:
142
+ """Check rate limit."""
143
+ now = datetime.now()
144
+
145
+ if (now - self.window_start).seconds >= 60:
146
+ self.request_count = 0
147
+ self.window_start = now
148
+
149
+ if self.request_count >= self.config.rate_limit_per_minute:
150
+ return False
151
+
152
+ self.request_count += 1
153
+ return True
154
+
155
+ def _extract_header(self, headers: List[Dict], name: str) -> str:
156
+ """Extract header value."""
157
+ for header in headers:
158
+ if header["name"].lower() == name.lower():
159
+ return header["value"]
160
+ return ""
161
+
162
+ def search_emails(
163
+ self,
164
+ query: str,
165
+ max_results: int = 10,
166
+ label: Optional[str] = None
167
+ ) -> Dict[str, Any]:
168
+ """
169
+ Search emails by query.
170
+
171
+ Args:
172
+ query: Search query (keywords, sender, subject, etc.)
173
+ max_results: Maximum results to return
174
+ label: Filter by label (INBOX, SENT, etc.)
175
+
176
+ Returns:
177
+ Search results
178
+ """
179
+ print(f"\n Searching emails: {query}")
180
+
181
+ if not self.config.enable_search:
182
+ return {
183
+ "success": False,
184
+ "error": "Search is disabled"
185
+ }
186
+
187
+ if not self._check_rate_limit():
188
+ return {
189
+ "success": False,
190
+ "error": "Rate limit exceeded"
191
+ }
192
+
193
+ try:
194
+ # Search
195
+ label_ids = [label] if label else None
196
+ results = self.gmail.users_messages_list(
197
+ userId="me",
198
+ q=query,
199
+ maxResults=min(max_results, self.config.max_emails_per_search),
200
+ labelIds=label_ids
201
+ )
202
+
203
+ # Get email details
204
+ emails = []
205
+ for msg in results.get("messages", []):
206
+ email = self.gmail.users_messages_get("me", msg["id"])
207
+
208
+ headers = email["payload"]["headers"]
209
+ emails.append({
210
+ "id": email["id"],
211
+ "from": self._extract_header(headers, "From"),
212
+ "to": self._extract_header(headers, "To"),
213
+ "subject": self._extract_header(headers, "Subject"),
214
+ "date": self._extract_header(headers, "Date"),
215
+ "snippet": email["snippet"],
216
+ "labels": email["labelIds"]
217
+ })
218
+
219
+ print(f" [OK] Found {len(emails)} emails")
220
+
221
+ return {
222
+ "success": True,
223
+ "query": query,
224
+ "emails": emails,
225
+ "count": len(emails)
226
+ }
227
+
228
+ except Exception as e:
229
+ print(f" Error: {e}")
230
+ return {
231
+ "success": False,
232
+ "error": str(e)
233
+ }
234
+
235
+ def read_email(self, email_id: str) -> Dict[str, Any]:
236
+ """
237
+ Read specific email.
238
+
239
+ Args:
240
+ email_id: Gmail message ID
241
+
242
+ Returns:
243
+ Email content
244
+ """
245
+ print(f"\n Reading email: {email_id}")
246
+
247
+ if not self.config.enable_read:
248
+ return {
249
+ "success": False,
250
+ "error": "Read is disabled"
251
+ }
252
+
253
+ if not self._check_rate_limit():
254
+ return {
255
+ "success": False,
256
+ "error": "Rate limit exceeded"
257
+ }
258
+
259
+ try:
260
+ email = self.gmail.users_messages_get("me", email_id)
261
+
262
+ headers = email["payload"]["headers"]
263
+ body = email["payload"]["body"]["data"]
264
+
265
+ print(f" [OK] Read email from: {self._extract_header(headers, 'From')}")
266
+
267
+ return {
268
+ "success": True,
269
+ "email_id": email_id,
270
+ "from": self._extract_header(headers, "From"),
271
+ "to": self._extract_header(headers, "To"),
272
+ "subject": self._extract_header(headers, "Subject"),
273
+ "date": self._extract_header(headers, "Date"),
274
+ "body": body,
275
+ "labels": email["labelIds"]
276
+ }
277
+
278
+ except Exception as e:
279
+ print(f" Error: {e}")
280
+ return {
281
+ "success": False,
282
+ "error": str(e)
283
+ }
284
+
285
+ def send_email(
286
+ self,
287
+ to: str,
288
+ subject: str,
289
+ body: str,
290
+ cc: Optional[str] = None
291
+ ) -> Dict[str, Any]:
292
+ """
293
+ Send email.
294
+
295
+ Args:
296
+ to: Recipient email
297
+ subject: Email subject
298
+ body: Email body
299
+ cc: CC recipients (optional)
300
+
301
+ Returns:
302
+ Send result
303
+ """
304
+ print(f"\n Sending email to: {to}")
305
+
306
+ if not self.config.enable_send:
307
+ return {
308
+ "success": False,
309
+ "error": "Send is disabled (safety)"
310
+ }
311
+
312
+ if not self._check_rate_limit():
313
+ return {
314
+ "success": False,
315
+ "error": "Rate limit exceeded"
316
+ }
317
+
318
+ # Check blocked domains
319
+ domain = to.split("@")[-1]
320
+ if domain in self.config.blocked_domains:
321
+ return {
322
+ "success": False,
323
+ "error": f"Domain blocked: {domain}"
324
+ }
325
+
326
+ try:
327
+ # Create message
328
+ message = {
329
+ "payload": {
330
+ "headers": [
331
+ {"name": "To", "value": to},
332
+ {"name": "Subject", "value": subject}
333
+ ],
334
+ "body": {"data": body}
335
+ }
336
+ }
337
+
338
+ if cc:
339
+ message["payload"]["headers"].append({"name": "Cc", "value": cc})
340
+
341
+ # Send
342
+ result = self.gmail.users_messages_send("me", message)
343
+
344
+ print(f" [OK] Email sent: {result['id']}")
345
+
346
+ return {
347
+ "success": True,
348
+ "message_id": result["id"],
349
+ "to": to,
350
+ "subject": subject
351
+ }
352
+
353
+ except Exception as e:
354
+ print(f" Error: {e}")
355
+ return {
356
+ "success": False,
357
+ "error": str(e)
358
+ }
359
+
360
+ def list_labels(self) -> Dict[str, Any]:
361
+ """
362
+ List Gmail labels.
363
+
364
+ Returns:
365
+ List of labels
366
+ """
367
+ print(f"\n Listing labels")
368
+
369
+ if not self._check_rate_limit():
370
+ return {
371
+ "success": False,
372
+ "error": "Rate limit exceeded"
373
+ }
374
+
375
+ try:
376
+ result = self.gmail.users_labels_list("me")
377
+ labels = result.get("labels", [])
378
+
379
+ print(f" [OK] Found {len(labels)} labels")
380
+
381
+ return {
382
+ "success": True,
383
+ "labels": labels,
384
+ "count": len(labels)
385
+ }
386
+
387
+ except Exception as e:
388
+ print(f" Error: {e}")
389
+ return {
390
+ "success": False,
391
+ "error": str(e)
392
+ }
393
+
394
+ def get_tool_definitions(self) -> List[Dict]:
395
+ """Get MCP tool definitions for AI agents."""
396
+ tools = []
397
+
398
+ if self.config.enable_search:
399
+ tools.append({
400
+ "name": "gmail_search_emails",
401
+ "description": "Search emails by keywords, sender, subject, or content",
402
+ "input_schema": {
403
+ "type": "object",
404
+ "properties": {
405
+ "query": {
406
+ "type": "string",
407
+ "description": "Search query (e.g., 'from:sarah@company.com', 'subject:Project Zeus')"
408
+ },
409
+ "max_results": {
410
+ "type": "integer",
411
+ "description": "Maximum number of results",
412
+ "default": 10
413
+ },
414
+ "label": {
415
+ "type": "string",
416
+ "description": "Filter by label (INBOX, SENT, etc.)",
417
+ "enum": ["INBOX", "SENT", "DRAFT", "IMPORTANT", "STARRED"]
418
+ }
419
+ },
420
+ "required": ["query"]
421
+ }
422
+ })
423
+
424
+ if self.config.enable_read:
425
+ tools.append({
426
+ "name": "gmail_read_email",
427
+ "description": "Read the full content of a specific email",
428
+ "input_schema": {
429
+ "type": "object",
430
+ "properties": {
431
+ "email_id": {
432
+ "type": "string",
433
+ "description": "Gmail message ID"
434
+ }
435
+ },
436
+ "required": ["email_id"]
437
+ }
438
+ })
439
+
440
+ if self.config.enable_send:
441
+ tools.append({
442
+ "name": "gmail_send_email",
443
+ "description": "Send a new email",
444
+ "input_schema": {
445
+ "type": "object",
446
+ "properties": {
447
+ "to": {
448
+ "type": "string",
449
+ "description": "Recipient email address"
450
+ },
451
+ "subject": {
452
+ "type": "string",
453
+ "description": "Email subject"
454
+ },
455
+ "body": {
456
+ "type": "string",
457
+ "description": "Email body text"
458
+ },
459
+ "cc": {
460
+ "type": "string",
461
+ "description": "CC recipients (optional)"
462
+ }
463
+ },
464
+ "required": ["to", "subject", "body"]
465
+ }
466
+ })
467
+
468
+ tools.append({
469
+ "name": "gmail_list_labels",
470
+ "description": "List all Gmail labels (folders)",
471
+ "input_schema": {
472
+ "type": "object",
473
+ "properties": {}
474
+ }
475
+ })
476
+
477
+ return tools
478
+
479
+
480
+ # ============================================================================
481
+ # DEMO
482
+ # ============================================================================
483
+
484
+ def demo():
485
+ print("=" * 70)
486
+ print("GMAIL MCP SERVER DEMO")
487
+ print("=" * 70)
488
+ print("\nBased on Chapter 4: Model Context Protocol")
489
+ print("Allows AI agents to search and manage Gmail\n")
490
+ print("=" * 70)
491
+
492
+ # Initialize server
493
+ config = GmailConfig(
494
+ enable_read=True,
495
+ enable_send=True, # Enabled for demo
496
+ enable_search=True
497
+ )
498
+
499
+ server = GmailMCPServer(config)
500
+
501
+ # Demo 1: Search emails
502
+ print(f"\n{'='*70}")
503
+ print("DEMO 1: Search for emails about Project Zeus")
504
+ print('='*70)
505
+
506
+ result = server.search_emails("Project Zeus")
507
+ if result["success"]:
508
+ print(f"\nFound {result['count']} emails:")
509
+ for email in result["emails"]:
510
+ print(f"\n {email['subject']}")
511
+ print(f" From: {email['from']}")
512
+ print(f" Date: {email['date']}")
513
+ print(f" Preview: {email['snippet'][:60]}...")
514
+
515
+ # Demo 2: Read specific email
516
+ print(f"\n{'='*70}")
517
+ print("DEMO 2: Read full email")
518
+ print('='*70)
519
+
520
+ result = server.read_email("email002")
521
+ if result["success"]:
522
+ print(f"\n Email Details:")
523
+ print(f" From: {result['from']}")
524
+ print(f" To: {result['to']}")
525
+ print(f" Subject: {result['subject']}")
526
+ print(f"\n Body:")
527
+ print(" " + " " * 66)
528
+ for line in result['body'].split('\n'):
529
+ print(f" {line}")
530
+ print(" " + " " * 66)
531
+
532
+ # Demo 3: List labels
533
+ print(f"\n{'='*70}")
534
+ print("DEMO 3: List Gmail labels")
535
+ print('='*70)
536
+
537
+ result = server.list_labels()
538
+ if result["success"]:
539
+ print(f"\n Labels ({result['count']}):")
540
+ for label in result["labels"]:
541
+ print(f" {label['name']}")
542
+
543
+ # Demo 4: Send email (simulated)
544
+ print(f"\n{'='*70}")
545
+ print("DEMO 4: Send email")
546
+ print('='*70)
547
+
548
+ result = server.send_email(
549
+ to="team@company.com",
550
+ subject="AI Agent Test Email",
551
+ body="This is a test email sent by the AI agent via Gmail MCP server."
552
+ )
553
+
554
+ if result["success"]:
555
+ print(f"\n[OK] Email sent successfully")
556
+ print(f" Message ID: {result['message_id']}")
557
+ print(f" To: {result['to']}")
558
+ print(f" Subject: {result['subject']}")
559
+
560
+ # Show tool definitions
561
+ print(f"\n{'='*70}")
562
+ print("MCP TOOL DEFINITIONS")
563
+ print('='*70)
564
+
565
+ tools = server.get_tool_definitions()
566
+ print(f"\nAvailable tools: {len(tools)}")
567
+ for tool in tools:
568
+ print(f"\n {tool['name']}")
569
+ print(f" {tool['description']}")
570
+
571
+ print("\n" + "="*70)
572
+ print("USAGE WITH AI AGENT")
573
+ print("="*70)
574
+ print("""
575
+ # In your agent code:
576
+ from gmail_mcp_server import GmailMCPServer, GmailConfig
577
+
578
+ # Initialize
579
+ server = GmailMCPServer(GmailConfig())
580
+
581
+ # Get tools for agent
582
+ tools = server.get_tool_definitions()
583
+
584
+ # When agent calls tool:
585
+ if tool_name == "gmail_search_emails":
586
+ result = server.search_emails(args["query"])
587
+ elif tool_name == "gmail_read_email":
588
+ result = server.read_email(args["email_id"])
589
+ elif tool_name == "gmail_send_email":
590
+ result = server.send_email(args["to"], args["subject"], args["body"])
591
+
592
+ # Agent can now:
593
+ # - "Find all emails from Sarah about Project Zeus"
594
+ # - "Read the latest email from AlphaCorp"
595
+ # - "Send status update to the team"
596
+ # - "Search for budget approval emails from last month"
597
+ """)
598
+
599
+
600
+ if __name__ == "__main__":
601
+ demo()