VRDex MCP Read Tools
Status
Hosted MCP implementation checkpoint for #78.
The web app now serves a hosted Streamable HTTP MCP endpoint at /mcp using the official TypeScript MCP server SDK. The first tool set is anonymous and read-only, so public-safe search/browser-like use cases do not require login.
The broader platform plan for hosted MCP OAuth, local/private MCP, API tokens, OAuth applications, rate limiting, and Swagger/OpenAPI docs lives in docs/planning/public-api-and-mcp-platform.md. This page remains the first read-only tool contract for the MCP slice.
The first /api/v0 anonymous public read routes now exist for profiles, search,
events, worlds, and claim status, with schemas generated through
packages/api-contracts. Hosted MCP tools call those API/query surfaces instead
of scraping web pages. A local stdio MCP workspace package now exists at
packages/vrdex-mcp as @basicbit/vrdex-mcp; it calls the same /api/v0
routes and can run against hosted or self-hosted deployments.
If an OAuth bearer token is supplied to /mcp, it must be issued for the MCP
resource and include mcp:read; otherwise the anonymous public read tools still
work without credentials. Invalid or under-scoped bearer tokens return
WWW-Authenticate challenges with the protected-resource metadata URL and the
required mcp:read scope.
Anonymous hosted reads are a day-one requirement, not a degraded fallback. They must stay limited to public-safe read tools and use the anonymous MCP rate-limit class, but clients should be able to search and browse public VRDex records without completing OAuth first.
BASIC BIT hosted deployments keep that default. A self-hosted operator can set
VRDEX_HOSTED_MCP_ANONYMOUS_READS=false to make the same /mcp endpoint
OAuth-only without creating a separate admin MCP surface. In that mode,
anonymous requests receive a 401 protected-resource challenge and every tool
descriptor advertises only oauth2 with mcp:read. The reverse proxy must
still preserve the deployment's issuer/resource URLs and trusted client-IP
contract. The local stdio package is unaffected.
Clients that understand per-tool auth metadata should treat the current public
read tools as no-auth callable. OAuth is still available for authenticated MCP
traffic and future privileged tools, but public search/browser-like use should
not display a login prompt before a safe read. The server remains authoritative:
it validates any bearer token it receives, rejects wrong-resource tokens, and
applies anonymous or authenticated MCP route-class limits after auth resolution.
OpenAI/ChatGPT-style clients should receive per-tool noauth plus optional
oauth2 security metadata. The current hosted tool descriptors emit this
through _meta["securitySchemes"] for every curated public read tool. Hosted
MCP also exposes OpenAI/ChatGPT-compatible search and fetch aliases over
the same public records, because those product surfaces require that read-only
document search shape for deep research and Responses API integrations.
The OAuth issuer exposes POST /oauth/register for constrained Dynamic Client
Registration by hosted MCP clients and GET /oauth/authorize for public-client
Authorization Code with PKCE. Registered dynamic clients are public clients only:
exact redirect URIs, authorization_code metadata, code response type
metadata, token_endpoint_auth_method=none, the MCP resource, and only
mcp:read plus optional public:read scope. Anonymous MCP reads remain
available without OAuth.
Client ID Metadata Documents are supported for hosted MCP public clients that
prefer URL-form client IDs over Dynamic Client Registration. VRDex fetches the
metadata document during authorization, rejects redirects, requires exact
client_id document matching, caps responses at 5 KB, rejects special-use
address resolution, and stores accepted documents as dynamic MCP clients.
Documents that omit scope may request supported MCP scopes through user
consent; an explicit document scope remains a ceiling. This is client eligibility,
not an automatic token grant. Authorization defaults remain minimal reads.
The hosted endpoint also contains an authenticated write surface documented in
hosted-mcp-oauth-writes.md -- event
create/update and profile update/submit. Those tools are always listed, and the
connecting harness decides which of them it exposes; reaching one still requires
mcp:write plus the resource scope for that tool, which the user grants at
consent. None of it is an anonymous fallback, and all anonymous reads above
continue unchanged.
Locked Direction
- Default to a standalone VRDex MCP for VRDex public data.
- Keep optional VRChat MCP bridge tools out of scope unless a linked follow-up issue justifies them.
- Build curated tools first; generated API coverage needs its own linked issue or ADR before implementation.
- Use compact outputs with stable IDs/slugs for follow-up calls.
- Preserve public visibility, opt-out, trust, and provenance rules.
- Do not expose authenticated claim/write operations in the first read-only slice.
- Keep event-operator presence/readiness signals out of the standalone public read tool contract.
Current Hosted Tools
search
Purpose: OpenAI/ChatGPT-compatible public document search over VRDex profiles, worlds, and events.
Inputs:
query: human search text
Output:
resultscontaining stableid, human title, and canonical public URL- IDs are resolvable by the hosted
fetchcompatibility tool
fetch
Purpose: OpenAI/ChatGPT-compatible fetch for one public result returned by
search.
Inputs:
id: result ID returned bysearch
Output:
id, title, canonical public URL, public-safe text, and metadata- text is assembled from the existing public profile, event, or world read schema; it does not expose private fields beyond public API behavior
vrdex_search
Purpose: search public profiles, worlds, and events.
Inputs:
query: human search texttype: optionalall,person,community,profile,world, oreventlimit: optional bounded result count
Output:
- compact search results with
slug, entity type, title, route path, score, and public preview fields - clear empty result message
vrdex_get_profile
Purpose: read one public profile by slug.
Inputs:
slugprofileType: optional guard when caller knows the expected type
Output:
- public profile fields only
- trust/provenance labels
- public links and events where allowed
- no private or suppressed fields
vrdex_list_upcoming_events
Purpose: list upcoming public events.
Inputs:
limit: optional bounded result count
Output:
- public event cards with stable slugs/IDs, title, start/end time, public host/participant/world context, and canonical URL
vrdex_get_event
Purpose: read one public event.
Inputs:
slug
Output:
- public event details, participant links, media links, world association state, and provenance labels
vrdex_get_world
Purpose: read one public world by slug.
Inputs:
slug
Output:
- public world details, media, outbound links, creator attributions, event context, and provenance labels
vrdex_list_active_worlds
Purpose: list public worlds with upcoming or live events.
Inputs:
limit: optional bounded result count
Output:
- public active world cards with the next event preview and upcoming event count
Local Stdio MCP
Current checkpoint:
- workspace package:
@basicbit/vrdex-mcp - source path:
packages/vrdex-mcp - transport: stdio
- API surface:
/api/v0public read routes - credentials:
VRDEX_API_TOKEN,VRDEX_OAUTH_ACCESS_TOKEN, orVRDEX_OAUTH_TOKEN_FILE
The package defaults to https://vrdex.net/api/v0. Set VRDEX_API_BASE_URL
for self-hosted or staging deployments. The value can be either the deployment
origin or the explicit API base path; both https://example.test and
https://example.test/api/v0 normalize to the API route prefix.
Bearer credentials are optional for anonymous public reads. Set a personal API
token or OAuth access token to use authenticated public-read rate limits. The
OAuth token file can contain a plain access token or a JSON object with an
access_token field. Because the local package calls /api/v0, OAuth access
tokens used here must be issued for the API resource. Hosted /mcp OAuth
sessions use the MCP resource instead.
When a bearer credential is configured, local stdio also registers
vrdex_list_my_profiles, which lists the profiles the authenticated user owns
over GET /api/v0/me/profiles and needs profile:read. It is the only read that
returns drafts and profiles kept off public pages, and each row carries the
updatedAt that an update of that profile has to send back as
expectedUpdatedAt -- the public reads cannot serve that for a profile they are
right to hide.
On hosted MCP only, vrdex_list_my_profiles also accepts mediaSlug. That
focused read returns the selected owned claimed profile's recoverable media and
opaque mediaVersion for vrdex_profile_media_manage. It never returns source
URLs, storage identifiers, content hashes, upload intents, or upload
credentials. The local stdio package keeps its existing /api/v0/me/profiles
contract and does not expose media management in this slice.
Hosted MCP adds a contributor status read, vrdex_list_my_media_submissions. It
is an Issue 297 candidate and reaches production only after staged proof and
explicit approval. It requires mcp:read plus assets:contribute and returns at most
40 of that caller's own private-proposal statuses. It does not expose source
URLs, storage fields, processing state, review notes, or another contributor's
submissions. This tool is hosted-only because the local stdio package has no
matching public API route.
Hosted reviewer reads are vrdex_media_review_list,
vrdex_media_review_get, and vrdex_media_review_preview. They require
mcp:read plus assets:review:read and a user-delegated session with a current
verified email. List returns only the authorized queue. Detail returns the
candidate provenance, current placement and opaque review version. Preview
accepts that version and returns native MCP image content from the matching
stored candidate as a bounded PNG. It does not fetch the original source URL,
and it never returns storage identifiers or moderator identity fields outside
the caller's authorized projection.
A configured credential also registers four approval-gated write tools:
vrdex_event_create, vrdex_event_update, vrdex_profile_update, and
vrdex_profile_submit. They use the existing
/api/v0 routes and read the saved record back after every accepted mutation.
The event tools need events:write; profile updates need profile:write, plus
profile:contribute to correct a profile the user does not own; submissions
need profile:contribute. Registration does not inspect the token's scopes, so
all five credentialed tools are listed whenever a credential is present and the
route refuses the ones it is not entitled to. The hosted /mcp server registers
those five plus the hosted-only vrdex_profile_media_manage,
vrdex_profile_media_submit, and vrdex_list_my_media_submissions (the last
two pending the Issue 297 production gate), where
vrdex_list_my_profiles advertises mcp:read plus profile:read and the media
owner write advertises mcp:write plus assets:write. Contribution submission
advertises mcp:write plus assets:contribute; contribution status advertises
mcp:read plus assets:contribute. See
docs/developers/vrdex-mcp-event-writes.md and
docs/developers/hosted-mcp-oauth-writes.md for the write contracts.
Local workspace command:
pnpm --silent --dir <path-to-vrdex-checkout> exec tsx packages/vrdex-mcp/src/stdio.ts
Common MCP JSON configuration:
{
"mcpServers": {
"vrdex": {
"command": "pnpm",
"args": [
"--silent",
"--dir",
"<path-to-vrdex-checkout>",
"exec",
"tsx",
"packages/vrdex-mcp/src/stdio.ts"
],
"env": {
"VRDEX_API_BASE_URL": "https://vrdex.net",
"VRDEX_API_TOKEN": "<personal-api-token>"
}
}
}
}
Self-hosted example:
{
"mcpServers": {
"vrdex-local": {
"command": "pnpm",
"args": [
"--silent",
"--dir",
"<path-to-vrdex-checkout>",
"exec",
"tsx",
"packages/vrdex-mcp/src/stdio.ts"
],
"env": {
"VRDEX_API_BASE_URL": "https://vrdex.example.net",
"VRDEX_OAUTH_TOKEN_FILE": "<path-to-local-oauth-token-json>"
}
}
}
}
Claude Desktop, Cursor, VS Code MCP integrations, and other clients that accept
the common mcpServers JSON shape can use the same command, args, and env
block. Registry install snippets can replace the workspace command after the
package is published.
The current implementation-time client matrix lives in
docs/developers/mcp-client-compatibility.md. Use it before declaring hosted
or local MCP externally ready, because day-one support needs real smokes across
major clients rather than only repo-level protocol tests.
For hosted preview validation, treat empty-query transport checks and
data-backed public reads separately: pnpm smoke:mcp-compat -- --hosted-data
must pass against a same-branch or production-like Convex backend before
external readiness. That data-backed mode requires both vrdex_search and the
OpenAI-compatible search plus fetch aliases to return real public data; use
--hosted-query when the target needs a known non-empty public query.
Safety Rules
- Use public API/query behavior, not website scraping.
- Treat not found, private, opted-out, and suppressed records as the same public-safe absence unless the public API deliberately exposes a safer status.
- Return a public-safe MCP tool error with non-empty text when the hosted public data backend is temporarily unavailable; do not leak backend exception details.
- Do not expose raw provider IDs unless they are already public and documented as safe.
- Do not expose private contact details or moderation-only fields.
- Do not imply owner confirmation for unclaimed, imported, community-submitted, or partner-provided records.
- Include source/provenance summaries wherever public data may be mistaken as authoritative.
Hosted Vs Local MCP
Current recommendation:
- hosted/remote MCP is suitable for public read-only data because VRDex public data is not tied to private VRChat cookies
- anonymous hosted MCP read tools should be allowed for public-safe search/browser-like use cases, with their own rate-limit class
- no-auth tool metadata should be preferred for public read tools when a hosted client supports it, so anonymous search/browse workflows do not get forced into OAuth setup
- current hosted read tools advertise
_meta["securitySchemes"]withnoauthplus optionaloauth2/mcp:read - hosted
searchandfetchare compatibility aliases for clients that require generic document search/fetch names; the canonical VRDex-specific public read tools remain available - OAuth-authenticated hosted MCP callers use the authenticated MCP rate-limit class when the token is valid for the MCP resource
- dynamic MCP client registrations are stored separately from normal developer apps until an operator promotes or reviews them
- public-client PKCE consent issues short-lived MCP-bound access tokens and rotating refresh tokens
- local MCP is implemented as a stdio workspace package for self-hosted deployments and development
- authenticated write/claim tools, if ever added, need normal VRDex auth, scoped tokens, approvals, and audit trails
Optional VRChat bridge evaluation:
- a local bridge can be evaluated separately for operator-owned event workflows, not for the standalone public read tools
- candidate bridge tools can resolve VRChat users, groups, or worlds to candidate VRDex records, or provide private event-operator hints when the operator has local credentials
- bridge-derived presence or readiness must be treated as private, freshness-scoped, and non-authoritative
- bridge tools must not be required for
vrdex_event_get, event discovery, profile claims, or public event watch surfaces
Implementation Gate
The standalone local package gate is now cleared for the read-only slice:
@basicbit/vrdex-mcp uses shared API contract schemas and public /api/v0
routes instead of website scraping. #78
remains the prototype issue for compatibility validation, registry publishing,
and any future authenticated write tools.
Event roster playback fields
vrdex_get_event returns effective watchMode, participants and slot performers with discovery-visible outboundLinks, and typed schedule rows. Each row includes playbackKey; playable rows additionally expose stream: { streamId, pcUrl, questUrl }. Rows without a resolvable choice still appear. Saved explicit choices that disappear are never replaced automatically. Hosted MCP and stdio use the same public event schema in structured and text output. This extends the existing event tool, not the search-document rendering contract.