@basicbit/vrchat-mcp 0.1.7 → 0.1.9

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 (59) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +57 -10
  3. package/dist/bin/cli.js +1 -1
  4. package/dist/bin/cli.js.map +1 -1
  5. package/dist/package.json +5 -3
  6. package/dist/src/cli.js +78 -0
  7. package/dist/src/cli.js.map +1 -0
  8. package/dist/src/config/defaults.json +7 -0
  9. package/dist/src/config/index.js +51 -0
  10. package/dist/src/config/index.js.map +1 -1
  11. package/dist/src/core/client.js +4 -2
  12. package/dist/src/core/client.js.map +1 -1
  13. package/dist/src/core/generatedToolOverrides.js +3 -0
  14. package/dist/src/core/generatedToolOverrides.js.map +1 -1
  15. package/dist/src/core/retry.js +89 -0
  16. package/dist/src/core/retry.js.map +1 -0
  17. package/dist/src/core/writeToolRegistry.js +20 -0
  18. package/dist/src/core/writeToolRegistry.js.map +1 -1
  19. package/dist/src/index.js +66 -22
  20. package/dist/src/index.js.map +1 -1
  21. package/dist/src/models/groups.js +82 -10
  22. package/dist/src/models/groups.js.map +1 -1
  23. package/dist/src/resources/subscriptions.js +21 -8
  24. package/dist/src/resources/subscriptions.js.map +1 -1
  25. package/dist/src/server.js +42 -0
  26. package/dist/src/server.js.map +1 -0
  27. package/dist/src/services/api/client.js +3 -0
  28. package/dist/src/services/api/client.js.map +1 -1
  29. package/dist/src/services/cache.js +7 -0
  30. package/dist/src/services/cache.js.map +1 -1
  31. package/dist/src/services/groups/curated.js +7 -50
  32. package/dist/src/services/groups/curated.js.map +1 -1
  33. package/dist/src/services/groups/index.js +2 -0
  34. package/dist/src/services/groups/index.js.map +1 -1
  35. package/dist/src/services/groups/members.js +155 -0
  36. package/dist/src/services/groups/members.js.map +1 -0
  37. package/dist/src/services/groups/posts.js +0 -0
  38. package/dist/src/services/groups/posts.js.map +1 -0
  39. package/dist/src/services/groups/roles.js +4 -0
  40. package/dist/src/services/groups/roles.js.map +1 -1
  41. package/dist/src/services/invites/bulk.js +2 -56
  42. package/dist/src/services/invites/bulk.js.map +1 -1
  43. package/dist/src/services/pipeline/handlers.js +4 -4
  44. package/dist/src/services/pipeline/handlers.js.map +1 -1
  45. package/dist/src/tools/curated/groupPosts.js +173 -0
  46. package/dist/src/tools/curated/groupPosts.js.map +1 -0
  47. package/dist/src/tools/curated/groups.js +5 -43
  48. package/dist/src/tools/curated/groups.js.map +1 -1
  49. package/dist/src/tools/registerAllTools.js +2 -0
  50. package/dist/src/tools/registerAllTools.js.map +1 -1
  51. package/dist/src/transports/http.js +343 -0
  52. package/dist/src/transports/http.js.map +1 -0
  53. package/docs/architecture.md +5 -1
  54. package/docs/curated-tools.md +18 -0
  55. package/docs/privacy.md +2 -2
  56. package/docs/tools-guide.md +1 -0
  57. package/docs/tools.md +444 -24
  58. package/package.json +5 -3
  59. package/server.json +2 -2
@@ -0,0 +1,343 @@
1
+ import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
2
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
+ import { localhostHostValidation } from '@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js';
4
+ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
5
+ import express from 'express';
6
+ import { logger } from '../infra/logger.js';
7
+ const HTTP_HOST = '127.0.0.1';
8
+ const MIN_BEARER_TOKEN_LENGTH = 32;
9
+ const DEFAULT_SESSION_IDLE_TIMEOUT_MS = 30 * 60_000;
10
+ function jsonRpcError(res, status, message) {
11
+ res.status(status).json({
12
+ jsonrpc: '2.0',
13
+ error: { code: -32000, message },
14
+ id: null,
15
+ });
16
+ }
17
+ function validateOptions(options) {
18
+ if (!Number.isInteger(options.port) || options.port < 0 || options.port > 65535) {
19
+ throw new Error('HTTP port must be an integer from 0 to 65535.');
20
+ }
21
+ if (options.path.length < 2 ||
22
+ options.path.length > 128 ||
23
+ !/^\/[A-Za-z0-9._~/-]*$/.test(options.path) ||
24
+ /^\/healthz\/?$/i.test(options.path)) {
25
+ throw new Error('HTTP path must be a non-reserved URL path using safe characters.');
26
+ }
27
+ if (options.bearerToken.length < MIN_BEARER_TOKEN_LENGTH) {
28
+ throw new Error(`VRCHAT_MCP_HTTP_BEARER_TOKEN must be at least ${MIN_BEARER_TOKEN_LENGTH} characters.`);
29
+ }
30
+ if (!Number.isInteger(options.maxSessions) || options.maxSessions < 1) {
31
+ throw new Error('HTTP maxSessions must be a positive integer.');
32
+ }
33
+ if (!Number.isInteger(options.rateLimitPerMinute) || options.rateLimitPerMinute < 1) {
34
+ throw new Error('HTTP rateLimitPerMinute must be a positive integer.');
35
+ }
36
+ if (options.sessionIdleTimeoutMs !== undefined &&
37
+ (!Number.isInteger(options.sessionIdleTimeoutMs) || options.sessionIdleTimeoutMs < 1)) {
38
+ throw new Error('HTTP sessionIdleTimeoutMs must be a positive integer.');
39
+ }
40
+ }
41
+ function tokenDigest(value) {
42
+ return createHash('sha256').update(value, 'utf8').digest();
43
+ }
44
+ function bearerTokenMatches(header, expectedDigest) {
45
+ if (!header?.startsWith('Bearer '))
46
+ return false;
47
+ const candidate = header.slice('Bearer '.length);
48
+ return timingSafeEqual(tokenDigest(candidate), expectedDigest);
49
+ }
50
+ function headerValue(value) {
51
+ return Array.isArray(value) ? value[0] : value;
52
+ }
53
+ function isRequestTooLarge(error) {
54
+ if (typeof error !== 'object' || error === null)
55
+ return false;
56
+ return error.status === 413;
57
+ }
58
+ function createRateLimiter(limit) {
59
+ const windows = new Map();
60
+ const windowMs = 60_000;
61
+ return (req, res, next) => {
62
+ const now = Date.now();
63
+ const key = req.ip ?? 'loopback';
64
+ const current = windows.get(key);
65
+ if (!current || now - current.startedAt >= windowMs) {
66
+ windows.set(key, { startedAt: now, count: 1 });
67
+ next();
68
+ return;
69
+ }
70
+ current.count += 1;
71
+ if (current.count > limit) {
72
+ res.setHeader('Retry-After', '60');
73
+ jsonRpcError(res, 429, 'Too many requests');
74
+ return;
75
+ }
76
+ next();
77
+ };
78
+ }
79
+ async function listen(app, port) {
80
+ return await new Promise((resolve, reject) => {
81
+ const server = app.listen(port, HTTP_HOST);
82
+ const onError = (error) => {
83
+ server.off('listening', onListening);
84
+ reject(error);
85
+ };
86
+ const onListening = () => {
87
+ server.off('error', onError);
88
+ resolve(server);
89
+ };
90
+ server.once('error', onError);
91
+ server.once('listening', onListening);
92
+ });
93
+ }
94
+ export async function startHttpTransport(options) {
95
+ validateOptions(options);
96
+ const expectedTokenDigest = tokenDigest(options.bearerToken);
97
+ const sessions = new Map();
98
+ const app = express();
99
+ let boundPort = options.port;
100
+ let closing = false;
101
+ let pendingInitializations = 0;
102
+ const sessionIdleTimeoutMs = options.sessionIdleTimeoutMs ?? DEFAULT_SESSION_IDLE_TIMEOUT_MS;
103
+ app.disable('x-powered-by');
104
+ app.use(localhostHostValidation());
105
+ app.use((req, res, next) => {
106
+ res.setHeader('Cache-Control', 'no-store');
107
+ res.setHeader('X-Content-Type-Options', 'nosniff');
108
+ res.setHeader('Referrer-Policy', 'no-referrer');
109
+ const origin = req.headers.origin;
110
+ if (!origin) {
111
+ next();
112
+ return;
113
+ }
114
+ try {
115
+ const parsed = new URL(origin);
116
+ const allowedHostname = parsed.hostname === '127.0.0.1' ||
117
+ parsed.hostname === 'localhost' ||
118
+ parsed.hostname === '[::1]';
119
+ const allowedPort = Number(parsed.port || '80') === boundPort;
120
+ if (parsed.protocol === 'http:' && allowedHostname && allowedPort) {
121
+ next();
122
+ return;
123
+ }
124
+ }
125
+ catch {
126
+ // Rejected below.
127
+ }
128
+ jsonRpcError(res, 403, 'Invalid Origin header');
129
+ });
130
+ app.get('/healthz', (_req, res) => {
131
+ res.json({ status: closing ? 'stopping' : 'ok' });
132
+ });
133
+ app.use(options.path, createRateLimiter(options.rateLimitPerMinute));
134
+ app.use(options.path, (req, res, next) => {
135
+ if (!bearerTokenMatches(req.headers.authorization, expectedTokenDigest)) {
136
+ res.setHeader('WWW-Authenticate', 'Bearer');
137
+ jsonRpcError(res, 401, 'Unauthorized');
138
+ return;
139
+ }
140
+ next();
141
+ });
142
+ app.use(options.path, express.json({ limit: '256kb', strict: true }));
143
+ const releaseSession = (sessionId, session) => {
144
+ if (sessions.get(sessionId) !== session)
145
+ return;
146
+ sessions.delete(sessionId);
147
+ if (session.idleTimer) {
148
+ clearTimeout(session.idleTimer);
149
+ session.idleTimer = undefined;
150
+ }
151
+ options.releaseServer?.(session.server);
152
+ };
153
+ const scheduleSessionExpiry = (sessionId, session) => {
154
+ if (sessions.get(sessionId) !== session || session.activeResponses > 0)
155
+ return;
156
+ if (session.idleTimer)
157
+ clearTimeout(session.idleTimer);
158
+ session.idleTimer = setTimeout(() => {
159
+ if (sessions.get(sessionId) !== session || session.activeResponses > 0)
160
+ return;
161
+ releaseSession(sessionId, session);
162
+ void session.transport.close().catch((error) => {
163
+ logger.warn('Failed to close an expired MCP HTTP session.', {
164
+ message: error instanceof Error ? error.message : String(error),
165
+ });
166
+ });
167
+ }, sessionIdleTimeoutMs);
168
+ session.idleTimer.unref();
169
+ };
170
+ const trackSessionResponse = (sessionId, session, res) => {
171
+ if (session.idleTimer) {
172
+ clearTimeout(session.idleTimer);
173
+ session.idleTimer = undefined;
174
+ }
175
+ session.activeResponses += 1;
176
+ let settled = false;
177
+ const settle = () => {
178
+ if (settled)
179
+ return;
180
+ settled = true;
181
+ session.activeResponses = Math.max(0, session.activeResponses - 1);
182
+ scheduleSessionExpiry(sessionId, session);
183
+ };
184
+ res.once('finish', settle);
185
+ res.once('close', settle);
186
+ };
187
+ const handleSessionRequest = async (sessionId, session, req, res, parsedBody) => {
188
+ trackSessionResponse(sessionId, session, res);
189
+ try {
190
+ await session.transport.handleRequest(req, res, parsedBody);
191
+ }
192
+ catch (error) {
193
+ logger.warn('Failed to handle an established MCP HTTP session.', {
194
+ message: error instanceof Error ? error.message : String(error),
195
+ });
196
+ if (!res.headersSent)
197
+ jsonRpcError(res, 500, 'Internal server error');
198
+ }
199
+ };
200
+ const initializeSession = async (req, res) => {
201
+ let server;
202
+ let session;
203
+ let initializedSessionId;
204
+ pendingInitializations += 1;
205
+ try {
206
+ server = await options.serverFactory();
207
+ const transport = new StreamableHTTPServerTransport({
208
+ sessionIdGenerator: () => randomUUID(),
209
+ onsessioninitialized: (sessionId) => {
210
+ initializedSessionId = sessionId;
211
+ if (session) {
212
+ sessions.set(sessionId, session);
213
+ scheduleSessionExpiry(sessionId, session);
214
+ }
215
+ },
216
+ onsessionclosed: (sessionId) => {
217
+ if (session)
218
+ releaseSession(sessionId, session);
219
+ },
220
+ });
221
+ session = { server, transport, activeResponses: 0 };
222
+ transport.onclose = () => {
223
+ const sessionId = transport.sessionId ?? initializedSessionId;
224
+ if (sessionId && session)
225
+ releaseSession(sessionId, session);
226
+ };
227
+ await server.connect(transport);
228
+ await transport.handleRequest(req, res, req.body);
229
+ const sessionId = transport.sessionId ?? initializedSessionId;
230
+ if (sessionId)
231
+ scheduleSessionExpiry(sessionId, session);
232
+ }
233
+ catch (error) {
234
+ if (session) {
235
+ const sessionId = session.transport.sessionId ?? initializedSessionId;
236
+ if (sessionId)
237
+ releaseSession(sessionId, session);
238
+ else
239
+ options.releaseServer?.(session.server);
240
+ await session.transport.close().catch(() => undefined);
241
+ }
242
+ else if (server) {
243
+ options.releaseServer?.(server);
244
+ }
245
+ logger.error('Failed to handle MCP HTTP initialization.', {
246
+ message: error instanceof Error ? error.message : String(error),
247
+ });
248
+ if (!res.headersSent)
249
+ jsonRpcError(res, 500, 'Internal server error');
250
+ }
251
+ finally {
252
+ pendingInitializations -= 1;
253
+ }
254
+ };
255
+ app.post(options.path, async (req, res) => {
256
+ if (closing) {
257
+ jsonRpcError(res, 503, 'Server is shutting down');
258
+ return;
259
+ }
260
+ const requestedSessionId = headerValue(req.headers['mcp-session-id']);
261
+ const existing = requestedSessionId ? sessions.get(requestedSessionId) : undefined;
262
+ if (requestedSessionId && existing) {
263
+ await handleSessionRequest(requestedSessionId, existing, req, res, req.body);
264
+ return;
265
+ }
266
+ if (requestedSessionId) {
267
+ jsonRpcError(res, 404, 'MCP session not found');
268
+ return;
269
+ }
270
+ if (!isInitializeRequest(req.body)) {
271
+ jsonRpcError(res, 400, 'Invalid or missing MCP session');
272
+ return;
273
+ }
274
+ if (sessions.size + pendingInitializations >= options.maxSessions) {
275
+ jsonRpcError(res, 429, 'Maximum MCP session count reached');
276
+ return;
277
+ }
278
+ await initializeSession(req, res);
279
+ });
280
+ const handleEstablishedSession = async (req, res) => {
281
+ const sessionId = headerValue(req.headers['mcp-session-id']);
282
+ const session = sessionId ? sessions.get(sessionId) : undefined;
283
+ if (!sessionId || !session) {
284
+ jsonRpcError(res, sessionId ? 404 : 400, sessionId ? 'MCP session not found' : 'Missing MCP session');
285
+ return;
286
+ }
287
+ await handleSessionRequest(sessionId, session, req, res);
288
+ };
289
+ app.get(options.path, handleEstablishedSession);
290
+ app.delete(options.path, handleEstablishedSession);
291
+ const jsonErrorHandler = (error, _req, res, _next) => {
292
+ void _next;
293
+ const requestError = error;
294
+ logger.warn('Rejected an invalid MCP HTTP request body.', {
295
+ message: requestError instanceof Error ? requestError.message : String(requestError),
296
+ });
297
+ const status = isRequestTooLarge(requestError) ? 413 : 400;
298
+ if (!res.headersSent) {
299
+ jsonRpcError(res, status, status === 413 ? 'Request body too large' : 'Invalid JSON request body');
300
+ }
301
+ };
302
+ app.use(jsonErrorHandler);
303
+ const httpServer = await listen(app, options.port);
304
+ const address = httpServer.address();
305
+ if (!address || typeof address === 'string') {
306
+ await new Promise((resolve) => httpServer.close(() => resolve()));
307
+ throw new Error('Unable to determine HTTP listening port.');
308
+ }
309
+ boundPort = address.port;
310
+ httpServer.headersTimeout = 10_000;
311
+ httpServer.requestTimeout = 30_000;
312
+ httpServer.keepAliveTimeout = 5_000;
313
+ const close = async () => {
314
+ if (closing)
315
+ return;
316
+ closing = true;
317
+ for (const [sessionId, session] of [...sessions]) {
318
+ releaseSession(sessionId, session);
319
+ await session.transport.close().catch((error) => {
320
+ logger.warn('Failed to close an MCP HTTP session.', {
321
+ message: error instanceof Error ? error.message : String(error),
322
+ });
323
+ });
324
+ }
325
+ await new Promise((resolve, reject) => {
326
+ httpServer.close((error) => {
327
+ if (error)
328
+ reject(error);
329
+ else
330
+ resolve();
331
+ });
332
+ });
333
+ };
334
+ return {
335
+ host: HTTP_HOST,
336
+ port: boundPort,
337
+ path: options.path,
338
+ url: `http://${HTTP_HOST}:${boundPort}${options.path}`,
339
+ sessionCount: () => sessions.size,
340
+ close,
341
+ };
342
+ }
343
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/transports/http.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,10 @@ This server is organized by responsibility so each file stays small and focused.
4
4
 
5
5
  ## Entry point
6
6
 
7
- - `src/index.ts` boots the MCP server, initializes auth, registers tools/resources, wires pipeline handlers, and connects the stdio transport.
7
+ - `src/index.ts` selects stdio (default) or loopback Streamable HTTP and owns process shutdown.
8
+ - `src/server.ts` initializes the process-wide VRChat runtime and creates transport-independent `McpServer` instances.
9
+ - `src/transports/http.ts` owns bearer authentication, Origin/Host protection, rate limits, stateful HTTP sessions, SSE routing, and graceful session cleanup.
10
+ - STDIO uses one `McpServer`. HTTP creates one `McpServer` per client session while sharing process-wide VRChat auth, cache, and pipeline state.
8
11
 
9
12
  ## Core API plumbing (`src/core/`)
10
13
 
@@ -34,6 +37,7 @@ This server is organized by responsibility so each file stays small and focused.
34
37
  ## Resources (`src/resources/`)
35
38
 
36
39
  - MCP resources backed by pipeline/cache (e.g., friend change delta feed).
40
+ - Subscription state is tracked per `McpServer`; pipeline updates fan out only to subscribed live sessions.
37
41
 
38
42
  ## Infra + utils
39
43
 
@@ -104,6 +104,24 @@ Groups and social writes:
104
104
  - `vrchat_friend_request`
105
105
  - `vrchat_boop`
106
106
 
107
+ Group posts (write):
108
+
109
+ - `vrchat_group_post_create`
110
+ - `vrchat_group_post_update`
111
+ - `vrchat_group_post_delete`
112
+
113
+ Neither create nor update notifies group members unless `sendNotification` is explicitly set, because a single post can ping the whole group.
114
+
115
+ All three accept `groupId` or `shortCode`, and the `groups.allowlist` guard always runs against the resolved ID so a short code cannot route around it.
116
+
117
+ VRChat replaces the entire post on edit, and its API has no single-post read. `vrchat_group_post_update` therefore always looks the post up first, scanning the most recent 300 posts fresh from the API rather than from the cached list, and fills in whatever fields the caller omitted. Omitting `roleIds` keeps the current role restrictions; pass `roleIds: []` to clear them. Omitting `imageId` keeps the current image; pass `imageId: null` to remove it. An update whose supplied values all match the post is rejected, since re-sending identical content only bumps the timestamp and can re-notify members.
118
+
119
+ If the post is older than that lookup window, supplying `title`, `text`, and `visibility` together still lets the edit land as an outright replace. That path cannot recover `roleIds` or `imageId`, so it clears them and reports `mergedFromExisting: false`. Skipping the lookup is deliberately not offered as an optimization: a replace that silently drops `roleIds` would widen a role-restricted post to the whole group.
120
+
121
+ Creates and updates report success even when the write lands but VRChat's response cannot be parsed, returning `post: null`. Neither call is idempotent, so reporting a failure there would invite a retry that posts twice and, with `sendNotification`, notifies twice.
122
+
123
+ All three tools invalidate cached group reads after the write, including when the response fails to parse. One gap worth knowing: a `vrchat_group_posts_recent` load already in flight when the write happens can still store its pre-write result afterwards, because `invalidateByTag` cannot see a request whose entry has not been written yet. Reads can then serve the old list for the remainder of the group cache TTL. This affects every cached area equally, not just posts, and fixing it needs generation-checking in `CacheManager` rather than anything post-specific.
124
+
107
125
  Invites (write, low-risk):
108
126
 
109
127
  - `vrchat_invite_self`
package/docs/privacy.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Privacy
2
2
 
3
- VRChat MCP runs locally on your machine as a stdio MCP server. The project does not operate a hosted service for your VRChat session.
3
+ VRChat MCP runs locally on your machine as a stdio MCP server by default. It also provides an opt-in Streamable HTTP listener that is hard-bound to `127.0.0.1` and protected by a required bearer token. The project does not operate a hosted service for your VRChat session.
4
4
 
5
5
  ## Data Access
6
6
 
@@ -20,4 +20,4 @@ Curated write tools, generated write tools for API gaps, generated read tools, a
20
20
 
21
21
  ## Hosted Integrations
22
22
 
23
- Do not send VRChat MCP cookies or session tokens to hosted third-party services. Public HTTP or hosted connector variants should only be used if a future VRChat OAuth or public-data-only design exists.
23
+ The local HTTP bearer token protects access to the MCP listener; it is not a VRChat credential and does not make the server suitable for public hosting. Do not place the bearer token in URLs or logs, and do not send VRChat MCP cookies or session tokens to hosted third-party services. Public HTTP or hosted connector variants should only be used if a future VRChat OAuth or public-data-only design exists.
@@ -23,6 +23,7 @@ Tool names, tool descriptions, argument names, argument descriptions, and schema
23
23
  ## Swagger UI (mcpo)
24
24
 
25
25
  - Run `npm run mcpo` to spin up an OpenAPI proxy with Swagger UI.
26
+ - This development proxy is separate from the native MCP Streamable HTTP transport (`--transport http`).
26
27
  - `npm run mcpo` requires `uvx` (from Astral’s `uv`) to be installed.
27
28
  - Open `http://localhost:8000/docs` to browse and try tool calls.
28
29
  - For config-based setup (Claude Desktop config + hot reload), see `README.md`.