Skip to main content

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​

Purpose: OpenAI/ChatGPT-compatible public document search over VRDex profiles, worlds, and events.

Inputs:

  • query: human search text

Output:

  • results containing stable id, human title, and canonical public URL
  • IDs are resolvable by the hosted fetch compatibility tool

fetch​

Purpose: OpenAI/ChatGPT-compatible fetch for one public result returned by search.

Inputs:

  • id: result ID returned by search

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

Purpose: search public profiles, worlds, and events.

Inputs:

  • query: human search text
  • type: optional all, person, community, profile, world, or event
  • limit: 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:

  • slug
  • profileType: 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/v0 public read routes
  • credentials: VRDEX_API_TOKEN, VRDEX_OAUTH_ACCESS_TOKEN, or VRDEX_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"] with noauth plus optional oauth2/mcp:read
  • hosted search and fetch are 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.