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,490 @@
1
+ """
2
+ MCP Server Implementation: PostgreSQL Database
3
+
4
+ A Model Context Protocol (MCP) server that allows AI agents to query PostgreSQL databases.
5
+ This is a production-ready implementation with security and safety features.
6
+
7
+ Run: python postgres_mcp_server.py
8
+ """
9
+
10
+ import os
11
+ import json
12
+ import asyncio
13
+ from typing import Dict, List, Any, Optional
14
+ from dataclasses import dataclass
15
+ import psycopg2
16
+ from psycopg2 import sql
17
+ from dotenv import load_dotenv
18
+
19
+ load_dotenv()
20
+
21
+
22
+ @dataclass
23
+ class MCPServerConfig:
24
+ """Configuration for the MCP server."""
25
+ host: str = "localhost"
26
+ port: int = 5432
27
+ database: str = "kite"
28
+ user: str = "postgres"
29
+ password: str = ""
30
+
31
+ # Safety limits
32
+ max_rows: int = 1000
33
+ max_query_length: int = 5000
34
+ timeout_seconds: int = 30
35
+
36
+ # Allowed operations
37
+ allow_select: bool = True
38
+ allow_insert: bool = False
39
+ allow_update: bool = False
40
+ allow_delete: bool = False
41
+
42
+
43
+ class PostgresMCPServer:
44
+ """
45
+ MCP Server for PostgreSQL database access.
46
+
47
+ This server exposes database operations as MCP tools that AI agents can use.
48
+ It includes security features like query validation, row limits, and timeouts.
49
+
50
+ Example:
51
+ config = MCPServerConfig(
52
+ host="localhost",
53
+ database="my_db",
54
+ allow_select=True
55
+ )
56
+ server = PostgresMCPServer(config)
57
+ result = await server.execute_query("SELECT * FROM users LIMIT 10")
58
+ """
59
+
60
+ def __init__(self, config: MCPServerConfig = None, connection_string: str = None, **kwargs):
61
+ self.config = config or MCPServerConfig()
62
+ if connection_string:
63
+ # Simple parsing for demo - in production use proper URL parser
64
+ # postgresql://user:pass@host:port/db
65
+ self.config.database = connection_string.split('/')[-1]
66
+ self.connection = None
67
+ self.tools = self._define_tools()
68
+
69
+ def connect(self):
70
+ """Establish database connection."""
71
+ try:
72
+ self.connection = psycopg2.connect(
73
+ host=self.config.host,
74
+ port=self.config.port,
75
+ database=self.config.database,
76
+ user=self.config.user,
77
+ password=self.config.password
78
+ )
79
+ print(f"[OK] Connected to PostgreSQL: {self.config.database}")
80
+ except Exception as e:
81
+ print(f" Connection failed: {e}")
82
+ raise
83
+
84
+ def disconnect(self):
85
+ """Close database connection."""
86
+ if self.connection:
87
+ self.connection.close()
88
+ print("[OK] Disconnected from PostgreSQL")
89
+
90
+ def _is_safe_query(self, query: str) -> tuple[bool, str]:
91
+ """
92
+ Validate query for safety.
93
+
94
+ Returns:
95
+ (is_safe, reason)
96
+ """
97
+ query_upper = query.upper().strip()
98
+
99
+ # Check length
100
+ if len(query) > self.config.max_query_length:
101
+ return False, f"Query too long ({len(query)} > {self.config.max_query_length})"
102
+
103
+ # Check allowed operations
104
+ dangerous_keywords = ['DROP', 'TRUNCATE', 'ALTER', 'CREATE TABLE', 'GRANT', 'REVOKE']
105
+ for keyword in dangerous_keywords:
106
+ if keyword in query_upper:
107
+ return False, f"Forbidden keyword: {keyword}"
108
+
109
+ # Check operation permissions
110
+ if query_upper.startswith('SELECT') and not self.config.allow_select:
111
+ return False, "SELECT queries not allowed"
112
+
113
+ if query_upper.startswith('INSERT') and not self.config.allow_insert:
114
+ return False, "INSERT queries not allowed"
115
+
116
+ if query_upper.startswith('UPDATE') and not self.config.allow_update:
117
+ return False, "UPDATE queries not allowed"
118
+
119
+ if query_upper.startswith('DELETE') and not self.config.allow_delete:
120
+ return False, "DELETE queries not allowed"
121
+
122
+ return True, "OK"
123
+
124
+ async def execute_query(self, query: str, params: tuple = None) -> Dict[str, Any]:
125
+ """
126
+ Execute SQL query with safety checks.
127
+
128
+ Args:
129
+ query: SQL query string
130
+ params: Query parameters (optional)
131
+
132
+ Returns:
133
+ Result dictionary with rows and metadata
134
+ """
135
+ print(f"\n[CHART] Executing query:")
136
+ print(f" {query[:100]}...")
137
+
138
+ # Validate query
139
+ is_safe, reason = self._is_safe_query(query)
140
+ if not is_safe:
141
+ return {
142
+ "success": False,
143
+ "error": f"Unsafe query: {reason}",
144
+ "rows": []
145
+ }
146
+
147
+ try:
148
+ cursor = self.connection.cursor()
149
+
150
+ # Execute with timeout
151
+ cursor.execute(f"SET statement_timeout = {self.config.timeout_seconds * 1000}")
152
+ cursor.execute(query, params)
153
+
154
+ # Fetch results (with row limit)
155
+ if query.upper().strip().startswith('SELECT'):
156
+ rows = cursor.fetchmany(self.config.max_rows)
157
+ columns = [desc[0] for desc in cursor.description]
158
+
159
+ # Convert to list of dicts
160
+ results = [
161
+ dict(zip(columns, row))
162
+ for row in rows
163
+ ]
164
+
165
+ self.connection.commit()
166
+ cursor.close()
167
+
168
+ print(f" [OK] Returned {len(results)} rows")
169
+
170
+ return {
171
+ "success": True,
172
+ "rows": results,
173
+ "count": len(results),
174
+ "columns": columns
175
+ }
176
+ else:
177
+ # For INSERT/UPDATE/DELETE
178
+ self.connection.commit()
179
+ affected_rows = cursor.rowcount
180
+ cursor.close()
181
+
182
+ print(f" [OK] Affected {affected_rows} rows")
183
+
184
+ return {
185
+ "success": True,
186
+ "affected_rows": affected_rows
187
+ }
188
+
189
+ except Exception as e:
190
+ self.connection.rollback()
191
+ print(f" Query failed: {e}")
192
+ return {
193
+ "success": False,
194
+ "error": str(e),
195
+ "rows": []
196
+ }
197
+
198
+ def _define_tools(self) -> List[Dict]:
199
+ """
200
+ Define MCP tools that agents can use.
201
+
202
+ These tool definitions follow the MCP standard format.
203
+ """
204
+ return [
205
+ {
206
+ "name": "query_database",
207
+ "description": """
208
+ Query the PostgreSQL database using SQL.
209
+ Use this to retrieve data from tables.
210
+
211
+ Safety features:
212
+ - Automatic row limit enforcement
213
+ - Query timeout protection
214
+ - Dangerous operations blocked
215
+
216
+ Examples:
217
+ - SELECT * FROM users WHERE age > 25 LIMIT 10
218
+ - SELECT product_name, price FROM products WHERE category = 'electronics'
219
+ """,
220
+ "input_schema": {
221
+ "type": "object",
222
+ "properties": {
223
+ "query": {
224
+ "type": "string",
225
+ "description": "SQL SELECT query to execute"
226
+ }
227
+ },
228
+ "required": ["query"]
229
+ }
230
+ },
231
+ {
232
+ "name": "get_table_schema",
233
+ "description": """
234
+ Get the schema (column names and types) of a database table.
235
+ Use this to understand table structure before querying.
236
+ """,
237
+ "input_schema": {
238
+ "type": "object",
239
+ "properties": {
240
+ "table_name": {
241
+ "type": "string",
242
+ "description": "Name of the table"
243
+ }
244
+ },
245
+ "required": ["table_name"]
246
+ }
247
+ },
248
+ {
249
+ "name": "list_tables",
250
+ "description": """
251
+ List all tables in the database.
252
+ Use this to discover what data is available.
253
+ """,
254
+ "input_schema": {
255
+ "type": "object",
256
+ "properties": {}
257
+ }
258
+ }
259
+ ]
260
+
261
+ async def handle_tool_call(self, tool_name: str, args: Dict) -> Dict:
262
+ """
263
+ Handle MCP tool call from agent.
264
+
265
+ Args:
266
+ tool_name: Name of the tool to execute
267
+ args: Tool arguments
268
+
269
+ Returns:
270
+ Tool execution result
271
+ """
272
+ if tool_name == "query_database":
273
+ return await self.execute_query(args["query"])
274
+
275
+ elif tool_name == "get_table_schema":
276
+ query = """
277
+ SELECT column_name, data_type, is_nullable
278
+ FROM information_schema.columns
279
+ WHERE table_name = %s
280
+ ORDER BY ordinal_position
281
+ """
282
+ result = await self.execute_query(query, (args["table_name"],))
283
+ return result
284
+
285
+ elif tool_name == "list_tables":
286
+ query = """
287
+ SELECT table_name, table_type
288
+ FROM information_schema.tables
289
+ WHERE table_schema = 'public'
290
+ ORDER BY table_name
291
+ """
292
+ result = await self.execute_query(query)
293
+ return result
294
+
295
+ else:
296
+ return {
297
+ "success": False,
298
+ "error": f"Unknown tool: {tool_name}"
299
+ }
300
+
301
+ def get_tool_definitions(self) -> List[Dict]:
302
+ """Get MCP tool definitions for AI agent."""
303
+ return self.tools
304
+
305
+
306
+ # ============================================================================
307
+ # DEMO: Using MCP Server with Mock Data
308
+ # ============================================================================
309
+
310
+ async def setup_demo_database():
311
+ """Create demo tables with sample data."""
312
+ config = MCPServerConfig(
313
+ host=os.getenv("POSTGRES_HOST", "localhost"),
314
+ database=os.getenv("POSTGRES_DB", "postgres"),
315
+ user=os.getenv("POSTGRES_USER", "postgres"),
316
+ password=os.getenv("POSTGRES_PASSWORD", "")
317
+ )
318
+
319
+ server = PostgresMCPServer(config)
320
+
321
+ try:
322
+ server.connect()
323
+
324
+ # Create demo tables
325
+ print("\n Setting up demo database...")
326
+
327
+ # Drop existing tables
328
+ await server.execute_query("DROP TABLE IF EXISTS orders CASCADE")
329
+ await server.execute_query("DROP TABLE IF EXISTS products CASCADE")
330
+ await server.execute_query("DROP TABLE IF EXISTS customers CASCADE")
331
+
332
+ # Create customers table
333
+ await server.execute_query("""
334
+ CREATE TABLE customers (
335
+ id SERIAL PRIMARY KEY,
336
+ name VARCHAR(100),
337
+ email VARCHAR(100),
338
+ joined_date DATE
339
+ )
340
+ """)
341
+
342
+ # Create products table
343
+ await server.execute_query("""
344
+ CREATE TABLE products (
345
+ id SERIAL PRIMARY KEY,
346
+ name VARCHAR(100),
347
+ category VARCHAR(50),
348
+ price DECIMAL(10, 2),
349
+ stock INT
350
+ )
351
+ """)
352
+
353
+ # Create orders table
354
+ await server.execute_query("""
355
+ CREATE TABLE orders (
356
+ id SERIAL PRIMARY KEY,
357
+ customer_id INT REFERENCES customers(id),
358
+ product_id INT REFERENCES products(id),
359
+ quantity INT,
360
+ total DECIMAL(10, 2),
361
+ order_date DATE
362
+ )
363
+ """)
364
+
365
+ # Insert sample data
366
+ await server.execute_query("""
367
+ INSERT INTO customers (name, email, joined_date) VALUES
368
+ ('John Doe', 'john@example.com', '2024-01-15'),
369
+ ('Jane Smith', 'jane@example.com', '2024-02-20'),
370
+ ('Bob Wilson', 'bob@example.com', '2024-03-10')
371
+ """)
372
+
373
+ await server.execute_query("""
374
+ INSERT INTO products (name, category, price, stock) VALUES
375
+ ('Laptop Pro', 'Electronics', 1299.99, 50),
376
+ ('Wireless Mouse', 'Electronics', 29.99, 200),
377
+ ('Office Chair', 'Furniture', 199.99, 30),
378
+ ('Desk Lamp', 'Furniture', 49.99, 100)
379
+ """)
380
+
381
+ await server.execute_query("""
382
+ INSERT INTO orders (customer_id, product_id, quantity, total, order_date) VALUES
383
+ (1, 1, 1, 1299.99, '2024-04-01'),
384
+ (1, 2, 2, 59.98, '2024-04-01'),
385
+ (2, 3, 1, 199.99, '2024-04-05'),
386
+ (3, 4, 3, 149.97, '2024-04-10')
387
+ """)
388
+
389
+ print("[OK] Demo database ready!")
390
+
391
+ return server
392
+
393
+ except Exception as e:
394
+ print(f" Setup failed: {e}")
395
+ server.disconnect()
396
+ return None
397
+
398
+
399
+ async def demo_mcp_server():
400
+ """Demonstrate MCP server usage."""
401
+ print("=" * 70)
402
+ print("MCP SERVER DEMO: PostgreSQL")
403
+ print("=" * 70)
404
+
405
+ # Setup database
406
+ server = await setup_demo_database()
407
+ if not server:
408
+ return
409
+
410
+ try:
411
+ print("\n" + "=" * 70)
412
+ print("EXAMPLE 1: List all tables")
413
+ print("=" * 70)
414
+ result = await server.handle_tool_call("list_tables", {})
415
+ print(json.dumps(result, indent=2, default=str))
416
+
417
+ print("\n" + "=" * 70)
418
+ print("EXAMPLE 2: Get table schema")
419
+ print("=" * 70)
420
+ result = await server.handle_tool_call("get_table_schema", {"table_name": "products"})
421
+ print(json.dumps(result, indent=2, default=str))
422
+
423
+ print("\n" + "=" * 70)
424
+ print("EXAMPLE 3: Query products")
425
+ print("=" * 70)
426
+ result = await server.handle_tool_call(
427
+ "query_database",
428
+ {"query": "SELECT * FROM products WHERE category = 'Electronics'"}
429
+ )
430
+ print(json.dumps(result, indent=2, default=str))
431
+
432
+ print("\n" + "=" * 70)
433
+ print("EXAMPLE 4: Complex join query")
434
+ print("=" * 70)
435
+ result = await server.handle_tool_call(
436
+ "query_database",
437
+ {"query": """
438
+ SELECT
439
+ c.name as customer_name,
440
+ p.name as product_name,
441
+ o.quantity,
442
+ o.total
443
+ FROM orders o
444
+ JOIN customers c ON o.customer_id = c.id
445
+ JOIN products p ON o.product_id = p.id
446
+ ORDER BY o.order_date DESC
447
+ """}
448
+ )
449
+ print(json.dumps(result, indent=2, default=str))
450
+
451
+ print("\n" + "=" * 70)
452
+ print("EXAMPLE 5: Safety check - dangerous query blocked")
453
+ print("=" * 70)
454
+ result = await server.handle_tool_call(
455
+ "query_database",
456
+ {"query": "DROP TABLE products"}
457
+ )
458
+ print(json.dumps(result, indent=2, default=str))
459
+
460
+ print("\n" + "=" * 70)
461
+ print("[OK] Demo completed successfully!")
462
+ print("=" * 70)
463
+
464
+ finally:
465
+ server.disconnect()
466
+
467
+
468
+ if __name__ == "__main__":
469
+ # Run demo
470
+ asyncio.run(demo_mcp_server())
471
+
472
+ print("\n TO USE WITH AI AGENT:")
473
+ print("""
474
+ # In your agent code:
475
+ from postgres_mcp_server import PostgresMCPServer, MCPServerConfig
476
+
477
+ # Initialize MCP server
478
+ config = MCPServerConfig(database="your_db")
479
+ mcp_server = PostgresMCPServer(config)
480
+ mcp_server.connect()
481
+
482
+ # Get tool definitions for agent
483
+ tools = mcp_server.get_tool_definitions()
484
+
485
+ # When agent calls a tool:
486
+ result = await mcp_server.handle_tool_call(
487
+ tool_name="query_database",
488
+ args={"query": "SELECT * FROM users"}
489
+ )
490
+ """)