## Summary - Add `WEBMCP_ENABLED` setting and guarded `webmcp-tools.js` loader on public pages - Register navigation tools (`list_services`, `get_page_content`, `navigate_to_service`, `open_contact_with_subject`) and contact tool (`submit_contact_inquiry`) - Annotate contact form with declarative WebMCP attributes for Lighthouse form coverage - Extend `PUBLIC_PAGE_ENTRIES` with summaries shared by SEO and WebMCP tools - Add `docs/webmcp.md`, update `docs/agentic-browsing.md`, and regression tests Closes #9 ## Test plan - [x] `python manage.py test public.tests` — 20 tests pass - [ ] Set `WEBMCP_ENABLED=True`, enable Chrome experimental web platform features flag - [ ] Verify homepage HTML includes `webmcp-config` and `webmcp-tools.js` - [ ] Verify `/contact` form has `toolname="submit_contact_inquiry"` - [ ] Run Lighthouse agentic-browsing on `/` and `/contact` with experimental flag - [ ] Call `list_services` from WebMCP-capable Chrome on homepage - [ ] Call `submit_contact_inquiry` on `/contact` with `DEBUG=True` (reCAPTCHA bypass) Reviewed-on: #12
165 lines
4.3 KiB
Python
165 lines
4.3 KiB
Python
"""Machine-readable site discovery endpoints for crawlers and AI agents."""
|
|
|
|
from django.http import HttpResponse
|
|
from django.template.loader import render_to_string
|
|
from django.urls import reverse
|
|
|
|
# Public marketing pages included in sitemap and llms.txt.
|
|
# Tuple: (url_name, title, changefreq, priority, summary)
|
|
PUBLIC_PAGE_ENTRIES = (
|
|
(
|
|
"public_index",
|
|
"Home",
|
|
"weekly",
|
|
"1.0",
|
|
"Company homepage with an overview of AI ML Operations services.",
|
|
),
|
|
(
|
|
"forward_deployed",
|
|
"Forward-Deployed AI",
|
|
"monthly",
|
|
"0.9",
|
|
"Embedded AI engineering — we work inside your environment to build production systems.",
|
|
),
|
|
(
|
|
"bot",
|
|
"AI Agents",
|
|
"monthly",
|
|
"0.9",
|
|
"Custom AI agents and agentic workflows tailored to your operational bottlenecks.",
|
|
),
|
|
(
|
|
"ml_model",
|
|
"ML Models",
|
|
"monthly",
|
|
"0.8",
|
|
"Machine learning model development, training, and deployment for production use.",
|
|
),
|
|
(
|
|
"chat",
|
|
"Secure AI Chat",
|
|
"monthly",
|
|
"0.8",
|
|
"Private, hosted AI chat deployments with enterprise-grade security.",
|
|
),
|
|
(
|
|
"ai_sensor",
|
|
"AI Sensor Algorithms",
|
|
"monthly",
|
|
"0.7",
|
|
"Computer vision and sensor-fusion algorithms for real-world sensing applications.",
|
|
),
|
|
(
|
|
"ai_education",
|
|
"AI Education",
|
|
"monthly",
|
|
"0.7",
|
|
"Hands-on AI training and workshops for teams adopting agentic workflows.",
|
|
),
|
|
(
|
|
"computers",
|
|
"Computer Builds",
|
|
"monthly",
|
|
"0.7",
|
|
"Custom workstation and server builds optimized for AI and ML workloads.",
|
|
),
|
|
(
|
|
"file_hosting",
|
|
"File Hosting",
|
|
"monthly",
|
|
"0.7",
|
|
"Managed file hosting and storage for teams that need reliable data access.",
|
|
),
|
|
(
|
|
"web_design",
|
|
"Web Design and Hosting",
|
|
"monthly",
|
|
"0.8",
|
|
"Web design, development, and managed hosting for business sites and apps.",
|
|
),
|
|
(
|
|
"contact",
|
|
"Contact",
|
|
"monthly",
|
|
"0.9",
|
|
"Contact form to inquire about forward-deployed AI engineering services.",
|
|
),
|
|
(
|
|
"terms_of_service",
|
|
"Terms of Service and Privacy",
|
|
"yearly",
|
|
"0.3",
|
|
"Terms of service and privacy policy for AI ML Operations, LLC.",
|
|
),
|
|
)
|
|
|
|
SERVICE_URL_NAMES = frozenset({
|
|
"forward_deployed",
|
|
"bot",
|
|
"ml_model",
|
|
"chat",
|
|
"ai_sensor",
|
|
"ai_education",
|
|
"computers",
|
|
"file_hosting",
|
|
"web_design",
|
|
})
|
|
|
|
|
|
def get_service_entries():
|
|
"""Return service page metadata for WebMCP navigation tools."""
|
|
return [
|
|
{
|
|
"slug": url_name,
|
|
"name": title,
|
|
"summary": summary,
|
|
}
|
|
for url_name, title, _changefreq, _priority, summary in PUBLIC_PAGE_ENTRIES
|
|
if url_name in SERVICE_URL_NAMES
|
|
]
|
|
|
|
|
|
def _absolute_url(request, url_name):
|
|
return request.build_absolute_uri(reverse(url_name))
|
|
|
|
|
|
def robots_txt(request):
|
|
sitemap_url = _absolute_url(request, "sitemap_xml")
|
|
content = render_to_string(
|
|
"public/robots.txt",
|
|
{"sitemap_url": sitemap_url},
|
|
)
|
|
return HttpResponse(content, content_type="text/plain; charset=utf-8")
|
|
|
|
|
|
def sitemap_xml(request):
|
|
pages = [
|
|
{
|
|
"loc": _absolute_url(request, url_name),
|
|
"changefreq": changefreq,
|
|
"priority": priority,
|
|
}
|
|
for url_name, _title, changefreq, priority, _summary in PUBLIC_PAGE_ENTRIES
|
|
]
|
|
content = render_to_string("public/sitemap.xml", {"pages": pages})
|
|
return HttpResponse(content, content_type="application/xml; charset=utf-8")
|
|
|
|
|
|
def llms_txt(request):
|
|
pages = [
|
|
{
|
|
"title": title,
|
|
"url": _absolute_url(request, url_name),
|
|
}
|
|
for url_name, title, _changefreq, _priority, _summary in PUBLIC_PAGE_ENTRIES
|
|
]
|
|
content = render_to_string(
|
|
"public/llms.txt",
|
|
{
|
|
"site_url": request.build_absolute_uri("/"),
|
|
"contact_url": _absolute_url(request, "contact"),
|
|
"pages": pages,
|
|
},
|
|
)
|
|
return HttpResponse(content, content_type="text/plain; charset=utf-8")
|