@creeperhost/modlens-mcp 1.6.27 → 1.6.28

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/README.md +6 -4
  2. package/dist/generated/sqlite/internal/class.js +5 -5
  3. package/dist/generated/sqlite/internal/class.js.map +1 -1
  4. package/dist/generated/sqlite/internal/prismaNamespace.d.ts +1 -0
  5. package/dist/generated/sqlite/internal/prismaNamespace.d.ts.map +1 -1
  6. package/dist/generated/sqlite/internal/prismaNamespace.js +1 -0
  7. package/dist/generated/sqlite/internal/prismaNamespace.js.map +1 -1
  8. package/dist/generated/sqlite/internal/prismaNamespaceBrowser.d.ts +1 -0
  9. package/dist/generated/sqlite/internal/prismaNamespaceBrowser.d.ts.map +1 -1
  10. package/dist/generated/sqlite/internal/prismaNamespaceBrowser.js +1 -0
  11. package/dist/generated/sqlite/internal/prismaNamespaceBrowser.js.map +1 -1
  12. package/dist/generated/sqlite/models/Mod.d.ts +45 -1
  13. package/dist/generated/sqlite/models/Mod.d.ts.map +1 -1
  14. package/dist/hosted-mod-license.d.ts.map +1 -1
  15. package/dist/hosted-mod-license.js +23 -7
  16. package/dist/hosted-mod-license.js.map +1 -1
  17. package/dist/hosted-oauth.d.ts +3 -0
  18. package/dist/hosted-oauth.d.ts.map +1 -1
  19. package/dist/hosted-oauth.js +75 -27
  20. package/dist/hosted-oauth.js.map +1 -1
  21. package/dist/hosted-policy.d.ts.map +1 -1
  22. package/dist/hosted-policy.js +3 -1
  23. package/dist/hosted-policy.js.map +1 -1
  24. package/dist/hosted-tool-catalog.js +1 -1
  25. package/dist/hosted-tool-catalog.js.map +1 -1
  26. package/dist/mod-reference.d.ts +24 -0
  27. package/dist/mod-reference.d.ts.map +1 -0
  28. package/dist/mod-reference.js +199 -0
  29. package/dist/mod-reference.js.map +1 -0
  30. package/dist/repositories/mod.d.ts +3 -0
  31. package/dist/repositories/mod.d.ts.map +1 -1
  32. package/dist/repositories/mod.js +19 -2
  33. package/dist/repositories/mod.js.map +1 -1
  34. package/dist/runtime/modlens-agent.jar +0 -0
  35. package/dist/server.js +41 -4
  36. package/dist/server.js.map +1 -1
  37. package/dist/sqlite-schema.d.ts.map +1 -1
  38. package/dist/sqlite-schema.js +7 -0
  39. package/dist/sqlite-schema.js.map +1 -1
  40. package/dist/tools/ingest.d.ts.map +1 -1
  41. package/dist/tools/ingest.js +3 -0
  42. package/dist/tools/ingest.js.map +1 -1
  43. package/dist/tools/mixin-scan.d.ts +1 -0
  44. package/dist/tools/mixin-scan.d.ts.map +1 -1
  45. package/package.json +1 -1
  46. package/prisma/backends/schema.sqlite.prisma +2 -0
  47. package/prisma/backends/template.db +0 -0
  48. package/prisma/schema.prisma +2 -0
  49. package/scripts/test-hosted-oauth.mjs +52 -8
@@ -24,6 +24,7 @@ model Mod {
24
24
  loader String
25
25
  jarPath String @unique @map("jar_path")
26
26
  sha256 String?
27
+ sha1 String?
27
28
  murmur2 String?
28
29
  sha512 String?
29
30
  sourcePath String? @map("source_path")
@@ -53,6 +54,7 @@ model Mod {
53
54
 
54
55
  @@unique([modId, version, mcVersion, loader])
55
56
  @@index([modId])
57
+ @@index([sha1])
56
58
  @@index([mcVersion])
57
59
  @@index([loader])
58
60
  @@index([modrinthId])
Binary file
@@ -17,6 +17,7 @@ model Mod {
17
17
  loader String // fabric | neoforge | forge | quilt | unknown
18
18
  jarPath String @unique @map("jar_path")
19
19
  sha256 String?
20
+ sha1 String?
20
21
  murmur2 String?
21
22
  sha512 String?
22
23
  sourcePath String? @map("source_path")
@@ -47,6 +48,7 @@ model Mod {
47
48
 
48
49
  @@unique([modId, version, mcVersion, loader])
49
50
  @@index([modId])
51
+ @@index([sha1])
50
52
  @@index([mcVersion])
51
53
  @@index([loader])
52
54
  @@index([modrinthId])
@@ -29,6 +29,9 @@ let partner = true;
29
29
  let subject = 'customer:42';
30
30
  let serial = 0;
31
31
  let metadataRequests = 0;
32
+ let upstreamExpiresIn = 300;
33
+ let upstreamRefreshRequests = 0;
34
+ const revokedUpstreamRefreshes = new Set();
32
35
  const provider = createServer(async (req, res) => {
33
36
  const url = new URL(req.url, issuer);
34
37
  const write = (status, data) => { res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(data)); };
@@ -48,15 +51,20 @@ const provider = createServer(async (req, res) => {
48
51
  let raw = '';
49
52
  for await (const chunk of req) raw += chunk;
50
53
  const data = new URLSearchParams(raw);
51
- if (data.get('grant_type') === 'refresh_token' && !data.get('refresh_token')?.startsWith('upstream-refresh-'))
54
+ if (data.get('grant_type') === 'refresh_token' &&
55
+ (!data.get('refresh_token')?.startsWith('upstream-refresh-') ||
56
+ revokedUpstreamRefreshes.has(data.get('refresh_token'))))
52
57
  return write(400, { error: 'invalid_grant' });
53
- if (data.get('grant_type') === 'refresh_token')
58
+ if (data.get('grant_type') === 'refresh_token') {
59
+ upstreamRefreshRequests++;
54
60
  await new Promise(resolve => setTimeout(resolve, 100));
61
+ }
55
62
  if (data.get('grant_type') === 'authorization_code' && data.get('code') !== 'fake-code')
56
63
  return write(400, { error: 'invalid_grant' });
57
64
  const access = `upstream-${++serial}`;
58
65
  profiles.set(access, { sub: subject, is_partner: partner });
59
- return write(200, { access_token: access, refresh_token: `upstream-refresh-${serial}`, token_type: 'Bearer', expires_in: 300 });
66
+ return write(200, { access_token: access, refresh_token: `upstream-refresh-${serial}`,
67
+ token_type: 'Bearer', expires_in: upstreamExpiresIn });
60
68
  }
61
69
  if (url.pathname === '/profile') {
62
70
  const profile = profiles.get(req.headers.authorization?.slice(7));
@@ -72,7 +80,9 @@ const env = { ...process.env, MCP_PORT: String(mcpPort), MCP_HOST: '127.0.0.1',
72
80
  MODLENS_HOSTED_PROXY_SECRET: '', MODLENS_HOSTED_MC_SOURCE: '0', MODLENS_OAUTH_PUBLIC_URL: resource,
73
81
  MODLENS_OAUTH_ISSUER: issuer, MODLENS_OAUTH_CLIENT_ID: 'test-modlens', MODLENS_OAUTH_SCOPES: 'partner.read offline_access',
74
82
  MODLENS_OAUTH_PROFILE_URL: issuer + '/profile', MODLENS_OAUTH_REQUIRED_FIELD: 'is_partner',
75
- MODLENS_OAUTH_REQUIRED_VALUE: 'true', MODLENS_OAUTH_STORAGE_KEY: randomBytes(32).toString('base64') };
83
+ MODLENS_OAUTH_REQUIRED_VALUE: 'true', MODLENS_OAUTH_STORAGE_KEY: randomBytes(32).toString('base64'),
84
+ MODLENS_OAUTH_LOG: '1' };
85
+ delete env.MODLENS_OAUTH_CODEX_REFRESH_WORKAROUND;
76
86
  let server;
77
87
  let serverOutput = '';
78
88
  async function start() {
@@ -188,8 +198,9 @@ try {
188
198
  assert.equal(registered.client_name, 'ChatGPT & Codex <test>');
189
199
  clientId = registered.client_id;
190
200
  const tokens = await login();
191
- assert.match(tokens.access_token, /^mla_/);
201
+ assert.match(tokens.access_token, /^mlw_/);
192
202
  assert.match(tokens.refresh_token, /^mlr_/);
203
+ assert.equal(tokens.expires_in, 30 * 24 * 60 * 60, 'ModLens access token lasts 30 days');
193
204
  assert.equal((await initialize(tokens.access_token)).status, 200);
194
205
  const runtimeClient = new Client({ name: 'hosted-runtime-test', version: '1' });
195
206
  await runtimeClient.connect(new StreamableHTTPClientTransport(new URL(resource), {
@@ -263,7 +274,12 @@ try {
263
274
  assert.equal((await initialize(otherAccount.access_token, { 'mcp-session-id': sessionId })).status, 404,
264
275
  'one account cannot reuse another account session');
265
276
  profiles.delete(`upstream-${serial}`);
266
- assert.equal((await initialize(otherAccount.access_token)).status, 401, 'revoked upstream access fails closed');
277
+ assert.equal((await initialize(otherAccount.access_token)).status, 200,
278
+ 'upstream process-local access loss is recovered with its refresh token');
279
+ revokedUpstreamRefreshes.add(`upstream-refresh-${serial}`);
280
+ profiles.delete(`upstream-${serial}`);
281
+ assert.equal((await initialize(otherAccount.access_token)).status, 401,
282
+ 'revoked upstream refresh fails closed');
267
283
  partner = false;
268
284
  assert.equal((await initialize(rotated.access_token)).status, 403);
269
285
  assert.equal((await initialize(rotated.access_token)).status, 401);
@@ -284,8 +300,36 @@ try {
284
300
  headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ redirect_uris: ['http://127.0.0.1:54321/callback'] }) });
285
301
  assert.equal(unnamedRegistration.status, 201);
286
302
  clientId = (await unnamedRegistration.json()).client_id;
287
- await login(null);
288
- console.log('Hosted OAuth discovery, registration, sign-in, persistence, refresh, replay, session, revocation, partner, gateway-header and self-hosted checks passed.');
303
+ upstreamExpiresIn = 1;
304
+ const longLived = await login(null);
305
+ upstreamExpiresIn = 300;
306
+ await new Promise(resolve => setTimeout(resolve, 1200));
307
+ const beforeUpstreamRefresh = upstreamRefreshRequests;
308
+ const concurrentAccess = await Promise.all(Array.from({ length: 4 }, () => initialize(longLived.access_token)));
309
+ for (const response of concurrentAccess)
310
+ assert.equal(response.status, 200, '30-day ModLens access survives upstream access expiry');
311
+ assert.equal(upstreamRefreshRequests, beforeUpstreamRefresh + 1,
312
+ 'concurrent ModLens requests share one upstream token refresh');
313
+ await stop();
314
+ delete env.MODLENS_OAUTH_LOG;
315
+ await start();
316
+ assert.equal((await initialize(longLived.access_token)).status, 200);
317
+ assert.equal((await fetchManual(`http://127.0.0.1:${mcpPort}/oauth/token`, { method: 'POST',
318
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: form({ grant_type: 'refresh_token',
319
+ client_id: clientId, refresh_token: tokens.refresh_token }) })).status, 400);
320
+ assert.doesNotMatch(serverOutput, /\[modlens\] oauth /, 'OAuth diagnostics are off by default');
321
+ await stop();
322
+ env.MODLENS_OAUTH_CODEX_REFRESH_WORKAROUND = '0';
323
+ env.MODLENS_OAUTH_LOG = '1';
324
+ await start();
325
+ assert.equal((await initialize(longLived.access_token)).status, 401,
326
+ 'switching off the workaround immediately rejects previously issued long-lived tokens');
327
+ const shortLived = await login(null);
328
+ assert.match(shortLived.access_token, /^mla_/);
329
+ assert.ok(shortLived.expires_in >= 290 && shortLived.expires_in <= 295,
330
+ 'workaround can be switched off to restore short-lived access tokens');
331
+ assert.equal((await initialize(shortLived.access_token)).status, 200);
332
+ console.log('Hosted OAuth discovery, registration, sign-in, persistence, switchable month-long access, upstream refresh, logging switch, replay, session, revocation, partner, gateway-header and self-hosted checks passed.');
289
333
  } finally {
290
334
  await stop();
291
335
  await new Promise(resolve => provider.close(resolve));