{"schema_version":"verify.report.v1","generated_at":"2026-07-31T11:42:38.533758+00:00","snapshot_id":"trustsnap_1a841aa4901b9aec","server":{"namespace":"ai.agenticfabricationnetwork","name":"ufp","title":"Agent Fabrication Network (UFP)","description":"Turn designs into shipped parts: quote 3D printing, CNC, and decals, then check out.","homepage_url":null,"docs_url":null,"icon_url":null,"support_url":null,"remote_url":"https://agenticfabricationnetwork.ai/mcp","server_card_url":null,"latest_version":"0.1.0","current_status":"healthy","current_score":70.09,"transport_type":"streamable-http","has_oauth":false,"has_dcr":false,"has_prompts":false,"tool_count":9,"current_validation_schema_version":"16d1d270090d6c8f","last_validated_at":"2026-07-31T05:28:11.900192+00:00","registry_source":"official_registry","registry_identifier":"ai.agenticfabricationnetwork/ufp","canonical_identifier":"ai.agenticfabricationnetwork/ufp","current_score_components":{"auth_operability_score":2.0,"error_contract_score":2.12,"rate_limit_semantics_score":2.0,"schema_completeness_score":2.0,"backward_compatibility_score":2.0,"slo_health_score":4.0,"security_hygiene_score":2.5,"task_success_score":4.0,"trust_confidence_score":1.75,"abuse_noise_ratio_score":4.0,"prompt_contract_score":2.0,"resource_contract_score":4.0,"discovery_metadata_score":3.0,"registry_consistency_score":2.0,"installability_score":4.0,"session_semantics_score":4.0,"tool_surface_design_score":3.0,"result_shape_stability_score":2.0,"oauth_interop_score":3.0,"recovery_semantics_score":0.0,"maintenance_signal_score":3.0,"adoption_signal_score":2.0,"freshness_confidence_score":3.0,"transport_fidelity_score":4.0,"spec_recency_score":2.0,"session_resume_score":3.0,"step_up_auth_score":3.0,"transport_compliance_score":2.0,"utility_coverage_score":2.0,"advanced_capability_coverage_score":3.0,"connector_publishability_score":3.0,"tool_snapshot_churn_score":3.0,"connector_replay_score":3.0,"request_association_score":3.0,"interactive_flow_safety_score":3.0,"action_safety_score":2.0,"official_registry_presence_score":4.0,"provenance_divergence_score":4.0,"safety_transparency_score":4.0,"tool_capability_clarity_score":3.0,"destructive_operation_safety_score":3.0,"egress_ssrf_resilience_score":3.5,"execution_sandbox_safety_score":2.0,"data_exfiltration_resilience_score":3.0,"least_privilege_scope_score":2.0,"secret_handling_hygiene_score":2.0,"dependency_supply_chain_signal_score":2.5,"input_sanitization_safety_score":3.0,"tool_namespace_clarity_score":4.0},"capability_taxonomy":["development","communication","web","finance","read","write","delete","exec","network","filesystem","admin","secrets","files","other","automation","healthcare","resources","streamable_http"],"machine_summary":{"verdict":"safe_for_evaluation","best_for":["Claude Desktop","Smithery","Generic Streamable HTTP"],"avoid_if":["You need a low-risk tool surface."],"requires_auth":false,"supports_oauth":false,"risk_level":"critical"},"taxonomy_tags":["development","communication","web","finance"],"score_decomposition":[{"key":"access_protocol","label":"Access & Protocol","score":32.0,"max_score":44.0,"hint":"Connectivity, auth, and transport expectations for common clients.","components":[{"key":"auth_operability_score","score":2.0},{"key":"installability_score","score":4.0},{"key":"session_semantics_score","score":4.0},{"key":"transport_fidelity_score","score":4.0},{"key":"spec_recency_score","score":2.0},{"key":"session_resume_score","score":3.0},{"key":"step_up_auth_score","score":3.0},{"key":"transport_compliance_score","score":2.0},{"key":"request_association_score","score":3.0},{"key":"oauth_interop_score","score":3.0},{"key":"least_privilege_scope_score","score":2.0}]},{"key":"interface_quality","label":"Interface Quality","score":35.12,"max_score":56.0,"hint":"How well the tool/resource interface communicates and behaves under automation.","components":[{"key":"error_contract_score","score":2.12},{"key":"rate_limit_semantics_score","score":2.0},{"key":"schema_completeness_score","score":2.0},{"key":"prompt_contract_score","score":2.0},{"key":"resource_contract_score","score":4.0},{"key":"tool_surface_design_score","score":3.0},{"key":"tool_capability_clarity_score","score":3.0},{"key":"tool_namespace_clarity_score","score":4.0},{"key":"result_shape_stability_score","score":2.0},{"key":"utility_coverage_score","score":2.0},{"key":"advanced_capability_coverage_score","score":3.0},{"key":"tool_snapshot_churn_score","score":3.0},{"key":"connector_replay_score","score":3.0},{"key":"recovery_semantics_score","score":0.0}]},{"key":"security_posture","label":"Security Posture","score":24.0,"max_score":36.0,"hint":"How safely the exposed tool surface handles destructive actions, egress, execution, secrets, and risky inputs.","components":[{"key":"security_hygiene_score","score":2.5},{"key":"destructive_operation_safety_score","score":3.0},{"key":"egress_ssrf_resilience_score","score":3.5},{"key":"execution_sandbox_safety_score","score":2.0},{"key":"data_exfiltration_resilience_score","score":3.0},{"key":"secret_handling_hygiene_score","score":2.0},{"key":"input_sanitization_safety_score","score":3.0},{"key":"interactive_flow_safety_score","score":3.0},{"key":"action_safety_score","score":2.0}]},{"key":"reliability_trust","label":"Reliability & Trust","score":18.75,"max_score":24.0,"hint":"Operational stability, consistency, and trustworthiness over time.","components":[{"key":"backward_compatibility_score","score":2.0},{"key":"slo_health_score","score":4.0},{"key":"task_success_score","score":4.0},{"key":"trust_confidence_score","score":1.75},{"key":"abuse_noise_ratio_score","score":4.0},{"key":"freshness_confidence_score","score":3.0}]},{"key":"discovery_governance","label":"Discovery & Governance","score":22.5,"max_score":28.0,"hint":"How well the server is documented, listed, and governed in public registries.","components":[{"key":"discovery_metadata_score","score":3.0},{"key":"registry_consistency_score","score":2.0},{"key":"maintenance_signal_score","score":3.0},{"key":"safety_transparency_score","score":4.0},{"key":"dependency_supply_chain_signal_score","score":2.5},{"key":"official_registry_presence_score","score":4.0},{"key":"provenance_divergence_score","score":4.0}]},{"key":"adoption_market","label":"Adoption & Market","score":5.0,"max_score":8.0,"hint":"Adoption clues and public evidence that the server is intended for external use.","components":[{"key":"adoption_signal_score","score":2.0},{"key":"connector_publishability_score","score":3.0}]}],"validation_diff":null,"tool_snapshot_diff":null,"connector_replay":{"status":"missing","backward_compatible":false,"would_break_after_refresh":false,"added_tools":[],"removed_tools":[],"required_arg_breaks":[],"output_breaks":[],"additive_output_changes":[]},"request_association":{"status":"missing","advertised_capabilities":[],"session_id_present":false,"protocol_version":null,"observed_methods":[],"violating_methods":[],"http_status":null,"issues":[]},"production_readiness":{"code":"safe_for_evaluation","label":"Safe for evaluation","reason":"The server is suitable for evaluation, but remaining gaps should be resolved before broad production use.","badge":"score-medium","critical_alerts":0},"recommended_for":[{"label":"Claude Desktop","reason":"Claude Desktop is marked compatible with score 100."},{"label":"Smithery","reason":"Smithery is marked compatible with score 100."},{"label":"Generic Streamable HTTP","reason":"Generic Streamable HTTP is marked compatible with score 100."}],"history_summary":{"points":[{"timestamp":"2026-07-31T05:28:11.900192+00:00","score":70.09,"status":"healthy","latency_ms":932.24,"tool_count":9,"prompt_count":0,"resource_count":2}],"status_counts":{"healthy":1},"score_delta_7d":null,"score_delta_30d":null,"avg_latency_ms":932.24,"healthy_ratio_recent":1.0,"freshness_hours":6.24,"latest_status":"healthy"},"validation_timeline":[{"timestamp":"2026-07-31T05:28:11.900192+00:00","summary_status":"healthy","score":70.09,"protocol_version":"2025-03-26","auth_mode":"public","tool_count":9,"prompt_count":0,"resource_count":2,"high_risk_tools":2,"safe_to_publish":false,"change_flags":[]}],"evidence_confidence":{"score":76.25,"label":"high","reason":"Based on 1 recent validations, 26 captured checks, and validation age of 6.2 hours.","live_check_count":26,"validation_age_hours":6.24},"incident_feed":[{"type":"validation_snapshot","timestamp":"2026-07-31T05:28:11.900192+00:00","title":"Latest validation: healthy","message":"Score 70.1 with status healthy."}],"remediations":[{"code":"add_confirmation_semantics","severity":"high","title":"Add confirmation and dry-run semantics for risky actions","why":"High-risk write, delete, exec, or egress tools should communicate safeguards clearly.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"fix_transport_compliance","severity":"high","title":"Align session and protocol behavior with Streamable HTTP expectations","why":"Clients increasingly rely on MCP-Protocol-Version, session teardown, and expired-session semantics.","action":"Align MCP-Protocol-Version, MCP-Session-Id, DELETE teardown, and expired-session handling with the transport spec.","playbook":["Return `Mcp-Session-Id` and `Mcp-Protocol-Version` headers consistently on streamable HTTP responses.","Honor `DELETE` session teardown and return `404` when a deleted session is reused.","Reject invalid protocol-version headers with `400 Bad Request`."],"maintainer_context":null},{"code":"enforce_request_association","severity":"high","title":"Associate roots, sampling, and elicitation with active client requests","why":"Modern MCP guidance expects roots, sampling, and elicitation traffic to be tied to an active client request instead of arriving unsolicited on idle sessions.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"publish_oauth_protected_resource","severity":"high","title":"Expose /.well-known/oauth-protected-resource","why":"Without a protected-resource document, OAuth clients cannot discover auth requirements reliably.","action":"Serve /.well-known/oauth-protected-resource and point it at your authorization server metadata.","playbook":["Serve `/.well-known/oauth-protected-resource` from the same host as the MCP endpoint.","Point it at the authorization server metadata URL.","Confirm clients receive consistent auth hints before tool execution."],"maintainer_context":null},{"code":"protect_connector_refreshes","severity":"high","title":"Keep connector refreshes backward compatible","why":"Managed connector clients freeze tool snapshots, so removed tools, new required args, and breaking output changes can break published integrations after refresh.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"publish_oauth_authorization_server","severity":"high","title":"Publish OAuth authorization-server metadata","why":"Clients need authorization-server metadata to discover issuer, endpoints, and DCR support.","action":"Publish /.well-known/oauth-authorization-server from your issuer and include registration_endpoint when supported.","playbook":["Publish `/.well-known/oauth-authorization-server` from the issuer.","Add `registration_endpoint` if DCR is supported.","Verify issuer, authorization, token, and jwks metadata are all reachable."],"maintainer_context":null},{"code":"publish_server_card","severity":"high","title":"Publish a complete server card","why":"Missing or incomplete server-card metadata weakens discovery, documentation, and trust signals.","action":"Serve /.well-known/mcp/server-card.json and include tools, prompts/resources, homepage, and support links.","playbook":["Publish `/.well-known/mcp/server-card.json`.","Include homepage, repository, support, tools, prompts/resources, and auth metadata.","Revalidate the server after publishing the card."],"maintainer_context":null},{"code":"harden_interactive_flows","severity":"high","title":"Stop asking users to paste secrets directly","why":"Public MCP servers should prefer OAuth or browser-based auth guidance over in-band secret collection.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"update_protocol_version","severity":"medium","title":"Adopt a current MCP protocol revision","why":"Older protocol revisions reduce compatibility with newer clients and registry programs.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"improve_connector_publishability","severity":"medium","title":"Close connector-publishing gaps","why":"Connector catalogs care about protocol recency, session behavior, auth clarity, and tool-surface stability.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"tighten_step_up_auth","severity":"medium","title":"Document minimal scopes and return cleaner auth challenges","why":"Modern clients expect granular scopes and step-up auth signals such as WWW-Authenticate scope hints.","action":"Return granular scopes and WWW-Authenticate challenge hints instead of forcing overly broad auth upfront.","playbook":["Advertise the narrowest viable scopes in OAuth metadata.","Return `WWW-Authenticate` challenges with scope or insufficient-scope hints when additional consent is needed.","Revalidate with both public discovery and auth-required flows."],"maintainer_context":null},{"code":"publish_openid_configuration","severity":"medium","title":"Publish OpenID configuration","why":"OIDC metadata improves token validation and client compatibility.","action":"Expose /.well-known/openid-configuration with issuer, jwks_uri, and supported grants.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"stabilize_tool_surface","severity":"medium","title":"Reduce tool-surface churn","why":"Frequent add/remove or output-shape drift makes published connectors and cached tool snapshots brittle.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"fix_prompts_list","severity":"medium","title":"Repair prompts/list or stop advertising prompts","why":"Prompt metadata should either work live or be removed from the advertised capability set.","action":"Only advertise prompts if prompts/list works and prompt arguments are documented.","playbook":["Only advertise prompts that are actually accessible.","Add prompt descriptions and argument docs.","Run a live `prompts/list` check after any prompt changes."],"maintainer_context":null},{"code":"repair_session_resume","severity":"medium","title":"Support resumable HTTP sessions cleanly","why":"Modern MCP clients increasingly expect resumable session behavior on streamable HTTP transports.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"expand_utility_coverage","severity":"low","title":"Expose modern utility surfaces like completions, pagination, or tasks","why":"Utility coverage improves interoperability with larger clients and long-lived agent workflows.","action":"Expose completions, pagination, and task metadata where supported so larger clients can plan and resume work safely.","playbook":["Advertise `completions`, pagination cursors, and `tasks` only when they are actually supported.","Return `nextCursor` on large list operations when pagination is available.","Document task support and whether it requires step-up auth."],"maintainer_context":null},{"code":"expand_advanced_capabilities","severity":"low","title":"Publish newer MCP capability signals","why":"Roots, sampling, elicitation, structured outputs, and related metadata improve client understanding and ranking.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null}],"client_remediation_modes":[{"key":"openai_connectors","label":"ChatGPT custom connector","status":"partial","why_not_ready":[{"label":"OpenAI connectors expect OAuth for remote server auth.","state":"blocked"},{"label":"Dynamic client registration materially improves connector setup.","state":"blocked"},{"label":"search fetch only is not yet satisfied","state":"blocked"},{"label":"oauth configured is not yet satisfied","state":"blocked"},{"label":"admin refresh required is not yet satisfied","state":"blocked"},{"label":"safe for company knowledge is not yet satisfied","state":"blocked"}],"maintainer_context":null},{"key":"claude_desktop","label":"Claude remote MCP","status":"ready","why_not_ready":[{"label":"search fetch only is not yet satisfied","state":"blocked"},{"label":"oauth configured is not yet satisfied","state":"blocked"},{"label":"admin refresh required is not yet satisfied","state":"blocked"},{"label":"safe for company knowledge is not yet satisfied","state":"blocked"},{"label":"safe for messages api remote mcp is not yet satisfied","state":"blocked"},{"label":"Transport compliance issues should be resolved before wider client rollout.","state":"blocked"}],"maintainer_context":null},{"key":"write_safe","label":"Write-safe publishing","status":"blocked","why_not_ready":[{"label":"Add a clearer auth boundary around risky write actions.","state":"blocked"},{"label":"Constrain or sandbox exec-capable tools before publishing broadly.","state":"blocked"}],"maintainer_context":null}],"client_profiles":[{"key":"openai_connectors","label":"OpenAI Connectors","score":77.8,"compatibility":"partial","missing_requirements":["OpenAI connectors expect OAuth for remote server auth.","Dynamic client registration materially improves connector setup."],"snippet":"Connector URL: https://agenticfabricationnetwork.ai/mcp\n# No OAuth metadata detected.\n# Server: ai.agenticfabricationnetwork/ufp"},{"key":"claude_desktop","label":"Claude Desktop","score":100.0,"compatibility":"compatible","missing_requirements":[],"snippet":"{\n  \"mcpServers\": {\n    \"ufp\": {\n      \"command\": \"npx\",\n      \"args\": [\"mcp-remote\", \"https://agenticfabricationnetwork.ai/mcp\"]\n    }\n  }\n}"},{"key":"smithery","label":"Smithery","score":100.0,"compatibility":"compatible","missing_requirements":[],"snippet":"smithery mcp add \"https://agenticfabricationnetwork.ai/mcp\""},{"key":"generic_streamable_http","label":"Generic Streamable HTTP","score":100.0,"compatibility":"compatible","missing_requirements":[],"snippet":"curl -sS https://agenticfabricationnetwork.ai/mcp -H 'content-type: application/json' -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{},\"clientInfo\":{\"name\":\"mcp-verify\",\"version\":\"0.1.0\"}}}'"}],"client_readiness_verdicts":[{"key":"openai_connectors","label":"Client compatibility: ChatGPT","status":"partial","reason":"OpenAI connectors expect OAuth for remote server auth.; Dynamic client registration materially improves connector setup.","evidence":[{"check":"initialize","status":"ok","source":"live_validation","note":null,"http_status":200},{"check":"tools_list","status":"ok","source":"live_validation","note":null,"http_status":200},{"check":"transport_compliance_probe","status":"warning","source":"live_validation","note":null,"http_status":null},{"check":"step_up_auth_probe","status":"missing","source":"live_validation","note":null,"http_status":null},{"check":"connector_replay_probe","status":"missing","source":"live_validation","note":"Frozen tool snapshots must survive refresh.","http_status":null},{"check":"request_association_probe","status":"missing","source":"live_validation","note":"Roots, sampling, and elicitation should stay request-scoped.","http_status":null}],"confidence":{"score":76.25,"label":"high"},"source_resolution":{"winner":"live_validation","supporting_sources":["live_validation","history","server_card"],"disagreements":[]}},{"key":"claude_desktop","label":"Client compatibility: Claude","status":"ready","reason":"No major blockers detected.","evidence":[{"check":"initialize","status":"ok","source":"live_validation","note":null,"http_status":200},{"check":"tools_list","status":"ok","source":"live_validation","note":null,"http_status":200},{"check":"transport_compliance_probe","status":"warning","source":"live_validation","note":null,"http_status":null}],"confidence":{"score":76.25,"label":"high"},"source_resolution":{"winner":"live_validation","supporting_sources":["live_validation","history","server_card"],"disagreements":[]}},{"key":"unsafe_for_write_actions","label":"Write-action publishing","status":"yes","reason":"Blocked until safeguards and confirmation semantics are verified for write, exec, or destructive tools.","evidence":[{"check":"action_safety_probe","status":"error","source":"live_validation","note":null,"http_status":null}],"confidence":{"score":76.25,"label":"high"},"source_resolution":{"winner":"live_validation","supporting_sources":["live_validation","history"],"disagreements":[]}},{"key":"snapshot_churn_risk","label":"Snapshot churn risk","status":"low","reason":"No material tool-surface churn detected in the latest comparison.","evidence":[{"check":"tool_snapshot_probe","status":"missing","source":"live_validation","note":null,"http_status":null},{"check":"connector_replay_probe","status":"missing","source":"live_validation","note":null,"http_status":null}],"confidence":{"score":76.25,"label":"high"},"source_resolution":{"winner":"history","supporting_sources":["history","live_validation"],"disagreements":[]}}],"publishability_policy_profiles":[{"key":"chatgpt_custom_connector","label":"ChatGPT custom connector compatibility","status":"caution","gates":{"search_fetch_only":false,"write_actions_present":true,"oauth_configured":false,"admin_refresh_required":false,"safe_for_company_knowledge":false,"safe_for_messages_api_remote_mcp":false},"reason":"OpenAI connectors expect OAuth for remote server auth.; Dynamic client registration materially improves connector setup."},{"key":"claude_remote_mcp","label":"Claude remote MCP compatibility","status":"ready","gates":{"search_fetch_only":false,"write_actions_present":true,"oauth_configured":false,"admin_refresh_required":false,"safe_for_company_knowledge":false,"safe_for_messages_api_remote_mcp":false},"reason":"Transport, discovery, and remote-MCP assumptions are satisfied."}],"compatibility_fixtures":[{"key":"chatgpt_custom_connector","label":"ChatGPT custom connector fixture","status":"degraded","assumptions":[{"name":"remote_http_endpoint","status":"passes"},{"name":"oauth_discovery","status":"degraded"},{"name":"frozen_tool_snapshot_refresh","status":"passes"},{"name":"request_association","status":"passes"}],"reason":"OpenAI connectors expect OAuth for remote server auth.; Dynamic client registration materially improves connector setup."},{"key":"anthropic_remote_mcp","label":"Anthropic remote MCP fixture","status":"passes","assumptions":[{"name":"remote_transport","status":"passes"},{"name":"tool_discovery","status":"passes"},{"name":"auth_connect","status":"passes"},{"name":"safe_write_review","status":"degraded"}],"reason":"Remote MCP transport and discovery assumptions are satisfied."}],"install_snippets":{"openai_connectors":"Connector URL: https://agenticfabricationnetwork.ai/mcp\n# No OAuth metadata detected.\n# Server: ai.agenticfabricationnetwork/ufp","claude_desktop":"{\n  \"mcpServers\": {\n    \"ufp\": {\n      \"command\": \"npx\",\n      \"args\": [\"mcp-remote\", \"https://agenticfabricationnetwork.ai/mcp\"]\n    }\n  }\n}","smithery":"smithery mcp add \"https://agenticfabricationnetwork.ai/mcp\"","generic_http":"curl -sS https://agenticfabricationnetwork.ai/mcp -H 'content-type: application/json' -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{},\"clientInfo\":{\"name\":\"mcp-verify\",\"version\":\"0.1.0\"}}}'"},"aliases":[{"identifier":"ai.agenticfabricationnetwork/ufp","registry_source":"official_registry","remote_url":"https://agenticfabricationnetwork.ai/mcp","canonical":true,"score":70.09}],"raw_evidence":{"server_identifier":"ai.agenticfabricationnetwork/ufp","remote_url":"https://agenticfabricationnetwork.ai/mcp","server_card_payload":null,"checks":{"server_card":{"status":"error","latency_ms":151.59,"details":{"url":"https://agenticfabricationnetwork.ai/.well-known/mcp/server-card.json","error":"Client error '404 Not Found' for url 'https://agenticfabricationnetwork.ai/.well-known/mcp/server-card.json'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"}},"oauth_protected_resource":{"status":"error","latency_ms":58.29,"details":{"url":"https://agenticfabricationnetwork.ai/.well-known/oauth-protected-resource","error":"Client error '404 Not Found' for url 'https://agenticfabricationnetwork.ai/.well-known/oauth-protected-resource'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"}},"oauth_authorization_server":{"status":"missing","latency_ms":null,"details":{"reason":"no_authorization_server"}},"openid_configuration":{"status":"missing","latency_ms":null,"details":{"reason":"no_authorization_server"}},"initialize":{"status":"ok","latency_ms":137.93,"details":{"url":"https://agenticfabricationnetwork.ai/mcp","payload":{"result":{"protocolVersion":"2025-03-26","capabilities":{"resources":{"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"ufp","version":"0.1.0"},"instructions":"UFP (Universal Fabrication Protocol) turns designs into shipped physical objects.\nFlow: (1) get_fabrication_quote with the design — STL, OBJ, PLY, 3MF, AMF, STEP, or IGES for 3D prints, STEP or IGES for CNC, DXF, STEP, or IGES for sheet metal and laser cutting, artwork (any common image or design file — PNG, JPG, HEIC, TIFF, GIF, BMP, WEBP, AVIF, SVG, PDF, AI, EPS, PSD, CDR; the network converts as needed) for decals/stickers, print, and apparel. Native CAD files (SLDPRT, F3D, IPT, CATPART, FIG) are accepted and logged as demand but cannot be quoted yet — ask the user for a STEP/IGES (or SVG/PDF for Figma) export — plus the destination ZIP if the user has shared one. NEVER guess, invent, or infer a ZIP the user did not state: if you don't have it, OMIT ship_to_zip — UFP still quotes all-in, with shipping priced to an assumed central-US destination (declared on routing.ship_to); relay that prices include shipping to an assumed destination and invite the user's ZIP to make them theirs (refine_quote accepts ship_to_zip later). If the user just drops a file and asks \"how much for 6?\", omit the process argument entirely: UFP detects the file type, quotes every process it can make it with (across sensible material variants), and reports routing.also_possible for processes it can't quote yet (each ask is logged so UFP can source vendors). Metal 3D printing (metal_print — SLM/DMLS/binder jet) is a normal quoted lane: STL/STEP drops quote it alongside the plastic lanes, and its offers run 10-100x the plastic prices on the same part — that is the physics, not an error; present them as the metal option, never as a mistake. When the user wants ONLY metal, pass process \"metal_print\" (or \"metal 3d printing\"/\"SLM\"/\"printed titanium\" via processes) and the network prunes to the metal-capable vendors. Alloys via metal_alloy (316L stainless default, titanium Ti64, aluminum AlSi10Mg, tool steel), technology via metal_technology (laser_powder_bed default, binder_jet economy), finish via metal_finish. Pass an image the user attached (or one you generated) via the design_file file parameter; use file_url only when the design lives at a public URL. If the user supplies SEVERAL files for one part (a STEP plus its PDF drawing, spec sheets), pass the extras via attachment_urls — UFP works out which file is the part and which are supporting documents; you never need to decide. Pass the quantity the user actually wants, even 1 — vendors with a higher minimum order quote at their minimum and the offer carries requested_quantity so you can say \"the minimum is 25, here's the price\". Never inflate the user's ask yourself. Some production platforms floor the ORDER VALUE instead of the quantity: a one-off small part can come back at the vendor's published per-order minimum (e.g. €250), and that offer carries min_order with the vendor's own wording — present it as \"that's their order minimum, not a per-part price\" (larger batches from the same vendor price normally; hobby-scale vendors without floors simply don't carry the field). Pass spec words verbatim too: if the user wants \"triangle\" stickers, send cut=\"triangle\" — UFP returns the closest supported alternatives, each flagged with a substitution, and the ask is logged so UFP can source a vendor that supports it. Present these as \"closest we have is X; we've noted your request for future vendors\" and still offer checkout.\n(2) Optionally refine_quote as the user adds constraints (\"must arrive by Friday\", \"must survive pool water\", \"under $30\"). Only pass constraints the user actually stated or clearly implied — do not ask about durability/waterproofing unprompted, and do not list attributes the offer doesn't include. Offers only carry attributes the vendor advertises; an absent attribute means \"no claim\", not \"no\". Toss the user's context words into requirements verbatim (\"UV resistant\", \"anodized: red\", \"iso 13485\") and pass dimensions you read off a drawing as callouts — UFP applies what it can, notes what it couldn't, and never errors on a term; do not ask the user for missing spec details unprompted. MATERIAL, COLOR, and QUANTITY changes (\"I need it in PLA\", \"make it 5\") are SPEC CHANGES, not constraints: pass them via refine_quote's material/color/quantities args — NOT as requirements terms — and the affected lanes re-price with the new spec through warm vendor sessions. PROCESS NARROWING (\"no, I meant in sheet metal\", \"just the CNC options\") IS a constraint, not a new quote: call refine_quote with processes — friendly terms are fine (\"sheet metal\", \"3d printing\", \"cnc\", \"laser\") — and the other lanes drop while warm vendor sessions survive; \"show everything again\" is simply processes [\"any\"]. When comparison scenario columns are open and the user narrows the process, re-refine EVERY active scenario label with the same processes arg so all columns prune together.\nMATERIALS — before you answer any material-choice, strength, durability, heat, or outdoor question (\"my PLA part broke, what's stronger?\", \"will this survive outdoors?\"), call get_material_guide first: it returns honest, cited material facts — published specs (tensile, heat-deflection, UV/outdoor, chemical) each tagged vendor-cited vs general engineering knowledge, vendor-admitted caveats surfaced FIRST, \"stronger/tougher/hotter than X\" ladders (PLA→PETG→ABS/ASA→nylon/PC→metals), and finish/coating options — so your recommendation is grounded in fact, not memory. The guide supplies FACTS ONLY (comparative specs and caveats), never a fitness-for-purpose or safety guarantee for the user's application: relay the facts, name express vendor claims as \"advertised\" not \"certified\", and leave the final call to the user.\n(3) When the user asks to order and the offer has several shipping speeds, present the REAL options in one breath — each with its cost and arrival (\"standard $5.99 arriving Jul 28, or express $14.99 by Jul 26?\") — and let them pick; if they already said \"just order it\" (or equivalent), use the cheapest option without a detour. Then call create_checkout right away — the address and email can be entered on the secure payment page, so do NOT block checkout to collect them in chat; NEVER ask for them. Pass ship-to details only if the user already provided them. Never collect card details yourself. Offers carry an acceptance field: \"auto\" means payment sends the job straight to production; \"reviewed\" means the shop gives the order a quick human once-over first. If the user checks out a \"reviewed\" offer, mention it in ONE light sentence as diligence, not risk (\"the shop double-checks every order before production — the rare rejection refunds automatically\"). Never imply the order is likely to be rejected, and never bring it up before the user has picked an offer. Offers ALSO carry a fulfillment field: \"connected\" means UFP's checkout reaches that vendor and create_checkout will work; \"not_connected\" means the price is real but the ordering path isn't live yet, and create_checkout WILL refuse it. Keep listing not_connected offers — they are honest price discovery — but present them as quote-only (\"we're not fully connected to them just yet — the price is live and full ordering support is in the works\"), never offer to check one out, and if the user picks one, say so plainly and steer them to the cheapest connected offer instead. Never call create_checkout on a not_connected offer.\n(4) get_order_status any time after.\n(5) After an order arrives, the user may volunteer feedback (\"the stickers came out great\", \"my parts were 2 days late\"). Offer to record it as a vendor review: use list_orders to resolve which order they mean (\"that order from last week\"), structure the feedback into stars + facet scores (quality/timeliness/accuracy), read your draft back to the user for confirmation, then call leave_review. Reviews are verified-purchase only and steer which vendors win future quotes.\nReorders: every paid order emails the buyer a receipt with a durable UFP part number (UFP-…). If the user mentions one — or asks to \"order more of the same\" — call get_fabrication_quote with part_number instead of a design file; UFP reuses the stored design and re-shops it across all current vendors. Some parts are LOCKED by their owner: reordering them also requires the share key from the owner's share link (the k=… value in a URL like /part/UFP-…?k=KEY) — pass it as share_key. If a locked part is rejected, ask the user for the owner's share link.\nPreviews: offers include a product preview image rendered by the vendor; if status is \"pending\", calling refine_quote with only quote_id re-reads the quote and usually returns ready previews.\nQuotes STREAM: a response can come back with status \"quoting\" — the fastest vendors' offers are already in it and more land over the next seconds (routing.expected_vendors lists who is still pricing and a rough wait). Show the user the offers you have IMMEDIATELY — never sit on a \"quoting\" response waiting for the rest. Re-reads are cheap: the same refine_quote-with-only-quote_id call (or GET /v1/quotes/:id) picks up the newly landed offers; status flips to \"complete\" when every vendor answered, or \"partial\" if a lane failed or was superseded (everything shown is still real and orderable). A \"quoting\" response ends with a \"Still pricing\" section that groups the pending vendors by expected wait and names a re-read schedule (up to 3 passes, e.g. \"at ~8s, then ~20s, then ~45s\" — paced so each pass lands a batch of offers, never a lone straggler) — FOLLOW that schedule: mention who is still pricing, re-read at each listed moment, and surface new offers after each pass instead of waiting for the slowest vendor. If the user changes constraints while offers are still landing, just call refine_quote right away — rapid back-and-forth is encouraged, not wasteful. Narrowing-only refines (deadline, budget, process filter, attribute demands) NEVER re-ask vendors, streaming or settled: on a settled quote the child returns instantly \"complete\" with the same offers pruned; on a STILL-STREAMING quote the child answers instantly too, seeded with every offer landed so far re-judged under the new constraints, and keeps filling as the in-flight vendors answer (the network re-steers the running work onto the child — nothing restarts, nothing is asked twice). Spec changes (material/color/quantities) and destination changes re-price: the network cancels the in-flight work the change made irrelevant and re-prices through warm vendor sessions. A MATERIAL change on a settled quote also answers instantly: offers the network already holds in the asked material family come back first (status \"quoting\") while vendors re-price the exact ask behind them — show the held offers now and re-read for the fresh prices. Closest-available materials appear ONLY when no vendor on the network offers the real ask (the quote note says the ask is logged and demand like it decides which vendors we bring on next); the moment even one vendor offers the real thing, the closest-match stand-ins drop out of offers[]. routing.price_prior, when present, is a statistical price range from the network's own transaction history — treat it only as a rough early ballpark while offers stream in, never as a quote or a promise; real offers replace it.\nCOMPARISONS: when the user asks \"how do these compare to X?\" (a different material, quantity, deadline…), call refine_quote with a scenario label per alternative (\"carbon steel\", \"qty 100\") plus the changed spec/constraints — one labeled refine per scenario. Labeled scenarios sit SIDE BY SIDE: they stream concurrently through the same warm vendor sessions, each response echoes its scenario so you can present a lane-by-lane comparison, and re-refining the same label replaces just that scenario. Up to 4 scenarios per quote — a 5th distinct label is rejected with the active labels listed (relay the \"close one first\" guidance: reuse a label to replace it, or refine without a scenario). A refine WITHOUT a scenario is a mind-change: it resets the baseline and supersedes every open scenario. Keep labels short and human.\nTHE PRICE STORY IS PART PRICE + SHIPPING — tell it that way. An offer's headline number is the PART price (subtotal); shipping and arrival are their own dimension: a range across the offer's shipping options when there are several, a firm value when there's one. Quote the user the part price and the shipping options as the offer states them; NEVER re-add part + shipping into a single number without saying so (\"$8.68 all-in with standard shipping\" is honest, a bare \"$8.68\" is not). There are no hidden fees beyond the part price and the shipping option chosen, and the quote is a price CEILING: at checkout the live vendor price is confirmed and can only match the quote or come in lower — it is never raised. In the rare case the vendor repriced upward, the order expires unpaid and you simply re-quote. Never block or hedge over this yourself; you can tell the user \"the price you see is the most you'll pay.\"\nORGANIZE / SORT: when the user asks to re-order the offer cards (\"sort by cheapest\", \"soonest arrival on top\", \"best rated first\"), call refine_quote with sort_by — \"price\" (part price), \"unit_price\", \"total_price\" (part + cheapest shipping), \"arrival\" (soonest possible arrival), \"rating\", or \"recommended\" to restore the default price+speed rank; sort_direction \"desc\" flips a sort (\"most expensive first\"). Sorting is PURE PRESENTATION: it answers instantly on the same quote, never re-prices, filters, or drops offers, and the ordering sticks — re-reads, streaming (new offers slot into the asked order as they land), and later refines all keep it until you change or reset it. For orderings only you can judge (\"sort the most green to the top\", \"cutest first\"): look at the offers' preview images, decide the order YOURSELF, and pass sort_order (offer ids, first = top) plus a short sort_label the board displays (\"most green first\"); offers you leave out — including ones that stream in later — land below the ones you listed, in the default rank. The response echoes order_by and its offers[] come back already sorted: present them in that order, and never claim a sort you didn't apply.\nNear misses: a quote may carry near_misses — REAL vendor bids that missed exactly ONE stated constraint; excluded_by says which one and by how much (earliest_eta/days_late for a deadline, cents_over for a budget, the missing claim for an attribute). They are context, never orderable as-is: create_checkout refuses their ids. Never present a near-miss as a qualifying offer. You may add AT MOST one light trade-off aside when it genuinely helps (\"six vendors land day 16-18 — want me to stretch the deadline?\"), and never relax a constraint the user didn't ask to relax. If the user agrees, relaxing the named constraint via refine_quote converts them into ordinary orderable offers."},"jsonrpc":"2.0","id":1},"http_status":200,"headers":{"content-type":"application/json"}}},"protocol_version_probe":{"status":"warning","latency_ms":null,"details":{"claimed_version":"2025-03-26","validator_protocol_version":"2025-03-26","latest_known_version":"2025-11-25","releases_behind":2,"lag_days":244}},"tools_list":{"status":"ok","latency_ms":66.03,"details":{"url":"https://agenticfabricationnetwork.ai/mcp","payload":{"result":{"tools":[{"name":"get_fabrication_quote","title":"Get fabrication quote","description":"Get ranked, purchasable offers (price, ETA, preview image) for fabricating a physical item from a design file. process=fdm_print for 3D printing a model (STL/OBJ/PLY/3MF/AMF/STEP/IGES), process=cnc or process=sheetmetal for machined/bent metal parts (STEP, IGES, DXF), process=decal for stickers/decals from artwork (any common image or design file — PNG/JPG/HEIC/TIFF/GIF/BMP/WEBP/AVIF/SVG/PDF/AI/EPS/PSD/CDR, auto-converted). A .ufp file (UFP part container: the design plus saved spec/constraints in one) is accepted anywhere a design file is — its saved intent applies automatically and anything the user states now wins. If the user just drops a file and asks for a price, omit process — UFP routes it. Provide the design either as design_file (an image/file the user attached or you generated — preferred) or file_url (a public URL). REORDERS: if the user has a UFP part number (from a receipt email or a previous session, looks like UFP-… or part_…), pass it as part_number INSTEAD of any file — the stored design and spec are reused and re-shopped across all current vendors. Locked parts additionally require share_key (from the owner's share link). Returns offers across vendors like Google Flights returns flights.","inputSchema":{"type":"object","properties":{"process":{"type":"string","enum":["fdm_print","resin_print","metal_print","cnc","sheetmetal","laser_cut","decal","print","apparel"],"description":"What to fabricate. If the user just drops a file and asks for a price, OMIT this — UFP detects the file type (artwork, mesh — STL/OBJ/PLY/3MF/AMF, BREP — STEP/IGES, DXF) and routes it to every process that can make it; the response's routing.also_possible lists processes UFP can't quote yet"},"part_number":{"type":"string","description":"UFP part number from a previous paid order (UFP-… or part_…). Reorders: replaces design_file/file_url entirely; any other fields provided override the stored spec"},"share_key":{"type":"string","description":"Share key for a LOCKED part — the k=… value from the owner's share link (…/part/UFP-…?k=KEY). Required with part_number when the part is locked; omit otherwise"},"design_file":{"anyOf":[{"type":"object","properties":{"file_id":{"type":"string"},"download_url":{"type":"string"},"mime_type":{"type":"string"},"file_name":{"type":"string"}},"required":["file_id","download_url"],"additionalProperties":false},{"type":"string"}],"description":"The design file itself (user-attached or generated image/STL). ChatGPT supplies the file reference automatically when the user attached a file — just bind the attachment here. Other clients may pass a data: URL (base64) or a public https URL string. Preferred over file_url."},"file_url":{"type":"string","format":"uri","description":"Public URL of the design file — only when the design lives at a URL"},"attachment_urls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":10,"description":"Additional files for the SAME part — the PDF drawing next to the STEP, spec sheets, BOMs. Pass everything the user gave you; UFP works out which file is the part and which are supporting documents."},"ship_to_zip":{"type":"string","pattern":"^\\d{5}(-\\d{4})?$","description":"Destination US ZIP code, exactly as the user stated it. OMIT if the user has not given one — NEVER guess or invent a ZIP. Without it, prices come back all-in with shipping to an assumed central-US destination, flagged on routing.ship_to"},"quantities":{"type":"array","items":{"type":"integer","exclusiveMinimum":0},"maxItems":5,"description":"Quantities the user actually wants — pass [1] or [2] if that's the ask. Vendors with a higher minimum quote AT their minimum and the offer notes it (requested_quantity). Default: 25 for decals, 1 for prints; a part_number reorder defaults to the quantity last purchased"},"material":{"type":"string","description":"fdm_print: pla|petg|asa (free-form — \"abs\", \"polycarbonate\" asks resolve to the closest stocked filament with a substitution note); resin_print: standard|tough|high_temp|high_detail resin (free-form asks resolve to the closest supported resin); cnc: aluminum_6061|aluminum_7075|stainless_304|titanium|brass|acetal|delrin|abs|nylon_pa6|peek|acrylic (free-form asks resolve to the closest supported stock); sheetmetal: mild_steel|aluminum_5052|stainless_304 (free-form — \"steel\", \"5052\", \"stainless\" resolve; unknown asks get closest-match offers). Omit to compare the defaults; laser_cut: acrylic|plywood|mdf|delrin|uhmw|hdpe|g10|carbon_fiber|mild_steel|aluminum_5052|stainless_304 (free-form — \"plexiglass\", \"birch\" resolve; unknown asks get closest-match offers); decal: vinyl|holographic|transparent|glitter (free-form — \"chrome\" etc. gets closest matches)"},"color":{"type":"string","description":"fdm_print: Filament color for fdm_print (e.g. black, red); resin_print: Resin color for resin_print (e.g. gray, white, black); print: Print color ask (\"full color\", \"black and white\"). Vendors that only price full color note the substitution; apparel: Garment color for apparel (\"black\", \"heather gray\") — vendor-resolved"},"metal_alloy":{"type":"string","description":"Metal AM alloy: stainless_316l|stainless_17_4ph|titanium_ti64|aluminum_alsi10mg|inconel_718|cobalt_chrome|tool_steel_h13|copper (free-form asks like \"Ti-6Al-4V\" resolve to the closest stocked powder)"},"metal_technology":{"type":"string","description":"Metal AM process: laser_powder_bed (SLM/DMLS/LPBF)|binder_jet|ebm. Omit to let the vendor pick its default for the alloy"},"metal_finish":{"type":"string","description":"Metal AM post-processing: as_printed|bead_blast|machined_critical|polished|heat_treated (as_printed keeps support witness marks)"},"finish":{"type":"string","description":"cnc: CNC surface finish: standard|anodized|polished|bead_blasted (default standard); sheetmetal: as_cut (default) | deburred | powder_coat — powder coat protects mild steel for outdoor use"},"thickness_in":{"type":"number","exclusiveMinimum":0,"description":"sheetmetal: Sheet thickness in inches (e.g. 0.063 = 14 ga aluminum). Quoted at the nearest stocked thickness; omit for ~16 ga default; laser_cut: Nominal thickness in inches; quoted at the vendor's nearest stocked thickness. Omit for the material's common stock"},"bend_count":{"type":"number","exclusiveMinimum":0,"description":"Number of bends in the design (bend lines must be in the file). Prices CNC bending; omit for flat parts"},"units":{"type":"string","description":"sheetmetal: DXF drawing units: \"mm\" (default) or \"in\". STEP files carry their own units; laser_cut: DXF drawing units: \"mm\" (default) or \"in\""},"width_in":{"type":"number","exclusiveMinimum":0,"description":"Decal width in inches (default 3)"},"height_in":{"type":"number","exclusiveMinimum":0,"description":"Decal height in inches (default 3)"},"cut":{"type":"string","description":"Decal cut style — pass the user's words verbatim (\"triangle\", \"die cut\", \"circle\"). Known: contour|square|oval|kiss_cut; anything else gets closest-match offers with a substitution note. Omit to compare the main options"},"product":{"type":"string","description":"print: Print product kind: business_cards|flyers|posters. Omit to quote every kind the artwork fits; apparel: apparel: t_shirt|hoodie|long_sleeve|tank_top (free-form — \"tee\", \"crewneck\" resolve to the closest product)"},"size":{"type":"string","description":"Print size — pass the user's words verbatim (\"letter\", \"24x36\", \"half letter\"). Cards are 3.5x2; flyers 8.5x5.5|8.5x11|11x17; posters 12x18 through 36x48. Unknown sizes get closest-match offers"},"paper":{"type":"string","description":"Paper stock — free-form (\"matte\", \"glossy\", \"recycled\", \"14pt\"). Resolved to the closest stock the vendor runs"},"sides":{"type":"string","description":"single|double sided printing (defaults: cards double, flyers/posters single)"},"placement":{"type":"string","description":"Print placement for apparel: front (default) | back | front_back"},"processes":{"type":"array","items":{"type":"string","minLength":1},"maxItems":8,"description":"Only quote these fabrication processes — a constraint that narrows the auto-routing fan. Friendly terms accepted (\"sheet metal\", \"3d printing\", \"cnc\", \"laser\"); unmappable terms are reported, never an error. Usually OMIT: auto-routing quotes every process that can make the file. Pass it only when the user explicitly limits the process"},"deadline":{"type":"string","description":"Latest acceptable delivery date, ISO YYYY-MM-DD"},"outdoor_rated":{"type":"boolean","description":"Must survive outdoor exposure"},"submersible":{"type":"boolean","description":"Must survive submersion in water"},"high_temp":{"type":"boolean","description":"Must tolerate sustained heat (vendor heat-resistance claim)"},"food_safe":{"type":"boolean","description":"Must be vendor-certified food safe"},"requirements":{"type":"array","items":{"type":"string","minLength":1,"maxLength":120},"maxItems":20,"description":"Verbatim context terms from the user — materials, finishes, certifications, tolerances (\"UV resistant\", \"anodized: red\", \"iso 13485\", \"tolerance: 0.1mm\"). UFP applies what it can and reports what it could not; unresolvable terms are logged for vendor sourcing. Do not interrogate the user to fill this — pass what they said."},"callouts":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"number"},"unit":{"type":"string","enum":["mm","in"]},"tolerance_mm":{"type":"number","exclusiveMinimum":0},"critical":{"type":"boolean"}},"required":["name","value","unit"],"additionalProperties":false},"maxItems":50,"description":"Dimensions you read off the user's drawing that matter (\"hole_diameter 6.5mm ±0.1\"). The tightest tolerance prunes vendors that cannot hold it."},"max_budget_usd":{"type":"number","exclusiveMinimum":0,"description":"Hard budget cap in USD, all-in"},"min_vendor_stars":{"type":"number","minimum":1,"maximum":5,"description":"Only vendors whose displayed review rating is at least this many stars (1-5). Vendors with no reviews yet are excluded — the filter demands demonstrated quality"},"min_vendor_reviews":{"type":"integer","exclusiveMinimum":0,"description":"Only vendors with at least this many verified-purchase reviews"},"proven_spec_orders":{"type":"integer","exclusiveMinimum":0,"description":"Only vendors with at least N completed orders for this exact process+material — \"a vendor that prints PC reliably\""}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"resourceUri":"ui://widget/quote-compare-v4.html"},"openai/outputTemplate":"ui://widget/quote-compare-v4.html","openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Checking fabrication vendors…","openai/toolInvocation/invoked":"Fetched fabrication offers","openai/fileParams":["design_file"]}},{"name":"refine_quote","title":"Refine quote","description":"Narrow an existing quote with new constraints (destination ZIP the user just provided, deadline, outdoor/waterproof/heat/food-safety requirements, budget, vendor reliability, verbatim requirements terms, drawing callouts, or a process filter — \"no, I meant in sheet metal\" → processes) — or CHANGE its spec: material, color, or quantities. Changing material/quantity is a spec change, not a narrowing — pass it via the material/color/quantities args, NOT as a requirements term; the affected lanes re-price with the new spec through warm vendor sessions. For side-by-side comparisons (\"how does carbon steel compare?\"), add a scenario label — labeled refines coexist instead of replacing each other (max 4). \"Organize by\" (\"sort by cheapest\", \"soonest arrival on top\", \"most green first\") is also this tool: sort_by for objective orders, sort_order + sort_label for orders you judged yourself from the previews — presentation only, instant, nothing re-prices. Call with ONLY quote_id to re-read the quote (e.g. to pick up preview images that finished rendering).","inputSchema":{"type":"object","properties":{"quote_id":{"type":"string"},"scenario":{"type":"string","minLength":1,"maxLength":40,"description":"Comparison lane label. A labeled refine sits SIDE BY SIDE with the quote's other offers instead of replacing them: refines with the same label replace that scenario, different labels coexist (max 4 per quote), and a refine WITHOUT a scenario resets the baseline and supersedes every lane. \"How does X compare to Y?\" = one labeled refine per scenario (e.g. scenario \"carbon steel\" + material \"carbon steel\", then scenario \"aluminum\" + material \"aluminum\"). Keep labels short and human, like \"carbon steel\""},"material":{"type":"string","description":"New material for this part (\"I need it in PLA\" → \"pla\"). A spec change: lanes requote with this material through warm vendor sessions; an explicit material here replaces the quote's material fan-out. Free-form — unstocked asks resolve to the closest stocked material with a substitution note. Do NOT pass material changes as requirements terms"},"color":{"type":"string","description":"New color for this part (spec change — lanes requote)"},"finish":{"type":"string","description":"New surface finish (\"powder coat\", \"anodized\", \"bead blasted\") — a spec change: finish-capable lanes (sheet metal, CNC, metal print) requote WITH it; 3D-print lanes have no coating step, so they ignore the finish but keep the COLOR. A colored finish ask (\"black powder coat\") should set BOTH finish and color, so every lane lands on the buyer's color"},"quantities":{"type":"array","items":{"type":"integer","exclusiveMinimum":0},"maxItems":5,"description":"New quantities the user wants (spec change — lanes requote at the new quantity). Same rules as on get_fabrication_quote: pass the real ask, never inflate it"},"processes":{"type":"array","items":{"type":"string","minLength":1},"maxItems":8,"description":"Narrow this quote to specific fabrication processes (\"no, I meant in sheet metal\" → [\"sheet metal\"]). A CONSTRAINT, not a new quote: excluded lanes drop while warm vendor sessions survive, and further refines inherit the filter. Friendly terms accepted (\"sheet metal\", \"3d printing\", \"cnc\", \"laser\"); pass [\"any\"] to clear the filter and show every process again. When comparison scenarios are open and the user narrows the process, re-refine EVERY active scenario label with this same arg so all columns prune together"},"ship_to_zip":{"type":"string","pattern":"^\\d{5}(-\\d{4})?$","description":"Destination US ZIP code, exactly as the user stated it — use this when the user provides their ZIP after a quote priced to the assumed central-US destination (routing.ship_to); the refined prices become theirs. NEVER guess or invent a ZIP"},"deadline":{"type":"string","description":"Latest acceptable delivery date, ISO YYYY-MM-DD"},"outdoor_rated":{"type":"boolean"},"submersible":{"type":"boolean"},"high_temp":{"type":"boolean"},"food_safe":{"type":"boolean"},"requirements":{"type":"array","items":{"type":"string","minLength":1,"maxLength":120},"maxItems":20,"description":"Verbatim context terms from the user — materials, finishes, certifications, tolerances (\"UV resistant\", \"anodized: red\", \"iso 13485\", \"tolerance: 0.1mm\"). UFP applies what it can and reports what it could not; unresolvable terms are logged for vendor sourcing. Do not interrogate the user to fill this — pass what they said."},"callouts":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"number"},"unit":{"type":"string","enum":["mm","in"]},"tolerance_mm":{"type":"number","exclusiveMinimum":0},"critical":{"type":"boolean"}},"required":["name","value","unit"],"additionalProperties":false},"maxItems":50,"description":"Dimensions you read off the user's drawing that matter (\"hole_diameter 6.5mm ±0.1\"). The tightest tolerance prunes vendors that cannot hold it."},"max_budget_usd":{"type":"number","exclusiveMinimum":0},"min_vendor_stars":{"type":"number","minimum":1,"maximum":5,"description":"Only vendors whose displayed review rating is at least this many stars (1-5); vendors with no reviews yet are excluded"},"min_vendor_reviews":{"type":"integer","exclusiveMinimum":0,"description":"Only vendors with at least this many verified-purchase reviews"},"proven_spec_orders":{"type":"integer","exclusiveMinimum":0,"description":"Only vendors with at least N completed orders for this exact process+material — \"a vendor that prints PC reliably\""},"sort_by":{"type":"string","enum":["recommended","price","unit_price","total_price","arrival","rating"],"description":"\"Organize by\": re-order the quote's offer cards (\"sort by cheapest\" → \"price\"; \"soonest arrival first\" → \"arrival\"; \"best rated\" → \"rating\"; \"total_price\" = part + cheapest shipping; \"recommended\" restores the default price+speed rank). PURE PRESENTATION — answers instantly on the same quote, never re-prices or drops offers, and the ordering sticks across re-reads, streaming, and later refines until changed"},"sort_direction":{"type":"string","enum":["asc","desc"],"description":"Flip a sort_by ordering: \"desc\" reverses the key's natural direction (\"most expensive first\" = sort_by \"price\" + \"desc\"). Only meaningful with sort_by"},"sort_order":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":200,"description":"EXPLICIT card order for asks only you can judge (\"sort the most green to the top\"): look at the offers' preview images, decide the order yourself, and pass the offer ids first-on-top. Offers you leave out (including ones that stream in later) land below the listed ones in the default rank. Pass sort_label with it"},"sort_label":{"type":"string","minLength":1,"maxLength":60,"description":"Short human wording for a sort_order, displayed on the board (\"most green first\") — required with sort_order, since only you know what your order means"}},"required":["quote_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"resourceUri":"ui://widget/quote-compare-v4.html"},"openai/outputTemplate":"ui://widget/quote-compare-v4.html","openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Checking fabrication vendors…","openai/toolInvocation/invoked":"Fetched fabrication offers"}},{"name":"poll_quote","title":"Poll quote (board internal)","description":"INTERNAL — the offer board calls this to refresh itself while vendors finish pricing. Never call this yourself: to pick up new offers, use refine_quote with just quote_id.","inputSchema":{"type":"object","properties":{"quote_id":{"type":"string","description":"Quote id the board is watching"}},"required":["quote_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"visibility":["model","app"]},"openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Refreshing offers…","openai/toolInvocation/invoked":"Offers refreshed"}},{"name":"finalize_quote","title":"Finalize quote (wait for remaining vendors)","description":"Step 2 after a quote response says the board is still filling: waits for the remaining vendors to answer (up to ~40s) and returns the final board and offers. Call with just quote_id, immediately after the response that told you to. Never call it otherwise.","inputSchema":{"type":"object","properties":{"quote_id":{"type":"string","description":"Quote id from the still-quoting response"}},"required":["quote_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"resourceUri":"ui://widget/quote-compare-v4.html"},"openai/outputTemplate":"ui://widget/quote-compare-v4.html","openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Waiting for the remaining vendors…","openai/toolInvocation/invoked":"All vendors answered"}},{"name":"create_checkout","title":"Create checkout","description":"Create the order and get a hosted payment link for a chosen offer. Call this as soon as the user picks an offer + shipping speed — do NOT ask for their address first: the secure payment page collects the shipping address and email. Only pass address fields if the user already volunteered them. Returns checkout_url — give it to the user to pay; UFP places the vendor order automatically after payment.","inputSchema":{"type":"object","properties":{"offer_id":{"type":"string"},"shipping_option_id":{"type":"string","description":"One of the offer's shipping option ids"},"name":{"type":"string","description":"Recipient full name (only if the user provided it)"},"street1":{"type":"string","description":"Street address (only if the user provided it)"},"street2":{"type":"string"},"city":{"type":"string"},"state":{"type":"string","minLength":2,"maxLength":2,"description":"Two-letter US state"},"zip":{"type":"string"},"email":{"type":"string","format":"email","description":"Buyer email (only if the user provided it)"},"phone":{"type":"string"},"billing_name":{"type":"string","description":"Billing address, only when the buyer answered the billing question (e.g. said it differs from shipping, or a checkout UI's 'same as shipping' checkbox — then send the shipping values verbatim). Never ask for it; omit and the payment page handles billing."},"billing_street1":{"type":"string"},"billing_street2":{"type":"string"},"billing_city":{"type":"string"},"billing_state":{"type":"string","minLength":2,"maxLength":2,"description":"Two-letter US state"},"billing_zip":{"type":"string"},"return_url":{"type":"string","format":"uri","description":"Exact URL of the page the user is on right now, IF the client application knows it (a web app embedding this tool passes its own page URL). The post-payment page shows a 'Return to <site>' button pointing here. Omit when unknown — never invent one."}},"required":["offer_id","shipping_option_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"visibility":["model","app"]},"openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Creating your checkout…","openai/toolInvocation/invoked":"Checkout created"}},{"name":"get_order_status","title":"Get order status","description":"Check status, ETA and tracking for a UFP order.","inputSchema":{"type":"object","properties":{"order_id":{"type":"string"}},"required":["order_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"visibility":["model","app"]},"openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Checking order status…","openai/toolInvocation/invoked":"Fetched order status"}},{"name":"list_orders","title":"List recent orders","description":"List this agent's recent UFP orders (newest first): order id, what was made, vendor, status, total, ETA, created date. Use it to resolve vague references — \"my sticker order\", \"that order from 6 days ago\" — to a concrete order_id before calling get_order_status or leave_review.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{}},"execution":{"taskSupport":"forbidden"}},{"name":"leave_review","title":"Leave vendor review","description":"Record the user's verified-purchase review of how a UFP order turned out. YOUR JOB IS TO STRUCTURE THE FEEDBACK: turn what the user actually said into stars (1-5 overall) and facet scores — \"the cut was sloppy\" -> quality, \"parts arrived 2 days late\" -> timeliness, \"wrong color\" -> accuracy. Keep the optional comment to a short factual summary of their words; never embellish. ALWAYS draft the review (stars, facets, comment) and confirm it with the user BEFORE calling this tool — e.g. \"I'll rate this 2 stars with timeliness 1 because it arrived 2 days late — send it?\". One review per order: submitting again updates the existing review. Use list_orders first if you need to resolve which order they mean.","inputSchema":{"type":"object","properties":{"order_id":{"type":"string","description":"The UFP order being reviewed (ord_…) — see list_orders"},"stars":{"type":"integer","minimum":1,"maximum":5,"description":"Overall rating the user confirmed, 1-5"},"comment":{"type":"string","maxLength":2000,"description":"Short factual summary of the user's feedback, in their spirit — optional"},"facets":{"type":"object","properties":{"quality":{"type":"integer","minimum":1,"maximum":5,"description":"Build/print/cut quality"},"timeliness":{"type":"integer","minimum":1,"maximum":5,"description":"Delivery speed vs the quoted ETA"},"accuracy":{"type":"integer","minimum":1,"maximum":5,"description":"How well the item matched the ordered spec/design"}},"additionalProperties":false,"description":"Score only the facets the user actually spoke to"},"claims":{"type":"object","properties":{"received_material":{"type":"string","maxLength":80,"description":"Material the user says actually ARRIVED, when they complain it differs from the order (\"they sent aluminum\")"},"received_quantity":{"type":"integer","exclusiveMinimum":0,"description":"Count the user says actually arrived, when it differs from the order"},"delivery_claim":{"type":"string","enum":["not_delivered","late","on_time"],"description":"The user's delivery claim (\"never showed up\" -> not_delivered)"}},"additionalProperties":false,"description":"CHECKABLE claims from the user's feedback — the network stamps each against its own records (the ordered spec, polled tracking + delivery events) and attaches evidence chips to the review. Only pass what the user actually asserted."}},"required":["order_id","stars"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"}},{"name":"get_material_guide","title":"Get material guide","description":"Honest, cited material intelligence for material-choice / strength / durability / heat / outdoor questions (\"my PLA part broke, what's stronger?\", \"will this survive outdoors?\"). Returns caveats-FIRST facts: published specs (tensile, heat-deflection, UV/outdoor, chemical) each tagged vendor-cited vs general engineering knowledge, \"stronger/tougher/hotter than X\" ladders (PLA→PETG→ABS/ASA→nylon/PC→metals), and finish/coating options. Call it BEFORE answering such a question, then relay the facts. It supplies FACTS ONLY — comparative specs and vendor-admitted caveats, never a fitness-for-purpose or safety guarantee (that stays your judgment boundary); name express vendor claims \"advertised\", not \"certified\".","inputSchema":{"type":"object","properties":{"material":{"type":"string","description":"A material the user named or you are weighing (\"PLA\", \"6061 aluminum\", \"stainless\") — free-form; a family ask like \"stainless\" returns the whole family"},"process":{"type":"string","description":"A UFP process to scope to (\"fdm_print\", \"resin_print\", \"cnc\", \"sheetmetal\", \"laser_cut\") — lists the materials that process makes"},"question":{"type":"string","description":"The user's raw question — mined for intent (stronger/tougher, heat/hot, outdoor/UV) so the right ladder is surfaced"}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"visibility":["model","app"]},"openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Looking up material facts…","openai/toolInvocation/invoked":"Fetched material facts"}}]},"jsonrpc":"2.0","id":2},"http_status":200,"headers":{"content-type":"application/json"}}},"prompts_list":{"status":"missing","latency_ms":59.92,"details":{"url":"https://agenticfabricationnetwork.ai/mcp","payload":{"jsonrpc":"2.0","id":3,"error":{"code":-32601,"message":"Method not found"}},"http_status":200,"headers":{"content-type":"application/json"},"reason":"not_supported"}},"prompt_get":{"status":"missing","latency_ms":null,"details":{"reason":"not_advertised"}},"resources_list":{"status":"ok","latency_ms":44.73,"details":{"url":"https://agenticfabricationnetwork.ai/mcp","payload":{"result":{"resources":[{"uri":"ui://widget/quote-compare-v4.html","name":"quote-compare-widget","mimeType":"text/html;profile=mcp-app","description":"UFP offer comparison board"},{"uri":"ufp://materials/guide.md","name":"materials-intelligence","mimeType":"text/markdown","description":"UFP materials-intelligence pack — honest, cited material facts (caveats first), stronger/hotter/outdoor ladders, and a finishes/inspection glossary"}]},"jsonrpc":"2.0","id":5},"http_status":200,"headers":{"content-type":"application/json"}}},"resource_read":{"status":"ok","latency_ms":75.32,"details":{"url":"https://agenticfabricationnetwork.ai/mcp","payload":{"result":{"contents":[{"uri":"ui://widget/quote-compare-v4.html","mimeType":"text/html;profile=mcp-app","text":"<!doctype html>\n<html>\n<head>\n<meta charset=\"utf-8\" />\n<style>\n  :root { color-scheme: light dark; }\n  * { box-sizing: border-box; }\n  body { font-family: system-ui, -apple-system, sans-serif; margin: 0; padding: 12px; background: transparent; }\n  .board { display: flex; flex-direction: column; gap: 10px; }\n  .hdr { display: flex; justify-content: space-between; align-items: baseline; }\n  .hdr h2 { font-size: 15px; margin: 0; }\n  .hdr .sub { font-size: 12px; opacity: .65; }\n  .offer { display: grid; grid-template-columns: 86px 1fr auto; gap: 12px; align-items: center;\n           border: 1px solid rgba(128,128,128,.25); border-radius: 12px; padding: 10px; }\n  .offer.best { border-color: #88d870; box-shadow: 0 0 0 1px #88d87055; }\n  .thumb { width: 86px; height: 86px; border-radius: 8px; object-fit: contain;\n           background: rgba(128,128,128,.12); }\n  .thumb.pending { display:grid; place-items:center; font-size:10px; opacity:.6; text-align:center;\n           animation: pulse 1.4s ease-in-out infinite; }\n  @keyframes pulse { 50% { opacity: .3; } }\n  .meta { min-width: 0; }\n  .meta .spec { font-weight: 600; font-size: 13px; }\n  .meta .vendor { font-size: 12px; opacity: .7; }\n  /* The vendor's own mark beside its name (ALC-46) — the companies the\n     network integrates with, visible right on the board. A failed fetch\n     removes the img (onerror), so the line degrades to the plain name. */\n  .vicon { width: 14px; height: 14px; object-fit: contain; border-radius: 3px;\n           vertical-align: -3px; margin-right: 4px; }\n  /* The little origin flag after the vendor name (ALC-61) — where the\n     part ships from, for buyers who weigh customs (or enjoy watching\n     the world commoditize prices). */\n  .flag { margin-left: 4px; cursor: default; }\n  .rating { color: #d9b53a; white-space: nowrap; }\n  .rating.tappable { cursor: pointer; }\n  .rating .rcount { opacity: .75; }\n  /* The subprocess rating (ALC-38): the deepest niche of THIS offer's\n     spec the vendor has a track record for, right beside the overall\n     stars — \"great shop overall, shaky at flat black specifically\". */\n  .niche { color: #d9b53a; opacity: .85; white-space: nowrap; }\n  .niche .rcount { opacity: .75; }\n  .trend.down { color: #e05d5d; }\n  .trend.up { color: #88d870; }\n  .facets { display: none; font-size: 10px; opacity: .65; margin-top: 2px; }\n  .facets.open { display: block; }\n  .badges { margin-top: 4px; display: flex; gap: 4px; flex-wrap: wrap; }\n  .badge { font-size: 10px; border: 1px solid rgba(128,128,128,.35); border-radius: 99px; padding: 1px 7px; opacity: .8; }\n  .badge.best-badge { background:#88d870; color:#0d2405; border-color:#88d870; opacity:1; }\n  /* Not-orderable: amber like the near-miss \"why\" badge — the card is\n     honest context, not a failure, and must not read as an error. */\n  .badge.unconnected { border-color: #d98c3a88; color: #d98c3a; opacity: 1; }\n  .buy { text-align: right; }\n  .price { font-size: 17px; font-weight: 700; }\n  .unit { font-size: 11px; opacity: .6; }\n  .shipline { font-size: 11px; opacity: .7; margin-top: 2px; white-space: nowrap; }\n  .offer.nearmiss { border-style: dashed; opacity: .88; }\n  /* Placeholder card for a vendor still pricing: the whole card is a\n     loading bar — a solid translucent wash of the brand green that\n     proceeds left-to-right at a CONSTANT rate (linear, one run,\n     forwards fill), slated to go all the way across in the lane's\n     expected wait. The wait is p90 pacing, so the answer beats the\n     bar ~9 times in 10 and the card \"snaps\" into the real offer\n     mid-run. AFN owns the timer: lanes past their deadline are pruned\n     from expected_vendors server-side per read, so a bar that fills\n     completely just sits full until the next poll re-render drops the\n     card — the client never decides a lane is dead. The number is\n     never printed (loading-honesty decision, 2026-07-23). */\n  .offer.quoting { border-style: dashed; position: relative; overflow: hidden; }\n  .offer.quoting .thumb, .offer.quoting .meta { position: relative; }\n  .loadfill { position: absolute; left: 0; top: 0; bottom: 0; width: 0;\n           background: #88d87028;\n           animation-name: loadfill; animation-timing-function: linear;\n           animation-fill-mode: forwards; }\n  @keyframes loadfill { from { width: 0; } to { width: 100%; } }\n  .badge.why { border-color: #d98c3a88; color: #d98c3a; opacity: 1; }\n  .nm-note { font-size: 11px; opacity: .6; margin: -4px 2px 0; line-height: 1.4; }\n  select { font-size: 11px; margin-top: 4px; max-width: 150px; border-radius: 6px;\n           border: 1px solid rgba(128,128,128,.35); background: transparent; color: inherit; padding: 3px; }\n  button { margin-top: 6px; background: #88d870; border: 0; color: #0d2405; font-weight: 600;\n           border-radius: 8px; padding: 6px 14px; font-size: 12px; cursor: pointer; }\n  button.dead { background: transparent; color: inherit; opacity: .5; cursor: not-allowed;\n                border: 1px solid rgba(128,128,128,.35); }\n  button:hover { filter: brightness(1.08); }\n  .group-hdr { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;\n           opacity: .55; margin: 4px 2px -4px; }\n  .also-note { font-size: 11px; opacity: .6; line-height: 1.5; border-top: 1px dashed rgba(128,128,128,.3);\n           padding-top: 6px; }\n  .empty { padding: 24px; text-align: center; opacity: .7; font-size: 13px; }\n  .livediag { font-size: 10px; opacity: .4; margin-top: 6px; font-family: monospace; }\n  /* Pre-mount skeleton (shown from tool-call start until the result lands). */\n  .skelwrap { padding: 4px 0; }\n  .skelcap { font-size: 12px; opacity: .55; margin: 4px 0 10px; }\n  .skel { display: flex; align-items: center; gap: 12px; padding: 12px;\n          border: 1px solid rgba(0,0,0,.07); border-radius: 10px; margin-bottom: 8px; }\n  .skel-img, .skel-line, .skel-price { background: rgba(125,125,125,.16); border-radius: 6px;\n          animation: skelpulse 1.4s ease-in-out infinite; }\n  .skel-img { width: 56px; height: 56px; flex: none; }\n  .skel-lines { flex: 1; }\n  .skel-line { height: 10px; margin: 6px 0; }\n  .skel-line.w30 { width: 30%; } .skel-line.w35 { width: 35%; } .skel-line.w40 { width: 40%; }\n  .skel-line.w45 { width: 45%; } .skel-line.w50 { width: 50%; } .skel-line.w55 { width: 55%; }\n  .skel-line.w60 { width: 60%; } .skel-line.w65 { width: 65%; }\n  .skel-price { width: 64px; height: 22px; flex: none; }\n  .skel:nth-child(3) .skel-img, .skel:nth-child(3) .skel-line, .skel:nth-child(3) .skel-price { animation-delay: .2s; }\n  .skel:nth-child(4) .skel-img, .skel:nth-child(4) .skel-line, .skel:nth-child(4) .skel-price { animation-delay: .4s; }\n  .skel:nth-child(5) .skel-img, .skel:nth-child(5) .skel-line, .skel:nth-child(5) .skel-price { animation-delay: .6s; }\n  @keyframes skelpulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }\n</style>\n</head>\n<body>\n<!-- Pre-mount skeleton: ChatGPT mounts this widget the moment the tool\n     CALL starts, before any result exists — this IS the loading\n     experience (2026-07-26 redesign: the call blocks until the network\n     settles, then this same embed becomes the offer board; no separate\n     loading board, no stacking). Shimmering shapes only — no fake\n     numbers, no fake progress. -->\n<div id=\"root\" class=\"board\">\n  <div class=\"skelwrap\">\n    <div class=\"skelcap\">Pricing your part across the network — offers land here in seconds…</div>\n    <div class=\"skel\"><div class=\"skel-img\"></div><div class=\"skel-lines\"><div class=\"skel-line w60\"></div><div class=\"skel-line w40\"></div></div><div class=\"skel-price\"></div></div>\n    <div class=\"skel\"><div class=\"skel-img\"></div><div class=\"skel-lines\"><div class=\"skel-line w50\"></div><div class=\"skel-line w35\"></div></div><div class=\"skel-price\"></div></div>\n    <div class=\"skel\"><div class=\"skel-img\"></div><div class=\"skel-lines\"><div class=\"skel-line w65\"></div><div class=\"skel-line w45\"></div></div><div class=\"skel-price\"></div></div>\n    <div class=\"skel\"><div class=\"skel-img\"></div><div class=\"skel-lines\"><div class=\"skel-line w55\"></div><div class=\"skel-line w30\"></div></div><div class=\"skel-price\"></div></div>\n  </div>\n</div>\n<script>\n(function () {\n  var root = document.getElementById(\"root\");\n  // pendingSince: vendor|process → first-seen ms — the FALLBACK resume\n  // anchor for absolutes-less payloads (old servers). A lane carrying\n  // wire absolutes (published_at + deadline, with the payload's\n  // served_at) anchors to the server clock instead and never touches\n  // this map.\n  var state = { quote: null, pollTimer: null, polls: 0, pendingSince: {} };\n\n  function api() { return typeof window.openai === \"object\" ? window.openai : null; }\n\n  function money(cents) { return \"$\" + (cents / 100).toFixed(2); }\n\n  function fmtDate(iso) {\n    try {\n      var d = new Date(iso + \"T12:00:00\");\n      return d.toLocaleDateString(undefined, { weekday: \"short\", month: \"short\", day: \"numeric\" });\n    } catch (e) { return iso; }\n  }\n\n  // Buyer-facing name per process for the card's \"type\" bit.\n  var PROCESS_LABELS = { fdm_print: \"FDM 3D print\", resin_print: \"resin print\", cnc: \"CNC\",\n    sheetmetal: \"sheet metal\", laser_cut: \"laser cut\", decal: \"stickers\", print: \"print\", apparel: \"apparel\" };\n  function specLine(offer) {\n    var s = offer.spec_resolved || {};\n    var bits = [];\n    for (var k in s) {\n      // Plumbing never reaches the card: internal keys (note/model/process/\n      // fabricator), vendor-prefixed keys (justway_material_id,\n      // craftcloud_model), id/weight fields, and any prose-length value\n      // (vendor notes ride spec_resolved on some adapters).\n      if (k === \"note\" || k === \"model\" || k === \"process\" || k === \"fabricator\") continue;\n      if (/_id$|_g$/.test(k) || k.indexOf(offer.vendor) === 0 || k.indexOf(\"craftcloud\") === 0) continue;\n      var v = String(s[k]);\n      if (!v || v.length > 40) continue;\n      if (/.(stl|step|stp|obj|3mf|amf|ply|iges|igs|dxf|svg|pdf|ai|eps|png|jpe?g|ufp)$/i.test(v)) continue;\n      if (k === \"material\") v = v.replace(/_/g, \" \");\n      bits.push(v);\n    }\n    bits = bits.slice(0, 3);\n    bits.push(PROCESS_LABELS[offer.process] || offer.process);\n    var qty = offer.quantity + \" × \";\n    if (offer.requested_quantity != null) {\n      qty = offer.quantity + \" × (min. order — you asked for \" + offer.requested_quantity + \") \";\n    }\n    var line = qty + bits.join(\" · \");\n    if (offer.substitutions && offer.substitutions.length) {\n      var subs = [];\n      for (var i = 0; i < offer.substitutions.length; i++) {\n        var sub = offer.substitutions[i];\n        subs.push(sub.requested + \" → \" + sub.provided + \" (closest)\");\n      }\n      line += \" · \" + subs.join(\" · \");\n    }\n    return line;\n  }\n\n  // The price story, same philosophy as the text layer (server quoteResult)\n  // and the make-this cards: headline = PART price; shipping + arrival are\n  // their own dimension — a RANGE across shipping_options when there are\n  // several, a firm value when there's one.\n  function shipSummary(opts) {\n    opts = opts || [];\n    if (!opts.length) return { ship: \"\", eta: \"\" };\n    var minC = Infinity, maxC = -Infinity, minD = null, maxD = null;\n    opts.forEach(function (s) {\n      if (s.cost_cents < minC) minC = s.cost_cents;\n      if (s.cost_cents > maxC) maxC = s.cost_cents;\n      if (minD === null || s.eta_date < minD) minD = s.eta_date;\n      if (maxD === null || s.eta_date > maxD) maxD = s.eta_date;\n    });\n    var ship = minC === maxC\n      ? (minC === 0 ? \"free shipping\" : \"shipping \" + money(minC))\n      : \"shipping \" + (minC === 0 ? \"free\" : money(minC)) + \" \\u2013 \" + money(maxC);\n    var eta = minD === maxD\n      ? \"arrives \" + fmtDate(minD)\n      : \"arrives \" + fmtDate(minD) + \" \\u2013 \" + fmtDate(maxD);\n    return { ship: ship, eta: eta };\n  }\n\n  // Part price + shipping/arrival lines, shared by offer and near-miss cards.\n  function priceStoryHtml(offer) {\n    var sum = shipSummary(offer.shipping_options);\n    return \"<div class='price'>\" + money(offer.subtotal_cents) + \"</div>\" +\n      \"<div class='unit'>\" + money(offer.unit_price_cents) + \"/ea</div>\" +\n      (sum.ship ? \"<div class='shipline'>\" + sum.ship + \"</div>\" : \"\") +\n      (sum.eta ? \"<div class='shipline'>\" + sum.eta + \"</div>\" : \"\");\n  }\n\n  // Why a near-miss sits outside the constraints — derived from the MACHINE\n  // fields (earliest_eta/days_late/cents_over/field), matching the text layer,\n  // with the exact wire detail string as the fallback.\n  function nearMissWhy(ex) {\n    if (!ex) return \"\";\n    if (ex.reason === \"deadline\" && ex.earliest_eta) {\n      return \"arrives \" + fmtDate(ex.earliest_eta) +\n        (ex.days_late != null\n          ? \" \\u2014 \" + ex.days_late + (ex.days_late === 1 ? \" day\" : \" days\") + \" past the deadline\"\n          : \" \\u2014 past the deadline\");\n    }\n    if (ex.reason === \"budget\" && ex.cents_over != null) return money(ex.cents_over) + \" over budget\";\n    if (ex.reason === \"attribute\" && ex.field) return \"no \" + ex.field + \" claim\";\n    return ex.detail || \"\";\n  }\n\n  // Lanes promised in routing.expected_vendors whose offer hasn't landed\n  // yet — the placeholder-card list while the quote streams.\n  function pendingLanes() {\n    var q = state.quote;\n    if (!q || q.status !== \"quoting\" || !q.routing || !q.routing.expected_vendors) return [];\n    var offered = {};\n    (q.offers || []).forEach(function (o) { offered[o.vendor + \"|\" + o.process] = true; });\n    return q.routing.expected_vendors.filter(function (ev) {\n      return !offered[ev.vendor + \"|\" + ev.process];\n    });\n  }\n\n  function prettySlug(s) { return String(s).replace(/[_-]/g, \" \"); }\n\n  function escapeHtml(s) {\n    return String(s).replace(/[&<>\"']/g, function (c) {\n      return { \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" }[c];\n    });\n  }\n\n  // The vendor's mark ahead of its name (ALC-46). Empty string when the\n  // offer carries no logo — the name stands alone, never a broken image.\n  function vendorIconHtml(url) {\n    if (!url) return \"\";\n    return \"<img class='vicon' src='\" + escapeHtml(url) + \"' alt='' onerror='this.remove()'>\";\n  }\n\n  // The little origin flags after the vendor name (ALC-61): regional-\n  // indicator emoji from the wire's ships_from regions (1-2 ISO codes;\n  // \"EU\" bloc = one EU flag for a multi-EU-country network; Xometry\n  // shows US + EU; the lone \"WW\" = a globe for a truly worldwide\n  // network, a curated fact, never a fallback), tooltip = \"ships from\n  // <region> & <region>\" / \"ships worldwide\". Empty string when the\n  // wire carries no regions — an origin nobody researched shows\n  // nothing, never a guessed flag.\n  function flagHtml(shipsFrom) {\n    var codes = (Array.isArray(shipsFrom) ? shipsFrom : [shipsFrom])\n      .filter(function (c) { return c && /^[A-Za-z]{2}$/.test(String(c)); })\n      .slice(0, 2);\n    if (!codes.length) return \"\";\n    var flags = \"\", names = [], world = false;\n    codes.forEach(function (c) {\n      var cc = String(c).toUpperCase();\n      if (cc === \"WW\") {\n        world = true;\n        flags += \"\\uD83C\\uDF0D\";\n        names.push(\"worldwide\");\n        return;\n      }\n      flags += String.fromCodePoint(\n        0x1f1e6 + cc.charCodeAt(0) - 65, 0x1f1e6 + cc.charCodeAt(1) - 65);\n      var name = cc;\n      try { name = new Intl.DisplayNames([\"en\"], { type: \"region\" }).of(cc) || cc; } catch (e) {}\n      names.push(name);\n    });\n    var title = world && codes.length === 1\n      ? \"ships worldwide\" : \"ships from \" + names.join(\" & \");\n    return \"<span class='flag' title='\" + escapeHtml(title) + \"'>\" + flags + \"</span>\";\n  }\n\n  // \"Organize by\": the header names the applied ordering (order_by is\n  // echoed on every read) — same wording the text layer uses, so a\n  // ChatGPT user reads the same story a text-host user does. A custom\n  // order's label is agent-authored free text: escape it.\n  function orderLabel(ob) {\n    if (!ob) return \"ranked by price + speed\";\n    if (ob.custom) return \"sorted by \" + escapeHtml(ob.label || \"custom order\");\n    var names = { price: \"part price\", unit_price: \"per-piece price\",\n      total_price: \"all-in price\", arrival: \"soonest arrival\",\n      rating: \"vendor rating\", recommended: \"price + speed\" };\n    var name = names[ob.key] || escapeHtml(ob.key);\n    return \"sorted by \" + name + (ob.direction === \"desc\" ? \" (reversed)\" : \"\");\n  }\n\n  function render() {\n    var q = state.quote;\n    if (!q) return;\n    root.innerHTML = \"\";\n    if (!q.offers) q.offers = [];\n    var pending = pendingLanes();\n    var hdr = document.createElement(\"div\");\n    hdr.className = \"hdr\";\n    hdr.innerHTML = \"<h2>Fabrication offers</h2><span class='sub'>\" +\n      q.offers.length + \" options · \" + orderLabel(q.order_by) +\n      (pending.length ? \" · \" + pending.length + \" still pricing\" : \"\") + \"</span>\";\n    root.appendChild(hdr);\n\n    var nearMisses = q.near_misses || [];\n    if (q.offers.length === 0 && pending.length === 0) {\n      var empty = document.createElement(\"div\");\n      empty.className = \"empty\";\n      // Only-near-misses is NOT \"no offers\": real bids exist, each one\n      // constraint away — say so and show them below. A still-quoting\n      // board with nothing landed yet is NOT \"no offers\" either — the\n      // network is working; never present in-progress as failure.\n      // Notes are agent-facing wire context, never user copy (founder\n      // call, 2026-07-26 live test) — they stay out of the board.\n      empty.textContent = nearMisses.length\n        ? \"No offers met every constraint \\u2014 these real bids sit just outside:\"\n        : q.status === \"quoting\"\n          ? \"The network is pricing your part \\u2014 offers are seconds away.\"\n          : \"No offers matched.\";\n      root.appendChild(empty);\n      if (!nearMisses.length) return;\n    }\n\n    // Auto-routed quotes can span processes: group with light headers —\n    // unless an \"organize by\" ordering applies. A sort is a GLOBAL\n    // statement (\"cheapest on top\", \"most green first\"); regrouping by\n    // process would quietly break it, so sorted boards render flat in\n    // the server's order.\n    var processes = [];\n    q.offers.forEach(function (o) {\n      if (processes.indexOf(o.process) === -1) processes.push(o.process);\n    });\n    var ordered = q.offers;\n    if (processes.length > 1 && !q.order_by) {\n      ordered = [];\n      processes.forEach(function (p) {\n        ordered.push({ groupHeader: p });\n        q.offers.forEach(function (o) { if (o.process === p) ordered.push(o); });\n      });\n    }\n\n    ordered.forEach(function (offer) {\n      if (offer.groupHeader) {\n        var gh = document.createElement(\"div\");\n        gh.className = \"group-hdr\";\n        gh.textContent = String(offer.groupHeader).replace(/_/g, \" \");\n        root.appendChild(gh);\n        return;\n      }\n      // \"Best value\" is the RECOMMENDED rank's verdict: under a user\n      // ordering the top card is merely first in that sort, not a value\n      // claim — the badge (and green ring) sit out.\n      var best = !q.order_by && offer === q.offers[0];\n      var card = document.createElement(\"div\");\n      card.className = \"offer\" + (best ? \" best\" : \"\");\n\n      var thumb;\n      if (offer.preview && offer.preview.status === \"ready\" && offer.preview.url) {\n        thumb = document.createElement(\"img\");\n        thumb.className = \"thumb\";\n        thumb.src = offer.preview.url;\n        thumb.alt = \"product preview\";\n      } else {\n        thumb = document.createElement(\"div\");\n        thumb.className = \"thumb pending\";\n        thumb.textContent = offer.preview && offer.preview.status === \"pending\"\n          ? \"vendor preview rendering…\" : \"no preview\";\n      }\n      card.appendChild(thumb);\n\n      var meta = document.createElement(\"div\");\n      meta.className = \"meta\";\n      var badges = \"\";\n      if (best) badges += \"<span class='badge best-badge'>Best value</span>\";\n      if (offer.attributes && offer.attributes.outdoor_rated) badges += \"<span class='badge'>outdoor</span>\";\n      if (offer.attributes && offer.attributes.submersible) badges += \"<span class='badge'>waterproof</span>\";\n      if (offer.price_basis === \"estimate\") badges += \"<span class='badge'>estimate</span>\";\n      // Order-value floor: the price IS the vendor's per-order minimum, not a\n      // scaled per-part price — say so with the vendor's own wording (title).\n      if (offer.min_order) {\n        badges += \"<span class='badge' title='\" + String(offer.min_order.detail).replace(/'/g, \"&#39;\") +\n          \"'>vendor order minimum</span>\";\n      }\n      // A price we cannot actually buy. The card stays — it is real price\n      // discovery — but it must never look purchasable, so the badge is\n      // the loud one on the card and the buy affordance goes away.\n      if (offer.fulfillment === \"not_connected\") {\n        badges += \"<span class='badge unconnected' title='The price is live, \" +\n          \"and full ordering support is on the way.'>not orderable yet</span>\";\n      }\n      // Verified-purchase vendor rating; tap/click reveals facet sub-scores\n      // and the full subprocess drill-down (ALC-38). Inline, the deepest\n      // niche of THIS offer's spec with a track record shows beside the\n      // overall stars — the vendor's rating for what is actually being\n      // bought — with a trend marker when its recent reviews drift from\n      // the lifetime average.\n      var rating = offer.vendor_rating;\n      var facetBits = [];\n      if (rating && rating.facets) {\n        if (rating.facets.quality != null) facetBits.push(\"quality \" + Number(rating.facets.quality).toFixed(1));\n        if (rating.facets.timeliness != null) facetBits.push(\"timeliness \" + Number(rating.facets.timeliness).toFixed(1));\n        if (rating.facets.accuracy != null) facetBits.push(\"accuracy \" + Number(rating.facets.accuracy).toFixed(1));\n      }\n      var track = (rating && rating.spec_track) || [];\n      var trackLabel = function (t) {\n        var raw = t.spec_path ? t.spec_path[t.spec_path.length - 1]\n          : t.material != null ? String(t.material) : String(t.process);\n        return raw.replace(/_/g, \" \");\n      };\n      // \\u25BE below / \\u25B4 above the rung's lifetime average by >= 0.3.\n      var trendChar = function (t) {\n        if (t.stars == null || t.recent_stars == null) return \"\";\n        if (t.recent_stars <= t.stars - 0.3) return \"\\u25BE\";\n        if (t.recent_stars >= t.stars + 0.3) return \"\\u25B4\";\n        return \"\";\n      };\n      var trendHtml = function (t) {\n        var c = trendChar(t);\n        if (!c) return \"\";\n        return \"<span class='trend \" + (c === \"\\u25BE\" ? \"down\" : \"up\") +\n          \"' title='recent reviews average \" + Number(t.recent_stars).toFixed(1) + \"\\u2605'>\" + c + \"</span>\";\n      };\n      var trackBits = track.map(function (t) {\n        var num = t.stars != null\n          ? \"\\u2605 \" + Number(t.stars).toFixed(1) + \" (\" + t.orders + \")\"\n          : t.orders + \" order\" + (t.orders === 1 ? \"\" : \"s\");\n        return trackLabel(t) + \" \" + num + trendChar(t);\n      });\n      var niche = null;\n      for (var ti = track.length - 1; ti >= 0; ti--) {\n        if (track[ti].spec_path && track[ti].stars != null) { niche = track[ti]; break; }\n      }\n      var nicheHtml = niche\n        ? \" <span class='niche' title='\" + trackBits.join(\" \\u203A \").replace(/'/g, \"&#39;\") + \"'>\" +\n          trackLabel(niche) + \" \\u2605 \" + Number(niche.stars).toFixed(1) +\n          \" <span class='rcount'>(\" + niche.orders + \")</span>\" + trendHtml(niche) + \"</span>\"\n        : \"\";\n      var revealHtml = (trackBits.length ? \"<div>\" + trackBits.join(\" \\u203A \") + \"</div>\" : \"\") +\n        (facetBits.length ? \"<div>\" + facetBits.join(\" \\u00b7 \") + \"</div>\" : \"\");\n      var ratingHtml = rating\n        ? \" <span class='rating\" + (revealHtml ? \" tappable\" : \"\") + \"'>\\u2605 \" +\n          Number(rating.stars).toFixed(1) + \" <span class='rcount'>(\" + rating.review_count + \")</span></span>\"\n        : \"\";\n      meta.innerHTML = \"<div class='spec'>\" + specLine(offer) + \"</div>\" +\n        \"<div class='vendor'>\" + vendorIconHtml(offer.vendor_logo_url) +\n        offer.vendor_display_name + flagHtml(offer.ships_from) + ratingHtml + nicheHtml + \"</div>\" +\n        (revealHtml ? \"<div class='facets'>\" + revealHtml + \"</div>\" : \"\") +\n        \"<div class='badges'>\" + badges + \"</div>\";\n      if (revealHtml) {\n        var ratingEl = meta.querySelector(\".rating\");\n        var facetsEl = meta.querySelector(\".facets\");\n        if (ratingEl && facetsEl) {\n          ratingEl.addEventListener(\"click\", function () {\n            facetsEl.classList.toggle(\"open\");\n          });\n        }\n      }\n      card.appendChild(meta);\n\n      var buy = document.createElement(\"div\");\n      buy.className = \"buy\";\n      var sel = document.createElement(\"select\");\n      (offer.shipping_options || []).forEach(function (s) {\n        var opt = document.createElement(\"option\");\n        opt.value = s.id;\n        opt.textContent = s.label + \" — \" + (s.cost_cents === 0 ? \"free\" : money(s.cost_cents)) +\n          \" · \" + fmtDate(s.eta_date);\n        sel.appendChild(opt);\n      });\n      // Headline = PART price; shipping + arrival ranges beneath. The\n      // select still picks the concrete speed the Choose button orders.\n      var price = document.createElement(\"div\");\n      price.innerHTML = priceStoryHtml(offer);\n      // \"Checkout\", not \"Choose\": the button IS the path to payment —\n      // zero-friction doctrine (founder call, 2026-07-26 live test).\n      var btn = document.createElement(\"button\");\n      btn.textContent = \"Checkout\";\n      // An unconnected lane has no payment path to send the model down:\n      // the button is dead and says why, rather than starting a checkout\n      // the wire refuses two turns later.\n      if (offer.fulfillment === \"not_connected\") {\n        btn.textContent = \"Can't order yet\";\n        btn.disabled = true;\n        btn.className = \"dead\";\n        btn.title = \"We're not fully connected to \" + offer.vendor_display_name +\n          \" yet — the price is live, but we can't place the order for you just yet. \" +\n          \"We're working hard to bring them fully onto the network.\";\n        sel.disabled = true;\n      }\n      btn.onclick = function () {\n        var a = api();\n        var msg = \"I want offer \" + offer.id + \" with shipping option \\\"\" + sel.value +\n          \"\\\". Create the checkout now and give me the payment link (I'll enter my address on the payment page).\";\n        if (a && typeof a.sendFollowUpMessage === \"function\") {\n          a.sendFollowUpMessage({ prompt: msg });\n        } else if (a && typeof a.sendFollowupTurn === \"function\") {\n          a.sendFollowupTurn({ prompt: msg });\n        }\n      };\n      buy.appendChild(price);\n      buy.appendChild(sel);\n      buy.appendChild(btn);\n      card.appendChild(buy);\n      root.appendChild(card);\n    });\n\n    // Vendors still pricing: one placeholder card per pending lane, the\n    // whole card a constant-rate green fill (p90 pacing: the answer\n    // beats the bar ~9 times in 10). AFN owns every timer; the client\n    // only interpolates wire absolutes: a lane carrying published_at +\n    // deadline (with the payload's served_at as the clock anchor) is\n    // paced by (deadline - published_at) and mounted mid-fill via a\n    // negative animation-delay computed on the SERVER clock — exactly\n    // where the server says the lane is, regardless of the client's\n    // clock. When the vendor answers, the next render replaces the\n    // placeholder with the real offer card — the \"snap\". Overdue lanes\n    // are pruned from expected_vendors server-side per read, so a bar\n    // that fills completely sits full until the next poll re-render\n    // drops the card; the client never decides a lane is dead.\n    // Absolutes-less payloads (old servers) fall back to expected_wait_s\n    // paced from the client-side pendingSince first-seen anchor.\n    var now = Date.now();\n    // Clock offset, computed ONCE per render: client-now minus the\n    // server-now the payload was served at. Subtracting it from\n    // Date.now() re-expresses \"now\" on the server's clock.\n    var servedAtMs = q.served_at ? Date.parse(q.served_at) : NaN;\n    var offsetMs = isNaN(servedAtMs) ? null : now - servedAtMs;\n    var liveKeys = {};\n    if (pending.length) {\n      var phHdr = document.createElement(\"div\");\n      phHdr.className = \"group-hdr\";\n      phHdr.textContent = \"Still pricing\";\n      root.appendChild(phHdr);\n      pending.forEach(function (ev) {\n        // Wire-absolute pacing when the lane + payload carry it: the\n        // server clock alone decides where the bar sits.\n        var durationS = null, delayS = null;\n        if (offsetMs !== null && ev.published_at && ev.deadline) {\n          var pubMs = Date.parse(ev.published_at);\n          var deadMs = Date.parse(ev.deadline);\n          if (!isNaN(pubMs) && !isNaN(deadMs) && deadMs > pubMs) {\n            var serverNowMs = now - offsetMs;\n            durationS = (deadMs - pubMs) / 1000;\n            // Negative delay mounts the bar mid-fill at the server-said\n            // elapsed fraction; clamped so it never starts in the future.\n            delayS = Math.min(0, -((serverNowMs - pubMs) / 1000));\n          }\n        }\n        if (durationS === null) {\n          // Fallback for absolutes-less payloads (old servers): pace by\n          // expected_wait_s, resume across re-renders from the\n          // client-side first-seen anchor.\n          var key = ev.vendor + \"|\" + ev.process;\n          liveKeys[key] = true;\n          if (!state.pendingSince[key]) state.pendingSince[key] = now;\n          var elapsedS = (now - state.pendingSince[key]) / 1000;\n          durationS = ev.expected_wait_s > 0 ? ev.expected_wait_s : 8;\n          delayS = -Math.min(elapsedS, durationS);\n        }\n        var card = document.createElement(\"div\");\n        card.className = \"offer quoting\";\n        var fill = document.createElement(\"div\");\n        fill.className = \"loadfill\";\n        fill.style.animationDuration = durationS + \"s\";\n        fill.style.animationDelay = delayS + \"s\";\n        // No client-side kick: the server prunes overdue lanes from\n        // expected_vendors per read, so a completed bar sits full until\n        // the next poll re-render drops the placeholder. The client\n        // never decides a lane is dead.\n        card.appendChild(fill);\n        var thumb = document.createElement(\"div\");\n        thumb.className = \"thumb pending\";\n        thumb.textContent = \"pricing\\u2026\";\n        card.appendChild(thumb);\n        var meta = document.createElement(\"div\");\n        meta.className = \"meta\";\n        var spec = document.createElement(\"div\");\n        spec.className = \"spec\";\n        // The lane's brand mark while it prices (ALC-46): the buyer sees\n        // WHO is being asked, not just a slug. Missing/failed logo → name alone.\n        if (ev.logo_url) {\n          var icon = document.createElement(\"img\");\n          icon.className = \"vicon\";\n          icon.src = ev.logo_url;\n          icon.alt = \"\";\n          icon.onerror = function () { icon.remove(); };\n          spec.appendChild(icon);\n        }\n        spec.appendChild(document.createTextNode(prettySlug(ev.vendor)));\n        if (ev.ships_from) spec.insertAdjacentHTML(\"beforeend\", flagHtml(ev.ships_from));\n        var vendorLine = document.createElement(\"div\");\n        vendorLine.className = \"vendor\";\n        vendorLine.textContent = prettySlug(ev.process) + \" \\u00b7 quote on the way\";\n        meta.appendChild(spec);\n        meta.appendChild(vendorLine);\n        card.appendChild(meta);\n        // No printed seconds: the wait paces the bar's animation only —\n        // an estimate an animation can absorb becomes a lie in print.\n        root.appendChild(card);\n      });\n    }\n    // Prune snapped/cancelled lanes so a lane that comes back later\n    // (new refine) starts its bar fresh.\n    for (var pk in state.pendingSince) {\n      if (!liveKeys[pk]) delete state.pendingSince[pk];\n    }\n\n    // Near misses: REAL vendor bids that failed exactly ONE stated\n    // constraint — visually separated, reason per card, never orderable\n    // as-is (no Choose button; checkout refuses their ids).\n    if (nearMisses.length) {\n      var nmHdr = document.createElement(\"div\");\n      nmHdr.className = \"group-hdr\";\n      nmHdr.textContent = \"Just outside constraints\";\n      root.appendChild(nmHdr);\n      var nmNote = document.createElement(\"div\");\n      nmNote.className = \"nm-note\";\n      nmNote.textContent = \"Real bids that missed one constraint \\u2014 not orderable as-is; relax that constraint to convert them.\";\n      root.appendChild(nmNote);\n      nearMisses.forEach(function (o) {\n        var card = document.createElement(\"div\");\n        card.className = \"offer nearmiss\";\n        var thumb;\n        if (o.preview && o.preview.status === \"ready\" && o.preview.url) {\n          thumb = document.createElement(\"img\");\n          thumb.className = \"thumb\";\n          thumb.src = o.preview.url;\n          thumb.alt = \"product preview\";\n        } else {\n          thumb = document.createElement(\"div\");\n          thumb.className = \"thumb pending\";\n          thumb.textContent = o.preview && o.preview.status === \"pending\"\n            ? \"vendor preview rendering\\u2026\" : \"no preview\";\n        }\n        card.appendChild(thumb);\n        var meta = document.createElement(\"div\");\n        meta.className = \"meta\";\n        var why = nearMissWhy(o.excluded_by);\n        meta.innerHTML = \"<div class='spec'>\" + specLine(o) + \"</div>\" +\n          \"<div class='vendor'>\" + vendorIconHtml(o.vendor_logo_url) + o.vendor_display_name + flagHtml(o.ships_from) + \"</div>\" +\n          (why ? \"<div class='badges'><span class='badge why'>\" + why + \"</span></div>\" : \"\");\n        card.appendChild(meta);\n        var buy = document.createElement(\"div\");\n        buy.className = \"buy\";\n        buy.innerHTML = priceStoryHtml(o);\n        card.appendChild(buy);\n        root.appendChild(card);\n      });\n    }\n\n    // Processes UFP can't quote yet (auto-routing) — one-line note.\n    if (q.routing && q.routing.also_possible && q.routing.also_possible.length) {\n      var also = document.createElement(\"div\");\n      also.className = \"also-note\";\n      also.textContent = \"Also possible (no vendor yet): \" +\n        q.routing.also_possible.map(function (a) {\n          return String(a.process).replace(/_/g, \" \") + \" — \" + a.reason;\n        }).join(\" · \");\n      root.appendChild(also);\n    }\n\n    // quote.notes are agent-facing wire context (vendor availability,\n    // price-refresh mechanics) — the model relays what's relevant in its\n    // own words; the board never prints them raw (founder call,\n    // 2026-07-26 live test: the strip read as errors shot at the user).\n\n    // Experiment C verdict (2026-07-26, three live rounds): widget-\n    // initiated tool calls never reached the server on either transport\n    // (openai.callTool or the MCP Apps bridge) — today's ChatGPT runtime\n    // doesn't deliver them. The poll machinery above stays dormant for\n    // the day it does; the visible diag line is gone.\n  }\n\n  function hasPendingPreviews() {\n    var q = state.quote;\n    return !!(q && q.offers && q.offers.some(function (o) {\n      return o.preview && o.preview.status === \"pending\";\n    }));\n  }\n\n  function isStillQuoting() {\n    return !!(state.quote && state.quote.status === \"quoting\");\n  }\n\n  // Self-polling, take two (experiment C, 2026-07-26). The old poll went\n  // through refine_quote, whose openai/outputTemplate made ChatGPT stack\n  // a NEW board per pass (\"3 responses climbing to 100%\"). poll_quote\n  // carries no output template: the result flows back through the\n  // callTool promise and THIS board re-renders in place — one embed,\n  // tiles fill live. Polls while quoting or previews are pending; hard\n  // capped; any transport failure ends polling silently (the board then\n  // simply shows the last truth it received — wire bars still animate\n  // from their absolutes).\n  var POLL_MS = 2500;\n  var MAX_POLLS = 24;\n  // live: on-board diagnostics for the self-update channel (experiment C\n  // instrumentation — the widget console is unreachable from the dev\n  // box, so the board itself reports which transport worked / failed).\n  // TODO: strip the visible diag line once the transport is settled.\n  var live = { transport: \"?\", passes: 0, err: \"\" };\n\n  // MCP Apps standard bridge: JSON-RPC request to the host over\n  // postMessage — fallback when window.openai.callTool is absent.\n  var bridgeSeq = 0;\n  var pendingBridge = {};\n  function bridgeCall(name, args) {\n    return new Promise(function (resolve, reject) {\n      var id = \"wpoll_\" + (++bridgeSeq);\n      pendingBridge[id] = { resolve: resolve, reject: reject };\n      try {\n        window.parent.postMessage(\n          { jsonrpc: \"2.0\", id: id, method: \"tools/call\", params: { name: name, arguments: args } },\n          \"*\"\n        );\n      } catch (e) { delete pendingBridge[id]; reject(e); return; }\n      setTimeout(function () {\n        if (pendingBridge[id]) { delete pendingBridge[id]; reject(new Error(\"bridge timeout\")); }\n      }, 8000);\n    });\n  }\n  window.addEventListener(\"message\", function (ev) {\n    var m = ev.data;\n    if (!m || typeof m !== \"object\" || !m.id || !pendingBridge[m.id]) return;\n    var p = pendingBridge[m.id];\n    delete pendingBridge[m.id];\n    if (m.error) p.reject(new Error(m.error.message || \"bridge error\"));\n    else p.resolve(m.result);\n  });\n\n  function callPoll(args) {\n    var a = api();\n    if (a && typeof a.callTool === \"function\") {\n      live.transport = \"openai.callTool\";\n      return a.callTool(\"poll_quote\", args);\n    }\n    live.transport = \"mcp-bridge\";\n    return bridgeCall(\"poll_quote\", args);\n  }\n\n  function maybePoll() {\n    clearTimeout(state.pollTimer);\n    if (!isStillQuoting() && !hasPendingPreviews()) return;\n    if (state.polls >= MAX_POLLS) return;\n    state.pollTimer = setTimeout(function () {\n      state.polls++;\n      live.passes = state.polls;\n      var p;\n      try {\n        p = callPoll({ quote_id: state.quote.quote_id });\n      } catch (e) {\n        live.err = String(e && e.message || e);\n        render();\n        return;\n      }\n      p.then(\n        function (res) {\n          var out = res && (res.structuredContent || res.structured_content);\n          if (!out && res && res.result) out = res.result.structuredContent || res.result.structured_content;\n          if (out && out.quote_id) { state.quote = out; live.err = \"\"; render(); }\n          else { live.err = \"pass \" + state.polls + \": no structuredContent in result\"; render(); }\n          maybePoll();\n        },\n        function (e) {\n          live.err = \"pass \" + state.polls + \": \" + String(e && e.message || e);\n          render();\n          maybePoll();\n        }\n      );\n    }, POLL_MS);\n  }\n\n  function accept(output) {\n    if (output && output.quote_id) {\n      state.quote = output;\n      state.polls = 0;\n      render();\n      maybePoll();\n    }\n  }\n\n  // ChatGPT (window.openai) path.\n  var a = api();\n  if (a && a.toolOutput) accept(a.toolOutput);\n  window.addEventListener(\"openai:set_globals\", function () {\n    var b = api();\n    if (b && b.toolOutput) accept(b.toolOutput);\n  });\n\n  // MCP Apps standard bridge fallback (ui/* JSON-RPC over postMessage).\n  window.addEventListener(\"message\", function (ev) {\n    var m = ev.data;\n    if (!m || typeof m !== \"object\") return;\n    if (m.method === \"ui/notifications/tool-result\" && m.params) {\n      accept(m.params.structuredContent || m.params.structured_content);\n    }\n  });\n})();\n</script>\n</body>\n</html>"}]},"jsonrpc":"2.0","id":6},"http_status":200,"headers":{"content-type":"application/json"},"resource_uri":"ui://widget/quote-compare-v4.html"}},"probe_noise_resilience":{"status":"ok","latency_ms":38.38,"details":{"url":"https://agenticfabricationnetwork.ai/robots.txt","http_status":200,"headers":{"content-type":"text/plain; charset=UTF-8"}}},"determinism_probe":{"status":"ok","latency_ms":75.12,"details":{"attempts":2,"successful":2,"matches":2,"stable_ratio":1.0,"baseline_signature":"cc7f7dbd43d00d9920640fef26e8a20573e9817e8c8f9d0aed4e7a47ff5aad49","errors":[]}},"session_resume_probe":{"status":"warning","latency_ms":null,"details":{"reason":"no_session_id","transport":"streamable-http","resume_expected":true,"protocol_version":"2025-03-26"}},"step_up_auth_probe":{"status":"missing","latency_ms":null,"details":{"oauth_present":false,"auth_required_checks":[],"supported_scopes":[],"scope_specificity_ratio":0.0,"broad_scopes":[],"challenge_headers":[],"step_up_signals":[],"minimal_scope_documented":false}},"transport_compliance_probe":{"status":"warning","latency_ms":45.15,"details":{"transport":"streamable-http","session_id_present":false,"protocol_header_present":false,"last_event_id_visible":false,"requested_protocol_version":"2025-03-26","bad_protocol_status_code":400,"bad_protocol_payload":{"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: Unsupported protocol version: 1999-99-99 (supported versions: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07)"},"id":null},"bad_protocol_headers":{"content-type":"application/json"},"bad_protocol_error":null,"delete_status_code":null,"delete_error":null,"expired_session_status_code":null,"expired_session_error":null,"issues":["missing_session_id","missing_protocol_header"]}},"utility_coverage_probe":{"status":"missing","latency_ms":90.09,"details":{"completions":{"advertised":false,"sample_target":{"type":"resource","uri":"ui://widget/quote-compare-v4.html"},"live_probe":"not_executed"},"pagination":{"supported":false,"next_cursor_methods":[],"metadata_signal":false},"tasks":{"advertised":false,"probe_status":"missing","http_status":200},"initialize_capability_keys":["resources","tools"]}},"advanced_capabilities_probe":{"status":"warning","latency_ms":null,"details":{"capabilities":{"prompts":false,"resources":true,"completions":false,"roots":false,"sampling":false,"elicitation":false,"structured_outputs":false,"resource_links":true},"enabled_count":2,"enabled":["resource_links","resources"],"initialize_capability_keys":["resources","tools"]}},"tool_snapshot_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_historical_snapshot","current_tool_count":9}},"connector_replay_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_historical_snapshot"}},"request_association_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_request_association_capabilities_advertised"}},"interactive_flow_probe":{"status":"missing","latency_ms":null,"details":{"risk_hits":[],"safe_hits":[],"oauth_supported":false,"prompt_available":false}},"action_safety_probe":{"status":"error","latency_ms":null,"details":{"summary":{"tool_count":9,"high_risk_tools":2,"destructive_tools":2,"exec_tools":2,"egress_tools":2,"secret_tools":2,"bulk_access_tools":0,"risk_distribution":{"low":3,"medium":4,"high":0,"critical":2},"capability_distribution":{"read":7,"write":5,"delete":2,"exec":2,"network":2,"filesystem":1,"admin":5,"secrets":2,"other":2}},"auth_present":false,"safeguard_count":1,"confirmation_signals":["get_fabrication_quote","refine_quote","leave_review"]}},"official_registry_probe":{"status":"ok","latency_ms":null,"details":{"registry_source":"official_registry","registry_identifier":"ai.agenticfabricationnetwork/ufp","direct_match":true,"official_peer_count":1}},"provenance_divergence_probe":{"status":"ok","latency_ms":null,"details":{"direct_official_match":true,"registry_title":null,"server_card_title":null,"registry_version":null,"server_card_version":null,"registry_homepage":null,"server_card_homepage":null,"registry_repository":null,"server_card_repository":null,"drift_fields":[],"metadata_document_count":1}},"connector_publishability_probe":{"status":"warning","latency_ms":null,"details":{"transport":"streamable-http","tool_count":9,"high_risk_tools":2,"blockers":["action_safety","server_card"],"criteria":{"remote_transport":true,"initialize":true,"tools_list":true,"protocol_version":true,"session_resume":true,"step_up_auth":true,"transport_compliance":true,"connector_replay":true,"request_association":true,"action_safety":false,"server_card":false,"tool_surface":true,"auth_flow":true}}}},"failures":{"server_card":{"url":"https://agenticfabricationnetwork.ai/.well-known/mcp/server-card.json","error":"Client error '404 Not Found' for url 'https://agenticfabricationnetwork.ai/.well-known/mcp/server-card.json'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"},"oauth_protected_resource":{"url":"https://agenticfabricationnetwork.ai/.well-known/oauth-protected-resource","error":"Client error '404 Not Found' for url 'https://agenticfabricationnetwork.ai/.well-known/oauth-protected-resource'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"},"oauth_authorization_server":{"reason":"no_authorization_server"},"openid_configuration":{"reason":"no_authorization_server"}}},"active_alerts":[],"maintainer_analytics":{"validation_run_count":1,"average_latency_ms":932.24,"healthy_run_ratio_recent":1.0,"registry_presence_count":1,"active_alert_count":0,"watcher_count":0,"verified_claim":false,"taxonomy_tags":["development","communication","web","finance"],"score_trend":[70.09],"remediation_count":17,"high_risk_tool_count":2,"destructive_tool_count":2,"exec_tool_count":2},"public_server_reputation":{"validation_success_ratio_7d":1.0,"validation_success_ratio_30d":1.0,"mean_time_to_recover_hours":null,"breaking_diffs_30d":0,"registry_drift_frequency_30d":0,"snapshot_change_frequency_30d":0},"maintainer_response_quality":{"score":16.67,"signals":{"verified_maintainer_claim":false,"support_contact_present":false,"changelog_present":false,"incident_notes_present":false,"tool_change_documented":true,"annotation_history_present":false},"annotation_count":0,"latest_annotation_at":null},"maintainer_annotations":[],"maintainer_rebuttals":[],"security_posture_summary":{"tool_count":9,"high_risk_tools":2,"destructive_tools":2,"exec_tools":2,"egress_tools":2,"secret_tools":2,"bulk_access_tools":0,"risk_distribution":{"low":3,"medium":4,"high":0,"critical":2},"capability_distribution":{"read":7,"write":5,"delete":2,"exec":2,"network":2,"filesystem":1,"admin":5,"secrets":2,"other":2}},"tool_security_inventory":[{"name":"get_fabrication_quote","description":"Get ranked, purchasable offers (price, ETA, preview image) for fabricating a physical item from a design file. process=fdm_print for 3D printing a model (STL/OBJ/PLY/3MF/AMF/STEP/IGES), process=cnc or process=sheetmetal for machined/bent metal parts (STEP, IGES, DXF), process=decal for stickers/decals from artwork (any common image or design file — PNG/JPG/HEIC/TIFF/GIF/BMP/WEBP/AVIF/SVG/PDF/AI/EPS/PSD/CDR, auto-converted). A .ufp file (UFP part container: the design plus saved spec/constraints in one) is accepted anywhere a design file is — its saved intent applies automatically and anything the user states now wins. If the user just drops a file and asks for a price, omit process — UFP routes it. Provide the design either as design_file (an image/file the user attached or you generated — preferred) or file_url (a public URL). REORDERS: if the user has a UFP part number (from a receipt email or a previous session, looks like UFP-… or part_…), pass it as part_number INSTEAD of any file — the stored design and spec are reused and re-shopped across all current vendors. Locked parts additionally require share_key (from the owner's share link). Returns offers across vendors like Google Flights returns flights.","capabilities":["read","write","delete","exec","network","filesystem","admin","secrets"],"risk_flags":["destructive_operation","command_execution","arbitrary_network_egress","secret_material_access","filesystem_mutation","admin_mutation"],"risk_level":"critical","has_safeguards":false,"capability_taxonomy":["read","write","delete","exec","network","filesystem","admin","secrets","communication","web","files"]},{"name":"refine_quote","description":"Narrow an existing quote with new constraints (destination ZIP the user just provided, deadline, outdoor/waterproof/heat/food-safety requirements, budget, vendor reliability, verbatim requirements terms, drawing callouts, or a process filter — \"no, I meant in sheet metal\" → processes) — or CHANGE its spec: material, color, or quantities. Changing material/quantity is a spec change, not a narrowing — pass it via the material/color/quantities args, NOT as a requirements term; the affected lanes re-price with the new spec through warm vendor sessions. For side-by-side comparisons (\"how does carbon steel compare?\"), add a scenario label — labeled refines coexist instead of replacing each other (max 4). \"Organize by\" (\"sort by cheapest\", \"soonest arrival on top\", \"most green first\") is also this tool: sort_by for objective orders, sort_order + sort_label for orders you judged yourself from the previews — presentation only, instant, nothing re-prices. Call with ONLY quote_id to re-read the quote (e.g. to pick up preview images that finished rendering).","capabilities":["read","write","delete","exec","admin","secrets"],"risk_flags":["destructive_operation","command_execution","secret_material_access","admin_mutation"],"risk_level":"critical","has_safeguards":false,"capability_taxonomy":["read","write","delete","exec","admin","secrets","development"]},{"name":"poll_quote","description":"INTERNAL — the offer board calls this to refresh itself while vendors finish pricing. Never call this yourself: to pick up new offers, use refine_quote with just quote_id.","capabilities":["other"],"risk_flags":[],"risk_level":"low","has_safeguards":false,"capability_taxonomy":["other","development"]},{"name":"finalize_quote","description":"Step 2 after a quote response says the board is still filling: waits for the remaining vendors to answer (up to ~40s) and returns the final board and offers. Call with just quote_id, immediately after the response that told you to. Never call it otherwise.","capabilities":["other"],"risk_flags":[],"risk_level":"low","has_safeguards":false,"capability_taxonomy":["other"]},{"name":"create_checkout","description":"Create the order and get a hosted payment link for a chosen offer. Call this as soon as the user picks an offer + shipping speed — do NOT ask for their address first: the secure payment page collects the shipping address and email. Only pass address fields if the user already volunteered them. Returns checkout_url — give it to the user to pay; UFP places the vendor order automatically after payment.","capabilities":["read","write","network","admin"],"risk_flags":["arbitrary_network_egress","admin_mutation"],"risk_level":"medium","has_safeguards":false,"capability_taxonomy":["read","write","network","admin","communication","web","finance"]},{"name":"get_order_status","description":"Check status, ETA and tracking for a UFP order.","capabilities":["read"],"risk_flags":[],"risk_level":"low","has_safeguards":false,"capability_taxonomy":["read"]},{"name":"list_orders","description":"List this agent's recent UFP orders (newest first): order id, what was made, vendor, status, total, ETA, created date. Use it to resolve vague references — \"my sticker order\", \"that order from 6 days ago\" — to a concrete order_id before calling get_order_status or leave_review.","capabilities":["read","write"],"risk_flags":[],"risk_level":"medium","has_safeguards":false,"capability_taxonomy":["read","write","automation"]},{"name":"leave_review","description":"Record the user's verified-purchase review of how a UFP order turned out. YOUR JOB IS TO STRUCTURE THE FEEDBACK: turn what the user actually said into stars (1-5 overall) and facet scores — \"the cut was sloppy\" -> quality, \"parts arrived 2 days late\" -> timeliness, \"wrong color\" -> accuracy. Keep the optional comment to a short factual summary of their words; never embellish. ALWAYS draft the review (stars, facets, comment) and confirm it with the user BEFORE calling this tool — e.g. \"I'll rate this 2 stars with timeliness 1 because it arrived 2 days late — send it?\". One review per order: submitting again updates the existing review. Use list_orders first if you need to resolve which order they mean.","capabilities":["read","write","admin"],"risk_flags":["admin_mutation"],"risk_level":"medium","has_safeguards":true,"capability_taxonomy":["read","write","admin"]},{"name":"get_material_guide","description":"Honest, cited material intelligence for material-choice / strength / durability / heat / outdoor questions (\"my PLA part broke, what's stronger?\", \"will this survive outdoors?\"). Returns caveats-FIRST facts: published specs (tensile, heat-deflection, UV/outdoor, chemical) each tagged vendor-cited vs general engineering knowledge, \"stronger/tougher/hotter than X\" ladders (PLA→PETG→ABS/ASA→nylon/PC→metals), and finish/coating options. Call it BEFORE answering such a question, then relay the facts. It supplies FACTS ONLY — comparative specs and vendor-admitted caveats, never a fitness-for-purpose or safety guarantee (that stays your judgment boundary); name express vendor claims \"advertised\", not \"certified\".","capabilities":["read","admin"],"risk_flags":[],"risk_level":"medium","has_safeguards":false,"capability_taxonomy":["read","admin","development","healthcare"]}],"transport_compliance":{"status":"warning","transport":"streamable-http","session_id_present":false,"protocol_header_present":false,"last_event_id_visible":false,"bad_protocol_status_code":400,"delete_status_code":null,"expired_session_status_code":null,"issues":["missing_session_id","missing_protocol_header"]},"utility_coverage":{"status":"missing","completions":{"advertised":false,"sample_target":{"type":"resource","uri":"ui://widget/quote-compare-v4.html"},"live_probe":"not_executed"},"pagination":{"supported":false,"next_cursor_methods":[],"metadata_signal":false},"tasks":{"advertised":false,"probe_status":"missing","http_status":200},"initialize_capability_keys":["resources","tools"]},"write_action_governance":{"status":"error","safe_to_publish":false,"auth_boundary":"public_or_unclear","blast_radius":"medium","summary":{"tool_count":9,"high_risk_tools":2,"destructive_tools":2,"exec_tools":2,"egress_tools":2,"secret_tools":2,"bulk_access_tools":0,"risk_distribution":{"low":3,"medium":4,"high":0,"critical":2},"capability_distribution":{"read":7,"write":5,"delete":2,"exec":2,"network":2,"filesystem":1,"admin":5,"secrets":2,"other":2}},"high_risk_tools":[{"name":"get_fabrication_quote","risk_level":"critical","risk_flags":["destructive_operation","command_execution","arbitrary_network_egress","secret_material_access","filesystem_mutation","admin_mutation"],"has_safeguards":false},{"name":"refine_quote","risk_level":"critical","risk_flags":["destructive_operation","command_execution","secret_material_access","admin_mutation"],"has_safeguards":false}],"confirmation_signals":["get_fabrication_quote","refine_quote","leave_review"],"safeguard_count":1},"provenance_divergence":{"status":"ok","direct_official_match":true,"drift_fields":[],"registry_title":null,"server_card_title":null,"registry_version":null,"server_card_version":null,"registry_homepage":null,"server_card_homepage":null},"alias_consolidation":{"canonical_identifier":"ai.agenticfabricationnetwork/ufp","duplicate_count":0,"registry_sources":["official_registry"],"registry_identifiers":["ai.agenticfabricationnetwork/ufp"],"remote_urls":["https://agenticfabricationnetwork.ai/mcp"],"homepages":[],"source_disagreements":[],"source_disagreement_details":{}},"alert_routing":{"active_watch_count":0,"route_counts":{"generic_webhook":0,"slack":0,"teams":0,"email":0},"destinations":[]},"authenticated_validation":{"latest_profile":"remote_mcp","authenticated_session_used":false,"public_score_remains_anonymous":true,"preview_endpoint":"/v1/verify","ci_preview_endpoint":"/v1/ci/preview"},"hosted_runtime":{"schema_version":"verify.hosted_runtime.v1","server":"ai.agenticfabricationnetwork/ufp","tier":"community","readiness":{"allowed_to_activate":true,"blockers":[],"score":70.09,"min_score":70.0,"freshness_hours":6.240469266111111,"max_freshness_hours":168.0,"latest_validation_run_id":"d8eda160-4be1-4501-b3e6-07839cc8b3e2"},"active_deployment_id":null,"active_endpoint_url":null,"deployments":[]},"action_controls_diff":{"snapshot_changed":false,"new_actions":[],"changed_actions":[],"disabled_by_default_candidates":[],"manual_review_candidates":[]},"benchmark_tasks":[{"key":"discover_tools","label":"Discover tools","status":"passes","evidence":[{"check":"initialize","status":"ok","source":"live_validation","note":null,"http_status":200},{"check":"tools_list","status":"ok","source":"live_validation","note":null,"http_status":200}]},{"key":"read_only_fetch_flow","label":"Read-only fetch flow","status":"passes","evidence":[{"check":"resource_read","status":"ok","source":"live_validation","note":"resources/read is a strong read-path signal","http_status":200},{"check":"read_only_tool_surface","status":"ok","source":"derived_tool_inventory","note":"Low-risk read tools were inferred from the current tool surface.","http_status":null}]},{"key":"oauth_required_connect","label":"OAuth-required connect","status":"degraded","evidence":[{"check":"oauth_protected_resource","status":"error","source":"live_validation","note":null,"http_status":null},{"check":"step_up_auth_probe","status":"missing","source":"live_validation","note":null,"http_status":null}]},{"key":"safe_write_flow_with_confirmation","label":"Safe write flow with confirmation","status":"likely_to_fail","evidence":[{"check":"action_safety_probe","status":"error","source":"live_validation","note":null,"http_status":null}]}],"latest_capability_counts":{"tool_count":9,"prompt_count":0,"resource_count":2},"point_loss_breakdown":[{"key":"recovery_semantics_score","label":"Recovery Semantics","score":0.0,"max_score":4.0,"gap":4.0},{"key":"trust_confidence_score","label":"Trust Confidence","score":1.75,"max_score":4.0,"gap":2.25},{"key":"utility_coverage_score","label":"Utility Coverage","score":2.0,"max_score":4.0,"gap":2.0},{"key":"transport_compliance_score","label":"Transport Compliance","score":2.0,"max_score":4.0,"gap":2.0},{"key":"spec_recency_score","label":"Spec Recency","score":2.0,"max_score":4.0,"gap":2.0},{"key":"secret_handling_hygiene_score","label":"Secret Handling Hygiene","score":2.0,"max_score":4.0,"gap":2.0},{"key":"schema_completeness_score","label":"Schema Completeness","score":2.0,"max_score":4.0,"gap":2.0},{"key":"result_shape_stability_score","label":"Result Shape Stability","score":2.0,"max_score":4.0,"gap":2.0},{"key":"registry_consistency_score","label":"Registry Consistency","score":2.0,"max_score":4.0,"gap":2.0},{"key":"rate_limit_semantics_score","label":"Rate Limit Semantics","score":2.0,"max_score":4.0,"gap":2.0},{"key":"prompt_contract_score","label":"Prompt Contract","score":2.0,"max_score":4.0,"gap":2.0},{"key":"least_privilege_scope_score","label":"Least Privilege Scope","score":2.0,"max_score":4.0,"gap":2.0}],"verdict_traces":{"production_readiness":{"code":"safe_for_evaluation","label":"Safe for evaluation","reason":"The server is suitable for evaluation, but remaining gaps should be resolved before broad production use.","confidence":{"score":76.25,"label":"high"},"triggering_alerts":[],"winning_source":"live_validation"},"client_readiness":[{"key":"openai_connectors","status":"partial","reason":"OpenAI connectors expect OAuth for remote server auth.; Dynamic client registration materially improves connector setup.","triggering_checks":["initialize","tools_list","transport_compliance_probe","step_up_auth_probe","connector_replay_probe","request_association_probe"],"confidence":{"score":76.25,"label":"high"},"winning_source":"live_validation","conflicting_sources":[]},{"key":"claude_desktop","status":"ready","reason":"No major blockers detected.","triggering_checks":["initialize","tools_list","transport_compliance_probe"],"confidence":{"score":76.25,"label":"high"},"winning_source":"live_validation","conflicting_sources":[]},{"key":"unsafe_for_write_actions","status":"yes","reason":"Blocked until safeguards and confirmation semantics are verified for write, exec, or destructive tools.","triggering_checks":["action_safety_probe"],"confidence":{"score":76.25,"label":"high"},"winning_source":"live_validation","conflicting_sources":[]},{"key":"snapshot_churn_risk","status":"low","reason":"No material tool-surface churn detected in the latest comparison.","triggering_checks":["tool_snapshot_probe","connector_replay_probe"],"confidence":{"score":76.25,"label":"high"},"winning_source":"history","conflicting_sources":[]}]},"current_snapshot":{"schema_version":"verify.trust_snapshot.v1","snapshot_id":"trustsnap_1a841aa4901b9aec","generated_at":"2026-07-31T11:42:38.309507+00:00","source":"current_snapshot","server":"ai.agenticfabricationnetwork/ufp","last_validated_at":"2026-07-31T05:28:11.900192+00:00","validation_age_hours":6.24,"freshness":{"schema_version":"verify.freshness_profile.v1","last_validated_at":"2026-07-31T05:28:11.900192+00:00","age_hours":6.24,"bucket":"verified_last_24h","label":"Verified in last 24h","badges":["verified_last_24h"],"freshness_sla_hours":168.0,"freshness_sla_status":"met","stale_score_suppressed":false,"display_score":70.09,"raw_score":70.09,"confidence_score":76.2,"confidence_weighted_score":53.4,"tier_status":[{"tier":"community","label":"Community","freshness_sla_hours":720,"met":true,"priority_revalidation":false},{"tier":"pro","label":"Pro","freshness_sla_hours":168,"met":true,"priority_revalidation":true},{"tier":"enterprise","label":"Enterprise","freshness_sla_hours":24,"met":true,"priority_revalidation":true}]},"current_status":"healthy","current_score":70.09,"display_score":70.09,"stale_score_suppressed":false,"production_trust_decision":{"schema_version":"verify.executive_verdict.v1","decision":"Allow with approval","why":"exec-capable tools + no confirmation safeguards + high-risk tools need review","next_action":"export policy, require approval for writes, add authenticated validation","reason_count":2},"production_readiness_class":{"code":"safe_for_evaluation","label":"Safe for evaluation","reason":"The server is suitable for evaluation, but remaining gaps should be resolved before broad production use."},"evidence_confidence":{"score":76.25,"label":"high","validation_age_hours":6.24,"live_check_count":26},"active_alerts":[]},"trust_snapshot":{"schema_version":"verify.trust_snapshot.v1","snapshot_id":"trustsnap_1a841aa4901b9aec","generated_at":"2026-07-31T11:42:38.309507+00:00","source":"current_snapshot","server":"ai.agenticfabricationnetwork/ufp","last_validated_at":"2026-07-31T05:28:11.900192+00:00","validation_age_hours":6.24,"freshness":{"schema_version":"verify.freshness_profile.v1","last_validated_at":"2026-07-31T05:28:11.900192+00:00","age_hours":6.24,"bucket":"verified_last_24h","label":"Verified in last 24h","badges":["verified_last_24h"],"freshness_sla_hours":168.0,"freshness_sla_status":"met","stale_score_suppressed":false,"display_score":70.09,"raw_score":70.09,"confidence_score":76.2,"confidence_weighted_score":53.4,"tier_status":[{"tier":"community","label":"Community","freshness_sla_hours":720,"met":true,"priority_revalidation":false},{"tier":"pro","label":"Pro","freshness_sla_hours":168,"met":true,"priority_revalidation":true},{"tier":"enterprise","label":"Enterprise","freshness_sla_hours":24,"met":true,"priority_revalidation":true}]},"current_status":"healthy","current_score":70.09,"display_score":70.09,"stale_score_suppressed":false,"production_trust_decision":{"schema_version":"verify.executive_verdict.v1","decision":"Allow with approval","why":"exec-capable tools + no confirmation safeguards + high-risk tools need review","next_action":"export policy, require approval for writes, add authenticated validation","reason_count":2},"production_readiness_class":{"code":"safe_for_evaluation","label":"Safe for evaluation","reason":"The server is suitable for evaluation, but remaining gaps should be resolved before broad production use."},"evidence_confidence":{"score":76.25,"label":"high","validation_age_hours":6.24,"live_check_count":26},"active_alerts":[]},"agent_commerce":{"status":"beta","commerce_signal":"strong","payment_execution_detected":"yes","billing_or_usage_detected":"yes","quote_or_pricing_detected":"yes","numeric_price_context":"yes","commercial_quote_context":"yes","checkout_or_charge_detected":"yes","checkout_term_observed":"yes","payment_capable":"observed","payment_rails":["card"],"purchase_stage_supported":["quote","usage_metering","checkout_redirect","payment_initiation","purchase"],"human_confirmation_required":"yes","spending_policy_supported":"yes","receipt_supported":"yes","refund_policy_present":"unknown","operator_identity":"declared","auth_required":"no","auth_scheme":"none","tool_risk_level":"financial_action","tool_risk_score":85,"pricing_transparency":"clear","schema_change_detected":"unknown","delegation_level":"unknown_high_risk","evidence_level":"observed","confidence":"high","highest_risk_tools":[{"name":"get_fabrication_quote","risk_level":"administrative"},{"name":"refine_quote","risk_level":"administrative"},{"name":"create_checkout","risk_level":"financial_action"},{"name":"leave_review","risk_level":"administrative"},{"name":"get_material_guide","risk_level":"administrative"}],"skipped_unsafe_tools":["get_fabrication_quote","refine_quote","create_checkout","leave_review","get_material_guide"],"last_checked_at":"2026-07-31T11:42:38.237775+00:00","evidence":[{"field":"commerce_signal","value":"strong","evidence_level":"observed","source":"tool_schema","matched_terms":["checkout","order","payment","purchase","billing","limit","limits","receipt"],"sample":"get_fabrication_quote","confidence":"high","last_checked_at":"2026-07-31T11:42:38.237775+00:00"},{"field":"payment_rails","value":"card","evidence_level":"observed","source":"tool_schema","matched_terms":["card"],"sample":"card","confidence":"medium","last_checked_at":"2026-07-31T11:42:38.237775+00:00"}],"disclaimer":"Beta assessment. Verify detects evidence and risk signals but does not certify that this server is safe for autonomous purchases.","scores":{"auth_posture_score":10,"tool_risk_score":85,"payment_readiness_score":100,"agent_delegation_safety_score":55,"overall_agent_commerce_score":51},"warnings":["This server appears to expose payment-execution tools without observed authentication. Treat as high risk."],"recommended_fixes":["Require OAuth or equivalent auth for protected tools.","Separate quote/cart tools from charge/settlement tools.","Require explicit user confirmation for payment execution.","Return amount, currency, merchant, and receipt identifiers.","Publish pricing and refund/cancellation policy.","Add operator identity metadata.","Add spending policy support or mandate constraints."]},"latest_claim":null,"maintainer_profile_slug":null,"watch_summary":{"count":0,"teams":[],"emails":[]}},"latest_validation":{"id":"d8eda160-4be1-4501-b3e6-07839cc8b3e2","validation_profile":"remote_mcp","status":"completed","summary_status":"healthy","transport_type":"streamable-http","latency_ms":932.24,"failures":{"server_card":{"url":"https://agenticfabricationnetwork.ai/.well-known/mcp/server-card.json","error":"Client error '404 Not Found' for url 'https://agenticfabricationnetwork.ai/.well-known/mcp/server-card.json'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"},"oauth_protected_resource":{"url":"https://agenticfabricationnetwork.ai/.well-known/oauth-protected-resource","error":"Client error '404 Not Found' for url 'https://agenticfabricationnetwork.ai/.well-known/oauth-protected-resource'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"},"oauth_authorization_server":{"reason":"no_authorization_server"},"openid_configuration":{"reason":"no_authorization_server"}},"checks":{"server_card":{"status":"error","latency_ms":151.59,"details":{"url":"https://agenticfabricationnetwork.ai/.well-known/mcp/server-card.json","error":"Client error '404 Not Found' for url 'https://agenticfabricationnetwork.ai/.well-known/mcp/server-card.json'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"}},"oauth_protected_resource":{"status":"error","latency_ms":58.29,"details":{"url":"https://agenticfabricationnetwork.ai/.well-known/oauth-protected-resource","error":"Client error '404 Not Found' for url 'https://agenticfabricationnetwork.ai/.well-known/oauth-protected-resource'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"}},"oauth_authorization_server":{"status":"missing","latency_ms":null,"details":{"reason":"no_authorization_server"}},"openid_configuration":{"status":"missing","latency_ms":null,"details":{"reason":"no_authorization_server"}},"initialize":{"status":"ok","latency_ms":137.93,"details":{"url":"https://agenticfabricationnetwork.ai/mcp","payload":{"result":{"protocolVersion":"2025-03-26","capabilities":{"resources":{"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"ufp","version":"0.1.0"},"instructions":"UFP (Universal Fabrication Protocol) turns designs into shipped physical objects.\nFlow: (1) get_fabrication_quote with the design — STL, OBJ, PLY, 3MF, AMF, STEP, or IGES for 3D prints, STEP or IGES for CNC, DXF, STEP, or IGES for sheet metal and laser cutting, artwork (any common image or design file — PNG, JPG, HEIC, TIFF, GIF, BMP, WEBP, AVIF, SVG, PDF, AI, EPS, PSD, CDR; the network converts as needed) for decals/stickers, print, and apparel. Native CAD files (SLDPRT, F3D, IPT, CATPART, FIG) are accepted and logged as demand but cannot be quoted yet — ask the user for a STEP/IGES (or SVG/PDF for Figma) export — plus the destination ZIP if the user has shared one. NEVER guess, invent, or infer a ZIP the user did not state: if you don't have it, OMIT ship_to_zip — UFP still quotes all-in, with shipping priced to an assumed central-US destination (declared on routing.ship_to); relay that prices include shipping to an assumed destination and invite the user's ZIP to make them theirs (refine_quote accepts ship_to_zip later). If the user just drops a file and asks \"how much for 6?\", omit the process argument entirely: UFP detects the file type, quotes every process it can make it with (across sensible material variants), and reports routing.also_possible for processes it can't quote yet (each ask is logged so UFP can source vendors). Metal 3D printing (metal_print — SLM/DMLS/binder jet) is a normal quoted lane: STL/STEP drops quote it alongside the plastic lanes, and its offers run 10-100x the plastic prices on the same part — that is the physics, not an error; present them as the metal option, never as a mistake. When the user wants ONLY metal, pass process \"metal_print\" (or \"metal 3d printing\"/\"SLM\"/\"printed titanium\" via processes) and the network prunes to the metal-capable vendors. Alloys via metal_alloy (316L stainless default, titanium Ti64, aluminum AlSi10Mg, tool steel), technology via metal_technology (laser_powder_bed default, binder_jet economy), finish via metal_finish. Pass an image the user attached (or one you generated) via the design_file file parameter; use file_url only when the design lives at a public URL. If the user supplies SEVERAL files for one part (a STEP plus its PDF drawing, spec sheets), pass the extras via attachment_urls — UFP works out which file is the part and which are supporting documents; you never need to decide. Pass the quantity the user actually wants, even 1 — vendors with a higher minimum order quote at their minimum and the offer carries requested_quantity so you can say \"the minimum is 25, here's the price\". Never inflate the user's ask yourself. Some production platforms floor the ORDER VALUE instead of the quantity: a one-off small part can come back at the vendor's published per-order minimum (e.g. €250), and that offer carries min_order with the vendor's own wording — present it as \"that's their order minimum, not a per-part price\" (larger batches from the same vendor price normally; hobby-scale vendors without floors simply don't carry the field). Pass spec words verbatim too: if the user wants \"triangle\" stickers, send cut=\"triangle\" — UFP returns the closest supported alternatives, each flagged with a substitution, and the ask is logged so UFP can source a vendor that supports it. Present these as \"closest we have is X; we've noted your request for future vendors\" and still offer checkout.\n(2) Optionally refine_quote as the user adds constraints (\"must arrive by Friday\", \"must survive pool water\", \"under $30\"). Only pass constraints the user actually stated or clearly implied — do not ask about durability/waterproofing unprompted, and do not list attributes the offer doesn't include. Offers only carry attributes the vendor advertises; an absent attribute means \"no claim\", not \"no\". Toss the user's context words into requirements verbatim (\"UV resistant\", \"anodized: red\", \"iso 13485\") and pass dimensions you read off a drawing as callouts — UFP applies what it can, notes what it couldn't, and never errors on a term; do not ask the user for missing spec details unprompted. MATERIAL, COLOR, and QUANTITY changes (\"I need it in PLA\", \"make it 5\") are SPEC CHANGES, not constraints: pass them via refine_quote's material/color/quantities args — NOT as requirements terms — and the affected lanes re-price with the new spec through warm vendor sessions. PROCESS NARROWING (\"no, I meant in sheet metal\", \"just the CNC options\") IS a constraint, not a new quote: call refine_quote with processes — friendly terms are fine (\"sheet metal\", \"3d printing\", \"cnc\", \"laser\") — and the other lanes drop while warm vendor sessions survive; \"show everything again\" is simply processes [\"any\"]. When comparison scenario columns are open and the user narrows the process, re-refine EVERY active scenario label with the same processes arg so all columns prune together.\nMATERIALS — before you answer any material-choice, strength, durability, heat, or outdoor question (\"my PLA part broke, what's stronger?\", \"will this survive outdoors?\"), call get_material_guide first: it returns honest, cited material facts — published specs (tensile, heat-deflection, UV/outdoor, chemical) each tagged vendor-cited vs general engineering knowledge, vendor-admitted caveats surfaced FIRST, \"stronger/tougher/hotter than X\" ladders (PLA→PETG→ABS/ASA→nylon/PC→metals), and finish/coating options — so your recommendation is grounded in fact, not memory. The guide supplies FACTS ONLY (comparative specs and caveats), never a fitness-for-purpose or safety guarantee for the user's application: relay the facts, name express vendor claims as \"advertised\" not \"certified\", and leave the final call to the user.\n(3) When the user asks to order and the offer has several shipping speeds, present the REAL options in one breath — each with its cost and arrival (\"standard $5.99 arriving Jul 28, or express $14.99 by Jul 26?\") — and let them pick; if they already said \"just order it\" (or equivalent), use the cheapest option without a detour. Then call create_checkout right away — the address and email can be entered on the secure payment page, so do NOT block checkout to collect them in chat; NEVER ask for them. Pass ship-to details only if the user already provided them. Never collect card details yourself. Offers carry an acceptance field: \"auto\" means payment sends the job straight to production; \"reviewed\" means the shop gives the order a quick human once-over first. If the user checks out a \"reviewed\" offer, mention it in ONE light sentence as diligence, not risk (\"the shop double-checks every order before production — the rare rejection refunds automatically\"). Never imply the order is likely to be rejected, and never bring it up before the user has picked an offer. Offers ALSO carry a fulfillment field: \"connected\" means UFP's checkout reaches that vendor and create_checkout will work; \"not_connected\" means the price is real but the ordering path isn't live yet, and create_checkout WILL refuse it. Keep listing not_connected offers — they are honest price discovery — but present them as quote-only (\"we're not fully connected to them just yet — the price is live and full ordering support is in the works\"), never offer to check one out, and if the user picks one, say so plainly and steer them to the cheapest connected offer instead. Never call create_checkout on a not_connected offer.\n(4) get_order_status any time after.\n(5) After an order arrives, the user may volunteer feedback (\"the stickers came out great\", \"my parts were 2 days late\"). Offer to record it as a vendor review: use list_orders to resolve which order they mean (\"that order from last week\"), structure the feedback into stars + facet scores (quality/timeliness/accuracy), read your draft back to the user for confirmation, then call leave_review. Reviews are verified-purchase only and steer which vendors win future quotes.\nReorders: every paid order emails the buyer a receipt with a durable UFP part number (UFP-…). If the user mentions one — or asks to \"order more of the same\" — call get_fabrication_quote with part_number instead of a design file; UFP reuses the stored design and re-shops it across all current vendors. Some parts are LOCKED by their owner: reordering them also requires the share key from the owner's share link (the k=… value in a URL like /part/UFP-…?k=KEY) — pass it as share_key. If a locked part is rejected, ask the user for the owner's share link.\nPreviews: offers include a product preview image rendered by the vendor; if status is \"pending\", calling refine_quote with only quote_id re-reads the quote and usually returns ready previews.\nQuotes STREAM: a response can come back with status \"quoting\" — the fastest vendors' offers are already in it and more land over the next seconds (routing.expected_vendors lists who is still pricing and a rough wait). Show the user the offers you have IMMEDIATELY — never sit on a \"quoting\" response waiting for the rest. Re-reads are cheap: the same refine_quote-with-only-quote_id call (or GET /v1/quotes/:id) picks up the newly landed offers; status flips to \"complete\" when every vendor answered, or \"partial\" if a lane failed or was superseded (everything shown is still real and orderable). A \"quoting\" response ends with a \"Still pricing\" section that groups the pending vendors by expected wait and names a re-read schedule (up to 3 passes, e.g. \"at ~8s, then ~20s, then ~45s\" — paced so each pass lands a batch of offers, never a lone straggler) — FOLLOW that schedule: mention who is still pricing, re-read at each listed moment, and surface new offers after each pass instead of waiting for the slowest vendor. If the user changes constraints while offers are still landing, just call refine_quote right away — rapid back-and-forth is encouraged, not wasteful. Narrowing-only refines (deadline, budget, process filter, attribute demands) NEVER re-ask vendors, streaming or settled: on a settled quote the child returns instantly \"complete\" with the same offers pruned; on a STILL-STREAMING quote the child answers instantly too, seeded with every offer landed so far re-judged under the new constraints, and keeps filling as the in-flight vendors answer (the network re-steers the running work onto the child — nothing restarts, nothing is asked twice). Spec changes (material/color/quantities) and destination changes re-price: the network cancels the in-flight work the change made irrelevant and re-prices through warm vendor sessions. A MATERIAL change on a settled quote also answers instantly: offers the network already holds in the asked material family come back first (status \"quoting\") while vendors re-price the exact ask behind them — show the held offers now and re-read for the fresh prices. Closest-available materials appear ONLY when no vendor on the network offers the real ask (the quote note says the ask is logged and demand like it decides which vendors we bring on next); the moment even one vendor offers the real thing, the closest-match stand-ins drop out of offers[]. routing.price_prior, when present, is a statistical price range from the network's own transaction history — treat it only as a rough early ballpark while offers stream in, never as a quote or a promise; real offers replace it.\nCOMPARISONS: when the user asks \"how do these compare to X?\" (a different material, quantity, deadline…), call refine_quote with a scenario label per alternative (\"carbon steel\", \"qty 100\") plus the changed spec/constraints — one labeled refine per scenario. Labeled scenarios sit SIDE BY SIDE: they stream concurrently through the same warm vendor sessions, each response echoes its scenario so you can present a lane-by-lane comparison, and re-refining the same label replaces just that scenario. Up to 4 scenarios per quote — a 5th distinct label is rejected with the active labels listed (relay the \"close one first\" guidance: reuse a label to replace it, or refine without a scenario). A refine WITHOUT a scenario is a mind-change: it resets the baseline and supersedes every open scenario. Keep labels short and human.\nTHE PRICE STORY IS PART PRICE + SHIPPING — tell it that way. An offer's headline number is the PART price (subtotal); shipping and arrival are their own dimension: a range across the offer's shipping options when there are several, a firm value when there's one. Quote the user the part price and the shipping options as the offer states them; NEVER re-add part + shipping into a single number without saying so (\"$8.68 all-in with standard shipping\" is honest, a bare \"$8.68\" is not). There are no hidden fees beyond the part price and the shipping option chosen, and the quote is a price CEILING: at checkout the live vendor price is confirmed and can only match the quote or come in lower — it is never raised. In the rare case the vendor repriced upward, the order expires unpaid and you simply re-quote. Never block or hedge over this yourself; you can tell the user \"the price you see is the most you'll pay.\"\nORGANIZE / SORT: when the user asks to re-order the offer cards (\"sort by cheapest\", \"soonest arrival on top\", \"best rated first\"), call refine_quote with sort_by — \"price\" (part price), \"unit_price\", \"total_price\" (part + cheapest shipping), \"arrival\" (soonest possible arrival), \"rating\", or \"recommended\" to restore the default price+speed rank; sort_direction \"desc\" flips a sort (\"most expensive first\"). Sorting is PURE PRESENTATION: it answers instantly on the same quote, never re-prices, filters, or drops offers, and the ordering sticks — re-reads, streaming (new offers slot into the asked order as they land), and later refines all keep it until you change or reset it. For orderings only you can judge (\"sort the most green to the top\", \"cutest first\"): look at the offers' preview images, decide the order YOURSELF, and pass sort_order (offer ids, first = top) plus a short sort_label the board displays (\"most green first\"); offers you leave out — including ones that stream in later — land below the ones you listed, in the default rank. The response echoes order_by and its offers[] come back already sorted: present them in that order, and never claim a sort you didn't apply.\nNear misses: a quote may carry near_misses — REAL vendor bids that missed exactly ONE stated constraint; excluded_by says which one and by how much (earliest_eta/days_late for a deadline, cents_over for a budget, the missing claim for an attribute). They are context, never orderable as-is: create_checkout refuses their ids. Never present a near-miss as a qualifying offer. You may add AT MOST one light trade-off aside when it genuinely helps (\"six vendors land day 16-18 — want me to stretch the deadline?\"), and never relax a constraint the user didn't ask to relax. If the user agrees, relaxing the named constraint via refine_quote converts them into ordinary orderable offers."},"jsonrpc":"2.0","id":1},"http_status":200,"headers":{"content-type":"application/json"}}},"protocol_version_probe":{"status":"warning","latency_ms":null,"details":{"claimed_version":"2025-03-26","validator_protocol_version":"2025-03-26","latest_known_version":"2025-11-25","releases_behind":2,"lag_days":244}},"tools_list":{"status":"ok","latency_ms":66.03,"details":{"url":"https://agenticfabricationnetwork.ai/mcp","payload":{"result":{"tools":[{"name":"get_fabrication_quote","title":"Get fabrication quote","description":"Get ranked, purchasable offers (price, ETA, preview image) for fabricating a physical item from a design file. process=fdm_print for 3D printing a model (STL/OBJ/PLY/3MF/AMF/STEP/IGES), process=cnc or process=sheetmetal for machined/bent metal parts (STEP, IGES, DXF), process=decal for stickers/decals from artwork (any common image or design file — PNG/JPG/HEIC/TIFF/GIF/BMP/WEBP/AVIF/SVG/PDF/AI/EPS/PSD/CDR, auto-converted). A .ufp file (UFP part container: the design plus saved spec/constraints in one) is accepted anywhere a design file is — its saved intent applies automatically and anything the user states now wins. If the user just drops a file and asks for a price, omit process — UFP routes it. Provide the design either as design_file (an image/file the user attached or you generated — preferred) or file_url (a public URL). REORDERS: if the user has a UFP part number (from a receipt email or a previous session, looks like UFP-… or part_…), pass it as part_number INSTEAD of any file — the stored design and spec are reused and re-shopped across all current vendors. Locked parts additionally require share_key (from the owner's share link). Returns offers across vendors like Google Flights returns flights.","inputSchema":{"type":"object","properties":{"process":{"type":"string","enum":["fdm_print","resin_print","metal_print","cnc","sheetmetal","laser_cut","decal","print","apparel"],"description":"What to fabricate. If the user just drops a file and asks for a price, OMIT this — UFP detects the file type (artwork, mesh — STL/OBJ/PLY/3MF/AMF, BREP — STEP/IGES, DXF) and routes it to every process that can make it; the response's routing.also_possible lists processes UFP can't quote yet"},"part_number":{"type":"string","description":"UFP part number from a previous paid order (UFP-… or part_…). Reorders: replaces design_file/file_url entirely; any other fields provided override the stored spec"},"share_key":{"type":"string","description":"Share key for a LOCKED part — the k=… value from the owner's share link (…/part/UFP-…?k=KEY). Required with part_number when the part is locked; omit otherwise"},"design_file":{"anyOf":[{"type":"object","properties":{"file_id":{"type":"string"},"download_url":{"type":"string"},"mime_type":{"type":"string"},"file_name":{"type":"string"}},"required":["file_id","download_url"],"additionalProperties":false},{"type":"string"}],"description":"The design file itself (user-attached or generated image/STL). ChatGPT supplies the file reference automatically when the user attached a file — just bind the attachment here. Other clients may pass a data: URL (base64) or a public https URL string. Preferred over file_url."},"file_url":{"type":"string","format":"uri","description":"Public URL of the design file — only when the design lives at a URL"},"attachment_urls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":10,"description":"Additional files for the SAME part — the PDF drawing next to the STEP, spec sheets, BOMs. Pass everything the user gave you; UFP works out which file is the part and which are supporting documents."},"ship_to_zip":{"type":"string","pattern":"^\\d{5}(-\\d{4})?$","description":"Destination US ZIP code, exactly as the user stated it. OMIT if the user has not given one — NEVER guess or invent a ZIP. Without it, prices come back all-in with shipping to an assumed central-US destination, flagged on routing.ship_to"},"quantities":{"type":"array","items":{"type":"integer","exclusiveMinimum":0},"maxItems":5,"description":"Quantities the user actually wants — pass [1] or [2] if that's the ask. Vendors with a higher minimum quote AT their minimum and the offer notes it (requested_quantity). Default: 25 for decals, 1 for prints; a part_number reorder defaults to the quantity last purchased"},"material":{"type":"string","description":"fdm_print: pla|petg|asa (free-form — \"abs\", \"polycarbonate\" asks resolve to the closest stocked filament with a substitution note); resin_print: standard|tough|high_temp|high_detail resin (free-form asks resolve to the closest supported resin); cnc: aluminum_6061|aluminum_7075|stainless_304|titanium|brass|acetal|delrin|abs|nylon_pa6|peek|acrylic (free-form asks resolve to the closest supported stock); sheetmetal: mild_steel|aluminum_5052|stainless_304 (free-form — \"steel\", \"5052\", \"stainless\" resolve; unknown asks get closest-match offers). Omit to compare the defaults; laser_cut: acrylic|plywood|mdf|delrin|uhmw|hdpe|g10|carbon_fiber|mild_steel|aluminum_5052|stainless_304 (free-form — \"plexiglass\", \"birch\" resolve; unknown asks get closest-match offers); decal: vinyl|holographic|transparent|glitter (free-form — \"chrome\" etc. gets closest matches)"},"color":{"type":"string","description":"fdm_print: Filament color for fdm_print (e.g. black, red); resin_print: Resin color for resin_print (e.g. gray, white, black); print: Print color ask (\"full color\", \"black and white\"). Vendors that only price full color note the substitution; apparel: Garment color for apparel (\"black\", \"heather gray\") — vendor-resolved"},"metal_alloy":{"type":"string","description":"Metal AM alloy: stainless_316l|stainless_17_4ph|titanium_ti64|aluminum_alsi10mg|inconel_718|cobalt_chrome|tool_steel_h13|copper (free-form asks like \"Ti-6Al-4V\" resolve to the closest stocked powder)"},"metal_technology":{"type":"string","description":"Metal AM process: laser_powder_bed (SLM/DMLS/LPBF)|binder_jet|ebm. Omit to let the vendor pick its default for the alloy"},"metal_finish":{"type":"string","description":"Metal AM post-processing: as_printed|bead_blast|machined_critical|polished|heat_treated (as_printed keeps support witness marks)"},"finish":{"type":"string","description":"cnc: CNC surface finish: standard|anodized|polished|bead_blasted (default standard); sheetmetal: as_cut (default) | deburred | powder_coat — powder coat protects mild steel for outdoor use"},"thickness_in":{"type":"number","exclusiveMinimum":0,"description":"sheetmetal: Sheet thickness in inches (e.g. 0.063 = 14 ga aluminum). Quoted at the nearest stocked thickness; omit for ~16 ga default; laser_cut: Nominal thickness in inches; quoted at the vendor's nearest stocked thickness. Omit for the material's common stock"},"bend_count":{"type":"number","exclusiveMinimum":0,"description":"Number of bends in the design (bend lines must be in the file). Prices CNC bending; omit for flat parts"},"units":{"type":"string","description":"sheetmetal: DXF drawing units: \"mm\" (default) or \"in\". STEP files carry their own units; laser_cut: DXF drawing units: \"mm\" (default) or \"in\""},"width_in":{"type":"number","exclusiveMinimum":0,"description":"Decal width in inches (default 3)"},"height_in":{"type":"number","exclusiveMinimum":0,"description":"Decal height in inches (default 3)"},"cut":{"type":"string","description":"Decal cut style — pass the user's words verbatim (\"triangle\", \"die cut\", \"circle\"). Known: contour|square|oval|kiss_cut; anything else gets closest-match offers with a substitution note. Omit to compare the main options"},"product":{"type":"string","description":"print: Print product kind: business_cards|flyers|posters. Omit to quote every kind the artwork fits; apparel: apparel: t_shirt|hoodie|long_sleeve|tank_top (free-form — \"tee\", \"crewneck\" resolve to the closest product)"},"size":{"type":"string","description":"Print size — pass the user's words verbatim (\"letter\", \"24x36\", \"half letter\"). Cards are 3.5x2; flyers 8.5x5.5|8.5x11|11x17; posters 12x18 through 36x48. Unknown sizes get closest-match offers"},"paper":{"type":"string","description":"Paper stock — free-form (\"matte\", \"glossy\", \"recycled\", \"14pt\"). Resolved to the closest stock the vendor runs"},"sides":{"type":"string","description":"single|double sided printing (defaults: cards double, flyers/posters single)"},"placement":{"type":"string","description":"Print placement for apparel: front (default) | back | front_back"},"processes":{"type":"array","items":{"type":"string","minLength":1},"maxItems":8,"description":"Only quote these fabrication processes — a constraint that narrows the auto-routing fan. Friendly terms accepted (\"sheet metal\", \"3d printing\", \"cnc\", \"laser\"); unmappable terms are reported, never an error. Usually OMIT: auto-routing quotes every process that can make the file. Pass it only when the user explicitly limits the process"},"deadline":{"type":"string","description":"Latest acceptable delivery date, ISO YYYY-MM-DD"},"outdoor_rated":{"type":"boolean","description":"Must survive outdoor exposure"},"submersible":{"type":"boolean","description":"Must survive submersion in water"},"high_temp":{"type":"boolean","description":"Must tolerate sustained heat (vendor heat-resistance claim)"},"food_safe":{"type":"boolean","description":"Must be vendor-certified food safe"},"requirements":{"type":"array","items":{"type":"string","minLength":1,"maxLength":120},"maxItems":20,"description":"Verbatim context terms from the user — materials, finishes, certifications, tolerances (\"UV resistant\", \"anodized: red\", \"iso 13485\", \"tolerance: 0.1mm\"). UFP applies what it can and reports what it could not; unresolvable terms are logged for vendor sourcing. Do not interrogate the user to fill this — pass what they said."},"callouts":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"number"},"unit":{"type":"string","enum":["mm","in"]},"tolerance_mm":{"type":"number","exclusiveMinimum":0},"critical":{"type":"boolean"}},"required":["name","value","unit"],"additionalProperties":false},"maxItems":50,"description":"Dimensions you read off the user's drawing that matter (\"hole_diameter 6.5mm ±0.1\"). The tightest tolerance prunes vendors that cannot hold it."},"max_budget_usd":{"type":"number","exclusiveMinimum":0,"description":"Hard budget cap in USD, all-in"},"min_vendor_stars":{"type":"number","minimum":1,"maximum":5,"description":"Only vendors whose displayed review rating is at least this many stars (1-5). Vendors with no reviews yet are excluded — the filter demands demonstrated quality"},"min_vendor_reviews":{"type":"integer","exclusiveMinimum":0,"description":"Only vendors with at least this many verified-purchase reviews"},"proven_spec_orders":{"type":"integer","exclusiveMinimum":0,"description":"Only vendors with at least N completed orders for this exact process+material — \"a vendor that prints PC reliably\""}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"resourceUri":"ui://widget/quote-compare-v4.html"},"openai/outputTemplate":"ui://widget/quote-compare-v4.html","openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Checking fabrication vendors…","openai/toolInvocation/invoked":"Fetched fabrication offers","openai/fileParams":["design_file"]}},{"name":"refine_quote","title":"Refine quote","description":"Narrow an existing quote with new constraints (destination ZIP the user just provided, deadline, outdoor/waterproof/heat/food-safety requirements, budget, vendor reliability, verbatim requirements terms, drawing callouts, or a process filter — \"no, I meant in sheet metal\" → processes) — or CHANGE its spec: material, color, or quantities. Changing material/quantity is a spec change, not a narrowing — pass it via the material/color/quantities args, NOT as a requirements term; the affected lanes re-price with the new spec through warm vendor sessions. For side-by-side comparisons (\"how does carbon steel compare?\"), add a scenario label — labeled refines coexist instead of replacing each other (max 4). \"Organize by\" (\"sort by cheapest\", \"soonest arrival on top\", \"most green first\") is also this tool: sort_by for objective orders, sort_order + sort_label for orders you judged yourself from the previews — presentation only, instant, nothing re-prices. Call with ONLY quote_id to re-read the quote (e.g. to pick up preview images that finished rendering).","inputSchema":{"type":"object","properties":{"quote_id":{"type":"string"},"scenario":{"type":"string","minLength":1,"maxLength":40,"description":"Comparison lane label. A labeled refine sits SIDE BY SIDE with the quote's other offers instead of replacing them: refines with the same label replace that scenario, different labels coexist (max 4 per quote), and a refine WITHOUT a scenario resets the baseline and supersedes every lane. \"How does X compare to Y?\" = one labeled refine per scenario (e.g. scenario \"carbon steel\" + material \"carbon steel\", then scenario \"aluminum\" + material \"aluminum\"). Keep labels short and human, like \"carbon steel\""},"material":{"type":"string","description":"New material for this part (\"I need it in PLA\" → \"pla\"). A spec change: lanes requote with this material through warm vendor sessions; an explicit material here replaces the quote's material fan-out. Free-form — unstocked asks resolve to the closest stocked material with a substitution note. Do NOT pass material changes as requirements terms"},"color":{"type":"string","description":"New color for this part (spec change — lanes requote)"},"finish":{"type":"string","description":"New surface finish (\"powder coat\", \"anodized\", \"bead blasted\") — a spec change: finish-capable lanes (sheet metal, CNC, metal print) requote WITH it; 3D-print lanes have no coating step, so they ignore the finish but keep the COLOR. A colored finish ask (\"black powder coat\") should set BOTH finish and color, so every lane lands on the buyer's color"},"quantities":{"type":"array","items":{"type":"integer","exclusiveMinimum":0},"maxItems":5,"description":"New quantities the user wants (spec change — lanes requote at the new quantity). Same rules as on get_fabrication_quote: pass the real ask, never inflate it"},"processes":{"type":"array","items":{"type":"string","minLength":1},"maxItems":8,"description":"Narrow this quote to specific fabrication processes (\"no, I meant in sheet metal\" → [\"sheet metal\"]). A CONSTRAINT, not a new quote: excluded lanes drop while warm vendor sessions survive, and further refines inherit the filter. Friendly terms accepted (\"sheet metal\", \"3d printing\", \"cnc\", \"laser\"); pass [\"any\"] to clear the filter and show every process again. When comparison scenarios are open and the user narrows the process, re-refine EVERY active scenario label with this same arg so all columns prune together"},"ship_to_zip":{"type":"string","pattern":"^\\d{5}(-\\d{4})?$","description":"Destination US ZIP code, exactly as the user stated it — use this when the user provides their ZIP after a quote priced to the assumed central-US destination (routing.ship_to); the refined prices become theirs. NEVER guess or invent a ZIP"},"deadline":{"type":"string","description":"Latest acceptable delivery date, ISO YYYY-MM-DD"},"outdoor_rated":{"type":"boolean"},"submersible":{"type":"boolean"},"high_temp":{"type":"boolean"},"food_safe":{"type":"boolean"},"requirements":{"type":"array","items":{"type":"string","minLength":1,"maxLength":120},"maxItems":20,"description":"Verbatim context terms from the user — materials, finishes, certifications, tolerances (\"UV resistant\", \"anodized: red\", \"iso 13485\", \"tolerance: 0.1mm\"). UFP applies what it can and reports what it could not; unresolvable terms are logged for vendor sourcing. Do not interrogate the user to fill this — pass what they said."},"callouts":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"number"},"unit":{"type":"string","enum":["mm","in"]},"tolerance_mm":{"type":"number","exclusiveMinimum":0},"critical":{"type":"boolean"}},"required":["name","value","unit"],"additionalProperties":false},"maxItems":50,"description":"Dimensions you read off the user's drawing that matter (\"hole_diameter 6.5mm ±0.1\"). The tightest tolerance prunes vendors that cannot hold it."},"max_budget_usd":{"type":"number","exclusiveMinimum":0},"min_vendor_stars":{"type":"number","minimum":1,"maximum":5,"description":"Only vendors whose displayed review rating is at least this many stars (1-5); vendors with no reviews yet are excluded"},"min_vendor_reviews":{"type":"integer","exclusiveMinimum":0,"description":"Only vendors with at least this many verified-purchase reviews"},"proven_spec_orders":{"type":"integer","exclusiveMinimum":0,"description":"Only vendors with at least N completed orders for this exact process+material — \"a vendor that prints PC reliably\""},"sort_by":{"type":"string","enum":["recommended","price","unit_price","total_price","arrival","rating"],"description":"\"Organize by\": re-order the quote's offer cards (\"sort by cheapest\" → \"price\"; \"soonest arrival first\" → \"arrival\"; \"best rated\" → \"rating\"; \"total_price\" = part + cheapest shipping; \"recommended\" restores the default price+speed rank). PURE PRESENTATION — answers instantly on the same quote, never re-prices or drops offers, and the ordering sticks across re-reads, streaming, and later refines until changed"},"sort_direction":{"type":"string","enum":["asc","desc"],"description":"Flip a sort_by ordering: \"desc\" reverses the key's natural direction (\"most expensive first\" = sort_by \"price\" + \"desc\"). Only meaningful with sort_by"},"sort_order":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":200,"description":"EXPLICIT card order for asks only you can judge (\"sort the most green to the top\"): look at the offers' preview images, decide the order yourself, and pass the offer ids first-on-top. Offers you leave out (including ones that stream in later) land below the listed ones in the default rank. Pass sort_label with it"},"sort_label":{"type":"string","minLength":1,"maxLength":60,"description":"Short human wording for a sort_order, displayed on the board (\"most green first\") — required with sort_order, since only you know what your order means"}},"required":["quote_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"resourceUri":"ui://widget/quote-compare-v4.html"},"openai/outputTemplate":"ui://widget/quote-compare-v4.html","openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Checking fabrication vendors…","openai/toolInvocation/invoked":"Fetched fabrication offers"}},{"name":"poll_quote","title":"Poll quote (board internal)","description":"INTERNAL — the offer board calls this to refresh itself while vendors finish pricing. Never call this yourself: to pick up new offers, use refine_quote with just quote_id.","inputSchema":{"type":"object","properties":{"quote_id":{"type":"string","description":"Quote id the board is watching"}},"required":["quote_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"visibility":["model","app"]},"openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Refreshing offers…","openai/toolInvocation/invoked":"Offers refreshed"}},{"name":"finalize_quote","title":"Finalize quote (wait for remaining vendors)","description":"Step 2 after a quote response says the board is still filling: waits for the remaining vendors to answer (up to ~40s) and returns the final board and offers. Call with just quote_id, immediately after the response that told you to. Never call it otherwise.","inputSchema":{"type":"object","properties":{"quote_id":{"type":"string","description":"Quote id from the still-quoting response"}},"required":["quote_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"resourceUri":"ui://widget/quote-compare-v4.html"},"openai/outputTemplate":"ui://widget/quote-compare-v4.html","openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Waiting for the remaining vendors…","openai/toolInvocation/invoked":"All vendors answered"}},{"name":"create_checkout","title":"Create checkout","description":"Create the order and get a hosted payment link for a chosen offer. Call this as soon as the user picks an offer + shipping speed — do NOT ask for their address first: the secure payment page collects the shipping address and email. Only pass address fields if the user already volunteered them. Returns checkout_url — give it to the user to pay; UFP places the vendor order automatically after payment.","inputSchema":{"type":"object","properties":{"offer_id":{"type":"string"},"shipping_option_id":{"type":"string","description":"One of the offer's shipping option ids"},"name":{"type":"string","description":"Recipient full name (only if the user provided it)"},"street1":{"type":"string","description":"Street address (only if the user provided it)"},"street2":{"type":"string"},"city":{"type":"string"},"state":{"type":"string","minLength":2,"maxLength":2,"description":"Two-letter US state"},"zip":{"type":"string"},"email":{"type":"string","format":"email","description":"Buyer email (only if the user provided it)"},"phone":{"type":"string"},"billing_name":{"type":"string","description":"Billing address, only when the buyer answered the billing question (e.g. said it differs from shipping, or a checkout UI's 'same as shipping' checkbox — then send the shipping values verbatim). Never ask for it; omit and the payment page handles billing."},"billing_street1":{"type":"string"},"billing_street2":{"type":"string"},"billing_city":{"type":"string"},"billing_state":{"type":"string","minLength":2,"maxLength":2,"description":"Two-letter US state"},"billing_zip":{"type":"string"},"return_url":{"type":"string","format":"uri","description":"Exact URL of the page the user is on right now, IF the client application knows it (a web app embedding this tool passes its own page URL). The post-payment page shows a 'Return to <site>' button pointing here. Omit when unknown — never invent one."}},"required":["offer_id","shipping_option_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"visibility":["model","app"]},"openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Creating your checkout…","openai/toolInvocation/invoked":"Checkout created"}},{"name":"get_order_status","title":"Get order status","description":"Check status, ETA and tracking for a UFP order.","inputSchema":{"type":"object","properties":{"order_id":{"type":"string"}},"required":["order_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"visibility":["model","app"]},"openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Checking order status…","openai/toolInvocation/invoked":"Fetched order status"}},{"name":"list_orders","title":"List recent orders","description":"List this agent's recent UFP orders (newest first): order id, what was made, vendor, status, total, ETA, created date. Use it to resolve vague references — \"my sticker order\", \"that order from 6 days ago\" — to a concrete order_id before calling get_order_status or leave_review.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{}},"execution":{"taskSupport":"forbidden"}},{"name":"leave_review","title":"Leave vendor review","description":"Record the user's verified-purchase review of how a UFP order turned out. YOUR JOB IS TO STRUCTURE THE FEEDBACK: turn what the user actually said into stars (1-5 overall) and facet scores — \"the cut was sloppy\" -> quality, \"parts arrived 2 days late\" -> timeliness, \"wrong color\" -> accuracy. Keep the optional comment to a short factual summary of their words; never embellish. ALWAYS draft the review (stars, facets, comment) and confirm it with the user BEFORE calling this tool — e.g. \"I'll rate this 2 stars with timeliness 1 because it arrived 2 days late — send it?\". One review per order: submitting again updates the existing review. Use list_orders first if you need to resolve which order they mean.","inputSchema":{"type":"object","properties":{"order_id":{"type":"string","description":"The UFP order being reviewed (ord_…) — see list_orders"},"stars":{"type":"integer","minimum":1,"maximum":5,"description":"Overall rating the user confirmed, 1-5"},"comment":{"type":"string","maxLength":2000,"description":"Short factual summary of the user's feedback, in their spirit — optional"},"facets":{"type":"object","properties":{"quality":{"type":"integer","minimum":1,"maximum":5,"description":"Build/print/cut quality"},"timeliness":{"type":"integer","minimum":1,"maximum":5,"description":"Delivery speed vs the quoted ETA"},"accuracy":{"type":"integer","minimum":1,"maximum":5,"description":"How well the item matched the ordered spec/design"}},"additionalProperties":false,"description":"Score only the facets the user actually spoke to"},"claims":{"type":"object","properties":{"received_material":{"type":"string","maxLength":80,"description":"Material the user says actually ARRIVED, when they complain it differs from the order (\"they sent aluminum\")"},"received_quantity":{"type":"integer","exclusiveMinimum":0,"description":"Count the user says actually arrived, when it differs from the order"},"delivery_claim":{"type":"string","enum":["not_delivered","late","on_time"],"description":"The user's delivery claim (\"never showed up\" -> not_delivered)"}},"additionalProperties":false,"description":"CHECKABLE claims from the user's feedback — the network stamps each against its own records (the ordered spec, polled tracking + delivery events) and attaches evidence chips to the review. Only pass what the user actually asserted."}},"required":["order_id","stars"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"}},{"name":"get_material_guide","title":"Get material guide","description":"Honest, cited material intelligence for material-choice / strength / durability / heat / outdoor questions (\"my PLA part broke, what's stronger?\", \"will this survive outdoors?\"). Returns caveats-FIRST facts: published specs (tensile, heat-deflection, UV/outdoor, chemical) each tagged vendor-cited vs general engineering knowledge, \"stronger/tougher/hotter than X\" ladders (PLA→PETG→ABS/ASA→nylon/PC→metals), and finish/coating options. Call it BEFORE answering such a question, then relay the facts. It supplies FACTS ONLY — comparative specs and vendor-admitted caveats, never a fitness-for-purpose or safety guarantee (that stays your judgment boundary); name express vendor claims \"advertised\", not \"certified\".","inputSchema":{"type":"object","properties":{"material":{"type":"string","description":"A material the user named or you are weighing (\"PLA\", \"6061 aluminum\", \"stainless\") — free-form; a family ask like \"stainless\" returns the whole family"},"process":{"type":"string","description":"A UFP process to scope to (\"fdm_print\", \"resin_print\", \"cnc\", \"sheetmetal\", \"laser_cut\") — lists the materials that process makes"},"question":{"type":"string","description":"The user's raw question — mined for intent (stronger/tougher, heat/hot, outdoor/UV) so the right ladder is surfaced"}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"},"_meta":{"ui":{"visibility":["model","app"]},"openai/widgetAccessible":true,"openai/toolInvocation/invoking":"Looking up material facts…","openai/toolInvocation/invoked":"Fetched material facts"}}]},"jsonrpc":"2.0","id":2},"http_status":200,"headers":{"content-type":"application/json"}}},"prompts_list":{"status":"missing","latency_ms":59.92,"details":{"url":"https://agenticfabricationnetwork.ai/mcp","payload":{"jsonrpc":"2.0","id":3,"error":{"code":-32601,"message":"Method not found"}},"http_status":200,"headers":{"content-type":"application/json"},"reason":"not_supported"}},"prompt_get":{"status":"missing","latency_ms":null,"details":{"reason":"not_advertised"}},"resources_list":{"status":"ok","latency_ms":44.73,"details":{"url":"https://agenticfabricationnetwork.ai/mcp","payload":{"result":{"resources":[{"uri":"ui://widget/quote-compare-v4.html","name":"quote-compare-widget","mimeType":"text/html;profile=mcp-app","description":"UFP offer comparison board"},{"uri":"ufp://materials/guide.md","name":"materials-intelligence","mimeType":"text/markdown","description":"UFP materials-intelligence pack — honest, cited material facts (caveats first), stronger/hotter/outdoor ladders, and a finishes/inspection glossary"}]},"jsonrpc":"2.0","id":5},"http_status":200,"headers":{"content-type":"application/json"}}},"resource_read":{"status":"ok","latency_ms":75.32,"details":{"url":"https://agenticfabricationnetwork.ai/mcp","payload":{"result":{"contents":[{"uri":"ui://widget/quote-compare-v4.html","mimeType":"text/html;profile=mcp-app","text":"<!doctype html>\n<html>\n<head>\n<meta charset=\"utf-8\" />\n<style>\n  :root { color-scheme: light dark; }\n  * { box-sizing: border-box; }\n  body { font-family: system-ui, -apple-system, sans-serif; margin: 0; padding: 12px; background: transparent; }\n  .board { display: flex; flex-direction: column; gap: 10px; }\n  .hdr { display: flex; justify-content: space-between; align-items: baseline; }\n  .hdr h2 { font-size: 15px; margin: 0; }\n  .hdr .sub { font-size: 12px; opacity: .65; }\n  .offer { display: grid; grid-template-columns: 86px 1fr auto; gap: 12px; align-items: center;\n           border: 1px solid rgba(128,128,128,.25); border-radius: 12px; padding: 10px; }\n  .offer.best { border-color: #88d870; box-shadow: 0 0 0 1px #88d87055; }\n  .thumb { width: 86px; height: 86px; border-radius: 8px; object-fit: contain;\n           background: rgba(128,128,128,.12); }\n  .thumb.pending { display:grid; place-items:center; font-size:10px; opacity:.6; text-align:center;\n           animation: pulse 1.4s ease-in-out infinite; }\n  @keyframes pulse { 50% { opacity: .3; } }\n  .meta { min-width: 0; }\n  .meta .spec { font-weight: 600; font-size: 13px; }\n  .meta .vendor { font-size: 12px; opacity: .7; }\n  /* The vendor's own mark beside its name (ALC-46) — the companies the\n     network integrates with, visible right on the board. A failed fetch\n     removes the img (onerror), so the line degrades to the plain name. */\n  .vicon { width: 14px; height: 14px; object-fit: contain; border-radius: 3px;\n           vertical-align: -3px; margin-right: 4px; }\n  /* The little origin flag after the vendor name (ALC-61) — where the\n     part ships from, for buyers who weigh customs (or enjoy watching\n     the world commoditize prices). */\n  .flag { margin-left: 4px; cursor: default; }\n  .rating { color: #d9b53a; white-space: nowrap; }\n  .rating.tappable { cursor: pointer; }\n  .rating .rcount { opacity: .75; }\n  /* The subprocess rating (ALC-38): the deepest niche of THIS offer's\n     spec the vendor has a track record for, right beside the overall\n     stars — \"great shop overall, shaky at flat black specifically\". */\n  .niche { color: #d9b53a; opacity: .85; white-space: nowrap; }\n  .niche .rcount { opacity: .75; }\n  .trend.down { color: #e05d5d; }\n  .trend.up { color: #88d870; }\n  .facets { display: none; font-size: 10px; opacity: .65; margin-top: 2px; }\n  .facets.open { display: block; }\n  .badges { margin-top: 4px; display: flex; gap: 4px; flex-wrap: wrap; }\n  .badge { font-size: 10px; border: 1px solid rgba(128,128,128,.35); border-radius: 99px; padding: 1px 7px; opacity: .8; }\n  .badge.best-badge { background:#88d870; color:#0d2405; border-color:#88d870; opacity:1; }\n  /* Not-orderable: amber like the near-miss \"why\" badge — the card is\n     honest context, not a failure, and must not read as an error. */\n  .badge.unconnected { border-color: #d98c3a88; color: #d98c3a; opacity: 1; }\n  .buy { text-align: right; }\n  .price { font-size: 17px; font-weight: 700; }\n  .unit { font-size: 11px; opacity: .6; }\n  .shipline { font-size: 11px; opacity: .7; margin-top: 2px; white-space: nowrap; }\n  .offer.nearmiss { border-style: dashed; opacity: .88; }\n  /* Placeholder card for a vendor still pricing: the whole card is a\n     loading bar — a solid translucent wash of the brand green that\n     proceeds left-to-right at a CONSTANT rate (linear, one run,\n     forwards fill), slated to go all the way across in the lane's\n     expected wait. The wait is p90 pacing, so the answer beats the\n     bar ~9 times in 10 and the card \"snaps\" into the real offer\n     mid-run. AFN owns the timer: lanes past their deadline are pruned\n     from expected_vendors server-side per read, so a bar that fills\n     completely just sits full until the next poll re-render drops the\n     card — the client never decides a lane is dead. The number is\n     never printed (loading-honesty decision, 2026-07-23). */\n  .offer.quoting { border-style: dashed; position: relative; overflow: hidden; }\n  .offer.quoting .thumb, .offer.quoting .meta { position: relative; }\n  .loadfill { position: absolute; left: 0; top: 0; bottom: 0; width: 0;\n           background: #88d87028;\n           animation-name: loadfill; animation-timing-function: linear;\n           animation-fill-mode: forwards; }\n  @keyframes loadfill { from { width: 0; } to { width: 100%; } }\n  .badge.why { border-color: #d98c3a88; color: #d98c3a; opacity: 1; }\n  .nm-note { font-size: 11px; opacity: .6; margin: -4px 2px 0; line-height: 1.4; }\n  select { font-size: 11px; margin-top: 4px; max-width: 150px; border-radius: 6px;\n           border: 1px solid rgba(128,128,128,.35); background: transparent; color: inherit; padding: 3px; }\n  button { margin-top: 6px; background: #88d870; border: 0; color: #0d2405; font-weight: 600;\n           border-radius: 8px; padding: 6px 14px; font-size: 12px; cursor: pointer; }\n  button.dead { background: transparent; color: inherit; opacity: .5; cursor: not-allowed;\n                border: 1px solid rgba(128,128,128,.35); }\n  button:hover { filter: brightness(1.08); }\n  .group-hdr { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;\n           opacity: .55; margin: 4px 2px -4px; }\n  .also-note { font-size: 11px; opacity: .6; line-height: 1.5; border-top: 1px dashed rgba(128,128,128,.3);\n           padding-top: 6px; }\n  .empty { padding: 24px; text-align: center; opacity: .7; font-size: 13px; }\n  .livediag { font-size: 10px; opacity: .4; margin-top: 6px; font-family: monospace; }\n  /* Pre-mount skeleton (shown from tool-call start until the result lands). */\n  .skelwrap { padding: 4px 0; }\n  .skelcap { font-size: 12px; opacity: .55; margin: 4px 0 10px; }\n  .skel { display: flex; align-items: center; gap: 12px; padding: 12px;\n          border: 1px solid rgba(0,0,0,.07); border-radius: 10px; margin-bottom: 8px; }\n  .skel-img, .skel-line, .skel-price { background: rgba(125,125,125,.16); border-radius: 6px;\n          animation: skelpulse 1.4s ease-in-out infinite; }\n  .skel-img { width: 56px; height: 56px; flex: none; }\n  .skel-lines { flex: 1; }\n  .skel-line { height: 10px; margin: 6px 0; }\n  .skel-line.w30 { width: 30%; } .skel-line.w35 { width: 35%; } .skel-line.w40 { width: 40%; }\n  .skel-line.w45 { width: 45%; } .skel-line.w50 { width: 50%; } .skel-line.w55 { width: 55%; }\n  .skel-line.w60 { width: 60%; } .skel-line.w65 { width: 65%; }\n  .skel-price { width: 64px; height: 22px; flex: none; }\n  .skel:nth-child(3) .skel-img, .skel:nth-child(3) .skel-line, .skel:nth-child(3) .skel-price { animation-delay: .2s; }\n  .skel:nth-child(4) .skel-img, .skel:nth-child(4) .skel-line, .skel:nth-child(4) .skel-price { animation-delay: .4s; }\n  .skel:nth-child(5) .skel-img, .skel:nth-child(5) .skel-line, .skel:nth-child(5) .skel-price { animation-delay: .6s; }\n  @keyframes skelpulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }\n</style>\n</head>\n<body>\n<!-- Pre-mount skeleton: ChatGPT mounts this widget the moment the tool\n     CALL starts, before any result exists — this IS the loading\n     experience (2026-07-26 redesign: the call blocks until the network\n     settles, then this same embed becomes the offer board; no separate\n     loading board, no stacking). Shimmering shapes only — no fake\n     numbers, no fake progress. -->\n<div id=\"root\" class=\"board\">\n  <div class=\"skelwrap\">\n    <div class=\"skelcap\">Pricing your part across the network — offers land here in seconds…</div>\n    <div class=\"skel\"><div class=\"skel-img\"></div><div class=\"skel-lines\"><div class=\"skel-line w60\"></div><div class=\"skel-line w40\"></div></div><div class=\"skel-price\"></div></div>\n    <div class=\"skel\"><div class=\"skel-img\"></div><div class=\"skel-lines\"><div class=\"skel-line w50\"></div><div class=\"skel-line w35\"></div></div><div class=\"skel-price\"></div></div>\n    <div class=\"skel\"><div class=\"skel-img\"></div><div class=\"skel-lines\"><div class=\"skel-line w65\"></div><div class=\"skel-line w45\"></div></div><div class=\"skel-price\"></div></div>\n    <div class=\"skel\"><div class=\"skel-img\"></div><div class=\"skel-lines\"><div class=\"skel-line w55\"></div><div class=\"skel-line w30\"></div></div><div class=\"skel-price\"></div></div>\n  </div>\n</div>\n<script>\n(function () {\n  var root = document.getElementById(\"root\");\n  // pendingSince: vendor|process → first-seen ms — the FALLBACK resume\n  // anchor for absolutes-less payloads (old servers). A lane carrying\n  // wire absolutes (published_at + deadline, with the payload's\n  // served_at) anchors to the server clock instead and never touches\n  // this map.\n  var state = { quote: null, pollTimer: null, polls: 0, pendingSince: {} };\n\n  function api() { return typeof window.openai === \"object\" ? window.openai : null; }\n\n  function money(cents) { return \"$\" + (cents / 100).toFixed(2); }\n\n  function fmtDate(iso) {\n    try {\n      var d = new Date(iso + \"T12:00:00\");\n      return d.toLocaleDateString(undefined, { weekday: \"short\", month: \"short\", day: \"numeric\" });\n    } catch (e) { return iso; }\n  }\n\n  // Buyer-facing name per process for the card's \"type\" bit.\n  var PROCESS_LABELS = { fdm_print: \"FDM 3D print\", resin_print: \"resin print\", cnc: \"CNC\",\n    sheetmetal: \"sheet metal\", laser_cut: \"laser cut\", decal: \"stickers\", print: \"print\", apparel: \"apparel\" };\n  function specLine(offer) {\n    var s = offer.spec_resolved || {};\n    var bits = [];\n    for (var k in s) {\n      // Plumbing never reaches the card: internal keys (note/model/process/\n      // fabricator), vendor-prefixed keys (justway_material_id,\n      // craftcloud_model), id/weight fields, and any prose-length value\n      // (vendor notes ride spec_resolved on some adapters).\n      if (k === \"note\" || k === \"model\" || k === \"process\" || k === \"fabricator\") continue;\n      if (/_id$|_g$/.test(k) || k.indexOf(offer.vendor) === 0 || k.indexOf(\"craftcloud\") === 0) continue;\n      var v = String(s[k]);\n      if (!v || v.length > 40) continue;\n      if (/.(stl|step|stp|obj|3mf|amf|ply|iges|igs|dxf|svg|pdf|ai|eps|png|jpe?g|ufp)$/i.test(v)) continue;\n      if (k === \"material\") v = v.replace(/_/g, \" \");\n      bits.push(v);\n    }\n    bits = bits.slice(0, 3);\n    bits.push(PROCESS_LABELS[offer.process] || offer.process);\n    var qty = offer.quantity + \" × \";\n    if (offer.requested_quantity != null) {\n      qty = offer.quantity + \" × (min. order — you asked for \" + offer.requested_quantity + \") \";\n    }\n    var line = qty + bits.join(\" · \");\n    if (offer.substitutions && offer.substitutions.length) {\n      var subs = [];\n      for (var i = 0; i < offer.substitutions.length; i++) {\n        var sub = offer.substitutions[i];\n        subs.push(sub.requested + \" → \" + sub.provided + \" (closest)\");\n      }\n      line += \" · \" + subs.join(\" · \");\n    }\n    return line;\n  }\n\n  // The price story, same philosophy as the text layer (server quoteResult)\n  // and the make-this cards: headline = PART price; shipping + arrival are\n  // their own dimension — a RANGE across shipping_options when there are\n  // several, a firm value when there's one.\n  function shipSummary(opts) {\n    opts = opts || [];\n    if (!opts.length) return { ship: \"\", eta: \"\" };\n    var minC = Infinity, maxC = -Infinity, minD = null, maxD = null;\n    opts.forEach(function (s) {\n      if (s.cost_cents < minC) minC = s.cost_cents;\n      if (s.cost_cents > maxC) maxC = s.cost_cents;\n      if (minD === null || s.eta_date < minD) minD = s.eta_date;\n      if (maxD === null || s.eta_date > maxD) maxD = s.eta_date;\n    });\n    var ship = minC === maxC\n      ? (minC === 0 ? \"free shipping\" : \"shipping \" + money(minC))\n      : \"shipping \" + (minC === 0 ? \"free\" : money(minC)) + \" \\u2013 \" + money(maxC);\n    var eta = minD === maxD\n      ? \"arrives \" + fmtDate(minD)\n      : \"arrives \" + fmtDate(minD) + \" \\u2013 \" + fmtDate(maxD);\n    return { ship: ship, eta: eta };\n  }\n\n  // Part price + shipping/arrival lines, shared by offer and near-miss cards.\n  function priceStoryHtml(offer) {\n    var sum = shipSummary(offer.shipping_options);\n    return \"<div class='price'>\" + money(offer.subtotal_cents) + \"</div>\" +\n      \"<div class='unit'>\" + money(offer.unit_price_cents) + \"/ea</div>\" +\n      (sum.ship ? \"<div class='shipline'>\" + sum.ship + \"</div>\" : \"\") +\n      (sum.eta ? \"<div class='shipline'>\" + sum.eta + \"</div>\" : \"\");\n  }\n\n  // Why a near-miss sits outside the constraints — derived from the MACHINE\n  // fields (earliest_eta/days_late/cents_over/field), matching the text layer,\n  // with the exact wire detail string as the fallback.\n  function nearMissWhy(ex) {\n    if (!ex) return \"\";\n    if (ex.reason === \"deadline\" && ex.earliest_eta) {\n      return \"arrives \" + fmtDate(ex.earliest_eta) +\n        (ex.days_late != null\n          ? \" \\u2014 \" + ex.days_late + (ex.days_late === 1 ? \" day\" : \" days\") + \" past the deadline\"\n          : \" \\u2014 past the deadline\");\n    }\n    if (ex.reason === \"budget\" && ex.cents_over != null) return money(ex.cents_over) + \" over budget\";\n    if (ex.reason === \"attribute\" && ex.field) return \"no \" + ex.field + \" claim\";\n    return ex.detail || \"\";\n  }\n\n  // Lanes promised in routing.expected_vendors whose offer hasn't landed\n  // yet — the placeholder-card list while the quote streams.\n  function pendingLanes() {\n    var q = state.quote;\n    if (!q || q.status !== \"quoting\" || !q.routing || !q.routing.expected_vendors) return [];\n    var offered = {};\n    (q.offers || []).forEach(function (o) { offered[o.vendor + \"|\" + o.process] = true; });\n    return q.routing.expected_vendors.filter(function (ev) {\n      return !offered[ev.vendor + \"|\" + ev.process];\n    });\n  }\n\n  function prettySlug(s) { return String(s).replace(/[_-]/g, \" \"); }\n\n  function escapeHtml(s) {\n    return String(s).replace(/[&<>\"']/g, function (c) {\n      return { \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" }[c];\n    });\n  }\n\n  // The vendor's mark ahead of its name (ALC-46). Empty string when the\n  // offer carries no logo — the name stands alone, never a broken image.\n  function vendorIconHtml(url) {\n    if (!url) return \"\";\n    return \"<img class='vicon' src='\" + escapeHtml(url) + \"' alt='' onerror='this.remove()'>\";\n  }\n\n  // The little origin flags after the vendor name (ALC-61): regional-\n  // indicator emoji from the wire's ships_from regions (1-2 ISO codes;\n  // \"EU\" bloc = one EU flag for a multi-EU-country network; Xometry\n  // shows US + EU; the lone \"WW\" = a globe for a truly worldwide\n  // network, a curated fact, never a fallback), tooltip = \"ships from\n  // <region> & <region>\" / \"ships worldwide\". Empty string when the\n  // wire carries no regions — an origin nobody researched shows\n  // nothing, never a guessed flag.\n  function flagHtml(shipsFrom) {\n    var codes = (Array.isArray(shipsFrom) ? shipsFrom : [shipsFrom])\n      .filter(function (c) { return c && /^[A-Za-z]{2}$/.test(String(c)); })\n      .slice(0, 2);\n    if (!codes.length) return \"\";\n    var flags = \"\", names = [], world = false;\n    codes.forEach(function (c) {\n      var cc = String(c).toUpperCase();\n      if (cc === \"WW\") {\n        world = true;\n        flags += \"\\uD83C\\uDF0D\";\n        names.push(\"worldwide\");\n        return;\n      }\n      flags += String.fromCodePoint(\n        0x1f1e6 + cc.charCodeAt(0) - 65, 0x1f1e6 + cc.charCodeAt(1) - 65);\n      var name = cc;\n      try { name = new Intl.DisplayNames([\"en\"], { type: \"region\" }).of(cc) || cc; } catch (e) {}\n      names.push(name);\n    });\n    var title = world && codes.length === 1\n      ? \"ships worldwide\" : \"ships from \" + names.join(\" & \");\n    return \"<span class='flag' title='\" + escapeHtml(title) + \"'>\" + flags + \"</span>\";\n  }\n\n  // \"Organize by\": the header names the applied ordering (order_by is\n  // echoed on every read) — same wording the text layer uses, so a\n  // ChatGPT user reads the same story a text-host user does. A custom\n  // order's label is agent-authored free text: escape it.\n  function orderLabel(ob) {\n    if (!ob) return \"ranked by price + speed\";\n    if (ob.custom) return \"sorted by \" + escapeHtml(ob.label || \"custom order\");\n    var names = { price: \"part price\", unit_price: \"per-piece price\",\n      total_price: \"all-in price\", arrival: \"soonest arrival\",\n      rating: \"vendor rating\", recommended: \"price + speed\" };\n    var name = names[ob.key] || escapeHtml(ob.key);\n    return \"sorted by \" + name + (ob.direction === \"desc\" ? \" (reversed)\" : \"\");\n  }\n\n  function render() {\n    var q = state.quote;\n    if (!q) return;\n    root.innerHTML = \"\";\n    if (!q.offers) q.offers = [];\n    var pending = pendingLanes();\n    var hdr = document.createElement(\"div\");\n    hdr.className = \"hdr\";\n    hdr.innerHTML = \"<h2>Fabrication offers</h2><span class='sub'>\" +\n      q.offers.length + \" options · \" + orderLabel(q.order_by) +\n      (pending.length ? \" · \" + pending.length + \" still pricing\" : \"\") + \"</span>\";\n    root.appendChild(hdr);\n\n    var nearMisses = q.near_misses || [];\n    if (q.offers.length === 0 && pending.length === 0) {\n      var empty = document.createElement(\"div\");\n      empty.className = \"empty\";\n      // Only-near-misses is NOT \"no offers\": real bids exist, each one\n      // constraint away — say so and show them below. A still-quoting\n      // board with nothing landed yet is NOT \"no offers\" either — the\n      // network is working; never present in-progress as failure.\n      // Notes are agent-facing wire context, never user copy (founder\n      // call, 2026-07-26 live test) — they stay out of the board.\n      empty.textContent = nearMisses.length\n        ? \"No offers met every constraint \\u2014 these real bids sit just outside:\"\n        : q.status === \"quoting\"\n          ? \"The network is pricing your part \\u2014 offers are seconds away.\"\n          : \"No offers matched.\";\n      root.appendChild(empty);\n      if (!nearMisses.length) return;\n    }\n\n    // Auto-routed quotes can span processes: group with light headers —\n    // unless an \"organize by\" ordering applies. A sort is a GLOBAL\n    // statement (\"cheapest on top\", \"most green first\"); regrouping by\n    // process would quietly break it, so sorted boards render flat in\n    // the server's order.\n    var processes = [];\n    q.offers.forEach(function (o) {\n      if (processes.indexOf(o.process) === -1) processes.push(o.process);\n    });\n    var ordered = q.offers;\n    if (processes.length > 1 && !q.order_by) {\n      ordered = [];\n      processes.forEach(function (p) {\n        ordered.push({ groupHeader: p });\n        q.offers.forEach(function (o) { if (o.process === p) ordered.push(o); });\n      });\n    }\n\n    ordered.forEach(function (offer) {\n      if (offer.groupHeader) {\n        var gh = document.createElement(\"div\");\n        gh.className = \"group-hdr\";\n        gh.textContent = String(offer.groupHeader).replace(/_/g, \" \");\n        root.appendChild(gh);\n        return;\n      }\n      // \"Best value\" is the RECOMMENDED rank's verdict: under a user\n      // ordering the top card is merely first in that sort, not a value\n      // claim — the badge (and green ring) sit out.\n      var best = !q.order_by && offer === q.offers[0];\n      var card = document.createElement(\"div\");\n      card.className = \"offer\" + (best ? \" best\" : \"\");\n\n      var thumb;\n      if (offer.preview && offer.preview.status === \"ready\" && offer.preview.url) {\n        thumb = document.createElement(\"img\");\n        thumb.className = \"thumb\";\n        thumb.src = offer.preview.url;\n        thumb.alt = \"product preview\";\n      } else {\n        thumb = document.createElement(\"div\");\n        thumb.className = \"thumb pending\";\n        thumb.textContent = offer.preview && offer.preview.status === \"pending\"\n          ? \"vendor preview rendering…\" : \"no preview\";\n      }\n      card.appendChild(thumb);\n\n      var meta = document.createElement(\"div\");\n      meta.className = \"meta\";\n      var badges = \"\";\n      if (best) badges += \"<span class='badge best-badge'>Best value</span>\";\n      if (offer.attributes && offer.attributes.outdoor_rated) badges += \"<span class='badge'>outdoor</span>\";\n      if (offer.attributes && offer.attributes.submersible) badges += \"<span class='badge'>waterproof</span>\";\n      if (offer.price_basis === \"estimate\") badges += \"<span class='badge'>estimate</span>\";\n      // Order-value floor: the price IS the vendor's per-order minimum, not a\n      // scaled per-part price — say so with the vendor's own wording (title).\n      if (offer.min_order) {\n        badges += \"<span class='badge' title='\" + String(offer.min_order.detail).replace(/'/g, \"&#39;\") +\n          \"'>vendor order minimum</span>\";\n      }\n      // A price we cannot actually buy. The card stays — it is real price\n      // discovery — but it must never look purchasable, so the badge is\n      // the loud one on the card and the buy affordance goes away.\n      if (offer.fulfillment === \"not_connected\") {\n        badges += \"<span class='badge unconnected' title='The price is live, \" +\n          \"and full ordering support is on the way.'>not orderable yet</span>\";\n      }\n      // Verified-purchase vendor rating; tap/click reveals facet sub-scores\n      // and the full subprocess drill-down (ALC-38). Inline, the deepest\n      // niche of THIS offer's spec with a track record shows beside the\n      // overall stars — the vendor's rating for what is actually being\n      // bought — with a trend marker when its recent reviews drift from\n      // the lifetime average.\n      var rating = offer.vendor_rating;\n      var facetBits = [];\n      if (rating && rating.facets) {\n        if (rating.facets.quality != null) facetBits.push(\"quality \" + Number(rating.facets.quality).toFixed(1));\n        if (rating.facets.timeliness != null) facetBits.push(\"timeliness \" + Number(rating.facets.timeliness).toFixed(1));\n        if (rating.facets.accuracy != null) facetBits.push(\"accuracy \" + Number(rating.facets.accuracy).toFixed(1));\n      }\n      var track = (rating && rating.spec_track) || [];\n      var trackLabel = function (t) {\n        var raw = t.spec_path ? t.spec_path[t.spec_path.length - 1]\n          : t.material != null ? String(t.material) : String(t.process);\n        return raw.replace(/_/g, \" \");\n      };\n      // \\u25BE below / \\u25B4 above the rung's lifetime average by >= 0.3.\n      var trendChar = function (t) {\n        if (t.stars == null || t.recent_stars == null) return \"\";\n        if (t.recent_stars <= t.stars - 0.3) return \"\\u25BE\";\n        if (t.recent_stars >= t.stars + 0.3) return \"\\u25B4\";\n        return \"\";\n      };\n      var trendHtml = function (t) {\n        var c = trendChar(t);\n        if (!c) return \"\";\n        return \"<span class='trend \" + (c === \"\\u25BE\" ? \"down\" : \"up\") +\n          \"' title='recent reviews average \" + Number(t.recent_stars).toFixed(1) + \"\\u2605'>\" + c + \"</span>\";\n      };\n      var trackBits = track.map(function (t) {\n        var num = t.stars != null\n          ? \"\\u2605 \" + Number(t.stars).toFixed(1) + \" (\" + t.orders + \")\"\n          : t.orders + \" order\" + (t.orders === 1 ? \"\" : \"s\");\n        return trackLabel(t) + \" \" + num + trendChar(t);\n      });\n      var niche = null;\n      for (var ti = track.length - 1; ti >= 0; ti--) {\n        if (track[ti].spec_path && track[ti].stars != null) { niche = track[ti]; break; }\n      }\n      var nicheHtml = niche\n        ? \" <span class='niche' title='\" + trackBits.join(\" \\u203A \").replace(/'/g, \"&#39;\") + \"'>\" +\n          trackLabel(niche) + \" \\u2605 \" + Number(niche.stars).toFixed(1) +\n          \" <span class='rcount'>(\" + niche.orders + \")</span>\" + trendHtml(niche) + \"</span>\"\n        : \"\";\n      var revealHtml = (trackBits.length ? \"<div>\" + trackBits.join(\" \\u203A \") + \"</div>\" : \"\") +\n        (facetBits.length ? \"<div>\" + facetBits.join(\" \\u00b7 \") + \"</div>\" : \"\");\n      var ratingHtml = rating\n        ? \" <span class='rating\" + (revealHtml ? \" tappable\" : \"\") + \"'>\\u2605 \" +\n          Number(rating.stars).toFixed(1) + \" <span class='rcount'>(\" + rating.review_count + \")</span></span>\"\n        : \"\";\n      meta.innerHTML = \"<div class='spec'>\" + specLine(offer) + \"</div>\" +\n        \"<div class='vendor'>\" + vendorIconHtml(offer.vendor_logo_url) +\n        offer.vendor_display_name + flagHtml(offer.ships_from) + ratingHtml + nicheHtml + \"</div>\" +\n        (revealHtml ? \"<div class='facets'>\" + revealHtml + \"</div>\" : \"\") +\n        \"<div class='badges'>\" + badges + \"</div>\";\n      if (revealHtml) {\n        var ratingEl = meta.querySelector(\".rating\");\n        var facetsEl = meta.querySelector(\".facets\");\n        if (ratingEl && facetsEl) {\n          ratingEl.addEventListener(\"click\", function () {\n            facetsEl.classList.toggle(\"open\");\n          });\n        }\n      }\n      card.appendChild(meta);\n\n      var buy = document.createElement(\"div\");\n      buy.className = \"buy\";\n      var sel = document.createElement(\"select\");\n      (offer.shipping_options || []).forEach(function (s) {\n        var opt = document.createElement(\"option\");\n        opt.value = s.id;\n        opt.textContent = s.label + \" — \" + (s.cost_cents === 0 ? \"free\" : money(s.cost_cents)) +\n          \" · \" + fmtDate(s.eta_date);\n        sel.appendChild(opt);\n      });\n      // Headline = PART price; shipping + arrival ranges beneath. The\n      // select still picks the concrete speed the Choose button orders.\n      var price = document.createElement(\"div\");\n      price.innerHTML = priceStoryHtml(offer);\n      // \"Checkout\", not \"Choose\": the button IS the path to payment —\n      // zero-friction doctrine (founder call, 2026-07-26 live test).\n      var btn = document.createElement(\"button\");\n      btn.textContent = \"Checkout\";\n      // An unconnected lane has no payment path to send the model down:\n      // the button is dead and says why, rather than starting a checkout\n      // the wire refuses two turns later.\n      if (offer.fulfillment === \"not_connected\") {\n        btn.textContent = \"Can't order yet\";\n        btn.disabled = true;\n        btn.className = \"dead\";\n        btn.title = \"We're not fully connected to \" + offer.vendor_display_name +\n          \" yet — the price is live, but we can't place the order for you just yet. \" +\n          \"We're working hard to bring them fully onto the network.\";\n        sel.disabled = true;\n      }\n      btn.onclick = function () {\n        var a = api();\n        var msg = \"I want offer \" + offer.id + \" with shipping option \\\"\" + sel.value +\n          \"\\\". Create the checkout now and give me the payment link (I'll enter my address on the payment page).\";\n        if (a && typeof a.sendFollowUpMessage === \"function\") {\n          a.sendFollowUpMessage({ prompt: msg });\n        } else if (a && typeof a.sendFollowupTurn === \"function\") {\n          a.sendFollowupTurn({ prompt: msg });\n        }\n      };\n      buy.appendChild(price);\n      buy.appendChild(sel);\n      buy.appendChild(btn);\n      card.appendChild(buy);\n      root.appendChild(card);\n    });\n\n    // Vendors still pricing: one placeholder card per pending lane, the\n    // whole card a constant-rate green fill (p90 pacing: the answer\n    // beats the bar ~9 times in 10). AFN owns every timer; the client\n    // only interpolates wire absolutes: a lane carrying published_at +\n    // deadline (with the payload's served_at as the clock anchor) is\n    // paced by (deadline - published_at) and mounted mid-fill via a\n    // negative animation-delay computed on the SERVER clock — exactly\n    // where the server says the lane is, regardless of the client's\n    // clock. When the vendor answers, the next render replaces the\n    // placeholder with the real offer card — the \"snap\". Overdue lanes\n    // are pruned from expected_vendors server-side per read, so a bar\n    // that fills completely sits full until the next poll re-render\n    // drops the card; the client never decides a lane is dead.\n    // Absolutes-less payloads (old servers) fall back to expected_wait_s\n    // paced from the client-side pendingSince first-seen anchor.\n    var now = Date.now();\n    // Clock offset, computed ONCE per render: client-now minus the\n    // server-now the payload was served at. Subtracting it from\n    // Date.now() re-expresses \"now\" on the server's clock.\n    var servedAtMs = q.served_at ? Date.parse(q.served_at) : NaN;\n    var offsetMs = isNaN(servedAtMs) ? null : now - servedAtMs;\n    var liveKeys = {};\n    if (pending.length) {\n      var phHdr = document.createElement(\"div\");\n      phHdr.className = \"group-hdr\";\n      phHdr.textContent = \"Still pricing\";\n      root.appendChild(phHdr);\n      pending.forEach(function (ev) {\n        // Wire-absolute pacing when the lane + payload carry it: the\n        // server clock alone decides where the bar sits.\n        var durationS = null, delayS = null;\n        if (offsetMs !== null && ev.published_at && ev.deadline) {\n          var pubMs = Date.parse(ev.published_at);\n          var deadMs = Date.parse(ev.deadline);\n          if (!isNaN(pubMs) && !isNaN(deadMs) && deadMs > pubMs) {\n            var serverNowMs = now - offsetMs;\n            durationS = (deadMs - pubMs) / 1000;\n            // Negative delay mounts the bar mid-fill at the server-said\n            // elapsed fraction; clamped so it never starts in the future.\n            delayS = Math.min(0, -((serverNowMs - pubMs) / 1000));\n          }\n        }\n        if (durationS === null) {\n          // Fallback for absolutes-less payloads (old servers): pace by\n          // expected_wait_s, resume across re-renders from the\n          // client-side first-seen anchor.\n          var key = ev.vendor + \"|\" + ev.process;\n          liveKeys[key] = true;\n          if (!state.pendingSince[key]) state.pendingSince[key] = now;\n          var elapsedS = (now - state.pendingSince[key]) / 1000;\n          durationS = ev.expected_wait_s > 0 ? ev.expected_wait_s : 8;\n          delayS = -Math.min(elapsedS, durationS);\n        }\n        var card = document.createElement(\"div\");\n        card.className = \"offer quoting\";\n        var fill = document.createElement(\"div\");\n        fill.className = \"loadfill\";\n        fill.style.animationDuration = durationS + \"s\";\n        fill.style.animationDelay = delayS + \"s\";\n        // No client-side kick: the server prunes overdue lanes from\n        // expected_vendors per read, so a completed bar sits full until\n        // the next poll re-render drops the placeholder. The client\n        // never decides a lane is dead.\n        card.appendChild(fill);\n        var thumb = document.createElement(\"div\");\n        thumb.className = \"thumb pending\";\n        thumb.textContent = \"pricing\\u2026\";\n        card.appendChild(thumb);\n        var meta = document.createElement(\"div\");\n        meta.className = \"meta\";\n        var spec = document.createElement(\"div\");\n        spec.className = \"spec\";\n        // The lane's brand mark while it prices (ALC-46): the buyer sees\n        // WHO is being asked, not just a slug. Missing/failed logo → name alone.\n        if (ev.logo_url) {\n          var icon = document.createElement(\"img\");\n          icon.className = \"vicon\";\n          icon.src = ev.logo_url;\n          icon.alt = \"\";\n          icon.onerror = function () { icon.remove(); };\n          spec.appendChild(icon);\n        }\n        spec.appendChild(document.createTextNode(prettySlug(ev.vendor)));\n        if (ev.ships_from) spec.insertAdjacentHTML(\"beforeend\", flagHtml(ev.ships_from));\n        var vendorLine = document.createElement(\"div\");\n        vendorLine.className = \"vendor\";\n        vendorLine.textContent = prettySlug(ev.process) + \" \\u00b7 quote on the way\";\n        meta.appendChild(spec);\n        meta.appendChild(vendorLine);\n        card.appendChild(meta);\n        // No printed seconds: the wait paces the bar's animation only —\n        // an estimate an animation can absorb becomes a lie in print.\n        root.appendChild(card);\n      });\n    }\n    // Prune snapped/cancelled lanes so a lane that comes back later\n    // (new refine) starts its bar fresh.\n    for (var pk in state.pendingSince) {\n      if (!liveKeys[pk]) delete state.pendingSince[pk];\n    }\n\n    // Near misses: REAL vendor bids that failed exactly ONE stated\n    // constraint — visually separated, reason per card, never orderable\n    // as-is (no Choose button; checkout refuses their ids).\n    if (nearMisses.length) {\n      var nmHdr = document.createElement(\"div\");\n      nmHdr.className = \"group-hdr\";\n      nmHdr.textContent = \"Just outside constraints\";\n      root.appendChild(nmHdr);\n      var nmNote = document.createElement(\"div\");\n      nmNote.className = \"nm-note\";\n      nmNote.textContent = \"Real bids that missed one constraint \\u2014 not orderable as-is; relax that constraint to convert them.\";\n      root.appendChild(nmNote);\n      nearMisses.forEach(function (o) {\n        var card = document.createElement(\"div\");\n        card.className = \"offer nearmiss\";\n        var thumb;\n        if (o.preview && o.preview.status === \"ready\" && o.preview.url) {\n          thumb = document.createElement(\"img\");\n          thumb.className = \"thumb\";\n          thumb.src = o.preview.url;\n          thumb.alt = \"product preview\";\n        } else {\n          thumb = document.createElement(\"div\");\n          thumb.className = \"thumb pending\";\n          thumb.textContent = o.preview && o.preview.status === \"pending\"\n            ? \"vendor preview rendering\\u2026\" : \"no preview\";\n        }\n        card.appendChild(thumb);\n        var meta = document.createElement(\"div\");\n        meta.className = \"meta\";\n        var why = nearMissWhy(o.excluded_by);\n        meta.innerHTML = \"<div class='spec'>\" + specLine(o) + \"</div>\" +\n          \"<div class='vendor'>\" + vendorIconHtml(o.vendor_logo_url) + o.vendor_display_name + flagHtml(o.ships_from) + \"</div>\" +\n          (why ? \"<div class='badges'><span class='badge why'>\" + why + \"</span></div>\" : \"\");\n        card.appendChild(meta);\n        var buy = document.createElement(\"div\");\n        buy.className = \"buy\";\n        buy.innerHTML = priceStoryHtml(o);\n        card.appendChild(buy);\n        root.appendChild(card);\n      });\n    }\n\n    // Processes UFP can't quote yet (auto-routing) — one-line note.\n    if (q.routing && q.routing.also_possible && q.routing.also_possible.length) {\n      var also = document.createElement(\"div\");\n      also.className = \"also-note\";\n      also.textContent = \"Also possible (no vendor yet): \" +\n        q.routing.also_possible.map(function (a) {\n          return String(a.process).replace(/_/g, \" \") + \" — \" + a.reason;\n        }).join(\" · \");\n      root.appendChild(also);\n    }\n\n    // quote.notes are agent-facing wire context (vendor availability,\n    // price-refresh mechanics) — the model relays what's relevant in its\n    // own words; the board never prints them raw (founder call,\n    // 2026-07-26 live test: the strip read as errors shot at the user).\n\n    // Experiment C verdict (2026-07-26, three live rounds): widget-\n    // initiated tool calls never reached the server on either transport\n    // (openai.callTool or the MCP Apps bridge) — today's ChatGPT runtime\n    // doesn't deliver them. The poll machinery above stays dormant for\n    // the day it does; the visible diag line is gone.\n  }\n\n  function hasPendingPreviews() {\n    var q = state.quote;\n    return !!(q && q.offers && q.offers.some(function (o) {\n      return o.preview && o.preview.status === \"pending\";\n    }));\n  }\n\n  function isStillQuoting() {\n    return !!(state.quote && state.quote.status === \"quoting\");\n  }\n\n  // Self-polling, take two (experiment C, 2026-07-26). The old poll went\n  // through refine_quote, whose openai/outputTemplate made ChatGPT stack\n  // a NEW board per pass (\"3 responses climbing to 100%\"). poll_quote\n  // carries no output template: the result flows back through the\n  // callTool promise and THIS board re-renders in place — one embed,\n  // tiles fill live. Polls while quoting or previews are pending; hard\n  // capped; any transport failure ends polling silently (the board then\n  // simply shows the last truth it received — wire bars still animate\n  // from their absolutes).\n  var POLL_MS = 2500;\n  var MAX_POLLS = 24;\n  // live: on-board diagnostics for the self-update channel (experiment C\n  // instrumentation — the widget console is unreachable from the dev\n  // box, so the board itself reports which transport worked / failed).\n  // TODO: strip the visible diag line once the transport is settled.\n  var live = { transport: \"?\", passes: 0, err: \"\" };\n\n  // MCP Apps standard bridge: JSON-RPC request to the host over\n  // postMessage — fallback when window.openai.callTool is absent.\n  var bridgeSeq = 0;\n  var pendingBridge = {};\n  function bridgeCall(name, args) {\n    return new Promise(function (resolve, reject) {\n      var id = \"wpoll_\" + (++bridgeSeq);\n      pendingBridge[id] = { resolve: resolve, reject: reject };\n      try {\n        window.parent.postMessage(\n          { jsonrpc: \"2.0\", id: id, method: \"tools/call\", params: { name: name, arguments: args } },\n          \"*\"\n        );\n      } catch (e) { delete pendingBridge[id]; reject(e); return; }\n      setTimeout(function () {\n        if (pendingBridge[id]) { delete pendingBridge[id]; reject(new Error(\"bridge timeout\")); }\n      }, 8000);\n    });\n  }\n  window.addEventListener(\"message\", function (ev) {\n    var m = ev.data;\n    if (!m || typeof m !== \"object\" || !m.id || !pendingBridge[m.id]) return;\n    var p = pendingBridge[m.id];\n    delete pendingBridge[m.id];\n    if (m.error) p.reject(new Error(m.error.message || \"bridge error\"));\n    else p.resolve(m.result);\n  });\n\n  function callPoll(args) {\n    var a = api();\n    if (a && typeof a.callTool === \"function\") {\n      live.transport = \"openai.callTool\";\n      return a.callTool(\"poll_quote\", args);\n    }\n    live.transport = \"mcp-bridge\";\n    return bridgeCall(\"poll_quote\", args);\n  }\n\n  function maybePoll() {\n    clearTimeout(state.pollTimer);\n    if (!isStillQuoting() && !hasPendingPreviews()) return;\n    if (state.polls >= MAX_POLLS) return;\n    state.pollTimer = setTimeout(function () {\n      state.polls++;\n      live.passes = state.polls;\n      var p;\n      try {\n        p = callPoll({ quote_id: state.quote.quote_id });\n      } catch (e) {\n        live.err = String(e && e.message || e);\n        render();\n        return;\n      }\n      p.then(\n        function (res) {\n          var out = res && (res.structuredContent || res.structured_content);\n          if (!out && res && res.result) out = res.result.structuredContent || res.result.structured_content;\n          if (out && out.quote_id) { state.quote = out; live.err = \"\"; render(); }\n          else { live.err = \"pass \" + state.polls + \": no structuredContent in result\"; render(); }\n          maybePoll();\n        },\n        function (e) {\n          live.err = \"pass \" + state.polls + \": \" + String(e && e.message || e);\n          render();\n          maybePoll();\n        }\n      );\n    }, POLL_MS);\n  }\n\n  function accept(output) {\n    if (output && output.quote_id) {\n      state.quote = output;\n      state.polls = 0;\n      render();\n      maybePoll();\n    }\n  }\n\n  // ChatGPT (window.openai) path.\n  var a = api();\n  if (a && a.toolOutput) accept(a.toolOutput);\n  window.addEventListener(\"openai:set_globals\", function () {\n    var b = api();\n    if (b && b.toolOutput) accept(b.toolOutput);\n  });\n\n  // MCP Apps standard bridge fallback (ui/* JSON-RPC over postMessage).\n  window.addEventListener(\"message\", function (ev) {\n    var m = ev.data;\n    if (!m || typeof m !== \"object\") return;\n    if (m.method === \"ui/notifications/tool-result\" && m.params) {\n      accept(m.params.structuredContent || m.params.structured_content);\n    }\n  });\n})();\n</script>\n</body>\n</html>"}]},"jsonrpc":"2.0","id":6},"http_status":200,"headers":{"content-type":"application/json"},"resource_uri":"ui://widget/quote-compare-v4.html"}},"probe_noise_resilience":{"status":"ok","latency_ms":38.38,"details":{"url":"https://agenticfabricationnetwork.ai/robots.txt","http_status":200,"headers":{"content-type":"text/plain; charset=UTF-8"}}},"determinism_probe":{"status":"ok","latency_ms":75.12,"details":{"attempts":2,"successful":2,"matches":2,"stable_ratio":1.0,"baseline_signature":"cc7f7dbd43d00d9920640fef26e8a20573e9817e8c8f9d0aed4e7a47ff5aad49","errors":[]}},"session_resume_probe":{"status":"warning","latency_ms":null,"details":{"reason":"no_session_id","transport":"streamable-http","resume_expected":true,"protocol_version":"2025-03-26"}},"step_up_auth_probe":{"status":"missing","latency_ms":null,"details":{"oauth_present":false,"auth_required_checks":[],"supported_scopes":[],"scope_specificity_ratio":0.0,"broad_scopes":[],"challenge_headers":[],"step_up_signals":[],"minimal_scope_documented":false}},"transport_compliance_probe":{"status":"warning","latency_ms":45.15,"details":{"transport":"streamable-http","session_id_present":false,"protocol_header_present":false,"last_event_id_visible":false,"requested_protocol_version":"2025-03-26","bad_protocol_status_code":400,"bad_protocol_payload":{"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: Unsupported protocol version: 1999-99-99 (supported versions: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07)"},"id":null},"bad_protocol_headers":{"content-type":"application/json"},"bad_protocol_error":null,"delete_status_code":null,"delete_error":null,"expired_session_status_code":null,"expired_session_error":null,"issues":["missing_session_id","missing_protocol_header"]}},"utility_coverage_probe":{"status":"missing","latency_ms":90.09,"details":{"completions":{"advertised":false,"sample_target":{"type":"resource","uri":"ui://widget/quote-compare-v4.html"},"live_probe":"not_executed"},"pagination":{"supported":false,"next_cursor_methods":[],"metadata_signal":false},"tasks":{"advertised":false,"probe_status":"missing","http_status":200},"initialize_capability_keys":["resources","tools"]}},"advanced_capabilities_probe":{"status":"warning","latency_ms":null,"details":{"capabilities":{"prompts":false,"resources":true,"completions":false,"roots":false,"sampling":false,"elicitation":false,"structured_outputs":false,"resource_links":true},"enabled_count":2,"enabled":["resource_links","resources"],"initialize_capability_keys":["resources","tools"]}},"tool_snapshot_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_historical_snapshot","current_tool_count":9}},"connector_replay_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_historical_snapshot"}},"request_association_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_request_association_capabilities_advertised"}},"interactive_flow_probe":{"status":"missing","latency_ms":null,"details":{"risk_hits":[],"safe_hits":[],"oauth_supported":false,"prompt_available":false}},"action_safety_probe":{"status":"error","latency_ms":null,"details":{"summary":{"tool_count":9,"high_risk_tools":2,"destructive_tools":2,"exec_tools":2,"egress_tools":2,"secret_tools":2,"bulk_access_tools":0,"risk_distribution":{"low":3,"medium":4,"high":0,"critical":2},"capability_distribution":{"read":7,"write":5,"delete":2,"exec":2,"network":2,"filesystem":1,"admin":5,"secrets":2,"other":2}},"auth_present":false,"safeguard_count":1,"confirmation_signals":["get_fabrication_quote","refine_quote","leave_review"]}},"official_registry_probe":{"status":"ok","latency_ms":null,"details":{"registry_source":"official_registry","registry_identifier":"ai.agenticfabricationnetwork/ufp","direct_match":true,"official_peer_count":1}},"provenance_divergence_probe":{"status":"ok","latency_ms":null,"details":{"direct_official_match":true,"registry_title":null,"server_card_title":null,"registry_version":null,"server_card_version":null,"registry_homepage":null,"server_card_homepage":null,"registry_repository":null,"server_card_repository":null,"drift_fields":[],"metadata_document_count":1}},"connector_publishability_probe":{"status":"warning","latency_ms":null,"details":{"transport":"streamable-http","tool_count":9,"high_risk_tools":2,"blockers":["action_safety","server_card"],"criteria":{"remote_transport":true,"initialize":true,"tools_list":true,"protocol_version":true,"session_resume":true,"step_up_auth":true,"transport_compliance":true,"connector_replay":true,"request_association":true,"action_safety":false,"server_card":false,"tool_surface":true,"auth_flow":true}}}},"score_components":{"auth_operability_score":2.0,"error_contract_score":2.12,"rate_limit_semantics_score":2.0,"schema_completeness_score":2.0,"backward_compatibility_score":2.0,"slo_health_score":4.0,"security_hygiene_score":2.5,"task_success_score":4.0,"trust_confidence_score":1.75,"abuse_noise_ratio_score":4.0,"prompt_contract_score":2.0,"resource_contract_score":4.0,"discovery_metadata_score":3.0,"registry_consistency_score":2.0,"installability_score":4.0,"session_semantics_score":4.0,"tool_surface_design_score":3.0,"result_shape_stability_score":2.0,"oauth_interop_score":3.0,"recovery_semantics_score":0.0,"maintenance_signal_score":3.0,"adoption_signal_score":2.0,"freshness_confidence_score":3.0,"transport_fidelity_score":4.0,"spec_recency_score":2.0,"session_resume_score":3.0,"step_up_auth_score":3.0,"transport_compliance_score":2.0,"utility_coverage_score":2.0,"advanced_capability_coverage_score":3.0,"connector_publishability_score":3.0,"tool_snapshot_churn_score":3.0,"connector_replay_score":3.0,"request_association_score":3.0,"interactive_flow_safety_score":3.0,"action_safety_score":2.0,"official_registry_presence_score":4.0,"provenance_divergence_score":4.0,"safety_transparency_score":4.0,"tool_capability_clarity_score":3.0,"destructive_operation_safety_score":3.0,"egress_ssrf_resilience_score":3.5,"execution_sandbox_safety_score":2.0,"data_exfiltration_resilience_score":3.0,"least_privilege_scope_score":2.0,"secret_handling_hygiene_score":2.0,"dependency_supply_chain_signal_score":2.5,"input_sanitization_safety_score":3.0,"tool_namespace_clarity_score":4.0},"validation_schema_version":"16d1d270090d6c8f","started_at":"2026-07-31T05:28:10.966602+00:00","completed_at":"2026-07-31T05:28:11.900192+00:00"},"current_snapshot":{"schema_version":"verify.trust_snapshot.v1","snapshot_id":"trustsnap_1a841aa4901b9aec","generated_at":"2026-07-31T11:42:38.309507+00:00","source":"current_snapshot","server":"ai.agenticfabricationnetwork/ufp","last_validated_at":"2026-07-31T05:28:11.900192+00:00","validation_age_hours":6.24,"freshness":{"schema_version":"verify.freshness_profile.v1","last_validated_at":"2026-07-31T05:28:11.900192+00:00","age_hours":6.24,"bucket":"verified_last_24h","label":"Verified in last 24h","badges":["verified_last_24h"],"freshness_sla_hours":168.0,"freshness_sla_status":"met","stale_score_suppressed":false,"display_score":70.09,"raw_score":70.09,"confidence_score":76.2,"confidence_weighted_score":53.4,"tier_status":[{"tier":"community","label":"Community","freshness_sla_hours":720,"met":true,"priority_revalidation":false},{"tier":"pro","label":"Pro","freshness_sla_hours":168,"met":true,"priority_revalidation":true},{"tier":"enterprise","label":"Enterprise","freshness_sla_hours":24,"met":true,"priority_revalidation":true}]},"current_status":"healthy","current_score":70.09,"display_score":70.09,"stale_score_suppressed":false,"production_trust_decision":{"schema_version":"verify.executive_verdict.v1","decision":"Allow with approval","why":"exec-capable tools + no confirmation safeguards + high-risk tools need review","next_action":"export policy, require approval for writes, add authenticated validation","reason_count":2},"production_readiness_class":{"code":"safe_for_evaluation","label":"Safe for evaluation","reason":"The server is suitable for evaluation, but remaining gaps should be resolved before broad production use."},"evidence_confidence":{"score":76.25,"label":"high","validation_age_hours":6.24,"live_check_count":26},"active_alerts":[]},"trust_snapshot":{"schema_version":"verify.trust_snapshot.v1","snapshot_id":"trustsnap_1a841aa4901b9aec","generated_at":"2026-07-31T11:42:38.309507+00:00","source":"current_snapshot","server":"ai.agenticfabricationnetwork/ufp","last_validated_at":"2026-07-31T05:28:11.900192+00:00","validation_age_hours":6.24,"freshness":{"schema_version":"verify.freshness_profile.v1","last_validated_at":"2026-07-31T05:28:11.900192+00:00","age_hours":6.24,"bucket":"verified_last_24h","label":"Verified in last 24h","badges":["verified_last_24h"],"freshness_sla_hours":168.0,"freshness_sla_status":"met","stale_score_suppressed":false,"display_score":70.09,"raw_score":70.09,"confidence_score":76.2,"confidence_weighted_score":53.4,"tier_status":[{"tier":"community","label":"Community","freshness_sla_hours":720,"met":true,"priority_revalidation":false},{"tier":"pro","label":"Pro","freshness_sla_hours":168,"met":true,"priority_revalidation":true},{"tier":"enterprise","label":"Enterprise","freshness_sla_hours":24,"met":true,"priority_revalidation":true}]},"current_status":"healthy","current_score":70.09,"display_score":70.09,"stale_score_suppressed":false,"production_trust_decision":{"schema_version":"verify.executive_verdict.v1","decision":"Allow with approval","why":"exec-capable tools + no confirmation safeguards + high-risk tools need review","next_action":"export policy, require approval for writes, add authenticated validation","reason_count":2},"production_readiness_class":{"code":"safe_for_evaluation","label":"Safe for evaluation","reason":"The server is suitable for evaluation, but remaining gaps should be resolved before broad production use."},"evidence_confidence":{"score":76.25,"label":"high","validation_age_hours":6.24,"live_check_count":26},"active_alerts":[]},"snapshot_invariant":{"schema_version":"verify.snapshot_invariant.v1","server":"ai.agenticfabricationnetwork/ufp","ok":true,"surface_snapshot_ids":{"page":"trustsnap_1a841aa4901b9aec","badge":null,"report":"trustsnap_1a841aa4901b9aec","policy":null},"checked_at":"2026-07-31T11:42:38.535137+00:00"},"history":{"points":[{"timestamp":"2026-07-31T05:28:11.900192+00:00","score":70.09,"status":"healthy","latency_ms":932.24,"tool_count":9,"prompt_count":0,"resource_count":2}],"status_counts":{"healthy":1},"score_delta_7d":null,"score_delta_30d":null,"avg_latency_ms":932.24,"healthy_ratio_recent":1.0,"freshness_hours":6.24,"latest_status":"healthy"},"production_readiness":{"code":"safe_for_evaluation","label":"Safe for evaluation","reason":"The server is suitable for evaluation, but remaining gaps should be resolved before broad production use.","badge":"score-medium","critical_alerts":0},"agent_commerce_readiness":{"status":"beta","commerce_signal":"strong","payment_execution_detected":"yes","billing_or_usage_detected":"yes","quote_or_pricing_detected":"yes","numeric_price_context":"yes","commercial_quote_context":"yes","checkout_or_charge_detected":"yes","checkout_term_observed":"yes","payment_capable":"observed","payment_rails":["card"],"purchase_stage_supported":["quote","usage_metering","checkout_redirect","payment_initiation","purchase"],"human_confirmation_required":"yes","spending_policy_supported":"yes","receipt_supported":"yes","refund_policy_present":"unknown","operator_identity":"declared","auth_required":"no","auth_scheme":"none","tool_risk_level":"financial_action","tool_risk_score":85,"pricing_transparency":"clear","schema_change_detected":"unknown","delegation_level":"unknown_high_risk","evidence_level":"observed","confidence":"high","highest_risk_tools":[{"name":"get_fabrication_quote","risk_level":"administrative"},{"name":"refine_quote","risk_level":"administrative"},{"name":"create_checkout","risk_level":"financial_action"},{"name":"leave_review","risk_level":"administrative"},{"name":"get_material_guide","risk_level":"administrative"}],"skipped_unsafe_tools":["get_fabrication_quote","refine_quote","create_checkout","leave_review","get_material_guide"],"last_checked_at":"2026-07-31T11:42:38.237775+00:00","evidence":[{"field":"commerce_signal","value":"strong","evidence_level":"observed","source":"tool_schema","matched_terms":["checkout","order","payment","purchase","billing","limit","limits","receipt"],"sample":"get_fabrication_quote","confidence":"high","last_checked_at":"2026-07-31T11:42:38.237775+00:00"},{"field":"payment_rails","value":"card","evidence_level":"observed","source":"tool_schema","matched_terms":["card"],"sample":"card","confidence":"medium","last_checked_at":"2026-07-31T11:42:38.237775+00:00"}],"disclaimer":"Beta assessment. Verify detects evidence and risk signals but does not certify that this server is safe for autonomous purchases.","scores":{"auth_posture_score":10,"tool_risk_score":85,"payment_readiness_score":100,"agent_delegation_safety_score":55,"overall_agent_commerce_score":51},"warnings":["This server appears to expose payment-execution tools without observed authentication. Treat as high risk."],"recommended_fixes":["Require OAuth or equivalent auth for protected tools.","Separate quote/cart tools from charge/settlement tools.","Require explicit user confirmation for payment execution.","Return amount, currency, merchant, and receipt identifiers.","Publish pricing and refund/cancellation policy.","Add operator identity metadata.","Add spending policy support or mandate constraints."]},"evidence_confidence":{"score":76.25,"label":"high","reason":"Based on 1 recent validations, 26 captured checks, and validation age of 6.2 hours.","live_check_count":26,"validation_age_hours":6.24},"recommended_for":[{"label":"Claude Desktop","reason":"Claude Desktop is marked compatible with score 100."},{"label":"Smithery","reason":"Smithery is marked compatible with score 100."},{"label":"Generic Streamable HTTP","reason":"Generic Streamable HTTP is marked compatible with score 100."}],"active_alerts":[],"remediations":[{"code":"add_confirmation_semantics","severity":"high","title":"Add confirmation and dry-run semantics for risky actions","why":"High-risk write, delete, exec, or egress tools should communicate safeguards clearly.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"fix_transport_compliance","severity":"high","title":"Align session and protocol behavior with Streamable HTTP expectations","why":"Clients increasingly rely on MCP-Protocol-Version, session teardown, and expired-session semantics.","action":"Align MCP-Protocol-Version, MCP-Session-Id, DELETE teardown, and expired-session handling with the transport spec.","playbook":["Return `Mcp-Session-Id` and `Mcp-Protocol-Version` headers consistently on streamable HTTP responses.","Honor `DELETE` session teardown and return `404` when a deleted session is reused.","Reject invalid protocol-version headers with `400 Bad Request`."],"maintainer_context":null},{"code":"enforce_request_association","severity":"high","title":"Associate roots, sampling, and elicitation with active client requests","why":"Modern MCP guidance expects roots, sampling, and elicitation traffic to be tied to an active client request instead of arriving unsolicited on idle sessions.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"publish_oauth_protected_resource","severity":"high","title":"Expose /.well-known/oauth-protected-resource","why":"Without a protected-resource document, OAuth clients cannot discover auth requirements reliably.","action":"Serve /.well-known/oauth-protected-resource and point it at your authorization server metadata.","playbook":["Serve `/.well-known/oauth-protected-resource` from the same host as the MCP endpoint.","Point it at the authorization server metadata URL.","Confirm clients receive consistent auth hints before tool execution."],"maintainer_context":null},{"code":"protect_connector_refreshes","severity":"high","title":"Keep connector refreshes backward compatible","why":"Managed connector clients freeze tool snapshots, so removed tools, new required args, and breaking output changes can break published integrations after refresh.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"publish_oauth_authorization_server","severity":"high","title":"Publish OAuth authorization-server metadata","why":"Clients need authorization-server metadata to discover issuer, endpoints, and DCR support.","action":"Publish /.well-known/oauth-authorization-server from your issuer and include registration_endpoint when supported.","playbook":["Publish `/.well-known/oauth-authorization-server` from the issuer.","Add `registration_endpoint` if DCR is supported.","Verify issuer, authorization, token, and jwks metadata are all reachable."],"maintainer_context":null},{"code":"publish_server_card","severity":"high","title":"Publish a complete server card","why":"Missing or incomplete server-card metadata weakens discovery, documentation, and trust signals.","action":"Serve /.well-known/mcp/server-card.json and include tools, prompts/resources, homepage, and support links.","playbook":["Publish `/.well-known/mcp/server-card.json`.","Include homepage, repository, support, tools, prompts/resources, and auth metadata.","Revalidate the server after publishing the card."],"maintainer_context":null},{"code":"harden_interactive_flows","severity":"high","title":"Stop asking users to paste secrets directly","why":"Public MCP servers should prefer OAuth or browser-based auth guidance over in-band secret collection.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"update_protocol_version","severity":"medium","title":"Adopt a current MCP protocol revision","why":"Older protocol revisions reduce compatibility with newer clients and registry programs.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"improve_connector_publishability","severity":"medium","title":"Close connector-publishing gaps","why":"Connector catalogs care about protocol recency, session behavior, auth clarity, and tool-surface stability.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"tighten_step_up_auth","severity":"medium","title":"Document minimal scopes and return cleaner auth challenges","why":"Modern clients expect granular scopes and step-up auth signals such as WWW-Authenticate scope hints.","action":"Return granular scopes and WWW-Authenticate challenge hints instead of forcing overly broad auth upfront.","playbook":["Advertise the narrowest viable scopes in OAuth metadata.","Return `WWW-Authenticate` challenges with scope or insufficient-scope hints when additional consent is needed.","Revalidate with both public discovery and auth-required flows."],"maintainer_context":null},{"code":"publish_openid_configuration","severity":"medium","title":"Publish OpenID configuration","why":"OIDC metadata improves token validation and client compatibility.","action":"Expose /.well-known/openid-configuration with issuer, jwks_uri, and supported grants.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"stabilize_tool_surface","severity":"medium","title":"Reduce tool-surface churn","why":"Frequent add/remove or output-shape drift makes published connectors and cached tool snapshots brittle.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"fix_prompts_list","severity":"medium","title":"Repair prompts/list or stop advertising prompts","why":"Prompt metadata should either work live or be removed from the advertised capability set.","action":"Only advertise prompts if prompts/list works and prompt arguments are documented.","playbook":["Only advertise prompts that are actually accessible.","Add prompt descriptions and argument docs.","Run a live `prompts/list` check after any prompt changes."],"maintainer_context":null},{"code":"repair_session_resume","severity":"medium","title":"Support resumable HTTP sessions cleanly","why":"Modern MCP clients increasingly expect resumable session behavior on streamable HTTP transports.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null},{"code":"expand_utility_coverage","severity":"low","title":"Expose modern utility surfaces like completions, pagination, or tasks","why":"Utility coverage improves interoperability with larger clients and long-lived agent workflows.","action":"Expose completions, pagination, and task metadata where supported so larger clients can plan and resume work safely.","playbook":["Advertise `completions`, pagination cursors, and `tasks` only when they are actually supported.","Return `nextCursor` on large list operations when pagination is available.","Document task support and whether it requires step-up auth."],"maintainer_context":null},{"code":"expand_advanced_capabilities","severity":"low","title":"Publish newer MCP capability signals","why":"Roots, sampling, elicitation, structured outputs, and related metadata improve client understanding and ranking.","action":"Inspect the latest validation evidence and resolve the client-visible regression.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."],"maintainer_context":null}],"publisher_claim":{"verified":false,"status":"unclaimed","claim_url":"https://verify.sentinelsignal.io/claim?server=ai.agenticfabricationnetwork%2Fufp&source=report_json","reason_code":"machine_attention_detected","reason":"Agents and crawlers are already evaluating this server. Claim to publish authoritative owner, security, trust, and integration metadata.","score_neutral":true},"observed_attention":{"schema":"verify.observed_attention.v1","window_days":30,"level":"high","label":"High observed attention","summary":"Recent machine-readable trust and discovery activity observed for this server.","segments":{"useful_ai_user":{"level":"none","observed":false,"description":"AI-assisted user sessions such as ChatGPT/User or Claude/User."},"machine_trust_evaluator":{"level":"moderate","observed":true,"description":"Synthetic sessions inspecting multiple trust surfaces such as report, policy, ledger, badge, trust-summary, or compare."},"possible_agent_or_script":{"level":"moderate","observed":true,"description":"Structured direct sessions with rapid profile, compare, report, policy, badge, or trust-surface fan-out."},"isolated_machine_surface":{"level":"none","observed":false,"description":"Aged-out direct synthetic singleton sessions that touched a machine-readable trust surface without becoming a broader evaluator."},"ai_crawler":{"level":"moderate","observed":true,"description":"Known AI crawler activity such as ClaudeBot, GPTBot, or similar crawlers."},"search_crawler":{"level":"none","observed":false,"description":"Search and SEO crawler activity."},"browser_like_automation":{"level":"none","observed":false,"description":"Browser-like synthetic sessions with rapid structured endpoint activity."},"confirmed_human":{"level":"none","observed":false,"description":"Confirmed browser-session human activity."}},"surfaces_observed":{"server_profile":true,"compare":true,"compare_json":false,"compare_api":true,"report_json":true,"policy":true,"ledger":true,"badge_metadata":true,"badge_svg":true,"trust_summary":true,"mcp_tool":false},"claim_prompt":{"recommended":true,"reason":"This server has recent machine attention. A verified publisher claim can improve owner, policy, security, and trust metadata available to agents."},"notes":["Observed attention is based on segmented first-party telemetry.","Crawler and evaluator activity is not treated as confirmed human demand.","Public levels are bucketed to avoid exposing raw traffic counts."]},"owner_activation":{"claim_recommended":true,"reason":"ai_discovery_detected","headline":"AI discovery detected","message":"This server is being discovered by AI users, crawlers, or machine trust evaluators on Verify. Claim this profile to add publisher metadata, official links, a security contact, and machine-readable trust details agents can use.","claim_url":"https://verify.sentinelsignal.io/claim?server=ai.agenticfabricationnetwork%2Fufp&source=report_json","trust_summary_url":"https://verify.sentinelsignal.io/v1/servers/ai.agenticfabricationnetwork/ufp/trust-summary"},"related_machine_surfaces":{"compare_index":"/compare.json","compare_api":"/v1/compare?server=ai.agenticfabricationnetwork%2Fufp","trust_summary":"/v1/servers/ai.agenticfabricationnetwork/ufp/trust-summary","ledger":"/v1/servers/ai.agenticfabricationnetwork/ufp/ledger","policy":"/v1/servers/ai.agenticfabricationnetwork/ufp/policy","report":"/v1/servers/ai.agenticfabricationnetwork/ufp/report"},"intelligence_api":{"available":true,"signup_url":"https://verify.sentinelsignal.io/verify-intelligence-api","use_case":"Programmatic MCP server trust, comparison, policy, and evidence enrichment."}}