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:
parent
09deafbee9
commit
9716a79cc1
180
server.py
180
server.py
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue