feat: expose new deal filters + group_by_2 in MCP tools

Mirror the Cauldron API additions so the stdio MCP client stays aligned with
the HTTP transport:
- list_deals and deal_statistics gain firm, industry_category,
  buyer/seller industry+country, pending_rfh, and creation/closing/update
  date ranges (+ closing-year range)
- deal_statistics gains a second grouping dimension (group_by_2) for
  long-format cross-tabs, plus new dimensions (industry_category,
  crossborder, buyer/seller, year_closed)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XL5Z9wpd5eygTmePvD1c27
This commit is contained in:
Spaike 2026-07-09 17:25:24 +02:00
parent 09deafbee9
commit 9716a79cc1
1 changed files with 154 additions and 26 deletions

180
server.py
View File

@ -85,6 +85,19 @@ def _fmt(data: Any) -> str:
return str(data)
# Filter params shared by list_deals and deal_statistics, so the same filter set
# is combinable with any listing or grouping.
_DEAL_STR_FILTERS = (
"firm", "stage", "status", "deal_type", "industry", "industry_category",
"country", "target_industry", "target_country",
"buyer_industry", "buyer_country", "seller_industry", "seller_country",
"pending_rfh",
"creation_from", "creation_to", "closing_from", "closing_to",
"updated_from", "updated_to",
)
_DEAL_INT_FILTERS = ("year_from", "year_to", "closing_year_from", "closing_year_to")
# ---------------------------------------------------------------------------
# MCP Server
# ---------------------------------------------------------------------------
@ -139,23 +152,84 @@ async def list_tools() -> list[types.Tool]:
},
"industry": {
"type": "string",
"description": "Filter by industry sector (partial match on Target/Seller/Buyer). E.g. 'Automotive', 'Tech'.",
"description": "Filter by industry (partial match on Target/Seller/Buyer industry name). E.g. 'Automotive', 'Tech'.",
},
"industry_category": {
"type": "string",
"description": "Filter by Industry Category — the sector level above Industry (partial match on Target/Seller/Buyer). E.g. 'Industrials', 'Consumer'.",
},
"country": {
"type": "string",
"description": "Filter by country (partial match on Target/Seller/Buyer country). E.g. 'Italy', 'Germany'.",
},
"firm": {
"type": "string",
"description": "Filter by advisor firm name (partial match). E.g. 'Scouting', 'Quore'.",
},
"buyer_industry": {
"type": "string",
"description": "Filter specifically by the Buyer's industry (partial match).",
},
"buyer_country": {
"type": "string",
"description": "Filter specifically by the Buyer's country (partial match).",
},
"seller_industry": {
"type": "string",
"description": "Filter specifically by the Seller's industry (partial match).",
},
"seller_country": {
"type": "string",
"description": "Filter specifically by the Seller's country (partial match).",
},
"pending_rfh": {
"type": "string",
"enum": ["pending", "closed", "any"],
"description": "Filter by Request For Help state: 'pending' = open RFH, 'closed' = closed RFH, 'any' = has any RFH (open or closed).",
},
"search": {
"type": "string",
"description": "Free-text search across TargetName, BuyerName, SellerName, MandateDescription.",
},
"year_from": {
"type": "integer",
"description": "Filter deals created from this year onwards.",
"description": "Filter deals whose CREATION year is >= this.",
},
"year_to": {
"type": "integer",
"description": "Filter deals created up to this year.",
"description": "Filter deals whose CREATION year is <= this.",
},
"closing_year_from": {
"type": "integer",
"description": "Filter deals whose CLOSING year is >= this.",
},
"closing_year_to": {
"type": "integer",
"description": "Filter deals whose CLOSING year is <= this.",
},
"creation_from": {
"type": "string",
"description": "Creation date range start (YYYY-MM-DD).",
},
"creation_to": {
"type": "string",
"description": "Creation date range end (YYYY-MM-DD).",
},
"closing_from": {
"type": "string",
"description": "Closing date range start (YYYY-MM-DD).",
},
"closing_to": {
"type": "string",
"description": "Closing date range end (YYYY-MM-DD).",
},
"updated_from": {
"type": "string",
"description": "Last-update date range start (YYYY-MM-DD).",
},
"updated_to": {
"type": "string",
"description": "Last-update date range end (YYYY-MM-DD).",
},
"shared_only": {
"type": "boolean",
@ -198,29 +272,77 @@ async def list_tools() -> list[types.Tool]:
types.Tool(
name="deal_statistics",
description=(
"Returns aggregated statistics on visible deals, grouped by a chosen dimension. "
"Useful for analytics questions like:\n"
"- 'How many deals did we close per year?'\n"
"- 'Which industry has the most deals?'\n"
"- 'What is our deal breakdown by stage?'"
"Returns aggregated counts (and non-confidential total deal value) on visible "
"deals, grouped by one or two dimensions, with the same filters as list_deals so "
"any filter is combinable with any grouping.\n\n"
"Single dimension → rows of {label, count, total_value}.\n"
"Two dimensions (group_by_2) → cross-tab in long format: rows of "
"{label, label2, count, total_value} — ideal to then pivot (e.g. year × firm).\n\n"
"Useful for questions like:\n"
"- 'How many deals did we close per year?' (group_by='year_closed')\n"
"- 'Deals by firm and year' (group_by='year', group_by_2='firm')\n"
"- 'Crossborder vs domestic by industry category' (group_by='crossborder', group_by_2='industry_category')\n"
"- 'Buy-side deals by seller country in 2024' (group_by='seller_country', deal_type='Buy', year_from=2024, year_to=2024)"
),
inputSchema={
"type": "object",
"properties": {
"group_by": {
"type": "string",
"enum": ["stage", "industry", "deal_type", "year", "month", "firm", "country"],
"description": "Dimension to group by. 'month' = per month (YYYY-MM). 'country' = target country. Default: 'stage'.",
"enum": [
"stage", "industry", "industry_category", "deal_type",
"year", "year_closed", "month", "firm", "country",
"target_industry", "target_country",
"buyer_industry", "buyer_country",
"seller_industry", "seller_country", "crossborder",
],
"description": (
"Primary dimension. 'year'/'month' = by creation date; 'year_closed' = by closing date; "
"'country' = target country; 'industry_category' = sector above industry. Default: 'stage'."
),
"default": "stage",
},
"year_from": {
"type": "integer",
"description": "Include deals from this year onwards.",
"group_by_2": {
"type": "string",
"enum": [
"stage", "industry", "industry_category", "deal_type",
"year", "year_closed", "month", "firm", "country",
"target_industry", "target_country",
"buyer_industry", "buyer_country",
"seller_industry", "seller_country", "crossborder",
],
"description": "Optional second dimension for a cross-tab (e.g. group_by='year', group_by_2='firm').",
},
"year_to": {
"type": "integer",
"description": "Include deals up to this year.",
"firm": {"type": "string", "description": "Filter by advisor firm name (partial match)."},
"stage": {"type": "string", "description": "Filter by deal stage (partial match)."},
"status": {
"type": "string",
"enum": ["open", "closed", "all"],
"description": "'open' = not Closed/Lost; 'closed' = Closed/Lost.",
},
"deal_type": {"type": "string", "description": "Filter by deal type (partial match). E.g. 'Sell', 'Buy'."},
"industry": {"type": "string", "description": "Filter by industry name (Target/Seller/Buyer, partial match)."},
"industry_category": {"type": "string", "description": "Filter by Industry Category / sector (Target/Seller/Buyer, partial match)."},
"country": {"type": "string", "description": "Filter by country (Target/Seller/Buyer, partial match)."},
"buyer_industry": {"type": "string", "description": "Filter by the Buyer's industry (partial match)."},
"buyer_country": {"type": "string", "description": "Filter by the Buyer's country (partial match)."},
"seller_industry": {"type": "string", "description": "Filter by the Seller's industry (partial match)."},
"seller_country": {"type": "string", "description": "Filter by the Seller's country (partial match)."},
"pending_rfh": {
"type": "string",
"enum": ["pending", "closed", "any"],
"description": "Filter by Request For Help state.",
},
"year_from": {"type": "integer", "description": "Creation year >= this."},
"year_to": {"type": "integer", "description": "Creation year <= this."},
"closing_year_from": {"type": "integer", "description": "Closing year >= this."},
"closing_year_to": {"type": "integer", "description": "Closing year <= this."},
"creation_from": {"type": "string", "description": "Creation date range start (YYYY-MM-DD)."},
"creation_to": {"type": "string", "description": "Creation date range end (YYYY-MM-DD)."},
"closing_from": {"type": "string", "description": "Closing date range start (YYYY-MM-DD)."},
"closing_to": {"type": "string", "description": "Closing date range end (YYYY-MM-DD)."},
"updated_from": {"type": "string", "description": "Last-update date range start (YYYY-MM-DD)."},
"updated_to": {"type": "string", "description": "Last-update date range end (YYYY-MM-DD)."},
},
},
),
@ -475,7 +597,10 @@ async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
if "firm" in meta:
summary_parts.append(f"Firm: {meta['firm']}")
if "group_by" in meta:
summary_parts.append(f"Grouped by: {meta['group_by']}")
gb = meta["group_by"]
if meta.get("group_by_2"):
gb += " × " + meta["group_by_2"]
summary_parts.append(f"Grouped by: {gb}")
if "unanswered_only" in meta:
summary_parts.append(f"Unanswered only: {meta['unanswered_only']}")
if summary_parts:
@ -492,10 +617,10 @@ async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
# ------------------------------------------------------------------
elif name == "list_deals":
params: dict[str, Any] = {}
for key in ("deal_type", "stage", "status", "industry", "country", "search"):
for key in _DEAL_STR_FILTERS + ("search",):
if arguments.get(key):
params[key] = arguments[key]
for key in ("year_from", "year_to", "limit", "offset"):
for key in _DEAL_INT_FILTERS + ("limit", "offset"):
if arguments.get(key) is not None:
params[key] = int(arguments[key])
if arguments.get("shared_only"):
@ -511,13 +636,16 @@ async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
# ------------------------------------------------------------------
elif name == "deal_statistics":
params = {}
if arguments.get("group_by"):
params["group_by"] = arguments["group_by"]
if arguments.get("year_from"):
params["year_from"] = int(arguments["year_from"])
if arguments.get("year_to"):
params["year_to"] = int(arguments["year_to"])
params: dict[str, Any] = {}
for key in ("group_by", "group_by_2"):
if arguments.get(key):
params[key] = arguments[key]
for key in _DEAL_STR_FILTERS:
if arguments.get(key):
params[key] = arguments[key]
for key in _DEAL_INT_FILTERS:
if arguments.get(key) is not None:
params[key] = int(arguments[key])
result = await _get("/stats", params)
return await respond(result)