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:
2026-03-15 00:16:48 +01:00
parent 2a9337b8a3
commit 9b54d66322
2 changed files with 217 additions and 4 deletions
+13 -4
View File
@@ -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
+204
View File
@@ -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 ────────────────────────────────────────────────────────────────