@basicbit/vrchat-mcp 0.1.0

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 (230) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +266 -0
  4. package/assets/logo.svg +39 -0
  5. package/dist/bin/cli.js +4 -0
  6. package/dist/bin/cli.js.map +1 -0
  7. package/dist/package.json +109 -0
  8. package/dist/src/auth/cookieStore.js +136 -0
  9. package/dist/src/auth/cookieStore.js.map +1 -0
  10. package/dist/src/auth/index.js +398 -0
  11. package/dist/src/auth/index.js.map +1 -0
  12. package/dist/src/config/defaults.json +58 -0
  13. package/dist/src/config/index.js +405 -0
  14. package/dist/src/config/index.js.map +1 -0
  15. package/dist/src/core/client.js +262 -0
  16. package/dist/src/core/client.js.map +1 -0
  17. package/dist/src/core/generatedToolOverrides.js +104 -0
  18. package/dist/src/core/generatedToolOverrides.js.map +1 -0
  19. package/dist/src/core/generatedToolSkips.js +28 -0
  20. package/dist/src/core/generatedToolSkips.js.map +1 -0
  21. package/dist/src/core/operationSchemas.js +102 -0
  22. package/dist/src/core/operationSchemas.js.map +1 -0
  23. package/dist/src/core/readToolRegistry.js +93 -0
  24. package/dist/src/core/readToolRegistry.js.map +1 -0
  25. package/dist/src/core/readTools.js +197 -0
  26. package/dist/src/core/readTools.js.map +1 -0
  27. package/dist/src/core/spec.js +137 -0
  28. package/dist/src/core/spec.js.map +1 -0
  29. package/dist/src/core/writeToolRegistry.js +113 -0
  30. package/dist/src/core/writeToolRegistry.js.map +1 -0
  31. package/dist/src/generated/vrchat-schemas.js +3326 -0
  32. package/dist/src/generated/vrchat-schemas.js.map +1 -0
  33. package/dist/src/index.js +31 -0
  34. package/dist/src/index.js.map +1 -0
  35. package/dist/src/infra/logger.js +26 -0
  36. package/dist/src/infra/logger.js.map +1 -0
  37. package/dist/src/models/avatars.js +22 -0
  38. package/dist/src/models/avatars.js.map +1 -0
  39. package/dist/src/models/events.js +115 -0
  40. package/dist/src/models/events.js.map +1 -0
  41. package/dist/src/models/friends.js +148 -0
  42. package/dist/src/models/friends.js.map +1 -0
  43. package/dist/src/models/groups.js +228 -0
  44. package/dist/src/models/groups.js.map +1 -0
  45. package/dist/src/models/instances.js +56 -0
  46. package/dist/src/models/instances.js.map +1 -0
  47. package/dist/src/models/invites.js +34 -0
  48. package/dist/src/models/invites.js.map +1 -0
  49. package/dist/src/models/notifications.js +62 -0
  50. package/dist/src/models/notifications.js.map +1 -0
  51. package/dist/src/models/status.js +50 -0
  52. package/dist/src/models/status.js.map +1 -0
  53. package/dist/src/models/statusPage.js +84 -0
  54. package/dist/src/models/statusPage.js.map +1 -0
  55. package/dist/src/models/users.js +113 -0
  56. package/dist/src/models/users.js.map +1 -0
  57. package/dist/src/models/vrcx.js +144 -0
  58. package/dist/src/models/vrcx.js.map +1 -0
  59. package/dist/src/models/worlds.js +150 -0
  60. package/dist/src/models/worlds.js.map +1 -0
  61. package/dist/src/resources/friendsChanges.js +44 -0
  62. package/dist/src/resources/friendsChanges.js.map +1 -0
  63. package/dist/src/resources/friendsSnapshot.js +69 -0
  64. package/dist/src/resources/friendsSnapshot.js.map +1 -0
  65. package/dist/src/resources/index.js +9 -0
  66. package/dist/src/resources/index.js.map +1 -0
  67. package/dist/src/resources/subscriptions.js +39 -0
  68. package/dist/src/resources/subscriptions.js.map +1 -0
  69. package/dist/src/schemas/auth.js +3 -0
  70. package/dist/src/schemas/auth.js.map +1 -0
  71. package/dist/src/schemas/call.js +13 -0
  72. package/dist/src/schemas/call.js.map +1 -0
  73. package/dist/src/schemas/read.js +58 -0
  74. package/dist/src/schemas/read.js.map +1 -0
  75. package/dist/src/schemas/write.js +18 -0
  76. package/dist/src/schemas/write.js.map +1 -0
  77. package/dist/src/services/api/client.js +66 -0
  78. package/dist/src/services/api/client.js.map +1 -0
  79. package/dist/src/services/api/index.js +2 -0
  80. package/dist/src/services/api/index.js.map +1 -0
  81. package/dist/src/services/avatars/index.js +24 -0
  82. package/dist/src/services/avatars/index.js.map +1 -0
  83. package/dist/src/services/cache.js +158 -0
  84. package/dist/src/services/cache.js.map +1 -0
  85. package/dist/src/services/events/curated.js +229 -0
  86. package/dist/src/services/events/curated.js.map +1 -0
  87. package/dist/src/services/events/index.js +3 -0
  88. package/dist/src/services/events/index.js.map +1 -0
  89. package/dist/src/services/events/utils.js +75 -0
  90. package/dist/src/services/events/utils.js.map +1 -0
  91. package/dist/src/services/friends/changes.js +246 -0
  92. package/dist/src/services/friends/changes.js.map +1 -0
  93. package/dist/src/services/friends/curated.js +497 -0
  94. package/dist/src/services/friends/curated.js.map +1 -0
  95. package/dist/src/services/friends/fetch.js +69 -0
  96. package/dist/src/services/friends/fetch.js.map +1 -0
  97. package/dist/src/services/friends/index.js +5 -0
  98. package/dist/src/services/friends/index.js.map +1 -0
  99. package/dist/src/services/friends/match.js +98 -0
  100. package/dist/src/services/friends/match.js.map +1 -0
  101. package/dist/src/services/groups/allowlist.js +19 -0
  102. package/dist/src/services/groups/allowlist.js.map +1 -0
  103. package/dist/src/services/groups/curated.js +343 -0
  104. package/dist/src/services/groups/curated.js.map +1 -0
  105. package/dist/src/services/groups/index.js +3 -0
  106. package/dist/src/services/groups/index.js.map +1 -0
  107. package/dist/src/services/instances/curated.js +80 -0
  108. package/dist/src/services/instances/curated.js.map +1 -0
  109. package/dist/src/services/instances/index.js +3 -0
  110. package/dist/src/services/instances/index.js.map +1 -0
  111. package/dist/src/services/instances/read.js +14 -0
  112. package/dist/src/services/instances/read.js.map +1 -0
  113. package/dist/src/services/invites/curated.js +90 -0
  114. package/dist/src/services/invites/curated.js.map +1 -0
  115. package/dist/src/services/invites/index.js +2 -0
  116. package/dist/src/services/invites/index.js.map +1 -0
  117. package/dist/src/services/notifications/curated.js +54 -0
  118. package/dist/src/services/notifications/curated.js.map +1 -0
  119. package/dist/src/services/notifications/index.js +2 -0
  120. package/dist/src/services/notifications/index.js.map +1 -0
  121. package/dist/src/services/pipeline/events.js +40 -0
  122. package/dist/src/services/pipeline/events.js.map +1 -0
  123. package/dist/src/services/pipeline/handlers.js +21 -0
  124. package/dist/src/services/pipeline/handlers.js.map +1 -0
  125. package/dist/src/services/pipeline/index.js +4 -0
  126. package/dist/src/services/pipeline/index.js.map +1 -0
  127. package/dist/src/services/pipeline/manager.js +143 -0
  128. package/dist/src/services/pipeline/manager.js.map +1 -0
  129. package/dist/src/services/status/curated.js +53 -0
  130. package/dist/src/services/status/curated.js.map +1 -0
  131. package/dist/src/services/status/index.js +2 -0
  132. package/dist/src/services/status/index.js.map +1 -0
  133. package/dist/src/services/statusPage/curated.js +397 -0
  134. package/dist/src/services/statusPage/curated.js.map +1 -0
  135. package/dist/src/services/statusPage/index.js +2 -0
  136. package/dist/src/services/statusPage/index.js.map +1 -0
  137. package/dist/src/services/users/curated.js +124 -0
  138. package/dist/src/services/users/curated.js.map +1 -0
  139. package/dist/src/services/users/groups.js +54 -0
  140. package/dist/src/services/users/groups.js.map +1 -0
  141. package/dist/src/services/users/index.js +3 -0
  142. package/dist/src/services/users/index.js.map +1 -0
  143. package/dist/src/services/vrcx/db.js +13 -0
  144. package/dist/src/services/vrcx/db.js.map +1 -0
  145. package/dist/src/services/vrcx/gamelog.js +125 -0
  146. package/dist/src/services/vrcx/gamelog.js.map +1 -0
  147. package/dist/src/services/vrcx/index.js +5 -0
  148. package/dist/src/services/vrcx/index.js.map +1 -0
  149. package/dist/src/services/vrcx/memos.js +51 -0
  150. package/dist/src/services/vrcx/memos.js.map +1 -0
  151. package/dist/src/services/vrcx/paths.js +133 -0
  152. package/dist/src/services/vrcx/paths.js.map +1 -0
  153. package/dist/src/services/vrcx/relationships.js +138 -0
  154. package/dist/src/services/vrcx/relationships.js.map +1 -0
  155. package/dist/src/services/vrcx/shared.js +173 -0
  156. package/dist/src/services/vrcx/shared.js.map +1 -0
  157. package/dist/src/services/vrcx/status.js +54 -0
  158. package/dist/src/services/vrcx/status.js.map +1 -0
  159. package/dist/src/services/worlds/index.js +256 -0
  160. package/dist/src/services/worlds/index.js.map +1 -0
  161. package/dist/src/tools/auth.js +44 -0
  162. package/dist/src/tools/auth.js.map +1 -0
  163. package/dist/src/tools/cache.js +61 -0
  164. package/dist/src/tools/cache.js.map +1 -0
  165. package/dist/src/tools/curated/avatars.js +57 -0
  166. package/dist/src/tools/curated/avatars.js.map +1 -0
  167. package/dist/src/tools/curated/events.js +212 -0
  168. package/dist/src/tools/curated/events.js.map +1 -0
  169. package/dist/src/tools/curated/friends.js +168 -0
  170. package/dist/src/tools/curated/friends.js.map +1 -0
  171. package/dist/src/tools/curated/groups.js +368 -0
  172. package/dist/src/tools/curated/groups.js.map +1 -0
  173. package/dist/src/tools/curated/instances.js +35 -0
  174. package/dist/src/tools/curated/instances.js.map +1 -0
  175. package/dist/src/tools/curated/invites.js +78 -0
  176. package/dist/src/tools/curated/invites.js.map +1 -0
  177. package/dist/src/tools/curated/notifications.js +35 -0
  178. package/dist/src/tools/curated/notifications.js.map +1 -0
  179. package/dist/src/tools/curated/status.js +44 -0
  180. package/dist/src/tools/curated/status.js.map +1 -0
  181. package/dist/src/tools/curated/statusPage.js +27 -0
  182. package/dist/src/tools/curated/statusPage.js.map +1 -0
  183. package/dist/src/tools/curated/users.js +201 -0
  184. package/dist/src/tools/curated/users.js.map +1 -0
  185. package/dist/src/tools/curated/vrcx/gamelog.js +80 -0
  186. package/dist/src/tools/curated/vrcx/gamelog.js.map +1 -0
  187. package/dist/src/tools/curated/vrcx/index.js +11 -0
  188. package/dist/src/tools/curated/vrcx/index.js.map +1 -0
  189. package/dist/src/tools/curated/vrcx/memos.js +105 -0
  190. package/dist/src/tools/curated/vrcx/memos.js.map +1 -0
  191. package/dist/src/tools/curated/vrcx/relationships.js +86 -0
  192. package/dist/src/tools/curated/vrcx/relationships.js.map +1 -0
  193. package/dist/src/tools/curated/vrcx/status.js +43 -0
  194. package/dist/src/tools/curated/vrcx/status.js.map +1 -0
  195. package/dist/src/tools/curated/worlds.js +162 -0
  196. package/dist/src/tools/curated/worlds.js.map +1 -0
  197. package/dist/src/tools/generated.js +19 -0
  198. package/dist/src/tools/generated.js.map +1 -0
  199. package/dist/src/tools/raw.js +49 -0
  200. package/dist/src/tools/raw.js.map +1 -0
  201. package/dist/src/tools/read/common.js +58 -0
  202. package/dist/src/tools/read/common.js.map +1 -0
  203. package/dist/src/tools/read/system.js +19 -0
  204. package/dist/src/tools/read/system.js.map +1 -0
  205. package/dist/src/tools/registerAllTools.js +41 -0
  206. package/dist/src/tools/registerAllTools.js.map +1 -0
  207. package/dist/src/tools/write/common.js +10 -0
  208. package/dist/src/tools/write/common.js.map +1 -0
  209. package/dist/src/utils/json.js +53 -0
  210. package/dist/src/utils/json.js.map +1 -0
  211. package/dist/src/utils/schema.js +14 -0
  212. package/dist/src/utils/schema.js.map +1 -0
  213. package/dist/src/utils/stableStringify.js +36 -0
  214. package/dist/src/utils/stableStringify.js.map +1 -0
  215. package/dist/src/utils/strings.js +7 -0
  216. package/dist/src/utils/strings.js.map +1 -0
  217. package/dist/src/utils/toolAnnotations.js +17 -0
  218. package/dist/src/utils/toolAnnotations.js.map +1 -0
  219. package/dist/src/utils/toolNames.js +13 -0
  220. package/dist/src/utils/toolNames.js.map +1 -0
  221. package/dist/src/utils/toolResponses.js +12 -0
  222. package/dist/src/utils/toolResponses.js.map +1 -0
  223. package/docs/architecture.md +46 -0
  224. package/docs/curated-tools.md +132 -0
  225. package/docs/design-notes.md +48 -0
  226. package/docs/evals.md +129 -0
  227. package/docs/tools-guide.md +27 -0
  228. package/docs/tools.md +13849 -0
  229. package/docs/vrcx.md +69 -0
  230. package/package.json +109 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-05-15
4
+
5
+ Initial public release.
6
+
7
+ - MCP tools for VRChat friends, worlds, groups, events, notifications, status, avatars, and VRCX history.
8
+ - Local browser login flow with configurable cookie storage.
9
+ - Read-only defaults with explicit write opt-in and group write allowlists.
10
+ - Curated tools for common workflows plus generated tools from the VRChat OpenAPI spec.
11
+ - Local resources for friend snapshots and friend change deltas.
12
+ - Mock E2E coverage, opt-in live smoke checks, and generated tool catalog docs.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 BASICBIT
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,266 @@
1
+ <p align="center">
2
+ <img src="assets/logo.svg" alt="VRChat MCP logo" width="112" height="112" />
3
+ </p>
4
+
5
+ # VRChat MCP
6
+
7
+ MCP tools for VRChat friends, worlds, groups, events, notifications, and VRCX history.
8
+
9
+ VRChat MCP is an unofficial [Model Context Protocol](https://modelcontextprotocol.io/) server for VRChat. It works with Claude Desktop, OpenCode, and other MCP clients.
10
+
11
+ The server is read-only by default. Writes require an explicit config change. Authentication cookies stay on your machine.
12
+
13
+ This project is unofficial and is not affiliated with VRChat Inc.
14
+
15
+ ## What It Can Do
16
+
17
+ - List online friends and their locations.
18
+ - Show your current VRChat status and location.
19
+ - Look up a friend by display name.
20
+ - Search worlds and return IDs for follow-up calls.
21
+ - List public and group calendar events.
22
+ - Summarize active group instances.
23
+ - Read recent notifications.
24
+ - Read local VRCX memos and recent world visit history, if VRCX is installed.
25
+ - Invite yourself to a known instance, or invite a friend, after writes are enabled.
26
+
27
+ ## Write Controls
28
+
29
+ - Read-only by default.
30
+ - Write tools require explicit opt-in with `writes.allow = true` or `VRCHAT_MCP_ALLOW_WRITES=true`.
31
+ - Group write tools can be limited to specific group IDs with `groups.allowlist`.
32
+ - Authentication cookies can stay in memory, in a local file, or in the OS keychain.
33
+ - Logs go to stderr so stdout remains reserved for MCP protocol messages.
34
+ - Live tests, live smoke checks, and LLM evals are opt-in and use local fixture files.
35
+
36
+ ## Quick Start
37
+
38
+ Requirements:
39
+
40
+ - Node.js 22.19.0 or newer.
41
+ - An MCP client such as Claude Desktop, OpenCode, or another MCP-compatible host.
42
+
43
+ Run from npm:
44
+
45
+ ```bash
46
+ npx -y @basicbit/vrchat-mcp
47
+ ```
48
+
49
+ Install from source:
50
+
51
+ ```bash
52
+ git clone https://github.com/BASIC-BIT/vrchat-mcp.git
53
+ cd vrchat-mcp
54
+ npm install
55
+ npm run build
56
+ ```
57
+
58
+ Authentication depends on how you run the server.
59
+
60
+ For the npm package, add the server to your MCP client first, then call the `vrchat_auth_begin` tool from that client. It returns a local browser login URL and keeps cookies according to your config.
61
+
62
+ For a source checkout, you can use the local harness:
63
+
64
+ ```bash
65
+ npm run mcp:login
66
+ ```
67
+
68
+ The login helper opens a local browser flow and stores cookies according to your config. The default development helper uses file-backed cookie storage so subsequent MCP sessions can reuse the login.
69
+
70
+ ## MCP Client Config
71
+
72
+ Use the npm package for day-to-day use and set a descriptive VRChat API user agent.
73
+
74
+ For the npm package:
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "vrchat": {
80
+ "command": "npx",
81
+ "args": ["-y", "@basicbit/vrchat-mcp"],
82
+ "env": {
83
+ "VRCHAT_MCP_USER_AGENT": "your-name (email@example.com)",
84
+ "VRCHAT_MCP_COOKIE_STORE": "file"
85
+ }
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ For a source checkout, replace the path with your local checkout:
92
+
93
+ ```json
94
+ {
95
+ "mcpServers": {
96
+ "vrchat": {
97
+ "command": "node",
98
+ "args": ["<ABS_PATH_TO_REPO>/dist/bin/cli.js"],
99
+ "env": {
100
+ "VRCHAT_MCP_USER_AGENT": "your-name (email@example.com)",
101
+ "VRCHAT_MCP_COOKIE_STORE": "file"
102
+ }
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ For active development, you can point your MCP client at the TypeScript entrypoint instead:
109
+
110
+ ```json
111
+ {
112
+ "mcpServers": {
113
+ "vrchat-dev": {
114
+ "command": "npx",
115
+ "args": ["tsx", "<ABS_PATH_TO_REPO>/src/index.ts"],
116
+ "env": {
117
+ "VRCHAT_MCP_USER_AGENT": "your-name (email@example.com)",
118
+ "VRCHAT_MCP_COOKIE_STORE": "file"
119
+ }
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ ## Configuration
126
+
127
+ Defaults live in `src/config/defaults.json`. To override them, create a JSON config file and point to it with `VRCHAT_MCP_CONFIG_FILE`.
128
+
129
+ Example `vrchat-mcp.config.json`:
130
+
131
+ ```json
132
+ {
133
+ "api": { "userAgent": "your-name (email@example.com)" },
134
+ "auth": { "cookieStore": "file" },
135
+ "writes": { "allow": false },
136
+ "groups": { "allowlist": ["grp_abc123"] },
137
+ "cache": { "enabled": true }
138
+ }
139
+ ```
140
+
141
+ Environment variables override the config file when set.
142
+
143
+ Common environment variables:
144
+
145
+ - `VRCHAT_MCP_CONFIG_FILE`: path to a JSON config file.
146
+ - `VRCHAT_MCP_USER_AGENT`: descriptive user agent sent to the VRChat API. Include contact information when possible.
147
+ - `VRCHAT_MCP_API_BASE`: override the API base URL. Defaults to `https://api.vrchat.cloud/api/1`.
148
+ - `VRCHAT_MCP_SPEC_URL`: OpenAPI spec URL or local path. Supports `file:` and relative paths.
149
+ - `VRCHAT_MCP_LOG_LEVEL`: `debug`, `info`, `warn`, or `error`.
150
+ - `VRCHAT_MCP_COOKIE_STORE`: `memory`, `file`, or `keychain`.
151
+ - `VRCHAT_MCP_COOKIE_FILE`: cookie file path when `VRCHAT_MCP_COOKIE_STORE=file`.
152
+ - `VRCHAT_MCP_ALLOW_WRITES`: enable non-GET operations.
153
+ - `VRCHAT_MCP_GROUP_ALLOWLIST`: comma-separated list of group IDs permitted for group write actions.
154
+ - `VRCHAT_MCP_ENABLE_RAW_CALL`: enable the raw `vrchat_call` tool. Disabled by default.
155
+ - `VRCHAT_MCP_DISABLE_GENERATED_READ_TOOLS`: disable auto-generated read tools.
156
+ - `VRCHAT_MCP_DISABLE_GENERATED_WRITE_TOOLS`: disable auto-generated write tools.
157
+
158
+ Cache and realtime pipeline tuning are configured in JSON. See `src/config/defaults.json` for the full set of defaults.
159
+
160
+ ## Tool Surface
161
+
162
+ VRChat MCP exposes three layers:
163
+
164
+ - Curated tools for common tasks, such as `vrchat_me`, `vrchat_friends_search`, `vrchat_friend_details`, `vrchat_worlds_search`, `vrchat_group_profile`, `vrchat_events_upcoming`, and `vrchat_notifications_recent`.
165
+ - Auto-generated read tools named `vrchat_read_<operationId>` for GET operations from the VRChat OpenAPI spec.
166
+ - Auto-generated write tools named `vrchat_write_<operationId>` for non-GET operations. These remain gated by the write configuration.
167
+
168
+ Local-only tools and resources include:
169
+
170
+ - `vrchat_auth_begin`, `vrchat_auth_status`, and `vrchat_auth_logout` for local authentication.
171
+ - `vrchat_cache_invalidate` for MCP-local cache control.
172
+ - `vrchat://friends/changes{?after,limit}` for friend change deltas.
173
+ - `vrchat://friends/snapshot{?includeOffline,pageSize,maxPages}` for friend snapshots.
174
+
175
+ The generated catalog lives in `docs/tools.md`. The shorter usage guide lives in `docs/tools-guide.md`.
176
+
177
+ ## Optional Swagger UI
178
+
179
+ If you want a Swagger UI and OpenAPI-style proxy for the MCP tools, use `mcpo`:
180
+
181
+ ```bash
182
+ uvx mcpo --port 8000 --api-key "top-secret" -- node <ABS_PATH_TO_REPO>/dist/bin/cli.js
183
+ ```
184
+
185
+ Then open `http://localhost:8000/docs`.
186
+
187
+ You can also run config mode against an MCP client config file:
188
+
189
+ ```bash
190
+ mcpo --config <PATH_TO_MCP_CONFIG.json> --hot-reload --api-key "top-secret"
191
+ ```
192
+
193
+ Each MCP server is exposed under its own route, such as `http://localhost:8000/vrchat`, with Swagger UI at `http://localhost:8000/vrchat/docs`.
194
+
195
+ ## Development
196
+
197
+ Useful scripts:
198
+
199
+ - `npm run dev`: run `src/index.ts` through `tsx`.
200
+ - `npm run build`: type-check and emit to `dist/`.
201
+ - `npm run start`: run the built server from `dist/`.
202
+ - `npm run lint`: run ESLint.
203
+ - `npm run typecheck`: type-check without emit.
204
+ - `npm test`: run Vitest.
205
+ - `npm run check`: lint, type-check, and test.
206
+ - `npm run mcp:login`: launch the local login helper.
207
+ - `npm run mcp:status`: check local auth status through the harness.
208
+ - `npm run mcp:logout`: clear the local auth session through the harness.
209
+ - `npm run smoke:live`: run the opt-in read-only live smoke matrix against the built server.
210
+ - `npm run generate:tools-docs`: regenerate `docs/tools.md`.
211
+ - `npm run generate:schemas`: regenerate `src/generated/vrchat-schemas.ts` from the VRChat OpenAPI spec.
212
+ - `npm run generate:test-schemas`: regenerate mock test schemas.
213
+
214
+ Project layout:
215
+
216
+ - `src/index.ts`: server bootstrap.
217
+ - `src/core/`: VRChat API plumbing, spec parsing, request dispatch, and read helpers.
218
+ - `src/tools/`: MCP tool registration.
219
+ - `src/schemas/`: shared Zod schemas for tool inputs and outputs.
220
+ - `src/generated/`: generated OpenAPI Zod schemas.
221
+ - `src/services/`: domain services for auth, cache, friends, worlds, groups, VRCX, and more.
222
+ - `src/resources/`: MCP resources for snapshots and delta feeds.
223
+ - `src/auth/`: local login flow and cookie storage.
224
+ - `src/infra/`: logging and infrastructure utilities.
225
+ - `src/utils/`: lightweight shared helpers.
226
+ - `docs/`: architecture, tool inventory, evals, and design notes.
227
+
228
+ ## Testing And Evals
229
+
230
+ Local checks:
231
+
232
+ ```bash
233
+ npm run check
234
+ ```
235
+
236
+ Read-only live smoke checks are opt-in:
237
+
238
+ ```bash
239
+ npm run build
240
+ npm run mcp:login
241
+ npm run smoke:live
242
+ ```
243
+
244
+ Live E2E and LLM evals use gitignored local fixture files:
245
+
246
+ - `test/fixtures/e2e.live.json`
247
+ - `test/fixtures/evals.live.json`
248
+
249
+ Prefer keeping live fixture files outside the repository and pointing to them with `VRCHAT_MCP_LIVE_CONFIG_FILE` and `VRCHAT_MCP_EVAL_CONFIG_FILE`. Store LLM API keys in environment variables or a secret manager, not in JSON files.
250
+
251
+ See `docs/evals.md` for the repeatable smoke, LLM, and manual MCP client evaluation workflow.
252
+
253
+ ## Documentation
254
+
255
+ - `docs/tools.md`: generated tool catalog with schemas.
256
+ - `docs/tools-guide.md`: short human guide for the tool surface.
257
+ - `docs/architecture.md`: codebase overview and data flow.
258
+ - `docs/curated-tools.md`: curated tool charter and risk tiers.
259
+ - `docs/evals.md`: smoke, LLM, and manual MCP client eval workflow.
260
+ - [`docs/public-launch-plan.md`](https://github.com/BASIC-BIT/vrchat-mcp/blob/main/docs/public-launch-plan.md): release awareness, registry, and launch-channel plan.
261
+ - `docs/vrcx.md`: local VRCX integration notes.
262
+ - `docs/design-notes.md`: archived design notes and future-facing ideas.
263
+
264
+ ## License
265
+
266
+ MIT. See `LICENSE`.
@@ -0,0 +1,39 @@
1
+ <svg
2
+ xmlns="http://www.w3.org/2000/svg"
3
+ viewBox="0 0 256 256"
4
+ role="img"
5
+ aria-labelledby="title desc"
6
+ >
7
+ <title id="title">VRChat MCP logo</title>
8
+ <desc id="desc">A rounded blue chat mark connected to three small nodes.</desc>
9
+ <defs>
10
+ <linearGradient id="bg" x1="32" y1="24" x2="224" y2="232" gradientUnits="userSpaceOnUse">
11
+ <stop offset="0" stop-color="#38bdf8" />
12
+ <stop offset="0.52" stop-color="#2563eb" />
13
+ <stop offset="1" stop-color="#312e81" />
14
+ </linearGradient>
15
+ <linearGradient id="line" x1="76" y1="86" x2="184" y2="170" gradientUnits="userSpaceOnUse">
16
+ <stop offset="0" stop-color="#e0f2fe" />
17
+ <stop offset="1" stop-color="#bfdbfe" />
18
+ </linearGradient>
19
+ </defs>
20
+ <rect width="256" height="256" rx="56" fill="#07111f" />
21
+ <path
22
+ d="M68 56h120c22.1 0 40 17.9 40 40v52c0 22.1-17.9 40-40 40h-47.5l-43 31.4c-8.7 6.3-20.9.1-20.9-10.7V188H68c-22.1 0-40-17.9-40-40V96c0-22.1 17.9-40 40-40Z"
23
+ fill="url(#bg)"
24
+ />
25
+ <path
26
+ d="M84 116h42.5M126.5 116l37-30M126.5 116l45.5 44"
27
+ fill="none"
28
+ stroke="url(#line)"
29
+ stroke-width="12"
30
+ stroke-linecap="round"
31
+ stroke-linejoin="round"
32
+ />
33
+ <circle cx="84" cy="116" r="18" fill="#f8fafc" />
34
+ <circle cx="164" cy="86" r="18" fill="#f8fafc" />
35
+ <circle cx="172" cy="160" r="18" fill="#f8fafc" />
36
+ <circle cx="84" cy="116" r="7" fill="#2563eb" />
37
+ <circle cx="164" cy="86" r="7" fill="#2563eb" />
38
+ <circle cx="172" cy="160" r="7" fill="#2563eb" />
39
+ </svg>
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // CLI entry: starts the stdio MCP server.
3
+ import '../src/index.js';
4
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":";AACA,0CAA0C;AAC1C,OAAO,iBAAiB,CAAC"}
@@ -0,0 +1,109 @@
1
+ {
2
+ "name": "@basicbit/vrchat-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for VRChat friends, worlds, groups, events, notifications, and VRCX history.",
5
+ "type": "module",
6
+ "main": "dist/src/index.js",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "bin": {
11
+ "vrchat-mcp": "dist/bin/cli.js"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "assets/logo.svg",
16
+ "docs/architecture.md",
17
+ "docs/curated-tools.md",
18
+ "docs/design-notes.md",
19
+ "docs/evals.md",
20
+ "docs/tools-guide.md",
21
+ "docs/tools.md",
22
+ "docs/vrcx.md",
23
+ "README.md",
24
+ "CHANGELOG.md",
25
+ "LICENSE"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.json",
29
+ "typecheck": "tsc -p tsconfig.json --noEmit",
30
+ "dev": "tsx src/index.ts",
31
+ "lint": "eslint . --ext .ts,.tsx,.mts --max-warnings=0",
32
+ "lint:fix": "npm run lint -- --fix",
33
+ "format": "prettier --write .",
34
+ "format:check": "prettier --check .",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest",
37
+ "test:coverage": "vitest run --coverage",
38
+ "test:e2e": "vitest run test/e2e",
39
+ "test:e2e:live": "vitest run test/e2e/live.test.ts",
40
+ "test:evals": "vitest run test/evals",
41
+ "smoke:live": "tsx scripts/live-smoke.ts",
42
+ "check": "npm run lint && npm run typecheck && npm test",
43
+ "mcp:login": "tsx scripts/mcp-client.ts login",
44
+ "mcp:status": "tsx scripts/mcp-client.ts status",
45
+ "mcp:logout": "tsx scripts/mcp-client.ts logout",
46
+ "mcp:list-tools": "tsx scripts/mcp-client.ts list-tools",
47
+ "mcp:call": "tsx scripts/mcp-client.ts call",
48
+ "generate:schemas": "openapi-zod-client specs/vrchat-openapi.yaml -o src/generated/vrchat-schemas.ts -t scripts/templates/openapi-schemas.hbs --export-schemas -p .prettierrc.json && tsx scripts/postprocess-schemas.ts src/generated/vrchat-schemas.ts",
49
+ "generate:test-schemas": "openapi-zod-client test/fixtures/spec.yaml -o test/generated/mock-schemas.ts -t scripts/templates/openapi-schemas.hbs --export-schemas -p .prettierrc.json && tsx scripts/postprocess-schemas.ts test/generated/mock-schemas.ts",
50
+ "generate:tools-docs": "tsx scripts/generate-tools-docs.ts",
51
+ "mcpo": "uvx mcpo --port 8000 --api-key \"top-secret\" -- npx tsx src/index.ts",
52
+ "start": "node dist/bin/cli.js",
53
+ "metrics:scc": "tsx scripts/metrics/scc.ts",
54
+ "metrics:lizard": "tsx scripts/metrics/lizard.ts",
55
+ "metrics": "npm run metrics:scc && npm run metrics:lizard"
56
+ },
57
+ "engines": {
58
+ "node": ">=22.19.0"
59
+ },
60
+ "keywords": [
61
+ "vrchat",
62
+ "mcp",
63
+ "modelcontextprotocol",
64
+ "model-context-protocol",
65
+ "claude",
66
+ "opencode",
67
+ "vrchat-api",
68
+ "vrcx"
69
+ ],
70
+ "repository": {
71
+ "type": "git",
72
+ "url": "git+https://github.com/BASIC-BIT/vrchat-mcp.git"
73
+ },
74
+ "bugs": {
75
+ "url": "https://github.com/BASIC-BIT/vrchat-mcp/issues"
76
+ },
77
+ "homepage": "https://github.com/BASIC-BIT/vrchat-mcp#readme",
78
+ "license": "MIT",
79
+ "devDependencies": {
80
+ "@apidevtools/json-schema-ref-parser": "^15.3.5",
81
+ "@eslint/js": "^10.0.1",
82
+ "@types/better-sqlite3": "^7.6.13",
83
+ "@types/node": "^25.0.3",
84
+ "@typescript-eslint/eslint-plugin": "^8.50.0",
85
+ "@typescript-eslint/parser": "^8.50.0",
86
+ "@vitest/coverage-v8": "^4.0.16",
87
+ "ajv-formats": "^3.0.1",
88
+ "eslint": "^10.3.0",
89
+ "eslint-config-prettier": "^10.1.8",
90
+ "eslint-plugin-sonarjs": "^4.0.3",
91
+ "globals": "^17.6.0",
92
+ "mock-json-schema": "^1.1.2",
93
+ "openapi-backend": "^5.15.0",
94
+ "openapi-zod-client": "^1.18.3",
95
+ "prettier": "^3.7.4",
96
+ "tsx": "^4.21.0",
97
+ "typescript": "^6.0.3",
98
+ "vitest": "^4.0.16"
99
+ },
100
+ "dependencies": {
101
+ "@modelcontextprotocol/sdk": "^1.29.0",
102
+ "better-sqlite3": "^12.6.2",
103
+ "keytar": "^7.9.0",
104
+ "tough-cookie": "^6.0.0",
105
+ "undici": "^8.2.0",
106
+ "yaml": "^2.8.4",
107
+ "zod": "^4.2.1"
108
+ }
109
+ }
@@ -0,0 +1,136 @@
1
+ import { CookieJar } from 'tough-cookie';
2
+ import { promises as fs } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { getConfig } from '../config/index.js';
5
+ import { logger } from '../infra/logger.js';
6
+ async function serializeJar(jar) {
7
+ return await new Promise((resolve, reject) => {
8
+ jar.serialize((err, serialized) => {
9
+ if (err || !serialized)
10
+ return reject(err ?? new Error('Failed to serialize jar'));
11
+ resolve(serialized);
12
+ });
13
+ });
14
+ }
15
+ async function deserializeJar(data) {
16
+ return await new Promise((resolve, reject) => {
17
+ CookieJar.deserialize(data, (err, jar) => {
18
+ if (err || !jar)
19
+ return reject(err ?? new Error('Failed to deserialize jar'));
20
+ resolve(jar);
21
+ });
22
+ });
23
+ }
24
+ class MemoryStore {
25
+ jar = null;
26
+ load() {
27
+ return Promise.resolve(this.jar);
28
+ }
29
+ save(jar) {
30
+ this.jar = jar;
31
+ return Promise.resolve();
32
+ }
33
+ clear() {
34
+ this.jar = null;
35
+ return Promise.resolve();
36
+ }
37
+ }
38
+ class FileStore {
39
+ filePath;
40
+ constructor(filePath) {
41
+ this.filePath = filePath;
42
+ }
43
+ async load() {
44
+ try {
45
+ const content = await fs.readFile(this.filePath, 'utf8');
46
+ const obj = JSON.parse(content);
47
+ return await deserializeJar(obj);
48
+ }
49
+ catch (err) {
50
+ if (err.code === 'ENOENT')
51
+ return null;
52
+ logger.warn('Failed to load cookie file', { message: err.message });
53
+ return null;
54
+ }
55
+ }
56
+ async save(jar) {
57
+ const serialized = await serializeJar(jar);
58
+ await fs.mkdir(path.dirname(this.filePath), { recursive: true });
59
+ await fs.writeFile(this.filePath, JSON.stringify(serialized), {
60
+ encoding: 'utf8',
61
+ mode: 0o600,
62
+ });
63
+ if (process.platform !== 'win32') {
64
+ await fs.chmod(this.filePath, 0o600);
65
+ }
66
+ }
67
+ async clear() {
68
+ try {
69
+ await fs.unlink(this.filePath);
70
+ }
71
+ catch (err) {
72
+ if (err.code !== 'ENOENT') {
73
+ logger.warn('Failed to delete cookie file', { message: err.message });
74
+ }
75
+ }
76
+ }
77
+ }
78
+ class KeychainStore {
79
+ service = 'vrchat-mcp';
80
+ account = 'default';
81
+ keytar = null;
82
+ async ensureKeytar() {
83
+ if (this.keytar)
84
+ return this.keytar;
85
+ try {
86
+ const mod = await import('keytar');
87
+ this.keytar = mod.default ?? mod;
88
+ return this.keytar;
89
+ }
90
+ catch (err) {
91
+ logger.warn('keytar not available; falling back to memory', { message: err.message });
92
+ return null;
93
+ }
94
+ }
95
+ async load() {
96
+ const keytar = await this.ensureKeytar();
97
+ if (!keytar)
98
+ return null;
99
+ const val = await keytar.getPassword(this.service, this.account);
100
+ if (!val)
101
+ return null;
102
+ try {
103
+ const obj = JSON.parse(val);
104
+ return await deserializeJar(obj);
105
+ }
106
+ catch (err) {
107
+ logger.warn('Failed to parse keychain cookie data', { message: err.message });
108
+ return null;
109
+ }
110
+ }
111
+ async save(jar) {
112
+ const keytar = await this.ensureKeytar();
113
+ if (!keytar)
114
+ return;
115
+ const serialized = await serializeJar(jar);
116
+ await keytar.setPassword(this.service, this.account, JSON.stringify(serialized));
117
+ }
118
+ async clear() {
119
+ const keytar = await this.ensureKeytar();
120
+ if (!keytar)
121
+ return;
122
+ await keytar.deletePassword(this.service, this.account);
123
+ }
124
+ }
125
+ export function getCookieStore() {
126
+ const config = getConfig();
127
+ const mode = config.auth.cookieStore.toLowerCase();
128
+ if (mode === 'file') {
129
+ return new FileStore(path.resolve(config.auth.cookieFile));
130
+ }
131
+ if (mode === 'keychain') {
132
+ return new KeychainStore();
133
+ }
134
+ return new MemoryStore();
135
+ }
136
+ //# sourceMappingURL=cookieStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookieStore.js","sourceRoot":"","sources":["../../../src/auth/cookieStore.ts"],"names":[],"mappings":"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"}