Compare commits
1
Commits
master
...
16fb5e4350
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16fb5e4350 |
@@ -32,7 +32,7 @@ Test at minimum:
|
||||
When `WEBMCP_ENABLED=True`:
|
||||
|
||||
- Navigation tools (`list_services`, `get_page_content`, `navigate_to_service`, `open_contact_with_subject`) load on all public pages
|
||||
- Contact page registers `submit_contact_inquiry` and annotates the HTML form with `toolname` / `tooldescription`
|
||||
- Contact page registers `submit_contact_inquiry` via declarative form annotations (`toolname`, `tooldescription`, `toolparamdescription`); reCAPTCHA renders outside the annotated form
|
||||
- Default is **disabled** (`WEBMCP_ENABLED=False`) until deliberately enabled per environment
|
||||
|
||||
Full tool catalog, Chrome flag setup, and reCAPTCHA notes: **[docs/webmcp.md](webmcp.md)**
|
||||
@@ -45,7 +45,7 @@ Before merging public-facing template or CSS changes:
|
||||
2. Nav dropdowns use `<button>` triggers with `aria-expanded` / `aria-haspopup`.
|
||||
3. Cookie consent banner stays `position: fixed` (no document flow shift).
|
||||
4. `GET /robots.txt`, `/sitemap.xml`, `/llms.txt` return 200.
|
||||
5. With `WEBMCP_ENABLED=True`, public pages include `webmcp-config` and `webmcp-tools.js`; contact form has `toolname` when enabled.
|
||||
5. With `WEBMCP_ENABLED=True`, public pages include `webmcp-config` and `webmcp-tools.js`; contact form always has `toolname`, `tooldescription`, and `toolparamdescription` on fields.
|
||||
6. Re-run Lighthouse agentic-browsing on homepage and contact page (Chrome experimental flag on).
|
||||
|
||||
## References
|
||||
|
||||
@@ -26,18 +26,18 @@ Serve the site over HTTPS (or `localhost`) — WebMCP requires a secure context.
|
||||
### 3. Lighthouse audit
|
||||
|
||||
```bash
|
||||
npx lighthouse@latest http://127.0.0.1:8000/contact \
|
||||
npx lighthouse@latest https://aimloperations.com/contact \
|
||||
--only-categories=agentic-browsing \
|
||||
--chrome-flags="--enable-experimental-web-platform-features" \
|
||||
--output=html --output-path=agentic-browsing-contact.html
|
||||
|
||||
npx lighthouse@latest http://127.0.0.1:8000/ \
|
||||
npx lighthouse@latest https://aimloperations.com/ \
|
||||
--only-categories=agentic-browsing \
|
||||
--chrome-flags="--enable-experimental-web-platform-features" \
|
||||
--output=html --output-path=agentic-browsing-home.html
|
||||
```
|
||||
|
||||
Run against a local dev server with `WEBMCP_ENABLED=True`. For contact submission audits, `DEBUG=True` skips reCAPTCHA validation.
|
||||
Use `https://` URLs to avoid redirect warnings. Run with `WEBMCP_ENABLED=True` on the target environment.
|
||||
|
||||
## Registered tools
|
||||
|
||||
@@ -66,14 +66,18 @@ Run against a local dev server with `WEBMCP_ENABLED=True`. For contact submissio
|
||||
|
||||
### Declarative form annotation
|
||||
|
||||
The contact `<form>` also declares WebMCP coverage via HTML attributes when enabled:
|
||||
The contact `<form>` declares WebMCP coverage via HTML attributes (always rendered, independent of `WEBMCP_ENABLED`):
|
||||
|
||||
```html
|
||||
<form toolname="submit_contact_inquiry"
|
||||
<form id="contact-form" toolname="submit_contact_inquiry"
|
||||
tooldescription="Submit a contact inquiry to AI ML Operations">
|
||||
<input name="name" toolparamdescription="Full name of the person submitting the inquiry." required>
|
||||
...
|
||||
</form>
|
||||
<!-- reCAPTCHA renders outside the annotated form and links via form="contact-form" -->
|
||||
```
|
||||
|
||||
This satisfies Lighthouse **form coverage** without duplicating field schemas in JavaScript. The imperative `registerTool` on `/contact` adds structured `execute` behavior with success/error responses.
|
||||
The imperative `registerTool` on `/contact` adds structured `execute` behavior with success/error responses when `WEBMCP_ENABLED=True`.
|
||||
|
||||
## Data source
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ class FormWithCaptcha(forms.Form):
|
||||
captcha = ReCaptchaField(
|
||||
widget=ReCaptchaV3(
|
||||
attrs={
|
||||
'required_score':0.85,
|
||||
'required_score': 0.85,
|
||||
'form': 'contact-form',
|
||||
}
|
||||
),
|
||||
public_key=settings.RECAPTCHA_PUBLIC_KEY,
|
||||
|
||||
@@ -780,6 +780,14 @@ input:focus, select:focus, textarea:focus {
|
||||
margin: 0 0 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.contact-captcha-group {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.contact-captcha-group + .btn {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* Cookie consent banner */
|
||||
.cookie-consent-banner {
|
||||
position: fixed;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{% include "django_recaptcha/includes/js_v3.html" %}
|
||||
<input
|
||||
type="hidden"
|
||||
name="{{ widget.name }}"
|
||||
class="g-recaptcha"
|
||||
form="contact-form"
|
||||
{% for name, value in widget.attrs.items %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %}
|
||||
>
|
||||
@@ -33,41 +33,53 @@
|
||||
|
||||
<div class="card">
|
||||
<h2 class="card-title contact-form-heading" id="contact-form-heading" style="margin-bottom: 2rem;">Send Us a Message</h2>
|
||||
<form action="{% url 'contact' %}" method="POST" aria-labelledby="contact-form-heading"
|
||||
{% if webmcp_enabled %}toolname="submit_contact_inquiry" tooldescription="Submit a contact inquiry to AI ML Operations"{% endif %}>
|
||||
<form id="contact-form" action="{% url 'contact' %}" method="POST" aria-labelledby="contact-form-heading"
|
||||
toolname="submit_contact_inquiry"
|
||||
tooldescription="Submit a contact inquiry to AI ML Operations">
|
||||
{% csrf_token %}
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="contact-name">Your Name</label>
|
||||
<input type="text" class="form-control" name="name" id="contact-name" autocomplete="name" required>
|
||||
<input type="text" class="form-control" name="name" id="contact-name" autocomplete="name" required
|
||||
toolparamdescription="Full name of the person submitting the inquiry.">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="contact-email">Your Email</label>
|
||||
<input type="email" class="form-control" name="email" id="contact-email" autocomplete="email" required>
|
||||
<input type="email" class="form-control" name="email" id="contact-email" autocomplete="email" required
|
||||
toolparamdescription="Email address where AI ML Operations can reply.">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="contact-subject">Subject</label>
|
||||
<input type="text" class="form-control" name="subject" id="contact-subject" value="{{ request.GET.subject|default:'' }}" required>
|
||||
<input type="text" class="form-control" name="subject" id="contact-subject" value="{{ request.GET.subject|default:'' }}" required
|
||||
toolparamdescription="Short summary of the inquiry topic or service of interest.">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="contact-message">Message</label>
|
||||
<textarea name="message" class="form-control" id="contact-message" rows="5" placeholder="What workflow is costing you the most time? What systems does it touch?"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
{% if capchaForm %}
|
||||
{{ capchaForm }}
|
||||
{% endif %}
|
||||
<textarea name="message" class="form-control" id="contact-message" rows="5"
|
||||
placeholder="What workflow is costing you the most time? What systems does it touch?"
|
||||
toolparamdescription="Optional details about the workflow, bottleneck, or systems involved."></textarea>
|
||||
</div>
|
||||
|
||||
{% if not capchaForm %}
|
||||
<button class="btn" type="submit"
|
||||
data-tianji-event="contact_form_submit">
|
||||
Send Message
|
||||
</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
{% if capchaForm %}
|
||||
<div class="form-group contact-captcha-group">
|
||||
{{ capchaForm }}
|
||||
</div>
|
||||
<button class="btn" type="submit" form="contact-form"
|
||||
data-tianji-event="contact_form_submit">
|
||||
Send Message
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -196,10 +196,22 @@ class WebMcpTests(TestCase):
|
||||
response = self.client.get(reverse("contact"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'id="contact-form"')
|
||||
self.assertContains(response, 'toolname="submit_contact_inquiry"')
|
||||
self.assertContains(response, "tooldescription=")
|
||||
self.assertContains(response, "toolparamdescription=")
|
||||
self.assertContains(response, "submit_contact_inquiry")
|
||||
|
||||
@override_settings(DEBUG=False)
|
||||
def test_contact_page_renders_captcha_outside_annotated_form(self):
|
||||
response = self.client.get(reverse("contact"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
form_end = response.content.index(b"</form>")
|
||||
captcha_index = response.content.index(b"g-recaptcha")
|
||||
self.assertGreater(captcha_index, form_end)
|
||||
self.assertContains(response, b'form="contact-form"')
|
||||
|
||||
def test_webmcp_services_json_includes_all_service_pages(self):
|
||||
response = self.client.get(reverse("public_index"))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user