feat: extend MCP server with render/media metadata tools
Added 5 new tools: - get_product_detail: full product with parts, materials, render history, media assets - get_render_detail: complete render metadata for an order line (timing, engine, log) - get_completed_renders: filterable list of completed renders with timing/paths - get_failed_renders: recent failures with error messages - get_media_assets: browse media assets by product/type Total: 17 tools + 2 resources Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+13
-4
@@ -35,18 +35,27 @@ You should see `schaeffler` listed with status "connected".
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `query_database` | Run read-only SQL against PostgreSQL (SELECT only) |
|
||||
| **Orders** | |
|
||||
| `list_orders` | List recent orders with render progress |
|
||||
| `get_order_detail` | Get full order detail with all lines |
|
||||
| `search_products` | Search products by name, PIM-ID, category |
|
||||
| `check_materials` | Find unmapped materials in an order |
|
||||
| `list_materials` | List all library materials (with optional aliases) |
|
||||
| `dispatch_renders` | Dispatch/retry renders for an order |
|
||||
| `set_material_override` | Set material override on all order lines |
|
||||
| **Products & Renders** | |
|
||||
| `search_products` | Search products by name, PIM-ID, category |
|
||||
| `get_product_detail` | Full product detail: metadata, parts, materials, render history, media assets |
|
||||
| `get_render_detail` | Full render metadata for a specific order line: timing, engine, log |
|
||||
| `get_completed_renders` | List completed renders with timing, file paths, filterable by product/order/output type |
|
||||
| `get_failed_renders` | List recently failed renders with error messages |
|
||||
| `get_media_assets` | List media assets (renders, thumbnails, GLBs, USDs) with metadata |
|
||||
| **Materials** | |
|
||||
| `check_materials` | Find unmapped materials in an order |
|
||||
| `list_materials` | List all library materials (with optional aliases) |
|
||||
| **System** | |
|
||||
| `list_output_types` | List all output types with settings |
|
||||
| `get_worker_activity` | Recent STEP processing and render tasks |
|
||||
| `get_render_stats` | Dashboard stats: throughput, coverage, counts |
|
||||
| `get_queue_status` | Live render queue depth and worker status |
|
||||
| `query_database` | Run read-only SQL against PostgreSQL (SELECT only) |
|
||||
|
||||
## Available Resources
|
||||
|
||||
|
||||
@@ -315,6 +315,210 @@ def get_queue_status() -> str:
|
||||
return json.dumps(rows[0] if rows else {}, indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_product_detail(product_id: str) -> str:
|
||||
"""Get detailed information about a product: metadata, CAD file, parts, materials, render history.
|
||||
|
||||
Args:
|
||||
product_id: UUID of the product.
|
||||
"""
|
||||
rows = _db_query("""
|
||||
SELECT p.id, p.name, p.pim_id, p.category_key, p.ebene1, p.ebene2,
|
||||
p.baureihe, p.produkt_baureihe, p.lagertyp, p.name_cad_modell,
|
||||
p.is_active, p.cad_file_id,
|
||||
p.cad_part_materials, p.components,
|
||||
cf.original_name AS step_filename,
|
||||
cf.processing_status AS step_status,
|
||||
cf.parsed_objects,
|
||||
cf.file_hash AS step_hash,
|
||||
p.created_at, p.updated_at
|
||||
FROM products p
|
||||
LEFT JOIN cad_files cf ON cf.id = p.cad_file_id
|
||||
WHERE p.id = %s
|
||||
""", (product_id,))
|
||||
if not rows:
|
||||
return f"Product {product_id} not found."
|
||||
|
||||
product = rows[0]
|
||||
|
||||
# Get render history
|
||||
renders = _db_query("""
|
||||
SELECT ol.id AS line_id, ol.render_status, ol.result_path,
|
||||
ol.material_override, ol.render_started_at, ol.render_completed_at,
|
||||
ol.render_backend_used,
|
||||
ot.name AS output_type_name,
|
||||
o.order_number,
|
||||
ol.render_log->>'engine_used' AS engine,
|
||||
ol.render_log->>'render_duration_s' AS render_duration_s,
|
||||
ol.render_log->>'total_duration_s' AS total_duration_s
|
||||
FROM order_lines ol
|
||||
JOIN orders o ON o.id = ol.order_id
|
||||
LEFT JOIN output_types ot ON ot.id = ol.output_type_id
|
||||
WHERE ol.product_id = %s
|
||||
ORDER BY ol.render_completed_at DESC NULLS LAST
|
||||
LIMIT 20
|
||||
""", (product_id,))
|
||||
|
||||
# Get media assets
|
||||
assets = _db_query("""
|
||||
SELECT id, asset_type, storage_key, file_size_bytes, mime_type, created_at
|
||||
FROM media_assets
|
||||
WHERE product_id = %s
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 20
|
||||
""", (product_id,))
|
||||
|
||||
product["renders"] = renders
|
||||
product["media_assets"] = assets
|
||||
return json.dumps(product, indent=2, default=str)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_render_detail(order_line_id: str) -> str:
|
||||
"""Get full render metadata for a specific order line: timing, engine, materials, log.
|
||||
|
||||
Args:
|
||||
order_line_id: UUID of the order line.
|
||||
"""
|
||||
rows = _db_query("""
|
||||
SELECT ol.id, ol.order_id, ol.product_id, ol.output_type_id,
|
||||
ol.render_status, ol.result_path, ol.material_override,
|
||||
ol.render_started_at, ol.render_completed_at,
|
||||
ol.render_backend_used, ol.render_log,
|
||||
ol.unit_price, ol.item_status,
|
||||
p.name AS product_name, p.pim_id,
|
||||
ot.name AS output_type_name, ot.renderer, ot.output_format,
|
||||
ot.render_settings, ot.material_override AS ot_material_override,
|
||||
o.order_number
|
||||
FROM order_lines ol
|
||||
JOIN products p ON p.id = ol.product_id
|
||||
LEFT JOIN output_types ot ON ot.id = ol.output_type_id
|
||||
JOIN orders o ON o.id = ol.order_id
|
||||
WHERE ol.id = %s
|
||||
""", (order_line_id,))
|
||||
if not rows:
|
||||
return f"Order line {order_line_id} not found."
|
||||
return json.dumps(rows[0], indent=2, default=str)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_completed_renders(
|
||||
product_name: str = "",
|
||||
order_number: str = "",
|
||||
output_type: str = "",
|
||||
limit: int = 20,
|
||||
) -> str:
|
||||
"""List completed renders with metadata: product, output type, timing, file path.
|
||||
|
||||
Args:
|
||||
product_name: Filter by product name (partial match).
|
||||
order_number: Filter by order number (partial match).
|
||||
output_type: Filter by output type name (partial match).
|
||||
limit: Max results (default 20, max 100).
|
||||
"""
|
||||
limit = min(limit, 100)
|
||||
conditions = ["ol.render_status = 'completed'"]
|
||||
params: list = []
|
||||
|
||||
if product_name:
|
||||
conditions.append("p.name ILIKE %s")
|
||||
params.append(f"%{product_name}%")
|
||||
if order_number:
|
||||
conditions.append("o.order_number ILIKE %s")
|
||||
params.append(f"%{order_number}%")
|
||||
if output_type:
|
||||
conditions.append("ot.name ILIKE %s")
|
||||
params.append(f"%{output_type}%")
|
||||
|
||||
where = " AND ".join(conditions)
|
||||
rows = _db_query(f"""
|
||||
SELECT ol.id AS line_id, o.order_number,
|
||||
p.name AS product_name, p.pim_id,
|
||||
ot.name AS output_type_name, ot.output_format,
|
||||
ol.result_path, ol.material_override,
|
||||
ol.render_started_at, ol.render_completed_at,
|
||||
ol.render_backend_used,
|
||||
ol.render_log->>'engine_used' AS engine,
|
||||
ol.render_log->>'render_duration_s' AS render_duration_s,
|
||||
ol.render_log->>'total_duration_s' AS total_duration_s,
|
||||
ol.render_log->>'output_size_bytes' AS output_size_bytes
|
||||
FROM order_lines ol
|
||||
JOIN products p ON p.id = ol.product_id
|
||||
LEFT JOIN output_types ot ON ot.id = ol.output_type_id
|
||||
JOIN orders o ON o.id = ol.order_id
|
||||
WHERE {where}
|
||||
ORDER BY ol.render_completed_at DESC
|
||||
LIMIT %s
|
||||
""", tuple(params) + (limit,))
|
||||
return json.dumps(rows, indent=2, default=str)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_media_assets(
|
||||
product_id: str = "",
|
||||
asset_type: str = "",
|
||||
limit: int = 30,
|
||||
) -> str:
|
||||
"""List media assets (renders, thumbnails, GLBs, USDs) with metadata.
|
||||
|
||||
Args:
|
||||
product_id: Filter by product UUID.
|
||||
asset_type: Filter by type (thumbnail, still, turntable, gltf_geometry, usd_master, blend_production).
|
||||
limit: Max results (default 30, max 100).
|
||||
"""
|
||||
limit = min(limit, 100)
|
||||
conditions = ["1=1"]
|
||||
params: list = []
|
||||
|
||||
if product_id:
|
||||
conditions.append("ma.product_id = %s")
|
||||
params.append(product_id)
|
||||
if asset_type:
|
||||
conditions.append("ma.asset_type = %s")
|
||||
params.append(asset_type)
|
||||
|
||||
where = " AND ".join(conditions)
|
||||
rows = _db_query(f"""
|
||||
SELECT ma.id, ma.asset_type, ma.storage_key,
|
||||
ma.file_size_bytes, ma.mime_type,
|
||||
ma.product_id, p.name AS product_name, p.pim_id,
|
||||
ma.order_line_id, ma.cad_file_id,
|
||||
ma.created_at
|
||||
FROM media_assets ma
|
||||
LEFT JOIN products p ON p.id = ma.product_id
|
||||
WHERE {where}
|
||||
ORDER BY ma.created_at DESC
|
||||
LIMIT %s
|
||||
""", tuple(params) + (limit,))
|
||||
return json.dumps(rows, indent=2, default=str)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_failed_renders(limit: int = 20) -> str:
|
||||
"""List recently failed renders with error details.
|
||||
|
||||
Args:
|
||||
limit: Max results (default 20).
|
||||
"""
|
||||
rows = _db_query("""
|
||||
SELECT ol.id AS line_id, o.order_number,
|
||||
p.name AS product_name, p.pim_id,
|
||||
ot.name AS output_type_name,
|
||||
ol.render_completed_at AS failed_at,
|
||||
ol.render_log->>'error' AS error,
|
||||
ol.render_log->>'engine_used' AS engine,
|
||||
ol.render_backend_used
|
||||
FROM order_lines ol
|
||||
JOIN products p ON p.id = ol.product_id
|
||||
LEFT JOIN output_types ot ON ot.id = ol.output_type_id
|
||||
JOIN orders o ON o.id = ol.order_id
|
||||
WHERE ol.render_status = 'failed'
|
||||
ORDER BY ol.render_completed_at DESC NULLS LAST
|
||||
LIMIT %s
|
||||
""", (min(limit, 50),))
|
||||
return json.dumps(rows, indent=2, default=str)
|
||||
|
||||
|
||||
# ── Resources ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user