@basicbit/vrchat-mcp 0.1.7 → 0.1.8

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 (48) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +56 -9
  3. package/dist/bin/cli.js +1 -1
  4. package/dist/bin/cli.js.map +1 -1
  5. package/dist/package.json +3 -1
  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/retry.js +89 -0
  14. package/dist/src/core/retry.js.map +1 -0
  15. package/dist/src/core/writeToolRegistry.js +20 -0
  16. package/dist/src/core/writeToolRegistry.js.map +1 -1
  17. package/dist/src/index.js +66 -22
  18. package/dist/src/index.js.map +1 -1
  19. package/dist/src/models/groups.js +28 -10
  20. package/dist/src/models/groups.js.map +1 -1
  21. package/dist/src/resources/subscriptions.js +21 -8
  22. package/dist/src/resources/subscriptions.js.map +1 -1
  23. package/dist/src/server.js +42 -0
  24. package/dist/src/server.js.map +1 -0
  25. package/dist/src/services/cache.js +7 -0
  26. package/dist/src/services/cache.js.map +1 -1
  27. package/dist/src/services/groups/curated.js +7 -50
  28. package/dist/src/services/groups/curated.js.map +1 -1
  29. package/dist/src/services/groups/index.js +1 -0
  30. package/dist/src/services/groups/index.js.map +1 -1
  31. package/dist/src/services/groups/members.js +155 -0
  32. package/dist/src/services/groups/members.js.map +1 -0
  33. package/dist/src/services/groups/roles.js +4 -0
  34. package/dist/src/services/groups/roles.js.map +1 -1
  35. package/dist/src/services/invites/bulk.js +2 -56
  36. package/dist/src/services/invites/bulk.js.map +1 -1
  37. package/dist/src/services/pipeline/handlers.js +4 -4
  38. package/dist/src/services/pipeline/handlers.js.map +1 -1
  39. package/dist/src/tools/curated/groups.js +3 -2
  40. package/dist/src/tools/curated/groups.js.map +1 -1
  41. package/dist/src/transports/http.js +343 -0
  42. package/dist/src/transports/http.js.map +1 -0
  43. package/docs/architecture.md +5 -1
  44. package/docs/privacy.md +2 -2
  45. package/docs/tools-guide.md +1 -0
  46. package/docs/tools.md +35 -21
  47. package/package.json +3 -1
  48. 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
 
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`.
package/docs/tools.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Tool Catalog (generated)
2
2
 
3
- Generated: 2026-06-08T08:31:09.652Z
3
+ Generated: 2026-07-21T08:48:10.759Z
4
4
 
5
5
  Spec: VRChat API Documentation (1.20.7)
6
6
 
@@ -3865,7 +3865,7 @@ Output schema:
3865
3865
  ```
3866
3866
 
3867
3867
  ### vrchat_group_members
3868
- List group members by userId + displayName (read-only). (read-only)
3868
+ List one page of compact group members by default. Use view=all only when a complete, cached snapshot is required (read-only, capped at 10,000). (read-only)
3869
3869
 
3870
3870
  Input schema:
3871
3871
 
@@ -3875,36 +3875,41 @@ Input schema:
3875
3875
  "type": "object",
3876
3876
  "properties": {
3877
3877
  "groupId": {
3878
- "type": "string"
3878
+ "type": "string",
3879
+ "description": "Exact group ID. Provide groupId or shortCode."
3879
3880
  },
3880
3881
  "shortCode": {
3881
- "type": "string"
3882
+ "type": "string",
3883
+ "description": "Exact group short code. Provide groupId or shortCode."
3882
3884
  },
3883
3885
  "roleId": {
3884
- "type": "string"
3886
+ "type": "string",
3887
+ "description": "Only include members with this role."
3885
3888
  },
3886
3889
  "sort": {
3887
- "type": "string"
3888
- },
3889
- "pageSize": {
3890
- "type": "integer",
3891
- "minimum": 1,
3892
- "maximum": 100
3890
+ "type": "string",
3891
+ "description": "VRChat group-member sort expression."
3893
3892
  },
3894
- "maxPages": {
3895
- "type": "integer",
3896
- "minimum": 1,
3897
- "maximum": 200
3893
+ "view": {
3894
+ "description": "page fetches one bounded API page; all explicitly loads and caches a rate-aware snapshot capped at 10,000 members.",
3895
+ "default": "page",
3896
+ "type": "string",
3897
+ "enum": [
3898
+ "page",
3899
+ "all"
3900
+ ]
3898
3901
  },
3899
- "maxItems": {
3902
+ "pageSize": {
3900
3903
  "type": "integer",
3901
3904
  "minimum": 1,
3902
- "maximum": 9007199254740991
3905
+ "maximum": 100,
3906
+ "description": "Members to fetch when view=page. Ignored when view=all."
3903
3907
  },
3904
3908
  "offset": {
3905
3909
  "type": "integer",
3906
3910
  "minimum": 0,
3907
- "maximum": 9007199254740991
3911
+ "maximum": 9007199254740991,
3912
+ "description": "Starting member offset when view=page. Ignored when view=all."
3908
3913
  }
3909
3914
  },
3910
3915
  "additionalProperties": false
@@ -3921,7 +3926,14 @@ Output schema:
3921
3926
  "groupId": {
3922
3927
  "type": "string"
3923
3928
  },
3924
- "totalMembers": {
3929
+ "view": {
3930
+ "type": "string",
3931
+ "enum": [
3932
+ "page",
3933
+ "all"
3934
+ ]
3935
+ },
3936
+ "returnedMembers": {
3925
3937
  "type": "integer",
3926
3938
  "minimum": 0,
3927
3939
  "maximum": 9007199254740991
@@ -3989,9 +4001,11 @@ Output schema:
3989
4001
  },
3990
4002
  "required": [
3991
4003
  "groupId",
3992
- "totalMembers",
4004
+ "view",
4005
+ "returnedMembers",
3993
4006
  "truncated",
3994
- "stale"
4007
+ "stale",
4008
+ "members"
3995
4009
  ],
3996
4010
  "additionalProperties": false
3997
4011
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basicbit/vrchat-mcp",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "mcpName": "io.github.BASIC-BIT/vrchat-mcp",
5
5
  "description": "MCP server for VRChat friends, worlds, groups, events, notifications, and VRCX history.",
6
6
  "type": "module",
@@ -88,6 +88,7 @@
88
88
  "@apidevtools/json-schema-ref-parser": "^15.3.5",
89
89
  "@eslint/js": "^10.0.1",
90
90
  "@types/better-sqlite3": "^7.6.13",
91
+ "@types/express": "^5.0.6",
91
92
  "@types/node": "^25.9.2",
92
93
  "@typescript-eslint/eslint-plugin": "^8.50.0",
93
94
  "@typescript-eslint/parser": "^8.50.0",
@@ -108,6 +109,7 @@
108
109
  "dependencies": {
109
110
  "@modelcontextprotocol/sdk": "^1.29.0",
110
111
  "better-sqlite3": "^12.6.2",
112
+ "express": "^5.2.1",
111
113
  "keytar": "^7.9.0",
112
114
  "tough-cookie": "^6.0.0",
113
115
  "undici": "^8.4.0",
package/server.json CHANGED
@@ -8,12 +8,12 @@
8
8
  "source": "github",
9
9
  "id": "1122295987"
10
10
  },
11
- "version": "0.1.7",
11
+ "version": "0.1.8",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "@basicbit/vrchat-mcp",
16
- "version": "0.1.7",
16
+ "version": "0.1.8",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  },