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.
- kite/__init__.py +46 -0
- kite/ab_testing.py +384 -0
- kite/agent.py +556 -0
- kite/agents/__init__.py +3 -0
- kite/agents/plan_execute.py +191 -0
- kite/agents/react_agent.py +509 -0
- kite/agents/reflective_agent.py +90 -0
- kite/agents/rewoo.py +119 -0
- kite/agents/tot.py +151 -0
- kite/conversation.py +125 -0
- kite/core.py +974 -0
- kite/data_loaders.py +111 -0
- kite/embedding_providers.py +372 -0
- kite/llm_providers.py +1278 -0
- kite/memory/__init__.py +6 -0
- kite/memory/advanced_rag.py +333 -0
- kite/memory/graph_rag.py +719 -0
- kite/memory/session_memory.py +423 -0
- kite/memory/vector_memory.py +579 -0
- kite/monitoring.py +611 -0
- kite/observers.py +107 -0
- kite/optimization/__init__.py +9 -0
- kite/optimization/resource_router.py +80 -0
- kite/persistence.py +42 -0
- kite/pipeline/__init__.py +5 -0
- kite/pipeline/deterministic_pipeline.py +323 -0
- kite/pipeline/reactive_pipeline.py +171 -0
- kite/pipeline_manager.py +15 -0
- kite/routing/__init__.py +6 -0
- kite/routing/aggregator_router.py +325 -0
- kite/routing/llm_router.py +149 -0
- kite/routing/semantic_router.py +228 -0
- kite/safety/__init__.py +6 -0
- kite/safety/circuit_breaker.py +360 -0
- kite/safety/guardrails.py +82 -0
- kite/safety/idempotency_manager.py +304 -0
- kite/safety/kill_switch.py +75 -0
- kite/tool.py +183 -0
- kite/tool_registry.py +87 -0
- kite/tools/__init__.py +21 -0
- kite/tools/code_execution.py +53 -0
- kite/tools/contrib/__init__.py +19 -0
- kite/tools/contrib/calculator.py +26 -0
- kite/tools/contrib/datetime_utils.py +20 -0
- kite/tools/contrib/linkedin.py +428 -0
- kite/tools/contrib/web_search.py +30 -0
- kite/tools/mcp/__init__.py +31 -0
- kite/tools/mcp/database_mcp.py +267 -0
- kite/tools/mcp/gdrive_mcp_server.py +503 -0
- kite/tools/mcp/gmail_mcp_server.py +601 -0
- kite/tools/mcp/postgres_mcp_server.py +490 -0
- kite/tools/mcp/slack_mcp_server.py +538 -0
- kite/tools/mcp/stripe_mcp_server.py +219 -0
- kite/tools/search.py +90 -0
- kite/tools/system_tools.py +54 -0
- kite/tools_manager.py +27 -0
- kite_agent-0.1.0.dist-info/METADATA +621 -0
- kite_agent-0.1.0.dist-info/RECORD +61 -0
- kite_agent-0.1.0.dist-info/WHEEL +5 -0
- kite_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
- 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
|
+
""")
|