5.7 KiB
WebMCP prototype
Scope and design
Expose the existing intentional reader to agents through three React-owned tools. Search remains available across routes. Feed tools are registered only while a valid timeline, search, or conversation is open. Returning home or to an empty search/list page removes the feed tools. Unsupported browsers do not register tools or fetch posts automatically.
usewebmcp 5.1.0 owns browser registration and cleanup. No polyfill is
initialized. The root search tool navigates through TanStack Router and awaits
the same query options as the visible feed. Completed data is reused through
ensureInfiniteQueryData; an invalidated or in-progress query is awaited
through fetchInfiniteQuery, including refreshes after a profile change.
Reading the feed uses its current React Query result. Continuation and the
scroll observer use fetchNextPage({ cancelRefetch: false }) to join an
existing request. A feed change during continuation returns an error instead
of reporting old results as belonging to the new feed.
Tool contracts
search_posts
Search X, show the criteria and results in the UI, and return the first slice after retrieval. Searches use the currently selected relay profile.
Inputs match the existing search controls:
| Field | Default | Meaning |
|---|---|---|
q |
empty | Search text, including raw X search syntax |
from |
empty | Author handle, optionally prefixed with @ |
since |
empty | Inclusive YYYY-MM-DD date using X search semantics |
until |
empty | Exclusive YYYY-MM-DD date, later than since |
lang |
all |
all, ja, or en |
content |
all |
all, images, videos, or links |
excludeReplies, excludeReposts |
false |
Exclusions |
product |
Latest |
Latest or Top |
following |
false |
Restrict to followed accounts |
Supply q or from. Unknown properties and invalid inputs fail before
navigation. Existing query construction validates dates, handles, and the
512-character compiled-query limit. A concurrent search fails with an
actionable message. Navigating elsewhere during a search prevents a stale
success response.
get_loaded_posts
Return a slice of the current feed without a network request. Accepts
offset (default 0, nonnegative integer) and limit (default 20, integer
1–50). Conversation results place the selected post first, once.
The response includes status (loading, ready, or error), loading,
and error information alongside the common result. This is a snapshot of
all loaded posts, not just posts inside the viewport.
load_more_posts
Accepts {}. Wait for one continuation, append it to the UI, and return up to
20 newly appended posts. An in-progress scroll request is shared. Initial
loading or a feed refresh must finish first. A failed continuation can be retried explicitly.
At the end of the feed the result is an empty posts array and hasMore: false.
Results and errors
Successful tool results contain JSON in an MCP text content block:
request: active feed kind and normalized criteria.posts:id,author(username/name),text,textTruncated, optionalcreatedAt, and the original Xurl.loadedCount: number of deduplicated, loaded posts.offset: start of this slice within the loaded feed.nextOffset: next unread offset within the already loaded feed, ornull.hasMore: whether the last loaded page has an upstream continuation.
Use get_loaded_posts with nextOffset for loaded posts outside a returned
slice; use load_more_posts for an upstream continuation. Scrolling can load
more posts independently, so these fields describe a snapshot. Post text is
capped at 2,000 characters per post and explicitly marked when truncated;
the source URL remains available. Media, quote bodies, and article previews
are not included in this initial text-oriented tool response.
Failures use isError: true and JSON containing code, message, and
retryable. Existing relay error details are preserved. A successful empty
search is not an error.
All tools mark returned external content with untrustedContentHint: true.
Only get_loaded_posts has readOnlyHint: true: search and continuation
change local UI state. No tool performs X write actions. Annotations are
metadata, not authorization controls.
Verification and limits
nix develop -c pnpm test
nix develop -c pnpm typecheck
nix develop -c pnpm test:e2e e2e/integrations/webmcp.test.ts
The E2E file enables native Chromium WebMCP/testing flags and uses
navigator.modelContextTesting to invoke actual registered tools. The
standalone mock relay supplies deterministic data through the production
server-function boundary. Tests do not contact X or a personal relay.
For interactive testing, enable Chrome's WebMCP testing flag, restart, run
nix develop -c pnpm dev, and open the local site with Model Context Tool
Inspector. Registration uses document.modelContext; availability is checked
at mount, so reload after changing browser support. No production origin-trial
token or external MCP-client bridge is configured by this prototype.
The hook does not forward the browser's execution AbortSignal to application callbacks. Browser cancellation therefore does not guarantee cancellation of the shared read request. Tools do detect navigation changes before returning their asynchronous results. Agent task-selection quality still needs manual evaluation with the consuming agent; deterministic browser tests verify the tool contracts and UI behavior.
Design references: Chrome best practices, workflow design, and usewebmcp.