@basicbit/vrchat-mcp 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +1 -0
  3. package/dist/package.json +2 -1
  4. package/dist/src/auth/index.js +4 -1
  5. package/dist/src/auth/index.js.map +1 -1
  6. package/dist/src/core/client.js +27 -1
  7. package/dist/src/core/client.js.map +1 -1
  8. package/dist/src/core/generatedToolOverrides.js +2 -0
  9. package/dist/src/core/generatedToolOverrides.js.map +1 -1
  10. package/dist/src/core/operationPolicy.js +23 -1
  11. package/dist/src/core/operationPolicy.js.map +1 -1
  12. package/dist/src/generated/vrchat-schemas.js +3 -1
  13. package/dist/src/generated/vrchat-schemas.js.map +1 -1
  14. package/dist/src/models/avatars.js +85 -0
  15. package/dist/src/models/avatars.js.map +1 -1
  16. package/dist/src/models/groups.js +38 -0
  17. package/dist/src/models/groups.js.map +1 -1
  18. package/dist/src/models/instances.js +16 -0
  19. package/dist/src/models/instances.js.map +1 -1
  20. package/dist/src/models/invites.js +31 -2
  21. package/dist/src/models/invites.js.map +1 -1
  22. package/dist/src/models/status.js +4 -2
  23. package/dist/src/models/status.js.map +1 -1
  24. package/dist/src/services/api/client.js +3 -0
  25. package/dist/src/services/api/client.js.map +1 -1
  26. package/dist/src/services/avatars/index.js +166 -1
  27. package/dist/src/services/avatars/index.js.map +1 -1
  28. package/dist/src/services/events/curated.js +16 -10
  29. package/dist/src/services/events/curated.js.map +1 -1
  30. package/dist/src/services/instances/curated.js +76 -1
  31. package/dist/src/services/instances/curated.js.map +1 -1
  32. package/dist/src/services/invites/curated.js +35 -16
  33. package/dist/src/services/invites/curated.js.map +1 -1
  34. package/dist/src/tools/curated/avatars.js +41 -3
  35. package/dist/src/tools/curated/avatars.js.map +1 -1
  36. package/dist/src/tools/curated/groups.js +2 -2
  37. package/dist/src/tools/curated/groups.js.map +1 -1
  38. package/dist/src/tools/curated/instances.js +29 -2
  39. package/dist/src/tools/curated/instances.js.map +1 -1
  40. package/dist/src/tools/curated/invites.js +1 -1
  41. package/dist/src/tools/curated/invites.js.map +1 -1
  42. package/dist/src/tools/raw.js +5 -1
  43. package/dist/src/tools/raw.js.map +1 -1
  44. package/docs/curated-tools.md +14 -0
  45. package/docs/robust-notes.md +9 -1
  46. package/docs/spec-drift.md +104 -0
  47. package/docs/tools.md +339 -215
  48. package/package.json +2 -1
  49. package/server.json +2 -2
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { callOperation, CallError } from '../core/client.js';
3
- import { getBlockedOperationReason } from '../core/operationPolicy.js';
3
+ import { getBlockedOperationReason, getCuratedOnlyReason } from '../core/operationPolicy.js';
4
4
  import { CallInputSchema } from '../schemas/call.js';
5
5
  import { writeToolAnnotations } from '../utils/toolAnnotations.js';
6
6
  import { toolName } from '../utils/toolNames.js';
@@ -22,6 +22,10 @@ export function registerRawTools(server) {
22
22
  if (blockedReason) {
23
23
  return toolError(`Operation ${args.operationId} is disabled: ${blockedReason}`);
24
24
  }
25
+ const curatedOnlyReason = getCuratedOnlyReason(args.operationId);
26
+ if (curatedOnlyReason) {
27
+ return toolError(curatedOnlyReason);
28
+ }
25
29
  try {
26
30
  const result = await callOperation(args);
27
31
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"raw.js","sourceRoot":"","sources":["../../../src/tools/raw.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,SAAS,EAAkB,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAEtD,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IAChD,MAAM,CAAC,YAAY,CACjB,QAAQ,CAAC,aAAa,CAAC,EACvB;QACE,WAAW,EAAE,iDAAiD;QAC9D,WAAW,EAAE,eAAe;QAC5B,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC;YACrB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;YACf,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC7B,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;YACpD,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;YACxB,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;SAC/B,CAAC;QACF,WAAW,EAAE,oBAAoB;KAClC,EACD,KAAK,EAAE,IAAe,EAAE,EAAE;QACxB,MAAM,aAAa,GAAG,yBAAyB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAClE,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,SAAS,CACd,aAAa,IAAI,CAAC,WAAW,iBAAiB,aAAa,EAAE,CAC9D,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;YACzC,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;qBACtC;iBACF;gBACD,iBAAiB,EAAE,MAA4C;aAChE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,SAAS,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAC5C,OAAO,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YAC7C,CAAC;YACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YACrE,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"raw.js","sourceRoot":"","sources":["../../../src/tools/raw.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,SAAS,EAAkB,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAEtD,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IAChD,MAAM,CAAC,YAAY,CACjB,QAAQ,CAAC,aAAa,CAAC,EACvB;QACE,WAAW,EAAE,iDAAiD;QAC9D,WAAW,EAAE,eAAe;QAC5B,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC;YACrB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;YACf,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC7B,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;YACpD,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;YACxB,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;SAC/B,CAAC;QACF,WAAW,EAAE,oBAAoB;KAClC,EACD,KAAK,EAAE,IAAe,EAAE,EAAE;QACxB,MAAM,aAAa,GAAG,yBAAyB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAClE,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,SAAS,CACd,aAAa,IAAI,CAAC,WAAW,iBAAiB,aAAa,EAAE,CAC9D,CAAC;QACJ,CAAC;QAED,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACjE,IAAI,iBAAiB,EAAE,CAAC;YACtB,OAAO,SAAS,CAAC,iBAAiB,CAAC,CAAC;QACtC,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;YACzC,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;qBACtC;iBACF;gBACD,iBAAiB,EAAE,MAA4C;aAChE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,SAAS,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAC5C,OAAO,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YAC7C,CAAC;YACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YACrE,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -94,10 +94,24 @@ Events and calendar (write):
94
94
  Instances and invites (write):
95
95
 
96
96
  - `vrchat_instance_create`
97
+ - `vrchat_instance_link_event`
97
98
  - `vrchat_invite`
98
99
  - `vrchat_invite_user`
99
100
  - `vrchat_invite_user_to_me`
100
101
 
102
+ `vrchat_instance_link_event` accepts an explicit group, event, world, and instance ID. It first
103
+ checks `groups.allowlist`, then freshly reads both objects and requires the event and group-type
104
+ instance to have that same group owner. It refuses to replace a different existing event link.
105
+ Calls targeting one instance are serialized within the MCP process so concurrent requests cannot
106
+ replace one another after both read an unlinked instance. Re-linking the same event is an
107
+ idempotent no-op that still invalidates the relevant caches. The result includes the group and
108
+ event IDs plus readable event and instance names for safe follow-up calls. The tool can only set
109
+ `calendarEntryId`; it cannot unlink an event or change any other instance field, and it does not
110
+ send invitations or notifications. A conflicting existing link is rejected after invalidating the
111
+ same caches so later reads see that fresh state. VRChat accepts the link only when the event starts
112
+ within six hours or ended within the previous six hours. Like other account-changing tools,
113
+ approval belongs at the MCP client or agent-harness boundary.
114
+
101
115
  Groups and social writes:
102
116
 
103
117
  - `vrchat_group_invite`
@@ -26,4 +26,12 @@ The separate unresolved policy-sensitive boundary remains VRChat authentication:
26
26
 
27
27
  ## API Content Upload Boundary
28
28
 
29
- VRChat Creator Guidelines (API Usage / Bots, checked 2026-05-26) say not to create or use software, scripts, or applications that upload content such as worlds, avatars, or similar asset types on a user's behalf. Treat generated operations for uploading or managing uploaded asset content as policy-sensitive, not merely high-risk writes. Default policy blocks explicit avatar/world content-management operations (`createAvatar`, `updateAvatar`, `deleteAvatar`, `createWorld`, `updateWorld`, `deleteWorld`, `publishWorld`, `unpublishWorld`) in central operation validation and hides their generated write tools. Do not assume all image-like uploads are prohibited: icons, prints, gallery images, file upload pipeline operations, and similar user image/file operations remain separate policy decisions. Selecting or viewing avatars is a different class from uploading avatar content; do not over-block ordinary user account actions without mapping them to the guideline text.
29
+ VRChat Creator Guidelines (API Usage / Bots, checked 2026-05-26) say not to create or use software, scripts, or applications that upload content such as worlds, avatars, or similar asset types on a user's behalf. Treat generated operations for uploading or managing uploaded asset content as policy-sensitive, not merely high-risk writes. Default policy blocks explicit avatar/world content-management operations (`createAvatar`, `deleteAvatar`, `createWorld`, `updateWorld`, `deleteWorld`, `publishWorld`, `unpublishWorld`) in central operation validation and hides their generated write tools.
30
+
31
+ `updateAvatar` is the one exception, and the boundary moved rather than disappeared. It is no longer in central operation validation, because the curated `vrchat_avatar_update` tool has to reach it; it is listed as curated-only instead, so the raw call tool and the generated write registry both still refuse it. That tool writes `name`, `description`, `releaseStatus` and content tags, and never `assetUrl`, `unityPackageUrl`, `unityVersion` or `version`. The reasoning: editing metadata on already-uploaded content is a different act from uploading content on a user's behalf, which is what the guideline text addresses.
32
+
33
+ **Avatar `releaseStatus` is deliberately allowed** (decided 2026-08-03). It looks like the avatar analogue of `publishWorld` / `unpublishWorld`, but those are blocked for a reason avatars do not share: unpublishing a world sends it back through Community Labs, so toggling world publication has a real and costly consequence an agent should not be able to trigger. Avatars have no equivalent gate. The avatar act this reasoning permits is metadata editing and nothing more: `createAvatar` and `deleteAvatar` both remain hard-blocked, creation because it is upload and deletion because it is destructive and irreversible. Changing an existing avatar's publication state is metadata management, not upload or republication.
34
+
35
+ This does not make `releaseStatus` casual — it can still expose a creator's work against their terms, leak an avatar meant to stay private, or breach content policy depending on what the avatar contains, which is why the tool description tells callers to confirm intent first.
36
+
37
+ Do not assume all image-like uploads are prohibited: icons, prints, gallery images, file upload pipeline operations, and similar user image/file operations remain separate policy decisions. Selecting or viewing avatars is a different class from uploading avatar content; do not over-block ordinary user account actions without mapping them to the guideline text.
@@ -0,0 +1,104 @@
1
+ # Spec Drift
2
+
3
+ `specs/vrchat-openapi.yaml` is the **community** OpenAPI description
4
+ ([vrchatapi/specification](https://github.com/vrchatapi/specification)), not one VRChat
5
+ publishes. It is reverse-engineered and maintained by volunteers, so it drifts from the live API
6
+ continuously and permanently. That is expected, not a bug in the spec.
7
+
8
+ **The live API is the source of truth — not the spec, and not this file.** This is a log of
9
+ divergences we have actually observed, with dates and evidence, so nobody re-investigates the same
10
+ thing from scratch.
11
+
12
+ Every entry is a point-in-time observation and can go stale. VRChat may change behavior, the
13
+ community spec may catch up, and a workaround recorded here may become unnecessary or actively
14
+ wrong. **Before making a substantive change that depends on an entry, re-verify it against the
15
+ live API and update the entry with what you found.** An old date is a reason for suspicion, not
16
+ confidence.
17
+
18
+ Add an entry whenever you find a new divergence, including ones you decide not to work around.
19
+ Where a fix is needed, prefer `scripts/postprocess-schemas.ts` over editing the spec:
20
+ `specs/vrchat-openapi.yaml` is gitignored, so spec edits are local-only and vanish on a fresh
21
+ clone.
22
+
23
+ ---
24
+
25
+ ## Confirmed divergences
26
+
27
+ ### `GroupPermissions` enum is incomplete
28
+ *Observed 2026-08-02, not re-verified since · patched in `postprocess-schemas.ts`*
29
+
30
+ Spec lists 25 values. `GET /groups/{groupId}/permissions` returns **27**, the extras being
31
+ `group-instance-announcement-create` and `group-instance-bypass-avatar-performance`. Any role
32
+ update using them failed zod validation before reaching VRChat.
33
+
34
+ ### `Group.transferTargetId` is nullable
35
+ *Observed 2026-08-02, not re-verified since · patched in `postprocess-schemas.ts`*
36
+
37
+ Spec types it as a non-null `UserID`. Groups with no pending ownership transfer return `null`,
38
+ which failed the whole `getGroup` parse and took `vrchat_group_profile` down with it.
39
+
40
+ ### `InviteRequest.instanceId` needs the worldId prefix
41
+ *Observed 2026-08-02, not re-verified since · handled in `services/invites/curated.ts`*
42
+
43
+ The spec describes `InstanceID` as the bare instance part
44
+ (`12345~hidden(usr_…)~region(eu)`). `POST /invite/{userId}` rejects that form with
45
+ `400: Invalid location` and requires the **full** `worldId:instanceId~…` string.
46
+
47
+ Verified live: full string → `200`, worldId stripped → `400`.
48
+
49
+ ### `CreateInstanceRequest.canRequestInvite` is restricted to private instances
50
+ *Observed 2026-09-01 · handled in `services/instances/curated.ts`*
51
+
52
+ The community schema permits `canRequestInvite` for every instance type. The live API accepts
53
+ `true` only when `type` is `private`; a group instance returns
54
+ `400: Cannot add canRequestInvite to non-private instances.` The same group instance succeeds
55
+ when the field is omitted or set to `false`.
56
+
57
+ ### Single calendar events report `occurrenceKind: "single"`
58
+ *Observed 2026-09-01 · handled in `services/events/curated.ts`*
59
+
60
+ The generated `CalendarEvent` schema does not declare `occurrenceKind`. The live group calendar
61
+ event endpoint reports `"single"` for a confirmed non-recurring event, while the curated delete
62
+ tool deliberately exposes the more explicit `targetKind: "single_event"` safety value.
63
+
64
+ ### Role permissions have undocumented prerequisites
65
+ *Observed 2026-08-02, not re-verified since*
66
+
67
+ Not a schema issue — the API enforces dependencies the spec never mentions.
68
+ `group-members-remove` and `group-bans-manage` both require `group-members-manage` on the same
69
+ role, otherwise `PUT /groups/{groupId}/roles/{roleId}` returns
70
+ `400: Role missing required permissions: group-members-manage`.
71
+
72
+ ### `UserStatus` has no color mapping
73
+ *Observed 2026-08-02, not re-verified since*
74
+
75
+ The spec defines the enum but says nothing about colors, because they are a client-UI concept.
76
+ For reference: **Join Me = blue, Active = green, Ask Me = orange, Busy = red.**
77
+
78
+ ### Existing instances can be linked to calendar events with an undocumented `PUT`
79
+ *Observed and re-verified 2026-08-31 · handled by the curated instance linker*
80
+
81
+ The community spec has no update operation for an existing instance. The live API accepts
82
+ `PUT /instances/{worldId}:{instanceId}` with `{"calendarEntryId":"cal_..."}` and returns the
83
+ updated `Instance`. `PATCH` on the same path returned `405`.
84
+
85
+ VRChat returned `400` when the event was outside its link window, with the rule that an event
86
+ must start within six hours or have ended within the previous six hours. Moving the test event
87
+ inside that window made the same `PUT` return `200`. Sending `{"calendarEntryId":null}` also
88
+ returned `200` and removed the link, but the curated tool intentionally exposes linking only.
89
+
90
+ Verified live with an owned disposable test group, event, and group-only instance. No invitation
91
+ or announcement was sent. Because the endpoint is missing from the spec, `core/client.ts` carries
92
+ a narrow operation fallback until the community spec catches up. Raw access is blocked and the
93
+ curated tool sends only `calendarEntryId` after checking the configured group allowlist and both
94
+ objects' ownership.
95
+
96
+ ---
97
+
98
+ ## Suspected, not yet verified
99
+
100
+ ### `UpdateAvatarRequest.description` minLength
101
+ Spec declares `minLength: 1`, which would make an empty description an invalid request and leave
102
+ no way to clear one. BASIC reports descriptions are effectively optional in practice, so
103
+ `vrchat_avatar_update` deliberately does **not** enforce a minimum and lets VRChat decide. If a
104
+ `description: ""` write is ever seen failing, record the result here and add the constraint.