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 |
|
| Tool | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `query_database` | Run read-only SQL against PostgreSQL (SELECT only) |
|
| **Orders** | |
|
||||||
| `list_orders` | List recent orders with render progress |
|
| `list_orders` | List recent orders with render progress |
|
||||||
| `get_order_detail` | Get full order detail with all lines |
|
| `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 |
|
| `dispatch_renders` | Dispatch/retry renders for an order |
|
||||||
| `set_material_override` | Set material override on all order lines |
|
| `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 |
|
| `list_output_types` | List all output types with settings |
|
||||||
| `get_worker_activity` | Recent STEP processing and render tasks |
|
| `get_worker_activity` | Recent STEP processing and render tasks |
|
||||||
| `get_render_stats` | Dashboard stats: throughput, coverage, counts |
|
| `get_render_stats` | Dashboard stats: throughput, coverage, counts |
|
||||||
| `get_queue_status` | Live render queue depth and worker status |
|
| `get_queue_status` | Live render queue depth and worker status |
|
||||||
|
| `query_database` | Run read-only SQL against PostgreSQL (SELECT only) |
|
||||||
|
|
||||||
## Available Resources
|
## Available Resources
|
||||||
|
|
||||||
|
|||||||
@@ -315,6 +315,210 @@ def get_queue_status() -> str:
|
|||||||
return json.dumps(rows[0] if rows else {}, indent=2)
|
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 ────────────────────────────────────────────────────────────────
|
# ── Resources ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user