{"schema_version":"verify.report.v1","generated_at":"2026-07-31T11:43:04.262651+00:00","snapshot_id":"trustsnap_177b8e3caca9d74a","server":{"namespace":"awesome-homespunapps","name":"homespun","title":"homespunapps/homespun","description":"homespunapps/homespun homespunapps/homespun](https://glama.ai/mcp/servers/homespunapps/homespun) 🎖️ 📇 ☁️ 🏠 🍎 🪟 🐧 - Deploy and operate multi-user web apps from an agent: one call returns a live app on its own URL with magic-link sign-in, a shared realtime database, per-role permissions, email notifications and file uploads, and the agent keeps read and write access to the app's data afterwards to keep changing it. Remote `https://homespun.dev/mcp` or `npx -y @homespunapps/mcp`.","homepage_url":"https://github.com/homespunapps/homespun","docs_url":"https://github.com/homespunapps/homespun","icon_url":null,"support_url":"https://github.com/homespunapps/homespun","remote_url":"https://homespun.dev/mcp","server_card_url":null,"latest_version":null,"current_status":"failing","current_score":55.25,"transport_type":"streamable-http","has_oauth":true,"has_dcr":true,"has_prompts":false,"tool_count":0,"current_validation_schema_version":"16d1d270090d6c8f","last_validated_at":"2026-07-31T05:33:02.319322+00:00","registry_source":"awesome_mcp_servers","registry_identifier":"awesome_mcp_servers:homespunapps/homespun","canonical_identifier":"awesome-homespunapps/homespun","current_score_components":{"auth_operability_score":4.0,"error_contract_score":0.5,"rate_limit_semantics_score":2.0,"schema_completeness_score":0.0,"backward_compatibility_score":2.0,"slo_health_score":1.88,"security_hygiene_score":4.0,"task_success_score":3.33,"trust_confidence_score":0.1,"abuse_noise_ratio_score":2.5,"prompt_contract_score":2.0,"resource_contract_score":2.0,"discovery_metadata_score":4.0,"registry_consistency_score":3.65,"installability_score":2.0,"session_semantics_score":2.5,"tool_surface_design_score":0.0,"result_shape_stability_score":0.0,"oauth_interop_score":4.0,"recovery_semantics_score":0.38,"maintenance_signal_score":1.45,"adoption_signal_score":2.0,"freshness_confidence_score":0.5,"transport_fidelity_score":4.0,"spec_recency_score":2.0,"session_resume_score":4.0,"step_up_auth_score":4.0,"transport_compliance_score":3.0,"utility_coverage_score":2.0,"advanced_capability_coverage_score":2.0,"connector_publishability_score":2.0,"tool_snapshot_churn_score":0.0,"connector_replay_score":3.0,"request_association_score":3.0,"interactive_flow_safety_score":3.0,"action_safety_score":3.0,"official_registry_presence_score":3.0,"provenance_divergence_score":4.0,"safety_transparency_score":2.0,"tool_capability_clarity_score":0.0,"destructive_operation_safety_score":3.0,"egress_ssrf_resilience_score":3.0,"execution_sandbox_safety_score":4.0,"data_exfiltration_resilience_score":3.0,"least_privilege_scope_score":3.0,"secret_handling_hygiene_score":3.0,"dependency_supply_chain_signal_score":0.5,"input_sanitization_safety_score":0.0,"tool_namespace_clarity_score":0.0},"capability_taxonomy":["database","communication","web","files","oauth","dcr","streamable_http"],"machine_summary":{"verdict":"needs_remediation","best_for":["OpenAI connectors","Generic Streamable HTTP"],"avoid_if":["You need a production-approved server today.","You cannot complete an authenticated client flow.","Latest validation is failing"],"requires_auth":true,"supports_oauth":true,"risk_level":"low"},"taxonomy_tags":["database","communication","web","files"],"score_decomposition":[{"key":"access_protocol","label":"Access & Protocol","score":35.5,"max_score":44.0,"hint":"Connectivity, auth, and transport expectations for common clients.","components":[{"key":"auth_operability_score","score":4.0},{"key":"installability_score","score":2.0},{"key":"session_semantics_score","score":2.5},{"key":"transport_fidelity_score","score":4.0},{"key":"spec_recency_score","score":2.0},{"key":"session_resume_score","score":4.0},{"key":"step_up_auth_score","score":4.0},{"key":"transport_compliance_score","score":3.0},{"key":"request_association_score","score":3.0},{"key":"oauth_interop_score","score":4.0},{"key":"least_privilege_scope_score","score":3.0}]},{"key":"interface_quality","label":"Interface Quality","score":13.88,"max_score":56.0,"hint":"How well the tool/resource interface communicates and behaves under automation.","components":[{"key":"error_contract_score","score":0.5},{"key":"rate_limit_semantics_score","score":2.0},{"key":"schema_completeness_score","score":0.0},{"key":"prompt_contract_score","score":2.0},{"key":"resource_contract_score","score":2.0},{"key":"tool_surface_design_score","score":0.0},{"key":"tool_capability_clarity_score","score":0.0},{"key":"tool_namespace_clarity_score","score":0.0},{"key":"result_shape_stability_score","score":0.0},{"key":"utility_coverage_score","score":2.0},{"key":"advanced_capability_coverage_score","score":2.0},{"key":"tool_snapshot_churn_score","score":0.0},{"key":"connector_replay_score","score":3.0},{"key":"recovery_semantics_score","score":0.38}]},{"key":"security_posture","label":"Security Posture","score":26.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":4.0},{"key":"destructive_operation_safety_score","score":3.0},{"key":"egress_ssrf_resilience_score","score":3.0},{"key":"execution_sandbox_safety_score","score":4.0},{"key":"data_exfiltration_resilience_score","score":3.0},{"key":"secret_handling_hygiene_score","score":3.0},{"key":"input_sanitization_safety_score","score":0.0},{"key":"interactive_flow_safety_score","score":3.0},{"key":"action_safety_score","score":3.0}]},{"key":"reliability_trust","label":"Reliability & Trust","score":10.31,"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":1.88},{"key":"task_success_score","score":3.33},{"key":"trust_confidence_score","score":0.1},{"key":"abuse_noise_ratio_score","score":2.5},{"key":"freshness_confidence_score","score":0.5}]},{"key":"discovery_governance","label":"Discovery & Governance","score":18.6,"max_score":28.0,"hint":"How well the server is documented, listed, and governed in public registries.","components":[{"key":"discovery_metadata_score","score":4.0},{"key":"registry_consistency_score","score":3.65},{"key":"maintenance_signal_score","score":1.45},{"key":"safety_transparency_score","score":2.0},{"key":"dependency_supply_chain_signal_score","score":0.5},{"key":"official_registry_presence_score","score":3.0},{"key":"provenance_divergence_score","score":4.0}]},{"key":"adoption_market","label":"Adoption & Market","score":4.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":2.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":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first.","badge":"score-low","critical_alerts":1},"recommended_for":[{"label":"OpenAI connectors","reason":"OpenAI connectors is marked compatible with score 89."},{"label":"Generic Streamable HTTP","reason":"Generic Streamable HTTP is marked compatible with score 83."}],"history_summary":{"points":[{"timestamp":"2026-07-31T05:33:02.319322+00:00","score":55.25,"status":"failing","latency_ms":3563.84,"tool_count":0,"prompt_count":0,"resource_count":0}],"status_counts":{"failing":1},"score_delta_7d":null,"score_delta_30d":null,"avg_latency_ms":3563.84,"healthy_ratio_recent":0.0,"freshness_hours":6.17,"latest_status":"failing"},"validation_timeline":[{"timestamp":"2026-07-31T05:33:02.319322+00:00","summary_status":"failing","score":55.25,"protocol_version":"2025-03-26","auth_mode":"oauth_supported","tool_count":0,"prompt_count":0,"resource_count":0,"high_risk_tools":0,"safe_to_publish":true,"change_flags":[]}],"evidence_confidence":{"score":66.25,"label":"medium","reason":"Based on 1 recent validations, 26 captured checks, and validation age of 6.2 hours.","live_check_count":26,"validation_age_hours":6.17},"incident_feed":[{"type":"validation_snapshot","timestamp":"2026-07-31T05:33:02.319322+00:00","title":"Latest validation: failing","message":"Score 55.2 with status failing."}],"remediations":[{"code":"fix_tools_list","severity":"critical","title":"Ensure tools/list succeeds consistently","why":"Tools discovery is the minimum viable contract for most MCP clients and directories.","action":"Make tools/list succeed unauthenticated when possible, or document the auth flow in the server card.","playbook":["Make `tools/list` deterministic across repeated calls.","Document or relax auth requirements for discovery routes.","Check that tool names, descriptions, and schemas remain stable across deploys."],"maintainer_context":null},{"code":"respond_server_failing","severity":"critical","title":"Respond to latest validation is failing","why":"Core MCP flows did not validate successfully on the latest run.","action":"Fix the failing checks first, then revalidate to confirm the recovery path.","playbook":["Fix the failing checks first.","Review the latest incident feed and validation diff for the first regression.","Revalidate once the remediation lands."]},{"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":"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_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":"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":"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":"improve_adoption_market","severity":"medium","title":"Raise Adoption & Market score","why":"Adoption clues and public evidence that the server is intended for external use.","action":"Increase external documentation and directory coverage so users can discover and evaluate the server.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."]},{"code":"improve_interface_quality","severity":"medium","title":"Raise Interface Quality score","why":"How well the tool/resource interface communicates and behaves under automation.","action":"Improve schemas, error contracts, and recovery messages so agents can reason about the surface automatically.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."]},{"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":"fix_resources_list","severity":"medium","title":"Repair resources/list or stop advertising resources","why":"Resource metadata should either work live or be removed from the advertised capability set.","action":"Only advertise resources if resources/list works and resources expose stable URIs/types.","playbook":["Only advertise resources with stable URIs and read semantics.","Add MIME/type hints where possible.","Run a live `resources/list` and `resources/read` check after updates."],"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},{"code":"publish_to_official_registry","severity":"low","title":"Publish or reconcile the server in the official MCP registry","why":"Official registry presence improves discovery confidence and cross-source consistency.","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":"ready","why_not_ready":[{"label":"tools/list must succeed.","state":"blocked"},{"label":"search fetch only is not yet satisfied","state":"blocked"},{"label":"write actions present 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"}],"maintainer_context":null},{"key":"claude_desktop","label":"Claude remote MCP","status":"partial","why_not_ready":[{"label":"tools/list must succeed.","state":"blocked"},{"label":"A useful Claude integration needs at least one exposed tool.","state":"blocked"},{"label":"search fetch only is not yet satisfied","state":"blocked"},{"label":"write actions present 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":"write_safe","label":"Write-safe publishing","status":"ready","why_not_ready":[],"maintainer_context":null}],"client_profiles":[{"key":"openai_connectors","label":"OpenAI Connectors","score":88.9,"compatibility":"compatible","missing_requirements":["tools/list must succeed."],"snippet":"Connector URL: https://homespun.dev/mcp\n# Complete OAuth in the client when prompted.\n# Server: awesome-homespunapps/homespun"},{"key":"claude_desktop","label":"Claude Desktop","score":66.7,"compatibility":"partial","missing_requirements":["tools/list must succeed.","A useful Claude integration needs at least one exposed tool."],"snippet":"{\n  \"mcpServers\": {\n    \"homespun\": {\n      \"command\": \"npx\",\n      \"args\": [\"mcp-remote\", \"https://homespun.dev/mcp\"]\n    }\n  }\n}"},{"key":"smithery","label":"Smithery","score":60.0,"compatibility":"partial","missing_requirements":["Tool discovery must succeed.","Machine-readable failure semantics should be present."],"snippet":"smithery mcp add \"https://homespun.dev/mcp\""},{"key":"generic_streamable_http","label":"Generic Streamable HTTP","score":83.3,"compatibility":"compatible","missing_requirements":["tools/list must succeed."],"snippet":"curl -sS https://homespun.dev/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":"ready","reason":"tools/list must succeed.","evidence":[{"check":"initialize","status":"ok","source":"live_validation","note":null,"http_status":200},{"check":"tools_list","status":"error","source":"live_validation","note":null,"http_status":400},{"check":"transport_compliance_probe","status":"warning","source":"live_validation","note":null,"http_status":null},{"check":"step_up_auth_probe","status":"ok","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":66.25,"label":"medium"},"source_resolution":{"winner":"live_validation","supporting_sources":["live_validation","history","server_card"],"disagreements":[]}},{"key":"claude_desktop","label":"Client compatibility: Claude","status":"partial","reason":"tools/list must succeed.; A useful Claude integration needs at least one exposed tool.","evidence":[{"check":"initialize","status":"ok","source":"live_validation","note":null,"http_status":200},{"check":"tools_list","status":"error","source":"live_validation","note":null,"http_status":400},{"check":"transport_compliance_probe","status":"warning","source":"live_validation","note":null,"http_status":null}],"confidence":{"score":66.25,"label":"medium"},"source_resolution":{"winner":"live_validation","supporting_sources":["live_validation","history","server_card"],"disagreements":[]}},{"key":"unsafe_for_write_actions","label":"Write-action publishing","status":"no","reason":"Current write surface is bounded enough for cautious review with production policy controls.","evidence":[{"check":"action_safety_probe","status":"ok","source":"live_validation","note":null,"http_status":null}],"confidence":{"score":66.25,"label":"medium"},"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":66.25,"label":"medium"},"source_resolution":{"winner":"history","supporting_sources":["history","live_validation"],"disagreements":[]}}],"publishability_policy_profiles":[{"key":"chatgpt_custom_connector","label":"ChatGPT custom connector compatibility","status":"ready","gates":{"search_fetch_only":false,"write_actions_present":false,"oauth_configured":true,"admin_refresh_required":false,"safe_for_company_knowledge":false,"safe_for_messages_api_remote_mcp":false},"reason":"tools/list must succeed."},{"key":"claude_remote_mcp","label":"Claude remote MCP compatibility","status":"caution","gates":{"search_fetch_only":false,"write_actions_present":false,"oauth_configured":true,"admin_refresh_required":false,"safe_for_company_knowledge":false,"safe_for_messages_api_remote_mcp":false},"reason":"tools/list must succeed.; A useful Claude integration needs at least one exposed tool."}],"compatibility_fixtures":[{"key":"chatgpt_custom_connector","label":"ChatGPT custom connector fixture","status":"passes","assumptions":[{"name":"remote_http_endpoint","status":"passes"},{"name":"oauth_discovery","status":"passes"},{"name":"frozen_tool_snapshot_refresh","status":"passes"},{"name":"request_association","status":"passes"}],"reason":"tools/list must succeed."},{"key":"anthropic_remote_mcp","label":"Anthropic remote MCP fixture","status":"degraded","assumptions":[{"name":"remote_transport","status":"passes"},{"name":"tool_discovery","status":"likely_to_fail"},{"name":"auth_connect","status":"passes"},{"name":"safe_write_review","status":"passes"}],"reason":"tools/list must succeed.; A useful Claude integration needs at least one exposed tool."}],"install_snippets":{"openai_connectors":"Connector URL: https://homespun.dev/mcp\n# Complete OAuth in the client when prompted.\n# Server: awesome-homespunapps/homespun","claude_desktop":"{\n  \"mcpServers\": {\n    \"homespun\": {\n      \"command\": \"npx\",\n      \"args\": [\"mcp-remote\", \"https://homespun.dev/mcp\"]\n    }\n  }\n}","smithery":"smithery mcp add \"https://homespun.dev/mcp\"","generic_http":"curl -sS https://homespun.dev/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":"awesome-homespunapps/homespun","registry_source":"awesome_mcp_servers","remote_url":"https://homespun.dev/mcp","canonical":true,"score":55.25},{"identifier":"github-homespunapps/homespun","registry_source":"github_topic_registry","remote_url":null,"canonical":false,"score":40.52},{"identifier":"homespunapps/homespun","registry_source":"glama_registry","remote_url":"https://glama.ai/mcp/servers/d3czqp1azj","canonical":false,"score":43.29}],"raw_evidence":{"server_identifier":"awesome-homespunapps/homespun","remote_url":"https://homespun.dev/mcp","server_card_payload":null,"checks":{"server_card":{"status":"error","latency_ms":473.96,"details":{"url":"https://homespun.dev/.well-known/mcp/server-card.json","error":"Client error '404 Not Found' for url 'https://homespun.dev/.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":"ok","latency_ms":217.66,"details":{"url":"https://homespun.dev/.well-known/oauth-protected-resource","payload":{"resource":"https://homespun.dev/mcp","authorization_servers":["https://homespun.dev"],"scopes_supported":["homespun"],"bearer_methods_supported":["header"],"resource_documentation":"https://homespun.dev/skills/homespun/MCP.md"},"http_status":200,"headers":{"content-type":"application/json; charset=utf-8","strict-transport-security":"max-age=31536000"}}},"oauth_authorization_server":{"status":"ok","latency_ms":72.85,"details":{"url":"https://homespun.dev/.well-known/oauth-authorization-server","payload":{"issuer":"https://homespun.dev","authorization_endpoint":"https://homespun.dev/oauth/authorize","token_endpoint":"https://homespun.dev/oauth/token","registration_endpoint":"https://homespun.dev/oauth/register","revocation_endpoint":"https://homespun.dev/oauth/revoke","scopes_supported":["homespun"],"response_types_supported":["code"],"grant_types_supported":["authorization_code","refresh_token"],"code_challenge_methods_supported":["S256"],"token_endpoint_auth_methods_supported":["none","client_secret_basic","client_secret_post"]},"http_status":200,"headers":{"content-type":"application/json; charset=utf-8","strict-transport-security":"max-age=31536000"}}},"openid_configuration":{"status":"error","latency_ms":73.47,"details":{"url":"https://homespun.dev/.well-known/openid-configuration","error":"Client error '404 Not Found' for url 'https://homespun.dev/.well-known/openid-configuration'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"}},"initialize":{"status":"ok","latency_ms":457.99,"details":{"url":"https://homespun.dev/mcp","payload":{"result":{"protocolVersion":"2025-03-26","capabilities":{"resources":{"listChanged":true},"prompts":{"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"homespun","version":"1.6.34"}},"jsonrpc":"2.0","id":1},"http_status":200,"headers":{"content-type":"text/event-stream","mcp-session-id":"bca60eac-357c-4433-a5e5-344d41d9805e"}}},"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":"error","latency_ms":393.38,"details":{"url":"https://homespun.dev/mcp","error":"Client error '400 Bad Request' for url 'https://homespun.dev/mcp'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400","http_status":400,"payload":{},"headers":{"content-type":"application/json; charset=utf-8","strict-transport-security":"max-age=31536000"}}},"prompts_list":{"status":"missing","latency_ms":218.37,"details":{"url":"https://homespun.dev/mcp","error":"Client error '400 Bad Request' for url 'https://homespun.dev/mcp'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400","http_status":400,"payload":{},"headers":{"content-type":"application/json; charset=utf-8","strict-transport-security":"max-age=31536000"},"reason":"not_advertised"}},"prompt_get":{"status":"missing","latency_ms":null,"details":{"reason":"not_advertised"}},"resources_list":{"status":"missing","latency_ms":387.34,"details":{"url":"https://homespun.dev/mcp","error":"Client error '400 Bad Request' for url 'https://homespun.dev/mcp'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400","http_status":400,"payload":{},"headers":{"content-type":"application/json; charset=utf-8","strict-transport-security":"max-age=31536000"},"reason":"not_advertised"}},"resource_read":{"status":"missing","latency_ms":null,"details":{"reason":"not_advertised"}},"probe_noise_resilience":{"status":"ok","latency_ms":234.3,"details":{"url":"https://homespun.dev/robots.txt","http_status":200,"headers":{"content-type":"text/plain; charset=utf-8","strict-transport-security":"max-age=31536000"}}},"determinism_probe":{"status":"missing","latency_ms":null,"details":{"reason":"tools_list_unavailable"}},"session_resume_probe":{"status":"ok","latency_ms":235.99,"details":{"url":"https://homespun.dev/mcp","payload":{"result":{"tools":[{"name":"deploy_app","title":"Deploy App","description":"Deploy a v2 app: an HTML document plus a capability manifest, hosted at its own URL. The manifest carries eight extension keys: app metadata; collections, with per-collection write, update, read and delete role lists, where write gates creates and also gates updates unless an update list is declared; externalHosts, a fetch allowlist; cdn, to allow CDN scripts and styles; capabilities, for Permissions-Policy opt-ins; embeds, an iframe frame-src allowlist; notify, for email-on-row rules; and webhooks, for signed HTTP POST on-row rules. The manifest grammar is documented in the Homespun guide that get_skill returns.\n\nPass no `app_id` to create, which mints a slug and URL, or pass `app_id` to redeploy an existing app with new content. Supply the HTML inline as `html`, or as `html_path`, an absolute path read on the MCP-server host, which is the relay for a hosted connector or the CLI host for a locally-run one, and not the remote agent's machine; it avoids retransmitting a large HTML file on every deploy, only a locally-run connector can read it, and inline `html` wins if both are given. `dry_run:true` (alias `check`) validates only: it runs the full manifest and asset-shape validation, the redeploy compat gate and the schedule-timezone advisory, then returns { ok, warnings, compat?, breaks? } without creating a version or mutating anything.\n\nA redeploy is refused with manifest_incompatible_redeploy, unless force:true, when it would strand rows already written (dropping a collection, tightening a schema, flipping appendOnly), or when it would widen what the app's install screen discloses, such as a collection's read reaching further than the live manifest. The break quotes the sentence a user would now be asked to approve. Taking access away never prompts: dropping a role, or adding update:[\\\"creator\\\"] to a write:[\\\"anyone\\\"] collection, redeploys clean. A removed collection is detached rather than deleted.\n\nImages, fonts, audio, video and data files ship with the app in the same call via `assets[]`. Each is validated and stored app-scoped and served at its `path` on the app's own origin, so the HTML references it by a stable same-origin path such as `<img src=\\\"frames/000.jpg\\\">`; media and font paths support HTTP Range for seeking. A redeploy's assets replace the previous version's set.\n\nReturns { app_id, slug, url, version, visibility, created } on create, or { app_id, version, compat, breaks? } on redeploy.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"description":"Omit to CREATE a new app; pass an existing app's id to REDEPLOY it (a new version, compat-gated unless force:true).","type":"string"},"html":{"description":"The app's UI as a complete HTML document (single file, up to the relay's size cap), sent inline. Provide either this or `html_path`. Inline is required for a hosted or remote connector that has no filesystem. If both are given, inline `html` wins.","type":"string","minLength":1},"html_path":{"description":"ABSOLUTE path to the app's HTML document, read on the MCP-SERVER host (the machine running this connector: the relay for a hosted connector, or your CLI host for a locally-run one), NOT on the remote agent's machine. Alternative to inline `html` that avoids retransmitting a large HTML file on every deploy. Only works when the file is local to the MCP server, so it helps a locally-run connector, not a hosted/remote one (where the path will not exist and you get a clean error, so pass inline `html` there). If both `html` and `html_path` are given, inline `html` wins.","type":"string"},"dry_run":{"description":"Validate only: run the full manifest + asset-shape validation, the compat gate (for a redeploy), and the schedule-timezone advisory, then return { ok, warnings, compat?, breaks? } WITHOUT creating a version or mutating anything. An invalid manifest returns the SAME error a real deploy would; a redeploy the compat gate would refuse reports the break instead of applying it. `check` is an accepted alias.","type":"boolean"},"check":{"description":"Alias for `dry_run`.","type":"boolean"},"manifest":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The x-homespun-manifest capability document (a JSON object). Eight extension keys: app metadata; collections (+ per-collection write/update/read/delete role lists, where write gates creates and also updates unless the optional update list is declared); externalHosts (fetch allowlist); cdn (allow CDN scripts/styles); capabilities (Permissions-Policy opt-ins); embeds (iframe frame-src allowlist); notify (email-on-row rules); webhooks (signed HTTP POST on-row rules). Call get_skill for the full grammar before authoring one from scratch."},"visibility":{"description":"CREATE only. Default 'private' (owner plus invited members, sign-in gated). 'link' shares with anyone holding the returned share_url, whose #k= fragment carries a secret key that can be reset (rotate it via the apps tool, action share_link_rotate) to cut off everyone with the old link; a 'link' app always gets a server-generated unguessable slug. 'private' and 'public' accept an owner-chosen `slug`.","type":"string","enum":["private","link","public"]},"slug":{"description":"CREATE only. Accepted with visibility private or public, including the private default; rejected with explicit visibility 'link', where the slug is always server-generated.","type":"string"},"force":{"description":"REDEPLOY only. Bypass the compat gate, whether it fired on a stranded-rows narrowing or on a widening of what the install screen discloses (a removed collection is detached, never deleted).","type":"boolean"},"assets":{"description":"Optional bundle of files shipped WITH the app in ONE deploy: images, fonts, audio/video, data. Each asset either carries its bytes inline as `content_base64` OR references an already-uploaded attachment by `attachment_id` (prefer the reference form for real images/media: upload once via `attachments fetch` or presign, then bind it here with NO base64 in the deploy body). Each asset is validated + stored app-scoped exactly like a normal attachment (byte-sniff, allowlist, size cap, quota, scan) and served at its `path` on the app's OWN origin, so the page references it by a stable same-origin path (`<img src=\"frames/000.jpg\">`, `<video src=\"media/intro.mp4\">`; media/font paths support HTTP Range). The whole deploy is rejected atomically if any asset fails validation. A redeploy's assets REPLACE the previous version's set. Bounded by the relay's per-deploy asset-count cap; total bytes by the per-app blob quota.","type":"array","items":{"anyOf":[{"type":"object","properties":{"path":{"type":"string","description":"App-relative, same-origin reference the HTML uses, for example 'frames/000.jpg' or 'media/intro.mp4'. Must be relative: no leading '/', no '..' segment, no backslash, charset [A-Za-z0-9._/-], and not under a reserved prefix (_hs, b)."},"content_base64":{"type":"string","description":"Standard base64 of the asset's raw bytes."},"mime":{"description":"Advisory content-type. The relay sniffs the REAL type from the bytes and enforces the attachment allowlist; omit it (or set application/octet-stream) for data files like CSV that don't magic-byte-sniff, so they are stored + served as an inert download.","type":"string"}},"required":["path","content_base64"]},{"type":"object","properties":{"path":{"type":"string","description":"App-relative, same-origin reference the HTML uses (same rules as the inline form)."},"attachment_id":{"type":"string","description":"Id of an ALREADY-uploaded attachment to bind at this path, instead of carrying base64. Use with `attachments fetch` (URL, zero model-context cost) or presign + finalize: the attachment must be owned by YOU, app-scoped to THIS app (upload it with scope=app, app_id=this app), and ready. Skips decode/upload; the deploy just maps path -> attachment_id."}},"required":["path","attachment_id"]}]}}},"required":["manifest"]},"annotations":{"title":"Deploy App","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"}},{"name":"list_rows","title":"List Rows","description":"List rows in a v2 app's mutable collection. This is also how a collection's current state is polled, since MCP has no streaming: pass the prior next_cursor as `since` to fetch only rows that are new or changed. Returns { rows, next_cursor, has_more }.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"type":"string","minLength":1,"description":"The app id."},"collection":{"type":"string","minLength":1,"description":"The collection name declared in the app's manifest."},"since":{"description":"Opaque cursor from a previous call's next_cursor. Also the POLL handle: pass it back to fetch only newer/changed rows.","type":"string"},"limit":{"description":"Page size.","type":"integer","exclusiveMinimum":0,"maximum":1000}},"required":["app_id","collection"]},"annotations":{"title":"List Rows","readOnlyHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"get_row","title":"Get Row","description":"Fetch a single row by its key from a v2 app collection, through a dedicated relay route rather than a client-side scan. Returns { row }, or an isError row_not_found.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"type":"string","minLength":1,"description":"The app id."},"collection":{"type":"string","minLength":1,"description":"The collection name."},"key":{"type":"string","minLength":1,"description":"The key of the row to fetch."}},"required":["app_id","collection","key"]},"annotations":{"title":"Get Row","readOnlyHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"upsert_row","title":"Upsert Row","description":"Create a row in a v2 app's collection, or return the existing row when `key` is already present (deduped:true). Row creation goes through this tool; there is no separate strict-create verb. Omit `key` to add a new row with a server-generated key, or pass `key` to ensure a row exists at that key. The collection must be declared in the app's manifest with 'agent' in its `write` list, which is the list that gates creates. When `key` matches a row the collection's `read` list does not reach for this caller, the result is row_not_found rather than the row, matching what get_row would return, so this never reads past `read`. Returns { row, deduped? }.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"type":"string","minLength":1,"description":"The app id."},"collection":{"type":"string","minLength":1,"description":"The collection name."},"key":{"description":"Optional stable key. Reusing an existing key returns the existing row (deduped:true), or row_not_found when the collection's read list does not reach that row for you.","type":"string"},"data":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"array","items":{}},{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"}],"description":"The row body - any JSON value valid against the collection's row schema (an object, or any JSON value for a schemaless collection)."}},"required":["app_id","collection","data"]},"annotations":{"title":"Upsert Row","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"update_row","title":"Update Row","description":"Update an existing row in a v2 app's collection, replacing its data. Gated by the collection's `update` role list when it declares one, and by its `write` list otherwise, so a collection that scopes updates to the row's `creator` refuses an edit on someone else's row. Pass if_match with the row's current version for an optimistic-locked update; on a version mismatch the relay returns the current row, which is what a retry needs. Returns { row }.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"type":"string","minLength":1,"description":"The app id."},"collection":{"type":"string","minLength":1,"description":"The collection name."},"key":{"type":"string","minLength":1,"description":"The key of the row to update."},"data":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"array","items":{}},{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"}],"description":"The new row body (replaces the row's data) - any JSON value valid against the collection's row schema."},"if_match":{"description":"Optional optimistic-lock version. On mismatch the update is rejected with the current row in details.current.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["app_id","collection","key","data"]},"annotations":{"title":"Update Row","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"delete_row","title":"Delete Row","description":"Soft-delete a row from a v2 app's collection. A watcher sees the deletion live as op:delete on the change feed. Pass if_match for an optimistic-locked delete. Returns { deleted: true }.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"type":"string","minLength":1,"description":"The app id."},"collection":{"type":"string","minLength":1,"description":"The collection name."},"key":{"type":"string","minLength":1,"description":"The key of the row to delete."},"if_match":{"description":"Optional optimistic-lock version.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["app_id","collection","key"]},"annotations":{"title":"Delete Row","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"get_feed_events","title":"Get App Feed Events","description":"Poll a v2 app's change feed for what has happened: row creates, updates and deletes, from any writer, agent or human. It is the long-poll analogue of `homespun apps watch`, since MCP has no streaming. The loop is: call with no `since` first, process the returned entries, keep the cursor, then call again passing it as `since` to get only newer entries. Passing wait (around 25) holds the request open until an entry arrives or it times out, which is how the feed is waited on rather than busy-polled. A `since` older than the retention floor returns resync_required, and the collections are then re-listed with list_rows. Returns { entries, cursor, truncated }.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"type":"string","minLength":1,"description":"The app id."},"since":{"description":"Opaque numeric cursor from a previous call's cursor. Omit (or 0) to read from the beginning.","type":"integer","minimum":0,"maximum":9007199254740991},"limit":{"description":"Max entries per page (capped server-side by FEED_PAGE_MAX).","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"wait":{"description":"Optional long-poll: how long the relay holds the request open waiting for a new entry (0-30s). Use ~25 when waiting for activity, then call again with the same cursor.","type":"integer","minimum":0,"maximum":30}},"required":["app_id"]},"annotations":{"title":"Get App Feed Events","readOnlyHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"apps","title":"Manage Apps","description":"The v2 app lifecycle apart from creation and redeploy, which deploy_app covers. Actions: list returns the owning human's apps; show returns full detail including manifest, timezone and has_share_token; update changes visibility and timezone, the slug being immutable, and switching to 'link' returns a share_url once; share_link_rotate issues a new share token for a 'link' app, returning a new share_url and revoking the old link, and generates one if the app has none; delete is an idempotent soft-delete; wake wakes a dormant app and is otherwise a no-op that reports the actual status; domain_set binds a custom domain and returns the DNS records the domain owner must publish, where the first domain bound serves the app and every later one redirects to it, which is how apex plus www is configured; domain_status returns the serving domain and its `aliases`, live-refreshed against Cloudflare when that is enabled, with last_error carrying the reason a domain is not activating; domain_remove unbinds one domain, or all of them when no `domain` is given, and is idempotent.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["list","show","update","share_link_rotate","delete","wake","domain_set","domain_status","domain_remove"],"description":"list: YOUR owning human's apps. show/update/delete/wake: act on one app (app_id). share_link_rotate: rotate a 'link' app's share token, returning a new share_url (the old link stops working); also generates one if the app has none yet. domain_set/domain_status/domain_remove: manage the app's custom domains (app_id; domain_set also needs domain)."},"app_id":{"description":"Required for show/update/share_link_rotate/delete/wake/domain_set/domain_status/domain_remove.","type":"string"},"status":{"description":"list only. Default: active.","type":"string","enum":["active","dormant","archived","all"]},"limit":{"description":"list only. Page size.","type":"integer","exclusiveMinimum":0,"maximum":200},"cursor":{"description":"list only. Opaque cursor from a previous next_cursor.","type":"string"},"slug":{"description":"list only. Exact-match slug filter.","type":"string"},"visibility":{"description":"update only. The new visibility (slug is immutable).","type":"string","enum":["private","link","public"]},"timezone":{"description":"update only. The app's IANA timezone for `schedules` reminders (e.g. Europe/Berlin). An app that declares schedules with no timezone fires reminders at 08:00 UTC.","type":"string"},"domain":{"description":"domain_set: the bare custom domain to bind (e.g. app.example.com); the response's dns_records lists the DNS entries the domain owner must publish. domain_remove: OPTIONAL, the one domain to unbind - omit it to unbind them all.","type":"string"}},"required":["action"]},"annotations":{"title":"Manage Apps","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"members","title":"Manage App Members","description":"A v2 app's membership (auth spec section 6): who besides the owner can sign in to a private app and write to member-scoped collections. Actions: add invites or attaches a member by email, attaching immediately when the email already has a Human and otherwise sending a magic-link invite; list returns the app's owner and members; set_role changes an existing member's declared custom role in place, or clears it when null, and leaves their sessions intact, which is what makes it the way to re-role someone rather than removing and re-adding them; remove is idempotent and also revokes the human's live sessions on this app, and the app owner cannot be removed; roles returns the derived roles summary, giving the effective access a holder actually has per declared role and collection, reported separately for signed-in members and for grant-link holders because their role floors differ, along with member and active-grant-link counts.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["add","list","set_role","remove","roles"],"description":"add: invite-or-attach a member by email (app_id+email; optional custom_roles). list: the app's owner + members (app_id). set_role: replace an existing member's declared roles in place without signing them out (app_id+human_id+custom_roles, an empty list to clear). remove: drop a member (app_id+human_id). roles: the app's declared roles with what each one includes and, per collection, the EFFECTIVE access a holder has (separately for members and grant-link holders, whose role floors differ) plus how many members and live grant links hold each role (app_id)."},"app_id":{"type":"string","minLength":1,"description":"The app id."},"email":{"description":"add only. The email to invite/attach. If a Human already exists for it, the member row is attached immediately; otherwise the relay emails a magic-link invite.","type":"string"},"role":{"description":"add only. Defaults to 'member' server-side — no other role is assignable via this API (ownership transfer is not available here).","type":"string","enum":["member"]},"custom_roles":{"description":"add (optional) and set_role (required). The DECLARED roles (x-homespun-manifest.roles keys) attached to the member ALONGSIDE their base member powers. A member may hold several and holds the union of what each grants, plus everything those roles `includes`. A built-in/reserved role or an undeclared role is rejected. Omit on add for an ordinary member; pass [] on set_role to clear the roles back to a plain member.","type":"array","items":{"type":"string"}},"human_id":{"description":"remove and set_role. The Human id to target — see list's `humanId` field. The app owner can be neither removed nor re-roled.","type":"string"}},"required":["action","app_id"]},"annotations":{"title":"Manage App Members","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"grants","title":"Manage App Grant Links","description":"A v2 app's grant links (M5). A grant link is a capability URL that confers a declared custom role (x-homespun-manifest.roles) on a stable per-holder anonymous identity, so a holder's own rows are isolated by author/:own scoping. A grant does not escalate to owner, member or agent. Actions: mint creates a link and returns a `grant_url` carrying the token in its #g= fragment, shown once and not recoverable afterwards; list returns the app's links and never a token; revoke is idempotent. mode 'once' is one-time, claimed by the first browser to open it; 'multi' is shared, capped by max_uses within expiry. An optional pin (pin_row_key or pin_where) narrows a holder to specific rows and never widens their access. One consequence worth knowing when minting: a write-only grant pinned to a single row key can still read that row's existing data back through create dedup, so such a grant exposes that row's current contents to the holder.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["mint","list","revoke"],"description":"mint: create a grant link carrying a declared custom role (app_id+role). list: the app's grant links (app_id). revoke: revoke one link (app_id+grant_id)."},"app_id":{"type":"string","minLength":1,"description":"The app id."},"role":{"description":"mint only. A DECLARED custom role for the app (an x-homespun-manifest.roles key). A built-in role (owner/member/agent/anyone) is rejected: a grant can never escalate.","type":"string"},"mode":{"description":"mint only. once: one-time link, claimed by the first browser that opens it (a real per-person link; later opens by others are inert). multi (default): a shared link, capped by max_uses within expiry.","type":"string","enum":["once","multi"]},"max_uses":{"description":"mint only (multi mode). Cap total claims; omit for unlimited within expiry. Ignored for once (forced to 1).","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"label":{"description":"mint only. Optional owner label shown in the grant list.","type":"string"},"ttl_seconds":{"description":"mint only. Grant lifetime in seconds; defaults to the server default (30 days) and is clamped to the server max.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"pin_row_key":{"description":"mint only. Optional narrowing pin to a single row key. NARROWS within the role (never widens). Mutually exclusive with pin_where.","type":"string"},"pin_where":{"description":"mint only. Optional narrowing pin as Wave C2 where conditions ({field, op, value}[]). NARROWS within the role (never widens). Mutually exclusive with pin_row_key.","type":"array","items":{}},"grant_id":{"description":"revoke only. The grant link id (see list's `id` field).","type":"string"}},"required":["action","app_id"]},"annotations":{"title":"Manage App Grant Links","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"ingest","title":"Manage App Inbound Hooks","description":"A v2 app's inbound catch-hooks (inbound-webhooks). A catch-hook lets an external system such as Stripe, Zapier, Make, Home Assistant or an email router POST JSON to a secret URL that writes into a declared collection, so the app receives data with no agent online. Hooks are declared in the manifest (x-homespun-manifest.ingest) and materialized at deploy, so this tool has no create or delete: it reads back the URL, rotates a leaked one, and manages the opt-in signing secret. After deploying a manifest that declares a hook, list is what yields the exact URL to paste into the external system. Actions: list returns the app's hooks, each with its full secret URL, current rule collection, mode, wake and handshake settings, per-status delivery counts and signing-secret state; rotate mints a fresh URL secret for one hook by name and returns the new url once, after which the old url stops working immediately with no redeploy needed; set_signing_secret provisions or rotates a hook's signing secret, which is a different secret from the URL and is what a provider HMACs the body with, minting one returned once when `secret` is omitted or storing a provider value verbatim when it is passed, and never echoing it back; clear_signing_secret removes it. Signature verification currently ships dark: nothing verifies a signature yet.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["list","rotate","set_signing_secret","clear_signing_secret"],"description":"list: the app's inbound catch-hooks, each with its full secret URL, current rule (collection/mode/wake/handshake), and per-status delivery counts (app_id). rotate: mint a fresh URL secret for one hook and return its new URL once, invalidating the old URL immediately (app_id+name). set_signing_secret: provision or rotate a hook's OPT-IN signing secret, distinct from the URL secret (it is what a provider HMACs the body with); omit `secret` to mint one (returned ONCE) or pass `secret` to store a provider-generated value verbatim (never echoed) (app_id+name). clear_signing_secret: remove a hook's signing secret (app_id+name)."},"app_id":{"type":"string","minLength":1,"description":"The app id."},"name":{"description":"rotate / set_signing_secret / clear_signing_secret. The manifest ingest hook name (an x-homespun-manifest.ingest[].name). See list's `name` field.","type":"string"},"secret":{"description":"set_signing_secret only. A provider-generated signing secret to store verbatim (the Stripe path). Omit to have the relay mint one (the GitHub path), returned ONCE in the response.","type":"string"},"grace_seconds":{"description":"set_signing_secret only. On a rotation, how long the previous secret stays valid so deliveries verify while you update the provider (default 3600, max 86400).","type":"number"}},"required":["action","app_id"]},"annotations":{"title":"Manage App Inbound Hooks","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"attachments","title":"Manage Attachments","description":"Binary attachments (images, PDFs, audio, video) referenced from event payloads and input_data via `format: homespun-attachment-id`. Actions: upload, fetch, presign, finalize, download, show, list, delete, mint_token, revoke_token, list_tokens.\n\nChoosing an upload path matters for cost. An inline upload with `content_base64` carries the bytes in the tool-call arguments, so they enter the model context at a token cost proportional to file size, paid again on every retry; a few-hundred-KB image is already expensive. Two paths avoid that entirely: fetch, when the bytes are reachable at a URL, and presign plus finalize, when the client can PUT the raw bytes out of band. Inline upload suits small assets and clients that have neither a URL nor an out-of-band PUT.\n\nfetch takes { source_url (https), scope } and the relay downloads the URL itself behind an SSRF guard (https only, no private, loopback or metadata hosts, DNS pinned, redirects refused, size-capped and timed out), then runs the same byte-sniff, allowlist, size, quota and scan checks as any upload. It works on any storage backend. upload takes either `content_base64` (base64 bytes, no filesystem) or `file_path` (an absolute path read on the relay host, so it only applies when the file is local to the relay). presign plus finalize is token-free: presign with { mime, size, sha256, scope } returns { put_url, attachment_id }, the caller PUTs the raw bytes to put_url over plain HTTP out of band, then finalize with the attachment_id. At finalize the relay re-reads the stored bytes, sniffs the real type, and enforces the same allowlist, size, sha256, quota and scan checks, so a presign that misstates its mime is caught and never served inline. The presigned path requires the Azure storage backend; a filesystem self-host returns a clear not-supported error and fetch or inline upload apply there instead. download writes to an absolute out_path or returns base64. An upload is scoped to agent (the default, reusable) or app. mint_token returns a /b/<token> capability URL, shown once, that a browser can GET without the caller's API key.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["upload","fetch","presign","finalize","download","show","list","delete","mint_token","revoke_token","list_tokens"],"description":"Binary attachment operations. The upload path affects token cost: `fetch` and presign plus finalize keep the bytes out of the model context entirely, while upload with `content_base64` carries them in the tool-call arguments at a cost proportional to file size, paid again on every retry. fetch takes { source_url, scope } and the relay downloads the bytes itself (https only, SSRF-guarded), running the same sniff, allowlist, size, quota and scan checks as any upload. upload takes `content_base64` (base64 bytes, no filesystem) or `file_path` (absolute, read on the relay host), scoped agent or app. presign plus finalize is three steps: presign with { mime, size, sha256, scope }, PUT the bytes to put_url out of band, then finalize, which re-sniffs and re-checks them. download fetches bytes by attachment_id to an absolute out_path or returns base64. show returns metadata only. list returns the agent's attachments. delete is a soft-delete. mint_token mints a /b/<token> capability URL, returned once. revoke_token and list_tokens manage those tokens."},"size":{"description":"presign: the exact byte length you will PUT. Committed at presign and re-verified against the uploaded bytes at finalize.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sha256":{"description":"presign: the hex SHA-256 (64 chars) of the exact bytes you will PUT. Committed at presign and re-verified against the uploaded bytes at finalize.","type":"string"},"attachment_id":{"description":"Attachment id. Required for download/show/delete/mint_token/revoke_token/list_tokens.","type":"string"},"file_path":{"description":"upload: ABSOLUTE path to a file read on the SERVER host running this MCP connector (the relay), NOT your machine. Only works when the file is local to the relay (e.g. a locally-run CLI). For a hosted or remote agent, use `content_base64` instead.","type":"string"},"source_url":{"description":"fetch: an https URL the relay downloads server-side, so the bytes do not enter the model context and cost no tokens. SSRF-guarded: https only, no private, loopback, link-local or metadata hosts, DNS pinned, redirects refused, size-capped and timed out. The downloaded bytes run the same byte-sniff, allowlist, size, quota and scan checks as any upload. This and presign plus finalize are the zero-context paths for real images and media.","type":"string"},"content_base64":{"description":"upload: the file bytes as base64, sent inline with no filesystem access. The base64 rides in the tool-call arguments and enters the model context, costing tokens proportional to file size; a few-hundred-KB image is already expensive, and the cost repeats on every retry. presign plus finalize avoids that for any real image or media whenever the client can do an out-of-band HTTP PUT, which leaves `content_base64` suited to small assets such as a tiny icon, and to clients that cannot PUT out of band. If both `content_base64` and `file_path` are given, `content_base64` wins. The relay sniffs the real type and enforces the same size, allowlist and quota checks as a file upload.","type":"string"},"scope":{"description":"upload scope (default agent).","type":"string","enum":["agent","app"]},"app_id":{"description":"Required when scope=app.","type":"string"},"filename":{"description":"upload: display filename (defaults to the file's basename).","type":"string"},"mime":{"description":"upload/presign: advisory Content-Type. The relay BYTE-SNIFFS the actual bytes and stores/serves that sniffed type regardless (a lying mime is caught, never served inline). Required for presign (scopes the upload URL + fails fast against the allowlist).","type":"string"},"out_path":{"description":"download: ABSOLUTE path to write the bytes to. If omitted, the bytes are returned base64-encoded in the result.","type":"string"},"cursor":{"description":"list pagination cursor.","type":"string"},"limit":{"description":"list page size (1..100).","type":"integer","exclusiveMinimum":0,"maximum":100},"ttl_seconds":{"description":"mint_token: per-token TTL (clamped by scope default).","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"once":{"description":"mint_token: token self-deletes on first GET.","type":"boolean"},"token_id":{"description":"revoke_token: the token id to revoke.","type":"string"}},"required":["action"]},"annotations":{"title":"Manage Attachments","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"}},{"name":"taste","title":"Manage UI Taste Notes","description":"The agent's UI taste notes: a short freeform markdown document of presentation preferences gathered from human feedback, such as 'denser layout' or 'no rounded corners'. Reading it before generating or revising an app is what carries earlier feedback into new output. Actions: get returns the current document; set replaces it in whole, so it does not append; clear discards it. Scoped to presentation preferences rather than general storage.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["get","set","clear"],"description":"The agent's freeform UI taste notes (markdown) — presentation preferences learned from human feedback. get: read them before generating an app. set: whole-document replace (taste, non-empty). clear: delete them."},"taste":{"description":"The full markdown notes (required for set; whole-document replace, not append).","type":"string"}},"required":["action"]},"annotations":{"title":"Manage UI Taste Notes","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"key","title":"Manage API Key","description":"The calling agent's API key. Actions: list returns key info (agent_id, key_prefix, timestamps); mint creates a sibling API key for the caller's own agent identity with the same scope and ownership and returns its raw value once, which is how an MCP-driven agent hands a CLI or child process a working credential, and the raw value is not retrievable afterwards, the sibling appears in a later list made with it, and the owner can revoke it; revoke destroys the agent's own key, which stops working immediately and cannot be undone, so it requires confirm:true. The relay derives identity from the caller's token, so every action applies to the caller's own agent and mint cannot target another agent's id.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["list","revoke","mint"],"description":"The calling agent's API key. list: key info (agent_id, key_prefix, timestamps). mint: mint a NEW sibling API key for YOUR OWN agent identity (same scope/ownership) and return its raw value ONCE, so use it to hand a CLI or child process a working credential; the sibling is a distinct key that shows up in a subsequent `list` made WITH it, the owner can revoke it, and the raw value is never retrievable again. mint always acts on the calling agent, never another agent's id. revoke: self-destruct the agent's OWN key, which stops working immediately and is irreversible (requires confirm:true)."},"confirm":{"description":"Required (true) for revoke.","type":"boolean"}},"required":["action"]},"annotations":{"title":"Manage API Key","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"feedback","title":"Manage Feedback","description":"Feedback to the relay operator. Actions: create records a bug, feature or note with a message and an optional app_id; list returns the agent's own submissions, newest first, paginated by `before`.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["create","list"],"description":"Feedback to the relay operator. create: submit a bug|feature|note with a message (optional app_id). list: the agent's own submissions, newest first."},"type":{"description":"Feedback category (required for create).","type":"string","enum":["bug","feature","note"]},"message":{"description":"Message body (required for create).","type":"string"},"app_id":{"description":"Optional app this feedback relates to (create).","type":"string"},"limit":{"description":"list page size (default 50, max 100).","type":"integer","exclusiveMinimum":0,"maximum":100},"before":{"description":"list cursor from a prior page's next_before.","type":"string"}},"required":["action"]},"annotations":{"title":"Manage Feedback","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"agent","title":"Manage Agent Identity","description":"Agent identity and binding. Actions: whoami returns the resolved relay URL, the active profile and whether a key is configured, with no network call and no secrets; claim binds this agent to a human using a one-shot claim code from their Settings UI, and is one-way; logout clears the locally saved key and profile but does not revoke it on the relay, which is what the `key` tool's revoke action does.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["whoami","claim","logout"],"description":"Agent identity. whoami: show the resolved relay URL, active profile, and whether a key is configured (no network, no secrets). claim: bind this agent to a human via a one-shot claim code the human generated in their Settings UI (one-way). logout: clear the locally-saved key/profile (does NOT revoke it on the relay — use the key tool's revoke for that)."},"code":{"description":"The one-shot claim code (required for claim).","type":"string"}},"required":["action"]},"annotations":{"title":"Manage Agent Identity","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"community","title":"Community Templates","description":"Publishing an app as a community template, installing a template, and, for relay operators, reviewing submissions. Actions: publish, get_config_contract, install, list_pending, get_submission, approve, reject, set_trust_level.\n\npublish captures a live app (html, manifest, the seed rows of its seedOnInstall collections, and listing metadata) into a pending template. It is installable by the returned direct link but is not listed in the public gallery until an operator approves it, and it requires a verified email and no more than a few pending submissions at once. Privacy consequence: an approved template's content and its captured seed rows become public to every platform user, so seed data in a published app must be example-only rather than real personal data. attest_example_only:true records that this was checked. A template may take a per-publisher `slug` (namespaced as <handle>/<slug>) and a semver `version` defaulting to 1.0.0, and a republish under the same slug must bump the version.\n\nget_config_contract reads what a template needs at install, meaning its settings collection and its ordered config and upload steps, by `ref`. install creates a fresh private copy of a template for the caller's owning human, passing answers as `config`, where a 'config' value is a string and an 'upload' value is a pre-uploaded attachment id from the attachments tool.\n\nThe review actions are limited to the relay's configured community reviewers: list_pending returns the queue; get_submission returns a submission's full content by snapshot_id; approve lists it in the gallery, where a re-publish supersedes the app's prior approved version; reject takes a required note that lands in the publisher's app feed.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["publish","get_config_contract","install","list_pending","get_submission","approve","reject","set_trust_level"],"description":"publish: publish one of YOUR apps as a community template (app_id; optional title/description/category/tags). PRIVACY: publishing makes the template content AND the captured seed rows (the LIVE rows of every seedOnInstall collection, captured at publish time) PUBLIC to every platform user once approved. Do NOT publish an app whose seedOnInstall collections hold real personal data (names, emails, addresses, messages, anything private): seed data must be example-only. Pass attest_example_only:true to attest you have checked this. The capture (html + manifest + seed rows) lands PENDING review, installable by its returned direct link but not listed until approved; an ESTABLISHED publisher is fast-tracked (the response's expedited/auto_approved tell you which). get_config_contract: read a template's install-time config contract by `ref` (a namespaced '<handle>/<slug>' or a snapshot id): its settings_collection, ordered config_steps (each with key/kind/required/secret/choices/default), and connect_steps (inbound hooks the app receives on). An 'upload' step wants a file; pre-upload it with the attachments tool (scope agent) and pass its attachment id. After installing a template with connect_steps, run the `ingest` tool's list action on the new app_id to read its freshly provisioned hook URLs, and wire each into the external service. install: install a template by `ref` for YOU (your owning human becomes the owner). Pass `config` as { stepKey: value } from the contract: a 'config' step's value is a string, an 'upload' step's value is a pre-uploaded attachment id. A required step you omit is rejected. Returns the new app's id, slug, and url; installs always create a fresh private copy. list_pending / get_submission / approve / reject / set_trust_level are RELAY-OPERATOR-only review actions: list_pending (the review queue, expedited submissions first), get_submission (a submission's full html+manifest+seedRows plus external_destinations, the hosts it can send data to or pull data from, by snapshot_id), approve (snapshot_id, lists it in the gallery + supersedes the app's prior approved version), reject (snapshot_id + a required note that lands in the publisher's app feed), set_trust_level (promote/demote a publisher by handle: handle + trust_level 'new'|'established')."},"ref":{"description":"get_config_contract/install only. The template to read or install: a namespaced '<handle>/<slug>' or a community snapshot id.","type":"string"},"config":{"description":"install only. The install-time answers as { stepKey: value } from the config contract: a 'config' step's value is a string, an 'upload' step's value is a pre-uploaded attachment id. Omit for a template with no config steps.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"app_id":{"description":"publish only. The id of an app YOU own to publish.","type":"string"},"title":{"description":"publish only. Listing title (1 to 80 chars). Defaults to the app's manifest name.","type":"string"},"description":{"description":"publish only. Listing blurb (up to 200 chars). Defaults to the manifest description.","type":"string"},"long_description":{"description":"publish only. Optional long-form description (up to 4000 chars) shown on the template detail page below the short blurb, for readers and search ranking. Plain text: blank lines become paragraphs, and it is escaped (never rendered as raw HTML), so write prose, not markup.","type":"string"},"category":{"description":"publish only. Optional single-word category (e.g. 'household').","type":"string"},"tags":{"description":"publish only. Up to 6 curation tags.","type":"array","items":{"type":"string"}},"slug":{"description":"publish only. Optional per-publisher slug (lowercase, 3 to 48 chars, hyphens). Gives the template a namespaced id <your-handle>/<slug>; a republish reuses the slug and must bump the version.","type":"string"},"version":{"description":"publish only. Semver MAJOR.MINOR.PATCH (default '1.0.0'). A republish under the same slug must be strictly greater than the current version.","type":"string"},"changelog_note":{"description":"publish only. A short note recorded in this version's changelog.","type":"string"},"setup_steps":{"description":"publish only. Ordered typed setup steps an installing agent follows after install (up to 20). A 'config'/'upload' step may carry a `key` naming a field of the manifest's settingsCollection that its install-time answer is written into; a 'connect' step may carry an `ingestRule` naming a manifest ingest rule it wires up. Read back via get_submission and rendered on the template detail page.","type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["config","seed-data","connect","note","upload"],"description":"config = set a value; upload = an install-time file/image the app stores as an attachment id; connect = wire up an external data source; seed-data = review/replace captured starter data; note = a plain instruction."},"label":{"type":"string","description":"Short step label (<= 80 chars)."},"key":{"description":"The settings-collection field this answer is written into (letters, digits, '_', up to 64 chars). Required for an 'upload' step, optional for a 'config' step, not allowed on the others. Must name a declared top-level field of the manifest's x-homespun-manifest.settingsCollection; an 'upload' target must be a string-typed field.","type":"string"},"description":{"description":"Optional longer instruction (<= 300 chars).","type":"string"},"required":{"description":"Whether this step is required (default false).","type":"boolean"},"secret":{"description":"Mark a step whose value is sensitive (an API key/token). Its default is MASKED on the public detail page; publish only your own example default, never a real secret.","type":"boolean"},"default":{"description":"Optional example/default value (<= 200 chars).","type":"string"},"choices":{"description":"Optional list of allowed values (up to 12).","type":"array","items":{"type":"string"}},"valueHint":{"description":"Optional format hint (<= 120 chars).","type":"string"},"ingestRule":{"description":"The manifest `ingest` rule this step wires up. Allowed only on a 'connect' step, and optional there; publish rejects a name that x-homespun-manifest.ingest does not declare. Each install mints that rule its own hook URL, which the installer pastes into the external service.","type":"string"}},"required":["kind","label"]}},"derived_from_snapshot_id":{"description":"publish only. Optional remix/fork lineage: the snapshot id this template was derived from.","type":"string"},"attest_example_only":{"description":"publish only. Set true to attest that the template content AND the captured seed rows contain NO real personal data. Publishing makes both PUBLIC to every platform user, so seed data (the live rows of your seedOnInstall collections) must be example-only, never real names/emails/addresses/private messages. Recorded and shown to the reviewer; omitting it still publishes but is flagged to the operator as not attested.","type":"boolean"},"snapshot_id":{"description":"Required for get_submission/approve/reject. The submission's snapshot id (from publish's response or list_pending).","type":"string"},"note":{"description":"reject only. The required rejection note shown to the publisher (delivered to their app feed).","type":"string"},"limit":{"description":"list_pending only. Page size (1..200).","type":"integer","exclusiveMinimum":0,"maximum":200},"cursor":{"description":"list_pending only. Opaque cursor from a prior next_cursor.","type":"string"},"handle":{"description":"set_trust_level only. The @-handle of the publisher to promote or demote.","type":"string"},"trust_level":{"description":"set_trust_level only. 'established' fast-tracks the publisher's future submissions through review; 'new' reverts to full review.","type":"string","enum":["new","established"]}},"required":["action"]},"annotations":{"title":"Community Templates","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"}},{"name":"publisher","title":"Publisher Profile","description":"The caller's community publisher identity: the @-handle and public profile shown in the template gallery. Actions: get returns the profile, including the handle, whether it has been claimed, tenure, and the rating and template counters; claim sets the handle from a lowercase 3-to-32-character string and may be used only once, after which the handle is permanent, and it refuses a handle that is reserved or already taken; update changes display_name, bio or url at any time. claim and update require a verified email. An existing publisher may hold a provisional `maker-...` handle assigned automatically, which claim renames on its one allowed use.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["claim","get","update"],"description":"get: return YOUR publisher profile (handle, tenure, counters). claim: set your @-handle ONCE (handle arg; lowercase, 3 to 32 chars, permanent after claiming; needs a verified email). update: change your public display_name/bio/url (any of them; needs a verified email)."},"handle":{"description":"claim only. The lowercase @-handle to claim (^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$). Permanent once claimed.","type":"string"},"display_name":{"description":"update only. Public display name (up to 80 chars); null clears it.","anyOf":[{"type":"string"},{"type":"null"}]},"bio":{"description":"update only. Short public bio (up to 500 chars); null clears it.","anyOf":[{"type":"string"},{"type":"null"}]},"url":{"description":"update only. Public http(s) URL (up to 200 chars); null clears it.","anyOf":[{"type":"string"},{"type":"null"}]}},"required":["action"]},"annotations":{"title":"Publisher Profile","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"}},{"name":"review","title":"Community Reviews","description":"Ratings and reviews of community templates, responses from a template's own publisher, and, for relay operators, moderation. Actions: create leaves a 1-to-5 star rating and an optional written body on a template the caller has installed, identifying it by `template` (\\\"<handle>/<slug>\\\") or by `handle` plus `slug`, and requires a verified email; each install yields exactly one review, and the aggregate carries across template versions. A body containing a link or a contact email is held automatically for a moderator before it appears. respond replies to a review of the caller's own template line (review_id plus response, or null to clear it), with one editable response per review. report flags a review for the relay's moderators (review_id plus reason) and is deduped per account. remove and unhold are limited to the relay's configured community reviewers: remove takes a review down and adjusts the rating aggregate, and unhold publishes a previously held review into the aggregate.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["create","respond","report","remove","unhold"],"description":"create: leave a star rating (1..5) and optional body on a community template YOU have installed (identify it by `template` \"<handle>/<slug>\" or by `handle`+`slug`); requires a verified email, and one review per install. A body containing a link or contact email is auto-held for a moderator before it shows. respond: reply to a review of one of YOUR OWN templates (review_id + response; null clears it). report: flag a review for the relay's moderators (review_id + reason; one report per account). remove / unhold are RELAY-OPERATOR-only moderation actions on a review_id: remove takes a review down (adjusting the aggregate), unhold publishes a previously auto-held review."},"template":{"description":"create only. The namespaced template id <handle>/<slug> to review.","type":"string"},"handle":{"description":"create only. Publisher handle (with `slug`), an alternative to `template`.","type":"string"},"slug":{"description":"create only. Per-publisher slug (with `handle`).","type":"string"},"stars":{"description":"create only. Star rating, an integer 1 to 5.","type":"integer","minimum":1,"maximum":5},"body":{"description":"create only. Optional written review (up to 2000 chars).","type":"string"},"review_id":{"description":"Required for respond/report/remove/unhold. The review's id.","type":"string"},"response":{"description":"respond only. The publisher's public response (up to 2000 chars); null clears it.","anyOf":[{"type":"string"},{"type":"null"}]},"reason":{"description":"report only. Why you are reporting this review (up to 500 chars).","type":"string"}},"required":["action"]},"annotations":{"title":"Community Reviews","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"}},{"name":"get_skill","title":"Get Skill Guide","description":"The relay's SKILL.md, a generated guide to the Homespun workflow covering events versus records, the schema grammars and the poll loop. Needs no API key. Useful when working out how the other tools fit together, or to refresh a cached copy. Pass version_only:true to return just the relay's skill version string, which is enough to tell whether a cached copy is current.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"version_only":{"description":"If true, return only the relay's current skill version string instead of the full SKILL.md markdown.","type":"boolean"}}},"annotations":{"title":"Get Skill Guide","readOnlyHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}}]},"jsonrpc":"2.0","id":301},"http_status":200,"headers":{"content-type":"text/event-stream","mcp-session-id":"bca60eac-357c-4433-a5e5-344d41d9805e"},"session_id_present":true,"transport":"streamable-http","requested_protocol_version":"2025-03-26","resumed":true}},"step_up_auth_probe":{"status":"ok","latency_ms":null,"details":{"oauth_present":true,"auth_required_checks":[],"supported_scopes":["homespun"],"scope_specificity_ratio":0.5,"broad_scopes":[],"challenge_headers":[],"step_up_signals":[],"minimal_scope_documented":true}},"transport_compliance_probe":{"status":"warning","latency_ms":263.74,"details":{"transport":"streamable-http","session_id_present":true,"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; charset=utf-8"},"bad_protocol_error":null,"delete_status_code":200,"delete_error":null,"expired_session_status_code":404,"expired_session_error":null,"issues":["missing_protocol_header"]}},"utility_coverage_probe":{"status":"ok","latency_ms":132.15,"details":{"completions":{"advertised":false,"sample_target":null,"live_probe":"not_executed"},"pagination":{"supported":false,"next_cursor_methods":[],"metadata_signal":false},"tasks":{"advertised":false,"probe_status":"auth_required","http_status":401},"initialize_capability_keys":["prompts","resources","tools"]}},"advanced_capabilities_probe":{"status":"missing","latency_ms":null,"details":{"capabilities":{"prompts":false,"resources":false,"completions":false,"roots":false,"sampling":false,"elicitation":false,"structured_outputs":false,"resource_links":false},"enabled_count":0,"enabled":[],"initialize_capability_keys":["prompts","resources","tools"]}},"tool_snapshot_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_tools"}},"connector_replay_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_tools"}},"request_association_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_request_association_capabilities_advertised"}},"interactive_flow_probe":{"status":"ok","latency_ms":null,"details":{"risk_hits":[],"safe_hits":[],"oauth_supported":true,"prompt_available":false}},"action_safety_probe":{"status":"ok","latency_ms":null,"details":{"summary":{"tool_count":0,"high_risk_tools":0,"destructive_tools":0,"exec_tools":0,"egress_tools":0,"secret_tools":0,"bulk_access_tools":0,"risk_distribution":{"low":0,"medium":0,"high":0,"critical":0},"capability_distribution":{}},"auth_present":true,"safeguard_count":0,"confirmation_signals":[]}},"official_registry_probe":{"status":"warning","latency_ms":null,"details":{"registry_source":"awesome_mcp_servers","direct_match":false,"official_peer_count":10,"official_identifiers":["ai.ai-akari/one-minute-akari","ai.artidrop/artidrop","ai.adramp/google-ads","ai.adweave/meta-ads-mcp","ai.aetherwealth/mcp","ai.afmr/discovery","ai.agentberg/agentberg","ai.agentdm/agentdm","ai.assetlog/assetlog","ai.agentic-news/mcp"]}},"provenance_divergence_probe":{"status":"ok","latency_ms":null,"details":{"direct_official_match":false,"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":"error","latency_ms":null,"details":{"transport":"streamable-http","tool_count":0,"high_risk_tools":0,"blockers":["tools_list","server_card","tool_surface"],"criteria":{"remote_transport":true,"initialize":true,"tools_list":false,"protocol_version":true,"session_resume":true,"step_up_auth":true,"transport_compliance":true,"connector_replay":true,"request_association":true,"action_safety":true,"server_card":false,"tool_surface":false,"auth_flow":true}}}},"failures":{"server_card":{"url":"https://homespun.dev/.well-known/mcp/server-card.json","error":"Client error '404 Not Found' for url 'https://homespun.dev/.well-known/mcp/server-card.json'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"},"openid_configuration":{"url":"https://homespun.dev/.well-known/openid-configuration","error":"Client error '404 Not Found' for url 'https://homespun.dev/.well-known/openid-configuration'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"},"tools_list":{"url":"https://homespun.dev/mcp","error":"Client error '400 Bad Request' for url 'https://homespun.dev/mcp'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400","http_status":400,"payload":{},"headers":{"content-type":"application/json; charset=utf-8","strict-transport-security":"max-age=31536000"}}}},"active_alerts":[{"code":"server_failing","severity":"critical","title":"Latest validation is failing","message":"Core MCP flows did not validate successfully on the latest run."}],"maintainer_analytics":{"validation_run_count":1,"average_latency_ms":3563.84,"healthy_run_ratio_recent":0.0,"registry_presence_count":3,"active_alert_count":1,"watcher_count":0,"verified_claim":false,"taxonomy_tags":["database","communication","web","files"],"score_trend":[55.25],"remediation_count":16,"high_risk_tool_count":0,"destructive_tool_count":0,"exec_tool_count":0},"public_server_reputation":{"validation_success_ratio_7d":0.0,"validation_success_ratio_30d":0.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":33.33,"signals":{"verified_maintainer_claim":false,"support_contact_present":true,"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":0,"high_risk_tools":0,"destructive_tools":0,"exec_tools":0,"egress_tools":0,"secret_tools":0,"bulk_access_tools":0,"risk_distribution":{"low":0,"medium":0,"high":0,"critical":0},"capability_distribution":{}},"tool_security_inventory":[],"transport_compliance":{"status":"warning","transport":"streamable-http","session_id_present":true,"protocol_header_present":false,"last_event_id_visible":false,"bad_protocol_status_code":400,"delete_status_code":200,"expired_session_status_code":404,"issues":["missing_protocol_header"]},"utility_coverage":{"status":"ok","completions":{"advertised":false,"sample_target":null,"live_probe":"not_executed"},"pagination":{"supported":false,"next_cursor_methods":[],"metadata_signal":false},"tasks":{"advertised":false,"probe_status":"auth_required","http_status":401},"initialize_capability_keys":["prompts","resources","tools"]},"write_action_governance":{"status":"ok","safe_to_publish":true,"auth_boundary":"oauth_or_auth_required","blast_radius":"low","summary":{"tool_count":0,"high_risk_tools":0,"destructive_tools":0,"exec_tools":0,"egress_tools":0,"secret_tools":0,"bulk_access_tools":0,"risk_distribution":{"low":0,"medium":0,"high":0,"critical":0},"capability_distribution":{}},"high_risk_tools":[],"confirmation_signals":[],"safeguard_count":0},"provenance_divergence":{"status":"ok","direct_official_match":false,"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":"awesome-homespunapps/homespun","duplicate_count":2,"registry_sources":["awesome_mcp_servers","github_topic_registry","glama_registry"],"registry_identifiers":["awesome_mcp_servers:homespunapps/homespun","github_topic_registry:homespunapps/homespun","glama_registry:d3czqp1azj"],"remote_urls":["https://glama.ai/mcp/servers/d3czqp1azj","https://homespun.dev/mcp"],"homepages":["https://github.com/homespunapps/homespun","https://glama.ai/mcp/servers/d3czqp1azj","https://homespun.dev"],"source_disagreements":["registry_source","remote_url","homepage","registry_identifier"],"source_disagreement_details":{"registry_source":{"label":"Registry source","explanation":"Multiple registries or registry sync paths claim this same canonical server.","values":["awesome_mcp_servers","github_topic_registry","glama_registry"]},"remote_url":{"label":"Remote URL","explanation":"Aliases currently point at different MCP endpoints, which can indicate mirrors, stale registry data, or a real endpoint split.","values":["https://glama.ai/mcp/servers/d3czqp1azj","https://homespun.dev/mcp"]},"homepage":{"label":"Homepage","explanation":"Registry entries disagree on the primary homepage for this server.","values":["https://github.com/homespunapps/homespun","https://glama.ai/mcp/servers/d3czqp1azj","https://homespun.dev"]},"registry_identifier":{"label":"Registry identifier","explanation":"Different registry-specific identifiers resolve to the same canonical server record here.","values":["awesome_mcp_servers:homespunapps/homespun","github_topic_registry:homespunapps/homespun","glama_registry:d3czqp1azj"]}}},"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":"awesome-homespunapps/homespun","tier":"community","readiness":{"allowed_to_activate":false,"blockers":["score_below_hosting_threshold","server_not_healthy_or_degraded","latest_validation_not_passing"],"score":55.25,"min_score":70.0,"freshness_hours":6.166892705277778,"max_freshness_hours":168.0,"latest_validation_run_id":"11234a6d-611f-4b80-9bf0-a0d8be2c3f25"},"active_deployment_id":null,"active_endpoint_url":null,"deployments":[]},"action_controls_diff":null,"benchmark_tasks":[{"key":"discover_tools","label":"Discover tools","status":"likely_to_fail","evidence":[{"check":"initialize","status":"ok","source":"live_validation","note":null,"http_status":200},{"check":"tools_list","status":"error","source":"live_validation","note":null,"http_status":400}]},{"key":"read_only_fetch_flow","label":"Read-only fetch flow","status":"likely_to_fail","evidence":[{"check":"resource_read","status":"missing","source":"live_validation","note":"resources/read is a strong read-path signal","http_status":null},{"check":"read_only_tool_surface","status":"missing","source":"derived_tool_inventory","note":"No low-risk read-only tools were inferred from the current tool surface.","http_status":null}]},{"key":"oauth_required_connect","label":"OAuth-required connect","status":"passes","evidence":[{"check":"oauth_protected_resource","status":"ok","source":"live_validation","note":null,"http_status":200},{"check":"step_up_auth_probe","status":"ok","source":"live_validation","note":null,"http_status":null}]},{"key":"safe_write_flow_with_confirmation","label":"Safe write flow with confirmation","status":"passes","evidence":[{"check":"action_safety_probe","status":"ok","source":"live_validation","note":null,"http_status":null}]}],"latest_capability_counts":{"tool_count":0,"prompt_count":0,"resource_count":0},"point_loss_breakdown":[{"key":"tool_surface_design_score","label":"Tool Surface Design","score":0.0,"max_score":4.0,"gap":4.0},{"key":"tool_snapshot_churn_score","label":"Tool Snapshot Churn","score":0.0,"max_score":4.0,"gap":4.0},{"key":"tool_namespace_clarity_score","label":"Tool Namespace Clarity","score":0.0,"max_score":4.0,"gap":4.0},{"key":"tool_capability_clarity_score","label":"Tool Capability Clarity","score":0.0,"max_score":4.0,"gap":4.0},{"key":"schema_completeness_score","label":"Schema Completeness","score":0.0,"max_score":4.0,"gap":4.0},{"key":"result_shape_stability_score","label":"Result Shape Stability","score":0.0,"max_score":4.0,"gap":4.0},{"key":"input_sanitization_safety_score","label":"Input Sanitization Safety","score":0.0,"max_score":4.0,"gap":4.0},{"key":"trust_confidence_score","label":"Trust Confidence","score":0.1,"max_score":4.0,"gap":3.9},{"key":"recovery_semantics_score","label":"Recovery Semantics","score":0.38,"max_score":4.0,"gap":3.62},{"key":"freshness_confidence_score","label":"Freshness Confidence","score":0.5,"max_score":4.0,"gap":3.5},{"key":"error_contract_score","label":"Error Contract","score":0.5,"max_score":4.0,"gap":3.5},{"key":"dependency_supply_chain_signal_score","label":"Dependency Supply Chain Signal","score":0.5,"max_score":4.0,"gap":3.5}],"verdict_traces":{"production_readiness":{"code":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first.","confidence":{"score":66.25,"label":"medium"},"triggering_alerts":[{"code":"server_failing","severity":"critical","title":"Latest validation is failing"}],"winning_source":"live_validation"},"client_readiness":[{"key":"openai_connectors","status":"ready","reason":"tools/list must succeed.","triggering_checks":["initialize","tools_list","transport_compliance_probe","step_up_auth_probe","connector_replay_probe","request_association_probe"],"confidence":{"score":66.25,"label":"medium"},"winning_source":"live_validation","conflicting_sources":[]},{"key":"claude_desktop","status":"partial","reason":"tools/list must succeed.; A useful Claude integration needs at least one exposed tool.","triggering_checks":["initialize","tools_list","transport_compliance_probe"],"confidence":{"score":66.25,"label":"medium"},"winning_source":"live_validation","conflicting_sources":[]},{"key":"unsafe_for_write_actions","status":"no","reason":"Current write surface is bounded enough for cautious review with production policy controls.","triggering_checks":["action_safety_probe"],"confidence":{"score":66.25,"label":"medium"},"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":66.25,"label":"medium"},"winning_source":"history","conflicting_sources":[]}]},"current_snapshot":{"schema_version":"verify.trust_snapshot.v1","snapshot_id":"trustsnap_177b8e3caca9d74a","generated_at":"2026-07-31T11:43:03.183241+00:00","source":"current_snapshot","server":"awesome-homespunapps/homespun","last_validated_at":"2026-07-31T05:33:02.319322+00:00","validation_age_hours":6.17,"freshness":{"schema_version":"verify.freshness_profile.v1","last_validated_at":"2026-07-31T05:33:02.319322+00:00","age_hours":6.17,"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":55.25,"raw_score":55.25,"confidence_score":66.2,"confidence_weighted_score":36.6,"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":"failing","current_score":55.25,"display_score":55.25,"stale_score_suppressed":false,"production_trust_decision":{"schema_version":"verify.executive_verdict.v1","decision":"Block for production","why":"unauthenticated behavior not proven + failing live status + score below evaluation threshold","next_action":"revalidate, add safeguards, export policy","reason_count":3},"production_readiness_class":{"code":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first."},"evidence_confidence":{"score":66.25,"label":"medium","validation_age_hours":6.17,"live_check_count":26},"active_alerts":[{"code":"server_failing","severity":"critical","title":"Latest validation is failing"}]},"trust_snapshot":{"schema_version":"verify.trust_snapshot.v1","snapshot_id":"trustsnap_177b8e3caca9d74a","generated_at":"2026-07-31T11:43:03.183241+00:00","source":"current_snapshot","server":"awesome-homespunapps/homespun","last_validated_at":"2026-07-31T05:33:02.319322+00:00","validation_age_hours":6.17,"freshness":{"schema_version":"verify.freshness_profile.v1","last_validated_at":"2026-07-31T05:33:02.319322+00:00","age_hours":6.17,"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":55.25,"raw_score":55.25,"confidence_score":66.2,"confidence_weighted_score":36.6,"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":"failing","current_score":55.25,"display_score":55.25,"stale_score_suppressed":false,"production_trust_decision":{"schema_version":"verify.executive_verdict.v1","decision":"Block for production","why":"unauthenticated behavior not proven + failing live status + score below evaluation threshold","next_action":"revalidate, add safeguards, export policy","reason_count":3},"production_readiness_class":{"code":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first."},"evidence_confidence":{"score":66.25,"label":"medium","validation_age_hours":6.17,"live_check_count":26},"active_alerts":[{"code":"server_failing","severity":"critical","title":"Latest validation is failing"}]},"agent_commerce":{"status":"beta","commerce_signal":"weak","payment_execution_detected":"no","billing_or_usage_detected":"no","quote_or_pricing_detected":"no","numeric_price_context":"yes","commercial_quote_context":"no","checkout_or_charge_detected":"no","checkout_term_observed":"no","payment_capable":"none","payment_rails":[],"purchase_stage_supported":[],"human_confirmation_required":"unknown","spending_policy_supported":"yes","receipt_supported":"unknown","refund_policy_present":"unknown","operator_identity":"declared","auth_required":"yes","auth_scheme":"oauth","tool_risk_level":"unknown","tool_risk_score":60,"pricing_transparency":"unknown","schema_change_detected":"unknown","delegation_level":"none","evidence_level":"inferred","confidence":"low","highest_risk_tools":[],"skipped_unsafe_tools":[],"last_checked_at":"2026-07-31T11:43:03.176768+00:00","evidence":[{"field":"commerce_signal","value":"weak","evidence_level":"inferred","source":"server_metadata","matched_terms":["limit","rate"],"sample":null,"confidence":"low","last_checked_at":"2026-07-31T11:43:03.176768+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":80,"tool_risk_score":60,"payment_readiness_score":10,"agent_delegation_safety_score":50,"overall_agent_commerce_score":null},"warnings":[],"recommended_fixes":[]},"latest_claim":null,"maintainer_profile_slug":null,"watch_summary":{"count":0,"teams":[],"emails":[]}},"latest_validation":{"id":"11234a6d-611f-4b80-9bf0-a0d8be2c3f25","validation_profile":"remote_mcp","status":"completed","summary_status":"failing","transport_type":"streamable-http","latency_ms":3563.84,"failures":{"server_card":{"url":"https://homespun.dev/.well-known/mcp/server-card.json","error":"Client error '404 Not Found' for url 'https://homespun.dev/.well-known/mcp/server-card.json'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"},"openid_configuration":{"url":"https://homespun.dev/.well-known/openid-configuration","error":"Client error '404 Not Found' for url 'https://homespun.dev/.well-known/openid-configuration'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"},"tools_list":{"url":"https://homespun.dev/mcp","error":"Client error '400 Bad Request' for url 'https://homespun.dev/mcp'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400","http_status":400,"payload":{},"headers":{"content-type":"application/json; charset=utf-8","strict-transport-security":"max-age=31536000"}}},"checks":{"server_card":{"status":"error","latency_ms":473.96,"details":{"url":"https://homespun.dev/.well-known/mcp/server-card.json","error":"Client error '404 Not Found' for url 'https://homespun.dev/.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":"ok","latency_ms":217.66,"details":{"url":"https://homespun.dev/.well-known/oauth-protected-resource","payload":{"resource":"https://homespun.dev/mcp","authorization_servers":["https://homespun.dev"],"scopes_supported":["homespun"],"bearer_methods_supported":["header"],"resource_documentation":"https://homespun.dev/skills/homespun/MCP.md"},"http_status":200,"headers":{"content-type":"application/json; charset=utf-8","strict-transport-security":"max-age=31536000"}}},"oauth_authorization_server":{"status":"ok","latency_ms":72.85,"details":{"url":"https://homespun.dev/.well-known/oauth-authorization-server","payload":{"issuer":"https://homespun.dev","authorization_endpoint":"https://homespun.dev/oauth/authorize","token_endpoint":"https://homespun.dev/oauth/token","registration_endpoint":"https://homespun.dev/oauth/register","revocation_endpoint":"https://homespun.dev/oauth/revoke","scopes_supported":["homespun"],"response_types_supported":["code"],"grant_types_supported":["authorization_code","refresh_token"],"code_challenge_methods_supported":["S256"],"token_endpoint_auth_methods_supported":["none","client_secret_basic","client_secret_post"]},"http_status":200,"headers":{"content-type":"application/json; charset=utf-8","strict-transport-security":"max-age=31536000"}}},"openid_configuration":{"status":"error","latency_ms":73.47,"details":{"url":"https://homespun.dev/.well-known/openid-configuration","error":"Client error '404 Not Found' for url 'https://homespun.dev/.well-known/openid-configuration'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"}},"initialize":{"status":"ok","latency_ms":457.99,"details":{"url":"https://homespun.dev/mcp","payload":{"result":{"protocolVersion":"2025-03-26","capabilities":{"resources":{"listChanged":true},"prompts":{"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"homespun","version":"1.6.34"}},"jsonrpc":"2.0","id":1},"http_status":200,"headers":{"content-type":"text/event-stream","mcp-session-id":"bca60eac-357c-4433-a5e5-344d41d9805e"}}},"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":"error","latency_ms":393.38,"details":{"url":"https://homespun.dev/mcp","error":"Client error '400 Bad Request' for url 'https://homespun.dev/mcp'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400","http_status":400,"payload":{},"headers":{"content-type":"application/json; charset=utf-8","strict-transport-security":"max-age=31536000"}}},"prompts_list":{"status":"missing","latency_ms":218.37,"details":{"url":"https://homespun.dev/mcp","error":"Client error '400 Bad Request' for url 'https://homespun.dev/mcp'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400","http_status":400,"payload":{},"headers":{"content-type":"application/json; charset=utf-8","strict-transport-security":"max-age=31536000"},"reason":"not_advertised"}},"prompt_get":{"status":"missing","latency_ms":null,"details":{"reason":"not_advertised"}},"resources_list":{"status":"missing","latency_ms":387.34,"details":{"url":"https://homespun.dev/mcp","error":"Client error '400 Bad Request' for url 'https://homespun.dev/mcp'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400","http_status":400,"payload":{},"headers":{"content-type":"application/json; charset=utf-8","strict-transport-security":"max-age=31536000"},"reason":"not_advertised"}},"resource_read":{"status":"missing","latency_ms":null,"details":{"reason":"not_advertised"}},"probe_noise_resilience":{"status":"ok","latency_ms":234.3,"details":{"url":"https://homespun.dev/robots.txt","http_status":200,"headers":{"content-type":"text/plain; charset=utf-8","strict-transport-security":"max-age=31536000"}}},"determinism_probe":{"status":"missing","latency_ms":null,"details":{"reason":"tools_list_unavailable"}},"session_resume_probe":{"status":"ok","latency_ms":235.99,"details":{"url":"https://homespun.dev/mcp","payload":{"result":{"tools":[{"name":"deploy_app","title":"Deploy App","description":"Deploy a v2 app: an HTML document plus a capability manifest, hosted at its own URL. The manifest carries eight extension keys: app metadata; collections, with per-collection write, update, read and delete role lists, where write gates creates and also gates updates unless an update list is declared; externalHosts, a fetch allowlist; cdn, to allow CDN scripts and styles; capabilities, for Permissions-Policy opt-ins; embeds, an iframe frame-src allowlist; notify, for email-on-row rules; and webhooks, for signed HTTP POST on-row rules. The manifest grammar is documented in the Homespun guide that get_skill returns.\n\nPass no `app_id` to create, which mints a slug and URL, or pass `app_id` to redeploy an existing app with new content. Supply the HTML inline as `html`, or as `html_path`, an absolute path read on the MCP-server host, which is the relay for a hosted connector or the CLI host for a locally-run one, and not the remote agent's machine; it avoids retransmitting a large HTML file on every deploy, only a locally-run connector can read it, and inline `html` wins if both are given. `dry_run:true` (alias `check`) validates only: it runs the full manifest and asset-shape validation, the redeploy compat gate and the schedule-timezone advisory, then returns { ok, warnings, compat?, breaks? } without creating a version or mutating anything.\n\nA redeploy is refused with manifest_incompatible_redeploy, unless force:true, when it would strand rows already written (dropping a collection, tightening a schema, flipping appendOnly), or when it would widen what the app's install screen discloses, such as a collection's read reaching further than the live manifest. The break quotes the sentence a user would now be asked to approve. Taking access away never prompts: dropping a role, or adding update:[\\\"creator\\\"] to a write:[\\\"anyone\\\"] collection, redeploys clean. A removed collection is detached rather than deleted.\n\nImages, fonts, audio, video and data files ship with the app in the same call via `assets[]`. Each is validated and stored app-scoped and served at its `path` on the app's own origin, so the HTML references it by a stable same-origin path such as `<img src=\\\"frames/000.jpg\\\">`; media and font paths support HTTP Range for seeking. A redeploy's assets replace the previous version's set.\n\nReturns { app_id, slug, url, version, visibility, created } on create, or { app_id, version, compat, breaks? } on redeploy.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"description":"Omit to CREATE a new app; pass an existing app's id to REDEPLOY it (a new version, compat-gated unless force:true).","type":"string"},"html":{"description":"The app's UI as a complete HTML document (single file, up to the relay's size cap), sent inline. Provide either this or `html_path`. Inline is required for a hosted or remote connector that has no filesystem. If both are given, inline `html` wins.","type":"string","minLength":1},"html_path":{"description":"ABSOLUTE path to the app's HTML document, read on the MCP-SERVER host (the machine running this connector: the relay for a hosted connector, or your CLI host for a locally-run one), NOT on the remote agent's machine. Alternative to inline `html` that avoids retransmitting a large HTML file on every deploy. Only works when the file is local to the MCP server, so it helps a locally-run connector, not a hosted/remote one (where the path will not exist and you get a clean error, so pass inline `html` there). If both `html` and `html_path` are given, inline `html` wins.","type":"string"},"dry_run":{"description":"Validate only: run the full manifest + asset-shape validation, the compat gate (for a redeploy), and the schedule-timezone advisory, then return { ok, warnings, compat?, breaks? } WITHOUT creating a version or mutating anything. An invalid manifest returns the SAME error a real deploy would; a redeploy the compat gate would refuse reports the break instead of applying it. `check` is an accepted alias.","type":"boolean"},"check":{"description":"Alias for `dry_run`.","type":"boolean"},"manifest":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The x-homespun-manifest capability document (a JSON object). Eight extension keys: app metadata; collections (+ per-collection write/update/read/delete role lists, where write gates creates and also updates unless the optional update list is declared); externalHosts (fetch allowlist); cdn (allow CDN scripts/styles); capabilities (Permissions-Policy opt-ins); embeds (iframe frame-src allowlist); notify (email-on-row rules); webhooks (signed HTTP POST on-row rules). Call get_skill for the full grammar before authoring one from scratch."},"visibility":{"description":"CREATE only. Default 'private' (owner plus invited members, sign-in gated). 'link' shares with anyone holding the returned share_url, whose #k= fragment carries a secret key that can be reset (rotate it via the apps tool, action share_link_rotate) to cut off everyone with the old link; a 'link' app always gets a server-generated unguessable slug. 'private' and 'public' accept an owner-chosen `slug`.","type":"string","enum":["private","link","public"]},"slug":{"description":"CREATE only. Accepted with visibility private or public, including the private default; rejected with explicit visibility 'link', where the slug is always server-generated.","type":"string"},"force":{"description":"REDEPLOY only. Bypass the compat gate, whether it fired on a stranded-rows narrowing or on a widening of what the install screen discloses (a removed collection is detached, never deleted).","type":"boolean"},"assets":{"description":"Optional bundle of files shipped WITH the app in ONE deploy: images, fonts, audio/video, data. Each asset either carries its bytes inline as `content_base64` OR references an already-uploaded attachment by `attachment_id` (prefer the reference form for real images/media: upload once via `attachments fetch` or presign, then bind it here with NO base64 in the deploy body). Each asset is validated + stored app-scoped exactly like a normal attachment (byte-sniff, allowlist, size cap, quota, scan) and served at its `path` on the app's OWN origin, so the page references it by a stable same-origin path (`<img src=\"frames/000.jpg\">`, `<video src=\"media/intro.mp4\">`; media/font paths support HTTP Range). The whole deploy is rejected atomically if any asset fails validation. A redeploy's assets REPLACE the previous version's set. Bounded by the relay's per-deploy asset-count cap; total bytes by the per-app blob quota.","type":"array","items":{"anyOf":[{"type":"object","properties":{"path":{"type":"string","description":"App-relative, same-origin reference the HTML uses, for example 'frames/000.jpg' or 'media/intro.mp4'. Must be relative: no leading '/', no '..' segment, no backslash, charset [A-Za-z0-9._/-], and not under a reserved prefix (_hs, b)."},"content_base64":{"type":"string","description":"Standard base64 of the asset's raw bytes."},"mime":{"description":"Advisory content-type. The relay sniffs the REAL type from the bytes and enforces the attachment allowlist; omit it (or set application/octet-stream) for data files like CSV that don't magic-byte-sniff, so they are stored + served as an inert download.","type":"string"}},"required":["path","content_base64"]},{"type":"object","properties":{"path":{"type":"string","description":"App-relative, same-origin reference the HTML uses (same rules as the inline form)."},"attachment_id":{"type":"string","description":"Id of an ALREADY-uploaded attachment to bind at this path, instead of carrying base64. Use with `attachments fetch` (URL, zero model-context cost) or presign + finalize: the attachment must be owned by YOU, app-scoped to THIS app (upload it with scope=app, app_id=this app), and ready. Skips decode/upload; the deploy just maps path -> attachment_id."}},"required":["path","attachment_id"]}]}}},"required":["manifest"]},"annotations":{"title":"Deploy App","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"}},{"name":"list_rows","title":"List Rows","description":"List rows in a v2 app's mutable collection. This is also how a collection's current state is polled, since MCP has no streaming: pass the prior next_cursor as `since` to fetch only rows that are new or changed. Returns { rows, next_cursor, has_more }.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"type":"string","minLength":1,"description":"The app id."},"collection":{"type":"string","minLength":1,"description":"The collection name declared in the app's manifest."},"since":{"description":"Opaque cursor from a previous call's next_cursor. Also the POLL handle: pass it back to fetch only newer/changed rows.","type":"string"},"limit":{"description":"Page size.","type":"integer","exclusiveMinimum":0,"maximum":1000}},"required":["app_id","collection"]},"annotations":{"title":"List Rows","readOnlyHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"get_row","title":"Get Row","description":"Fetch a single row by its key from a v2 app collection, through a dedicated relay route rather than a client-side scan. Returns { row }, or an isError row_not_found.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"type":"string","minLength":1,"description":"The app id."},"collection":{"type":"string","minLength":1,"description":"The collection name."},"key":{"type":"string","minLength":1,"description":"The key of the row to fetch."}},"required":["app_id","collection","key"]},"annotations":{"title":"Get Row","readOnlyHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"upsert_row","title":"Upsert Row","description":"Create a row in a v2 app's collection, or return the existing row when `key` is already present (deduped:true). Row creation goes through this tool; there is no separate strict-create verb. Omit `key` to add a new row with a server-generated key, or pass `key` to ensure a row exists at that key. The collection must be declared in the app's manifest with 'agent' in its `write` list, which is the list that gates creates. When `key` matches a row the collection's `read` list does not reach for this caller, the result is row_not_found rather than the row, matching what get_row would return, so this never reads past `read`. Returns { row, deduped? }.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"type":"string","minLength":1,"description":"The app id."},"collection":{"type":"string","minLength":1,"description":"The collection name."},"key":{"description":"Optional stable key. Reusing an existing key returns the existing row (deduped:true), or row_not_found when the collection's read list does not reach that row for you.","type":"string"},"data":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"array","items":{}},{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"}],"description":"The row body - any JSON value valid against the collection's row schema (an object, or any JSON value for a schemaless collection)."}},"required":["app_id","collection","data"]},"annotations":{"title":"Upsert Row","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"update_row","title":"Update Row","description":"Update an existing row in a v2 app's collection, replacing its data. Gated by the collection's `update` role list when it declares one, and by its `write` list otherwise, so a collection that scopes updates to the row's `creator` refuses an edit on someone else's row. Pass if_match with the row's current version for an optimistic-locked update; on a version mismatch the relay returns the current row, which is what a retry needs. Returns { row }.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"type":"string","minLength":1,"description":"The app id."},"collection":{"type":"string","minLength":1,"description":"The collection name."},"key":{"type":"string","minLength":1,"description":"The key of the row to update."},"data":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"array","items":{}},{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"}],"description":"The new row body (replaces the row's data) - any JSON value valid against the collection's row schema."},"if_match":{"description":"Optional optimistic-lock version. On mismatch the update is rejected with the current row in details.current.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["app_id","collection","key","data"]},"annotations":{"title":"Update Row","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"delete_row","title":"Delete Row","description":"Soft-delete a row from a v2 app's collection. A watcher sees the deletion live as op:delete on the change feed. Pass if_match for an optimistic-locked delete. Returns { deleted: true }.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"type":"string","minLength":1,"description":"The app id."},"collection":{"type":"string","minLength":1,"description":"The collection name."},"key":{"type":"string","minLength":1,"description":"The key of the row to delete."},"if_match":{"description":"Optional optimistic-lock version.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["app_id","collection","key"]},"annotations":{"title":"Delete Row","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"get_feed_events","title":"Get App Feed Events","description":"Poll a v2 app's change feed for what has happened: row creates, updates and deletes, from any writer, agent or human. It is the long-poll analogue of `homespun apps watch`, since MCP has no streaming. The loop is: call with no `since` first, process the returned entries, keep the cursor, then call again passing it as `since` to get only newer entries. Passing wait (around 25) holds the request open until an entry arrives or it times out, which is how the feed is waited on rather than busy-polled. A `since` older than the retention floor returns resync_required, and the collections are then re-listed with list_rows. Returns { entries, cursor, truncated }.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"app_id":{"type":"string","minLength":1,"description":"The app id."},"since":{"description":"Opaque numeric cursor from a previous call's cursor. Omit (or 0) to read from the beginning.","type":"integer","minimum":0,"maximum":9007199254740991},"limit":{"description":"Max entries per page (capped server-side by FEED_PAGE_MAX).","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"wait":{"description":"Optional long-poll: how long the relay holds the request open waiting for a new entry (0-30s). Use ~25 when waiting for activity, then call again with the same cursor.","type":"integer","minimum":0,"maximum":30}},"required":["app_id"]},"annotations":{"title":"Get App Feed Events","readOnlyHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"apps","title":"Manage Apps","description":"The v2 app lifecycle apart from creation and redeploy, which deploy_app covers. Actions: list returns the owning human's apps; show returns full detail including manifest, timezone and has_share_token; update changes visibility and timezone, the slug being immutable, and switching to 'link' returns a share_url once; share_link_rotate issues a new share token for a 'link' app, returning a new share_url and revoking the old link, and generates one if the app has none; delete is an idempotent soft-delete; wake wakes a dormant app and is otherwise a no-op that reports the actual status; domain_set binds a custom domain and returns the DNS records the domain owner must publish, where the first domain bound serves the app and every later one redirects to it, which is how apex plus www is configured; domain_status returns the serving domain and its `aliases`, live-refreshed against Cloudflare when that is enabled, with last_error carrying the reason a domain is not activating; domain_remove unbinds one domain, or all of them when no `domain` is given, and is idempotent.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["list","show","update","share_link_rotate","delete","wake","domain_set","domain_status","domain_remove"],"description":"list: YOUR owning human's apps. show/update/delete/wake: act on one app (app_id). share_link_rotate: rotate a 'link' app's share token, returning a new share_url (the old link stops working); also generates one if the app has none yet. domain_set/domain_status/domain_remove: manage the app's custom domains (app_id; domain_set also needs domain)."},"app_id":{"description":"Required for show/update/share_link_rotate/delete/wake/domain_set/domain_status/domain_remove.","type":"string"},"status":{"description":"list only. Default: active.","type":"string","enum":["active","dormant","archived","all"]},"limit":{"description":"list only. Page size.","type":"integer","exclusiveMinimum":0,"maximum":200},"cursor":{"description":"list only. Opaque cursor from a previous next_cursor.","type":"string"},"slug":{"description":"list only. Exact-match slug filter.","type":"string"},"visibility":{"description":"update only. The new visibility (slug is immutable).","type":"string","enum":["private","link","public"]},"timezone":{"description":"update only. The app's IANA timezone for `schedules` reminders (e.g. Europe/Berlin). An app that declares schedules with no timezone fires reminders at 08:00 UTC.","type":"string"},"domain":{"description":"domain_set: the bare custom domain to bind (e.g. app.example.com); the response's dns_records lists the DNS entries the domain owner must publish. domain_remove: OPTIONAL, the one domain to unbind - omit it to unbind them all.","type":"string"}},"required":["action"]},"annotations":{"title":"Manage Apps","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"members","title":"Manage App Members","description":"A v2 app's membership (auth spec section 6): who besides the owner can sign in to a private app and write to member-scoped collections. Actions: add invites or attaches a member by email, attaching immediately when the email already has a Human and otherwise sending a magic-link invite; list returns the app's owner and members; set_role changes an existing member's declared custom role in place, or clears it when null, and leaves their sessions intact, which is what makes it the way to re-role someone rather than removing and re-adding them; remove is idempotent and also revokes the human's live sessions on this app, and the app owner cannot be removed; roles returns the derived roles summary, giving the effective access a holder actually has per declared role and collection, reported separately for signed-in members and for grant-link holders because their role floors differ, along with member and active-grant-link counts.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["add","list","set_role","remove","roles"],"description":"add: invite-or-attach a member by email (app_id+email; optional custom_roles). list: the app's owner + members (app_id). set_role: replace an existing member's declared roles in place without signing them out (app_id+human_id+custom_roles, an empty list to clear). remove: drop a member (app_id+human_id). roles: the app's declared roles with what each one includes and, per collection, the EFFECTIVE access a holder has (separately for members and grant-link holders, whose role floors differ) plus how many members and live grant links hold each role (app_id)."},"app_id":{"type":"string","minLength":1,"description":"The app id."},"email":{"description":"add only. The email to invite/attach. If a Human already exists for it, the member row is attached immediately; otherwise the relay emails a magic-link invite.","type":"string"},"role":{"description":"add only. Defaults to 'member' server-side — no other role is assignable via this API (ownership transfer is not available here).","type":"string","enum":["member"]},"custom_roles":{"description":"add (optional) and set_role (required). The DECLARED roles (x-homespun-manifest.roles keys) attached to the member ALONGSIDE their base member powers. A member may hold several and holds the union of what each grants, plus everything those roles `includes`. A built-in/reserved role or an undeclared role is rejected. Omit on add for an ordinary member; pass [] on set_role to clear the roles back to a plain member.","type":"array","items":{"type":"string"}},"human_id":{"description":"remove and set_role. The Human id to target — see list's `humanId` field. The app owner can be neither removed nor re-roled.","type":"string"}},"required":["action","app_id"]},"annotations":{"title":"Manage App Members","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"grants","title":"Manage App Grant Links","description":"A v2 app's grant links (M5). A grant link is a capability URL that confers a declared custom role (x-homespun-manifest.roles) on a stable per-holder anonymous identity, so a holder's own rows are isolated by author/:own scoping. A grant does not escalate to owner, member or agent. Actions: mint creates a link and returns a `grant_url` carrying the token in its #g= fragment, shown once and not recoverable afterwards; list returns the app's links and never a token; revoke is idempotent. mode 'once' is one-time, claimed by the first browser to open it; 'multi' is shared, capped by max_uses within expiry. An optional pin (pin_row_key or pin_where) narrows a holder to specific rows and never widens their access. One consequence worth knowing when minting: a write-only grant pinned to a single row key can still read that row's existing data back through create dedup, so such a grant exposes that row's current contents to the holder.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["mint","list","revoke"],"description":"mint: create a grant link carrying a declared custom role (app_id+role). list: the app's grant links (app_id). revoke: revoke one link (app_id+grant_id)."},"app_id":{"type":"string","minLength":1,"description":"The app id."},"role":{"description":"mint only. A DECLARED custom role for the app (an x-homespun-manifest.roles key). A built-in role (owner/member/agent/anyone) is rejected: a grant can never escalate.","type":"string"},"mode":{"description":"mint only. once: one-time link, claimed by the first browser that opens it (a real per-person link; later opens by others are inert). multi (default): a shared link, capped by max_uses within expiry.","type":"string","enum":["once","multi"]},"max_uses":{"description":"mint only (multi mode). Cap total claims; omit for unlimited within expiry. Ignored for once (forced to 1).","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"label":{"description":"mint only. Optional owner label shown in the grant list.","type":"string"},"ttl_seconds":{"description":"mint only. Grant lifetime in seconds; defaults to the server default (30 days) and is clamped to the server max.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"pin_row_key":{"description":"mint only. Optional narrowing pin to a single row key. NARROWS within the role (never widens). Mutually exclusive with pin_where.","type":"string"},"pin_where":{"description":"mint only. Optional narrowing pin as Wave C2 where conditions ({field, op, value}[]). NARROWS within the role (never widens). Mutually exclusive with pin_row_key.","type":"array","items":{}},"grant_id":{"description":"revoke only. The grant link id (see list's `id` field).","type":"string"}},"required":["action","app_id"]},"annotations":{"title":"Manage App Grant Links","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"ingest","title":"Manage App Inbound Hooks","description":"A v2 app's inbound catch-hooks (inbound-webhooks). A catch-hook lets an external system such as Stripe, Zapier, Make, Home Assistant or an email router POST JSON to a secret URL that writes into a declared collection, so the app receives data with no agent online. Hooks are declared in the manifest (x-homespun-manifest.ingest) and materialized at deploy, so this tool has no create or delete: it reads back the URL, rotates a leaked one, and manages the opt-in signing secret. After deploying a manifest that declares a hook, list is what yields the exact URL to paste into the external system. Actions: list returns the app's hooks, each with its full secret URL, current rule collection, mode, wake and handshake settings, per-status delivery counts and signing-secret state; rotate mints a fresh URL secret for one hook by name and returns the new url once, after which the old url stops working immediately with no redeploy needed; set_signing_secret provisions or rotates a hook's signing secret, which is a different secret from the URL and is what a provider HMACs the body with, minting one returned once when `secret` is omitted or storing a provider value verbatim when it is passed, and never echoing it back; clear_signing_secret removes it. Signature verification currently ships dark: nothing verifies a signature yet.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["list","rotate","set_signing_secret","clear_signing_secret"],"description":"list: the app's inbound catch-hooks, each with its full secret URL, current rule (collection/mode/wake/handshake), and per-status delivery counts (app_id). rotate: mint a fresh URL secret for one hook and return its new URL once, invalidating the old URL immediately (app_id+name). set_signing_secret: provision or rotate a hook's OPT-IN signing secret, distinct from the URL secret (it is what a provider HMACs the body with); omit `secret` to mint one (returned ONCE) or pass `secret` to store a provider-generated value verbatim (never echoed) (app_id+name). clear_signing_secret: remove a hook's signing secret (app_id+name)."},"app_id":{"type":"string","minLength":1,"description":"The app id."},"name":{"description":"rotate / set_signing_secret / clear_signing_secret. The manifest ingest hook name (an x-homespun-manifest.ingest[].name). See list's `name` field.","type":"string"},"secret":{"description":"set_signing_secret only. A provider-generated signing secret to store verbatim (the Stripe path). Omit to have the relay mint one (the GitHub path), returned ONCE in the response.","type":"string"},"grace_seconds":{"description":"set_signing_secret only. On a rotation, how long the previous secret stays valid so deliveries verify while you update the provider (default 3600, max 86400).","type":"number"}},"required":["action","app_id"]},"annotations":{"title":"Manage App Inbound Hooks","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"attachments","title":"Manage Attachments","description":"Binary attachments (images, PDFs, audio, video) referenced from event payloads and input_data via `format: homespun-attachment-id`. Actions: upload, fetch, presign, finalize, download, show, list, delete, mint_token, revoke_token, list_tokens.\n\nChoosing an upload path matters for cost. An inline upload with `content_base64` carries the bytes in the tool-call arguments, so they enter the model context at a token cost proportional to file size, paid again on every retry; a few-hundred-KB image is already expensive. Two paths avoid that entirely: fetch, when the bytes are reachable at a URL, and presign plus finalize, when the client can PUT the raw bytes out of band. Inline upload suits small assets and clients that have neither a URL nor an out-of-band PUT.\n\nfetch takes { source_url (https), scope } and the relay downloads the URL itself behind an SSRF guard (https only, no private, loopback or metadata hosts, DNS pinned, redirects refused, size-capped and timed out), then runs the same byte-sniff, allowlist, size, quota and scan checks as any upload. It works on any storage backend. upload takes either `content_base64` (base64 bytes, no filesystem) or `file_path` (an absolute path read on the relay host, so it only applies when the file is local to the relay). presign plus finalize is token-free: presign with { mime, size, sha256, scope } returns { put_url, attachment_id }, the caller PUTs the raw bytes to put_url over plain HTTP out of band, then finalize with the attachment_id. At finalize the relay re-reads the stored bytes, sniffs the real type, and enforces the same allowlist, size, sha256, quota and scan checks, so a presign that misstates its mime is caught and never served inline. The presigned path requires the Azure storage backend; a filesystem self-host returns a clear not-supported error and fetch or inline upload apply there instead. download writes to an absolute out_path or returns base64. An upload is scoped to agent (the default, reusable) or app. mint_token returns a /b/<token> capability URL, shown once, that a browser can GET without the caller's API key.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["upload","fetch","presign","finalize","download","show","list","delete","mint_token","revoke_token","list_tokens"],"description":"Binary attachment operations. The upload path affects token cost: `fetch` and presign plus finalize keep the bytes out of the model context entirely, while upload with `content_base64` carries them in the tool-call arguments at a cost proportional to file size, paid again on every retry. fetch takes { source_url, scope } and the relay downloads the bytes itself (https only, SSRF-guarded), running the same sniff, allowlist, size, quota and scan checks as any upload. upload takes `content_base64` (base64 bytes, no filesystem) or `file_path` (absolute, read on the relay host), scoped agent or app. presign plus finalize is three steps: presign with { mime, size, sha256, scope }, PUT the bytes to put_url out of band, then finalize, which re-sniffs and re-checks them. download fetches bytes by attachment_id to an absolute out_path or returns base64. show returns metadata only. list returns the agent's attachments. delete is a soft-delete. mint_token mints a /b/<token> capability URL, returned once. revoke_token and list_tokens manage those tokens."},"size":{"description":"presign: the exact byte length you will PUT. Committed at presign and re-verified against the uploaded bytes at finalize.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sha256":{"description":"presign: the hex SHA-256 (64 chars) of the exact bytes you will PUT. Committed at presign and re-verified against the uploaded bytes at finalize.","type":"string"},"attachment_id":{"description":"Attachment id. Required for download/show/delete/mint_token/revoke_token/list_tokens.","type":"string"},"file_path":{"description":"upload: ABSOLUTE path to a file read on the SERVER host running this MCP connector (the relay), NOT your machine. Only works when the file is local to the relay (e.g. a locally-run CLI). For a hosted or remote agent, use `content_base64` instead.","type":"string"},"source_url":{"description":"fetch: an https URL the relay downloads server-side, so the bytes do not enter the model context and cost no tokens. SSRF-guarded: https only, no private, loopback, link-local or metadata hosts, DNS pinned, redirects refused, size-capped and timed out. The downloaded bytes run the same byte-sniff, allowlist, size, quota and scan checks as any upload. This and presign plus finalize are the zero-context paths for real images and media.","type":"string"},"content_base64":{"description":"upload: the file bytes as base64, sent inline with no filesystem access. The base64 rides in the tool-call arguments and enters the model context, costing tokens proportional to file size; a few-hundred-KB image is already expensive, and the cost repeats on every retry. presign plus finalize avoids that for any real image or media whenever the client can do an out-of-band HTTP PUT, which leaves `content_base64` suited to small assets such as a tiny icon, and to clients that cannot PUT out of band. If both `content_base64` and `file_path` are given, `content_base64` wins. The relay sniffs the real type and enforces the same size, allowlist and quota checks as a file upload.","type":"string"},"scope":{"description":"upload scope (default agent).","type":"string","enum":["agent","app"]},"app_id":{"description":"Required when scope=app.","type":"string"},"filename":{"description":"upload: display filename (defaults to the file's basename).","type":"string"},"mime":{"description":"upload/presign: advisory Content-Type. The relay BYTE-SNIFFS the actual bytes and stores/serves that sniffed type regardless (a lying mime is caught, never served inline). Required for presign (scopes the upload URL + fails fast against the allowlist).","type":"string"},"out_path":{"description":"download: ABSOLUTE path to write the bytes to. If omitted, the bytes are returned base64-encoded in the result.","type":"string"},"cursor":{"description":"list pagination cursor.","type":"string"},"limit":{"description":"list page size (1..100).","type":"integer","exclusiveMinimum":0,"maximum":100},"ttl_seconds":{"description":"mint_token: per-token TTL (clamped by scope default).","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"once":{"description":"mint_token: token self-deletes on first GET.","type":"boolean"},"token_id":{"description":"revoke_token: the token id to revoke.","type":"string"}},"required":["action"]},"annotations":{"title":"Manage Attachments","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"}},{"name":"taste","title":"Manage UI Taste Notes","description":"The agent's UI taste notes: a short freeform markdown document of presentation preferences gathered from human feedback, such as 'denser layout' or 'no rounded corners'. Reading it before generating or revising an app is what carries earlier feedback into new output. Actions: get returns the current document; set replaces it in whole, so it does not append; clear discards it. Scoped to presentation preferences rather than general storage.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["get","set","clear"],"description":"The agent's freeform UI taste notes (markdown) — presentation preferences learned from human feedback. get: read them before generating an app. set: whole-document replace (taste, non-empty). clear: delete them."},"taste":{"description":"The full markdown notes (required for set; whole-document replace, not append).","type":"string"}},"required":["action"]},"annotations":{"title":"Manage UI Taste Notes","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"key","title":"Manage API Key","description":"The calling agent's API key. Actions: list returns key info (agent_id, key_prefix, timestamps); mint creates a sibling API key for the caller's own agent identity with the same scope and ownership and returns its raw value once, which is how an MCP-driven agent hands a CLI or child process a working credential, and the raw value is not retrievable afterwards, the sibling appears in a later list made with it, and the owner can revoke it; revoke destroys the agent's own key, which stops working immediately and cannot be undone, so it requires confirm:true. The relay derives identity from the caller's token, so every action applies to the caller's own agent and mint cannot target another agent's id.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["list","revoke","mint"],"description":"The calling agent's API key. list: key info (agent_id, key_prefix, timestamps). mint: mint a NEW sibling API key for YOUR OWN agent identity (same scope/ownership) and return its raw value ONCE, so use it to hand a CLI or child process a working credential; the sibling is a distinct key that shows up in a subsequent `list` made WITH it, the owner can revoke it, and the raw value is never retrievable again. mint always acts on the calling agent, never another agent's id. revoke: self-destruct the agent's OWN key, which stops working immediately and is irreversible (requires confirm:true)."},"confirm":{"description":"Required (true) for revoke.","type":"boolean"}},"required":["action"]},"annotations":{"title":"Manage API Key","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"feedback","title":"Manage Feedback","description":"Feedback to the relay operator. Actions: create records a bug, feature or note with a message and an optional app_id; list returns the agent's own submissions, newest first, paginated by `before`.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["create","list"],"description":"Feedback to the relay operator. create: submit a bug|feature|note with a message (optional app_id). list: the agent's own submissions, newest first."},"type":{"description":"Feedback category (required for create).","type":"string","enum":["bug","feature","note"]},"message":{"description":"Message body (required for create).","type":"string"},"app_id":{"description":"Optional app this feedback relates to (create).","type":"string"},"limit":{"description":"list page size (default 50, max 100).","type":"integer","exclusiveMinimum":0,"maximum":100},"before":{"description":"list cursor from a prior page's next_before.","type":"string"}},"required":["action"]},"annotations":{"title":"Manage Feedback","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"agent","title":"Manage Agent Identity","description":"Agent identity and binding. Actions: whoami returns the resolved relay URL, the active profile and whether a key is configured, with no network call and no secrets; claim binds this agent to a human using a one-shot claim code from their Settings UI, and is one-way; logout clears the locally saved key and profile but does not revoke it on the relay, which is what the `key` tool's revoke action does.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["whoami","claim","logout"],"description":"Agent identity. whoami: show the resolved relay URL, active profile, and whether a key is configured (no network, no secrets). claim: bind this agent to a human via a one-shot claim code the human generated in their Settings UI (one-way). logout: clear the locally-saved key/profile (does NOT revoke it on the relay — use the key tool's revoke for that)."},"code":{"description":"The one-shot claim code (required for claim).","type":"string"}},"required":["action"]},"annotations":{"title":"Manage Agent Identity","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}},{"name":"community","title":"Community Templates","description":"Publishing an app as a community template, installing a template, and, for relay operators, reviewing submissions. Actions: publish, get_config_contract, install, list_pending, get_submission, approve, reject, set_trust_level.\n\npublish captures a live app (html, manifest, the seed rows of its seedOnInstall collections, and listing metadata) into a pending template. It is installable by the returned direct link but is not listed in the public gallery until an operator approves it, and it requires a verified email and no more than a few pending submissions at once. Privacy consequence: an approved template's content and its captured seed rows become public to every platform user, so seed data in a published app must be example-only rather than real personal data. attest_example_only:true records that this was checked. A template may take a per-publisher `slug` (namespaced as <handle>/<slug>) and a semver `version` defaulting to 1.0.0, and a republish under the same slug must bump the version.\n\nget_config_contract reads what a template needs at install, meaning its settings collection and its ordered config and upload steps, by `ref`. install creates a fresh private copy of a template for the caller's owning human, passing answers as `config`, where a 'config' value is a string and an 'upload' value is a pre-uploaded attachment id from the attachments tool.\n\nThe review actions are limited to the relay's configured community reviewers: list_pending returns the queue; get_submission returns a submission's full content by snapshot_id; approve lists it in the gallery, where a re-publish supersedes the app's prior approved version; reject takes a required note that lands in the publisher's app feed.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["publish","get_config_contract","install","list_pending","get_submission","approve","reject","set_trust_level"],"description":"publish: publish one of YOUR apps as a community template (app_id; optional title/description/category/tags). PRIVACY: publishing makes the template content AND the captured seed rows (the LIVE rows of every seedOnInstall collection, captured at publish time) PUBLIC to every platform user once approved. Do NOT publish an app whose seedOnInstall collections hold real personal data (names, emails, addresses, messages, anything private): seed data must be example-only. Pass attest_example_only:true to attest you have checked this. The capture (html + manifest + seed rows) lands PENDING review, installable by its returned direct link but not listed until approved; an ESTABLISHED publisher is fast-tracked (the response's expedited/auto_approved tell you which). get_config_contract: read a template's install-time config contract by `ref` (a namespaced '<handle>/<slug>' or a snapshot id): its settings_collection, ordered config_steps (each with key/kind/required/secret/choices/default), and connect_steps (inbound hooks the app receives on). An 'upload' step wants a file; pre-upload it with the attachments tool (scope agent) and pass its attachment id. After installing a template with connect_steps, run the `ingest` tool's list action on the new app_id to read its freshly provisioned hook URLs, and wire each into the external service. install: install a template by `ref` for YOU (your owning human becomes the owner). Pass `config` as { stepKey: value } from the contract: a 'config' step's value is a string, an 'upload' step's value is a pre-uploaded attachment id. A required step you omit is rejected. Returns the new app's id, slug, and url; installs always create a fresh private copy. list_pending / get_submission / approve / reject / set_trust_level are RELAY-OPERATOR-only review actions: list_pending (the review queue, expedited submissions first), get_submission (a submission's full html+manifest+seedRows plus external_destinations, the hosts it can send data to or pull data from, by snapshot_id), approve (snapshot_id, lists it in the gallery + supersedes the app's prior approved version), reject (snapshot_id + a required note that lands in the publisher's app feed), set_trust_level (promote/demote a publisher by handle: handle + trust_level 'new'|'established')."},"ref":{"description":"get_config_contract/install only. The template to read or install: a namespaced '<handle>/<slug>' or a community snapshot id.","type":"string"},"config":{"description":"install only. The install-time answers as { stepKey: value } from the config contract: a 'config' step's value is a string, an 'upload' step's value is a pre-uploaded attachment id. Omit for a template with no config steps.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"app_id":{"description":"publish only. The id of an app YOU own to publish.","type":"string"},"title":{"description":"publish only. Listing title (1 to 80 chars). Defaults to the app's manifest name.","type":"string"},"description":{"description":"publish only. Listing blurb (up to 200 chars). Defaults to the manifest description.","type":"string"},"long_description":{"description":"publish only. Optional long-form description (up to 4000 chars) shown on the template detail page below the short blurb, for readers and search ranking. Plain text: blank lines become paragraphs, and it is escaped (never rendered as raw HTML), so write prose, not markup.","type":"string"},"category":{"description":"publish only. Optional single-word category (e.g. 'household').","type":"string"},"tags":{"description":"publish only. Up to 6 curation tags.","type":"array","items":{"type":"string"}},"slug":{"description":"publish only. Optional per-publisher slug (lowercase, 3 to 48 chars, hyphens). Gives the template a namespaced id <your-handle>/<slug>; a republish reuses the slug and must bump the version.","type":"string"},"version":{"description":"publish only. Semver MAJOR.MINOR.PATCH (default '1.0.0'). A republish under the same slug must be strictly greater than the current version.","type":"string"},"changelog_note":{"description":"publish only. A short note recorded in this version's changelog.","type":"string"},"setup_steps":{"description":"publish only. Ordered typed setup steps an installing agent follows after install (up to 20). A 'config'/'upload' step may carry a `key` naming a field of the manifest's settingsCollection that its install-time answer is written into; a 'connect' step may carry an `ingestRule` naming a manifest ingest rule it wires up. Read back via get_submission and rendered on the template detail page.","type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["config","seed-data","connect","note","upload"],"description":"config = set a value; upload = an install-time file/image the app stores as an attachment id; connect = wire up an external data source; seed-data = review/replace captured starter data; note = a plain instruction."},"label":{"type":"string","description":"Short step label (<= 80 chars)."},"key":{"description":"The settings-collection field this answer is written into (letters, digits, '_', up to 64 chars). Required for an 'upload' step, optional for a 'config' step, not allowed on the others. Must name a declared top-level field of the manifest's x-homespun-manifest.settingsCollection; an 'upload' target must be a string-typed field.","type":"string"},"description":{"description":"Optional longer instruction (<= 300 chars).","type":"string"},"required":{"description":"Whether this step is required (default false).","type":"boolean"},"secret":{"description":"Mark a step whose value is sensitive (an API key/token). Its default is MASKED on the public detail page; publish only your own example default, never a real secret.","type":"boolean"},"default":{"description":"Optional example/default value (<= 200 chars).","type":"string"},"choices":{"description":"Optional list of allowed values (up to 12).","type":"array","items":{"type":"string"}},"valueHint":{"description":"Optional format hint (<= 120 chars).","type":"string"},"ingestRule":{"description":"The manifest `ingest` rule this step wires up. Allowed only on a 'connect' step, and optional there; publish rejects a name that x-homespun-manifest.ingest does not declare. Each install mints that rule its own hook URL, which the installer pastes into the external service.","type":"string"}},"required":["kind","label"]}},"derived_from_snapshot_id":{"description":"publish only. Optional remix/fork lineage: the snapshot id this template was derived from.","type":"string"},"attest_example_only":{"description":"publish only. Set true to attest that the template content AND the captured seed rows contain NO real personal data. Publishing makes both PUBLIC to every platform user, so seed data (the live rows of your seedOnInstall collections) must be example-only, never real names/emails/addresses/private messages. Recorded and shown to the reviewer; omitting it still publishes but is flagged to the operator as not attested.","type":"boolean"},"snapshot_id":{"description":"Required for get_submission/approve/reject. The submission's snapshot id (from publish's response or list_pending).","type":"string"},"note":{"description":"reject only. The required rejection note shown to the publisher (delivered to their app feed).","type":"string"},"limit":{"description":"list_pending only. Page size (1..200).","type":"integer","exclusiveMinimum":0,"maximum":200},"cursor":{"description":"list_pending only. Opaque cursor from a prior next_cursor.","type":"string"},"handle":{"description":"set_trust_level only. The @-handle of the publisher to promote or demote.","type":"string"},"trust_level":{"description":"set_trust_level only. 'established' fast-tracks the publisher's future submissions through review; 'new' reverts to full review.","type":"string","enum":["new","established"]}},"required":["action"]},"annotations":{"title":"Community Templates","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"}},{"name":"publisher","title":"Publisher Profile","description":"The caller's community publisher identity: the @-handle and public profile shown in the template gallery. Actions: get returns the profile, including the handle, whether it has been claimed, tenure, and the rating and template counters; claim sets the handle from a lowercase 3-to-32-character string and may be used only once, after which the handle is permanent, and it refuses a handle that is reserved or already taken; update changes display_name, bio or url at any time. claim and update require a verified email. An existing publisher may hold a provisional `maker-...` handle assigned automatically, which claim renames on its one allowed use.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["claim","get","update"],"description":"get: return YOUR publisher profile (handle, tenure, counters). claim: set your @-handle ONCE (handle arg; lowercase, 3 to 32 chars, permanent after claiming; needs a verified email). update: change your public display_name/bio/url (any of them; needs a verified email)."},"handle":{"description":"claim only. The lowercase @-handle to claim (^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$). Permanent once claimed.","type":"string"},"display_name":{"description":"update only. Public display name (up to 80 chars); null clears it.","anyOf":[{"type":"string"},{"type":"null"}]},"bio":{"description":"update only. Short public bio (up to 500 chars); null clears it.","anyOf":[{"type":"string"},{"type":"null"}]},"url":{"description":"update only. Public http(s) URL (up to 200 chars); null clears it.","anyOf":[{"type":"string"},{"type":"null"}]}},"required":["action"]},"annotations":{"title":"Publisher Profile","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"}},{"name":"review","title":"Community Reviews","description":"Ratings and reviews of community templates, responses from a template's own publisher, and, for relay operators, moderation. Actions: create leaves a 1-to-5 star rating and an optional written body on a template the caller has installed, identifying it by `template` (\\\"<handle>/<slug>\\\") or by `handle` plus `slug`, and requires a verified email; each install yields exactly one review, and the aggregate carries across template versions. A body containing a link or a contact email is held automatically for a moderator before it appears. respond replies to a review of the caller's own template line (review_id plus response, or null to clear it), with one editable response per review. report flags a review for the relay's moderators (review_id plus reason) and is deduped per account. remove and unhold are limited to the relay's configured community reviewers: remove takes a review down and adjusts the rating aggregate, and unhold publishes a previously held review into the aggregate.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"action":{"type":"string","enum":["create","respond","report","remove","unhold"],"description":"create: leave a star rating (1..5) and optional body on a community template YOU have installed (identify it by `template` \"<handle>/<slug>\" or by `handle`+`slug`); requires a verified email, and one review per install. A body containing a link or contact email is auto-held for a moderator before it shows. respond: reply to a review of one of YOUR OWN templates (review_id + response; null clears it). report: flag a review for the relay's moderators (review_id + reason; one report per account). remove / unhold are RELAY-OPERATOR-only moderation actions on a review_id: remove takes a review down (adjusting the aggregate), unhold publishes a previously auto-held review."},"template":{"description":"create only. The namespaced template id <handle>/<slug> to review.","type":"string"},"handle":{"description":"create only. Publisher handle (with `slug`), an alternative to `template`.","type":"string"},"slug":{"description":"create only. Per-publisher slug (with `handle`).","type":"string"},"stars":{"description":"create only. Star rating, an integer 1 to 5.","type":"integer","minimum":1,"maximum":5},"body":{"description":"create only. Optional written review (up to 2000 chars).","type":"string"},"review_id":{"description":"Required for respond/report/remove/unhold. The review's id.","type":"string"},"response":{"description":"respond only. The publisher's public response (up to 2000 chars); null clears it.","anyOf":[{"type":"string"},{"type":"null"}]},"reason":{"description":"report only. Why you are reporting this review (up to 500 chars).","type":"string"}},"required":["action"]},"annotations":{"title":"Community Reviews","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"}},{"name":"get_skill","title":"Get Skill Guide","description":"The relay's SKILL.md, a generated guide to the Homespun workflow covering events versus records, the schema grammars and the poll loop. Needs no API key. Useful when working out how the other tools fit together, or to refresh a cached copy. Pass version_only:true to return just the relay's skill version string, which is enough to tell whether a cached copy is current.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"version_only":{"description":"If true, return only the relay's current skill version string instead of the full SKILL.md markdown.","type":"boolean"}}},"annotations":{"title":"Get Skill Guide","readOnlyHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"}}]},"jsonrpc":"2.0","id":301},"http_status":200,"headers":{"content-type":"text/event-stream","mcp-session-id":"bca60eac-357c-4433-a5e5-344d41d9805e"},"session_id_present":true,"transport":"streamable-http","requested_protocol_version":"2025-03-26","resumed":true}},"step_up_auth_probe":{"status":"ok","latency_ms":null,"details":{"oauth_present":true,"auth_required_checks":[],"supported_scopes":["homespun"],"scope_specificity_ratio":0.5,"broad_scopes":[],"challenge_headers":[],"step_up_signals":[],"minimal_scope_documented":true}},"transport_compliance_probe":{"status":"warning","latency_ms":263.74,"details":{"transport":"streamable-http","session_id_present":true,"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; charset=utf-8"},"bad_protocol_error":null,"delete_status_code":200,"delete_error":null,"expired_session_status_code":404,"expired_session_error":null,"issues":["missing_protocol_header"]}},"utility_coverage_probe":{"status":"ok","latency_ms":132.15,"details":{"completions":{"advertised":false,"sample_target":null,"live_probe":"not_executed"},"pagination":{"supported":false,"next_cursor_methods":[],"metadata_signal":false},"tasks":{"advertised":false,"probe_status":"auth_required","http_status":401},"initialize_capability_keys":["prompts","resources","tools"]}},"advanced_capabilities_probe":{"status":"missing","latency_ms":null,"details":{"capabilities":{"prompts":false,"resources":false,"completions":false,"roots":false,"sampling":false,"elicitation":false,"structured_outputs":false,"resource_links":false},"enabled_count":0,"enabled":[],"initialize_capability_keys":["prompts","resources","tools"]}},"tool_snapshot_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_tools"}},"connector_replay_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_tools"}},"request_association_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_request_association_capabilities_advertised"}},"interactive_flow_probe":{"status":"ok","latency_ms":null,"details":{"risk_hits":[],"safe_hits":[],"oauth_supported":true,"prompt_available":false}},"action_safety_probe":{"status":"ok","latency_ms":null,"details":{"summary":{"tool_count":0,"high_risk_tools":0,"destructive_tools":0,"exec_tools":0,"egress_tools":0,"secret_tools":0,"bulk_access_tools":0,"risk_distribution":{"low":0,"medium":0,"high":0,"critical":0},"capability_distribution":{}},"auth_present":true,"safeguard_count":0,"confirmation_signals":[]}},"official_registry_probe":{"status":"warning","latency_ms":null,"details":{"registry_source":"awesome_mcp_servers","direct_match":false,"official_peer_count":10,"official_identifiers":["ai.ai-akari/one-minute-akari","ai.artidrop/artidrop","ai.adramp/google-ads","ai.adweave/meta-ads-mcp","ai.aetherwealth/mcp","ai.afmr/discovery","ai.agentberg/agentberg","ai.agentdm/agentdm","ai.assetlog/assetlog","ai.agentic-news/mcp"]}},"provenance_divergence_probe":{"status":"ok","latency_ms":null,"details":{"direct_official_match":false,"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":"error","latency_ms":null,"details":{"transport":"streamable-http","tool_count":0,"high_risk_tools":0,"blockers":["tools_list","server_card","tool_surface"],"criteria":{"remote_transport":true,"initialize":true,"tools_list":false,"protocol_version":true,"session_resume":true,"step_up_auth":true,"transport_compliance":true,"connector_replay":true,"request_association":true,"action_safety":true,"server_card":false,"tool_surface":false,"auth_flow":true}}}},"score_components":{"auth_operability_score":4.0,"error_contract_score":0.5,"rate_limit_semantics_score":2.0,"schema_completeness_score":0.0,"backward_compatibility_score":2.0,"slo_health_score":1.88,"security_hygiene_score":4.0,"task_success_score":3.33,"trust_confidence_score":0.1,"abuse_noise_ratio_score":2.5,"prompt_contract_score":2.0,"resource_contract_score":2.0,"discovery_metadata_score":4.0,"registry_consistency_score":3.65,"installability_score":2.0,"session_semantics_score":2.5,"tool_surface_design_score":0.0,"result_shape_stability_score":0.0,"oauth_interop_score":4.0,"recovery_semantics_score":0.38,"maintenance_signal_score":1.45,"adoption_signal_score":2.0,"freshness_confidence_score":0.5,"transport_fidelity_score":4.0,"spec_recency_score":2.0,"session_resume_score":4.0,"step_up_auth_score":4.0,"transport_compliance_score":3.0,"utility_coverage_score":2.0,"advanced_capability_coverage_score":2.0,"connector_publishability_score":2.0,"tool_snapshot_churn_score":0.0,"connector_replay_score":3.0,"request_association_score":3.0,"interactive_flow_safety_score":3.0,"action_safety_score":3.0,"official_registry_presence_score":3.0,"provenance_divergence_score":4.0,"safety_transparency_score":2.0,"tool_capability_clarity_score":0.0,"destructive_operation_safety_score":3.0,"egress_ssrf_resilience_score":3.0,"execution_sandbox_safety_score":4.0,"data_exfiltration_resilience_score":3.0,"least_privilege_scope_score":3.0,"secret_handling_hygiene_score":3.0,"dependency_supply_chain_signal_score":0.5,"input_sanitization_safety_score":0.0,"tool_namespace_clarity_score":0.0},"validation_schema_version":"16d1d270090d6c8f","started_at":"2026-07-31T05:32:58.754075+00:00","completed_at":"2026-07-31T05:33:02.319322+00:00"},"current_snapshot":{"schema_version":"verify.trust_snapshot.v1","snapshot_id":"trustsnap_177b8e3caca9d74a","generated_at":"2026-07-31T11:43:03.183241+00:00","source":"current_snapshot","server":"awesome-homespunapps/homespun","last_validated_at":"2026-07-31T05:33:02.319322+00:00","validation_age_hours":6.17,"freshness":{"schema_version":"verify.freshness_profile.v1","last_validated_at":"2026-07-31T05:33:02.319322+00:00","age_hours":6.17,"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":55.25,"raw_score":55.25,"confidence_score":66.2,"confidence_weighted_score":36.6,"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":"failing","current_score":55.25,"display_score":55.25,"stale_score_suppressed":false,"production_trust_decision":{"schema_version":"verify.executive_verdict.v1","decision":"Block for production","why":"unauthenticated behavior not proven + failing live status + score below evaluation threshold","next_action":"revalidate, add safeguards, export policy","reason_count":3},"production_readiness_class":{"code":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first."},"evidence_confidence":{"score":66.25,"label":"medium","validation_age_hours":6.17,"live_check_count":26},"active_alerts":[{"code":"server_failing","severity":"critical","title":"Latest validation is failing"}]},"trust_snapshot":{"schema_version":"verify.trust_snapshot.v1","snapshot_id":"trustsnap_177b8e3caca9d74a","generated_at":"2026-07-31T11:43:03.183241+00:00","source":"current_snapshot","server":"awesome-homespunapps/homespun","last_validated_at":"2026-07-31T05:33:02.319322+00:00","validation_age_hours":6.17,"freshness":{"schema_version":"verify.freshness_profile.v1","last_validated_at":"2026-07-31T05:33:02.319322+00:00","age_hours":6.17,"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":55.25,"raw_score":55.25,"confidence_score":66.2,"confidence_weighted_score":36.6,"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":"failing","current_score":55.25,"display_score":55.25,"stale_score_suppressed":false,"production_trust_decision":{"schema_version":"verify.executive_verdict.v1","decision":"Block for production","why":"unauthenticated behavior not proven + failing live status + score below evaluation threshold","next_action":"revalidate, add safeguards, export policy","reason_count":3},"production_readiness_class":{"code":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first."},"evidence_confidence":{"score":66.25,"label":"medium","validation_age_hours":6.17,"live_check_count":26},"active_alerts":[{"code":"server_failing","severity":"critical","title":"Latest validation is failing"}]},"snapshot_invariant":{"schema_version":"verify.snapshot_invariant.v1","server":"awesome-homespunapps/homespun","ok":true,"surface_snapshot_ids":{"page":"trustsnap_177b8e3caca9d74a","badge":null,"report":"trustsnap_177b8e3caca9d74a","policy":null},"checked_at":"2026-07-31T11:43:04.264142+00:00"},"history":{"points":[{"timestamp":"2026-07-31T05:33:02.319322+00:00","score":55.25,"status":"failing","latency_ms":3563.84,"tool_count":0,"prompt_count":0,"resource_count":0}],"status_counts":{"failing":1},"score_delta_7d":null,"score_delta_30d":null,"avg_latency_ms":3563.84,"healthy_ratio_recent":0.0,"freshness_hours":6.17,"latest_status":"failing"},"production_readiness":{"code":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first.","badge":"score-low","critical_alerts":1},"agent_commerce_readiness":{"status":"beta","commerce_signal":"weak","payment_execution_detected":"no","billing_or_usage_detected":"no","quote_or_pricing_detected":"no","numeric_price_context":"yes","commercial_quote_context":"no","checkout_or_charge_detected":"no","checkout_term_observed":"no","payment_capable":"none","payment_rails":[],"purchase_stage_supported":[],"human_confirmation_required":"unknown","spending_policy_supported":"yes","receipt_supported":"unknown","refund_policy_present":"unknown","operator_identity":"declared","auth_required":"yes","auth_scheme":"oauth","tool_risk_level":"unknown","tool_risk_score":60,"pricing_transparency":"unknown","schema_change_detected":"unknown","delegation_level":"none","evidence_level":"inferred","confidence":"low","highest_risk_tools":[],"skipped_unsafe_tools":[],"last_checked_at":"2026-07-31T11:43:03.176768+00:00","evidence":[{"field":"commerce_signal","value":"weak","evidence_level":"inferred","source":"server_metadata","matched_terms":["limit","rate"],"sample":null,"confidence":"low","last_checked_at":"2026-07-31T11:43:03.176768+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":80,"tool_risk_score":60,"payment_readiness_score":10,"agent_delegation_safety_score":50,"overall_agent_commerce_score":null},"warnings":[],"recommended_fixes":[]},"evidence_confidence":{"score":66.25,"label":"medium","reason":"Based on 1 recent validations, 26 captured checks, and validation age of 6.2 hours.","live_check_count":26,"validation_age_hours":6.17},"recommended_for":[{"label":"OpenAI connectors","reason":"OpenAI connectors is marked compatible with score 89."},{"label":"Generic Streamable HTTP","reason":"Generic Streamable HTTP is marked compatible with score 83."}],"active_alerts":[{"code":"server_failing","severity":"critical","title":"Latest validation is failing","message":"Core MCP flows did not validate successfully on the latest run."}],"remediations":[{"code":"fix_tools_list","severity":"critical","title":"Ensure tools/list succeeds consistently","why":"Tools discovery is the minimum viable contract for most MCP clients and directories.","action":"Make tools/list succeed unauthenticated when possible, or document the auth flow in the server card.","playbook":["Make `tools/list` deterministic across repeated calls.","Document or relax auth requirements for discovery routes.","Check that tool names, descriptions, and schemas remain stable across deploys."],"maintainer_context":null},{"code":"respond_server_failing","severity":"critical","title":"Respond to latest validation is failing","why":"Core MCP flows did not validate successfully on the latest run.","action":"Fix the failing checks first, then revalidate to confirm the recovery path.","playbook":["Fix the failing checks first.","Review the latest incident feed and validation diff for the first regression.","Revalidate once the remediation lands."]},{"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":"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_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":"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":"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":"improve_adoption_market","severity":"medium","title":"Raise Adoption & Market score","why":"Adoption clues and public evidence that the server is intended for external use.","action":"Increase external documentation and directory coverage so users can discover and evaluate the server.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."]},{"code":"improve_interface_quality","severity":"medium","title":"Raise Interface Quality score","why":"How well the tool/resource interface communicates and behaves under automation.","action":"Improve schemas, error contracts, and recovery messages so agents can reason about the surface automatically.","playbook":["Inspect the latest validation evidence.","Resolve the highest-severity client-facing gap first.","Revalidate and confirm the score and verdict improve."]},{"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":"fix_resources_list","severity":"medium","title":"Repair resources/list or stop advertising resources","why":"Resource metadata should either work live or be removed from the advertised capability set.","action":"Only advertise resources if resources/list works and resources expose stable URIs/types.","playbook":["Only advertise resources with stable URIs and read semantics.","Add MIME/type hints where possible.","Run a live `resources/list` and `resources/read` check after updates."],"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},{"code":"publish_to_official_registry","severity":"low","title":"Publish or reconcile the server in the official MCP registry","why":"Official registry presence improves discovery confidence and cross-source consistency.","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=awesome-homespunapps%2Fhomespun&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":"moderate","label":"Moderate 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":"none","observed":false,"description":"Synthetic sessions inspecting multiple trust surfaces such as report, policy, ledger, badge, trust-summary, or compare."},"possible_agent_or_script":{"level":"none","observed":false,"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":false,"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=awesome-homespunapps%2Fhomespun&source=report_json","trust_summary_url":"https://verify.sentinelsignal.io/v1/servers/awesome-homespunapps/homespun/trust-summary"},"related_machine_surfaces":{"compare_index":"/compare.json","compare_api":"/v1/compare?server=awesome-homespunapps%2Fhomespun","trust_summary":"/v1/servers/awesome-homespunapps/homespun/trust-summary","ledger":"/v1/servers/awesome-homespunapps/homespun/ledger","policy":"/v1/servers/awesome-homespunapps/homespun/policy","report":"/v1/servers/awesome-homespunapps/homespun/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."}}