Skip to content

Commit 295d5d9

Browse files
authored
Merge pull request #57 from pamenon/main
Added python mcp project files
2 parents f2d105f + bbf4c6f commit 295d5d9

5 files changed

Lines changed: 1131 additions & 0 deletions

File tree

python-mcp-sample/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*.db
2+
__pycache__/
3+
.venv/
4+
*.pyc
5+
.python-version

python-mcp-sample/mcp_server.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""MCP server exposing insurance policy data from SQLite."""
2+
3+
import os
4+
import sqlite3
5+
from textwrap import dedent
6+
7+
from mcp.server.fastmcp import FastMCP
8+
9+
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "insurance.db")
10+
11+
mcp = FastMCP("Insurance Policy Server")
12+
13+
14+
def _get_connection() -> sqlite3.Connection:
15+
conn = sqlite3.connect(DB_PATH)
16+
conn.row_factory = sqlite3.Row
17+
return conn
18+
19+
20+
def _format_policy(row: sqlite3.Row) -> str:
21+
return dedent(f"""\
22+
Policy Number : {row["policy_number"]}
23+
Holder : {row["holder_name"]} ({row["holder_email"]})
24+
Type : {row["policy_type"]}
25+
Premium : ${row["premium"]:,.2f}
26+
Deductible : ${row["deductible"]:,.2f}
27+
Coverage Limit: ${row["coverage_limit"]:,.2f}
28+
Period : {row["start_date"]} to {row["end_date"]}
29+
Status : {row["status"]}""")
30+
31+
32+
@mcp.tool()
33+
def get_policy(policy_number: str) -> str:
34+
"""Look up a single insurance policy by its policy number.
35+
36+
Args:
37+
policy_number: The unique policy identifier (e.g. POL-2024005).
38+
"""
39+
conn = _get_connection()
40+
row = conn.execute(
41+
"SELECT * FROM policies WHERE policy_number = ?", (policy_number,)
42+
).fetchone()
43+
conn.close()
44+
45+
if row is None:
46+
return f"No policy found with number '{policy_number}'."
47+
return _format_policy(row)
48+
49+
50+
@mcp.tool()
51+
def list_policies(status: str | None = None, policy_type: str | None = None) -> str:
52+
"""List insurance policies with optional filters.
53+
54+
Args:
55+
status: Filter by status (Active, Expired, or Cancelled). None returns all.
56+
policy_type: Filter by type (Auto, Home, Life, or Health). None returns all.
57+
"""
58+
query = "SELECT * FROM policies WHERE 1=1"
59+
params: list[str] = []
60+
61+
if status is not None:
62+
query += " AND status = ?"
63+
params.append(status)
64+
if policy_type is not None:
65+
query += " AND policy_type = ?"
66+
params.append(policy_type)
67+
68+
query += " ORDER BY policy_number"
69+
70+
conn = _get_connection()
71+
rows = conn.execute(query, params).fetchall()
72+
conn.close()
73+
74+
if not rows:
75+
return "No policies match the given filters."
76+
77+
header = f"{'#':<4} {'Policy Number':<16} {'Holder':<25} {'Type':<8} {'Premium':>10} {'Status':<10}"
78+
separator = "-" * len(header)
79+
lines = [header, separator]
80+
81+
for i, row in enumerate(rows, 1):
82+
lines.append(
83+
f"{i:<4} {row['policy_number']:<16} {row['holder_name']:<25} "
84+
f"{row['policy_type']:<8} ${row['premium']:>9,.2f} {row['status']:<10}"
85+
)
86+
87+
lines.append(separator)
88+
lines.append(f"Total: {len(rows)} policies")
89+
return "\n".join(lines)
90+
91+
92+
@mcp.tool()
93+
def send_policy_to_agent(policy_number: str) -> str:
94+
"""Retrieve an insurance policy and send its data to the AI agent for processing.
95+
96+
Args:
97+
policy_number: The unique policy identifier to look up and forward.
98+
"""
99+
conn = _get_connection()
100+
row = conn.execute(
101+
"SELECT * FROM policies WHERE policy_number = ?", (policy_number,)
102+
).fetchone()
103+
conn.close()
104+
105+
if row is None:
106+
return f"No policy found with number '{policy_number}'. Nothing sent."
107+
108+
policy_data = dict(row)
109+
110+
# TODO: send extra info to the agent
111+
print(f"TODO: send policy {policy_number} data to AI agent: {policy_data}")
112+
113+
return (
114+
f"Policy {policy_number} data retrieved and queued for the AI agent (not yet implemented).\n\n"
115+
+ _format_policy(row)
116+
)
117+
118+
119+
if __name__ == "__main__":
120+
mcp.run()

python-mcp-sample/pyproject.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[project]
2+
name = "mcp-insurance"
3+
version = "0.1.0"
4+
description = "MCP server for insurance policy data"
5+
requires-python = ">=3.10"
6+
dependencies = [
7+
"faker>=40.11.0",
8+
"mcp[cli]>=1.26.0",
9+
]

python-mcp-sample/setup_db.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"""Create and populate the insurance.db SQLite database with fake policy data."""
2+
3+
import os
4+
import random
5+
import sqlite3
6+
from datetime import timedelta
7+
8+
from faker import Faker
9+
10+
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "insurance.db")
11+
NUM_POLICIES = 30
12+
13+
POLICY_TYPES = ["Auto", "Home", "Life", "Health"]
14+
STATUSES = ["Active", "Expired", "Cancelled"]
15+
STATUS_WEIGHTS = [0.6, 0.25, 0.15]
16+
17+
PREMIUM_RANGES = {
18+
"Auto": (600, 3000),
19+
"Home": (800, 4000),
20+
"Life": (300, 2000),
21+
"Health": (1500, 8000),
22+
}
23+
24+
DEDUCTIBLE_RANGES = {
25+
"Auto": (250, 2000),
26+
"Home": (500, 5000),
27+
"Life": (0, 500),
28+
"Health": (500, 6000),
29+
}
30+
31+
COVERAGE_LIMIT_RANGES = {
32+
"Auto": (25_000, 300_000),
33+
"Home": (100_000, 1_000_000),
34+
"Life": (50_000, 2_000_000),
35+
"Health": (100_000, 5_000_000),
36+
}
37+
38+
CREATE_TABLE_SQL = """
39+
CREATE TABLE IF NOT EXISTS policies (
40+
id INTEGER PRIMARY KEY AUTOINCREMENT,
41+
policy_number TEXT UNIQUE NOT NULL,
42+
holder_name TEXT NOT NULL,
43+
holder_email TEXT NOT NULL,
44+
policy_type TEXT NOT NULL,
45+
premium REAL NOT NULL,
46+
deductible REAL NOT NULL,
47+
coverage_limit REAL NOT NULL,
48+
start_date TEXT NOT NULL,
49+
end_date TEXT NOT NULL,
50+
status TEXT NOT NULL
51+
);
52+
"""
53+
54+
INSERT_SQL = """
55+
INSERT INTO policies (
56+
policy_number, holder_name, holder_email, policy_type,
57+
premium, deductible, coverage_limit, start_date, end_date, status
58+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
59+
"""
60+
61+
62+
def generate_policy_number(fake: Faker, index: int) -> str:
63+
prefix = fake.random_element(["POL", "INS", "COV"])
64+
return f"{prefix}-{2024_000 + index:07d}"
65+
66+
67+
def build_policy(fake: Faker, index: int) -> tuple:
68+
policy_number = generate_policy_number(fake, index)
69+
holder_name = fake.name()
70+
holder_email = fake.email()
71+
policy_type = fake.random_element(POLICY_TYPES)
72+
status = random.choices(STATUSES, weights=STATUS_WEIGHTS, k=1)[0]
73+
74+
low, high = PREMIUM_RANGES[policy_type]
75+
premium = round(random.uniform(low, high), 2)
76+
77+
low, high = DEDUCTIBLE_RANGES[policy_type]
78+
deductible = round(random.uniform(low, high), 2)
79+
80+
low, high = COVERAGE_LIMIT_RANGES[policy_type]
81+
coverage_limit = round(random.uniform(low, high), 2)
82+
83+
start_date = fake.date_between(start_date="-3y", end_date="today")
84+
end_date = start_date + timedelta(days=365)
85+
86+
return (
87+
policy_number,
88+
holder_name,
89+
holder_email,
90+
policy_type,
91+
premium,
92+
deductible,
93+
coverage_limit,
94+
start_date.isoformat(),
95+
end_date.isoformat(),
96+
status,
97+
)
98+
99+
100+
def main() -> None:
101+
if os.path.exists(DB_PATH):
102+
os.remove(DB_PATH)
103+
print(f"Removed existing database: {DB_PATH}")
104+
105+
conn = sqlite3.connect(DB_PATH)
106+
cursor = conn.cursor()
107+
cursor.execute(CREATE_TABLE_SQL)
108+
109+
fake = Faker()
110+
Faker.seed(42)
111+
random.seed(42)
112+
113+
policies = [build_policy(fake, i) for i in range(NUM_POLICIES)]
114+
cursor.executemany(INSERT_SQL, policies)
115+
conn.commit()
116+
117+
cursor.execute("SELECT COUNT(*) FROM policies")
118+
count = cursor.fetchone()[0]
119+
120+
cursor.execute(
121+
"SELECT policy_type, COUNT(*) FROM policies GROUP BY policy_type ORDER BY policy_type"
122+
)
123+
breakdown = cursor.fetchall()
124+
125+
cursor.execute(
126+
"SELECT status, COUNT(*) FROM policies GROUP BY status ORDER BY status"
127+
)
128+
status_breakdown = cursor.fetchall()
129+
130+
conn.close()
131+
132+
print(f"\nDatabase created at: {DB_PATH}")
133+
print(f"Total policies inserted: {count}\n")
134+
print("By type:")
135+
for ptype, cnt in breakdown:
136+
print(f" {ptype:<10} {cnt}")
137+
print("\nBy status:")
138+
for status, cnt in status_breakdown:
139+
print(f" {status:<12} {cnt}")
140+
141+
142+
if __name__ == "__main__":
143+
main()

0 commit comments

Comments
 (0)