@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.
- package/README.md +6 -4
- package/dist/generated/sqlite/internal/class.js +5 -5
- package/dist/generated/sqlite/internal/class.js.map +1 -1
- package/dist/generated/sqlite/internal/prismaNamespace.d.ts +1 -0
- package/dist/generated/sqlite/internal/prismaNamespace.d.ts.map +1 -1
- package/dist/generated/sqlite/internal/prismaNamespace.js +1 -0
- package/dist/generated/sqlite/internal/prismaNamespace.js.map +1 -1
- package/dist/generated/sqlite/internal/prismaNamespaceBrowser.d.ts +1 -0
- package/dist/generated/sqlite/internal/prismaNamespaceBrowser.d.ts.map +1 -1
- package/dist/generated/sqlite/internal/prismaNamespaceBrowser.js +1 -0
- package/dist/generated/sqlite/internal/prismaNamespaceBrowser.js.map +1 -1
- package/dist/generated/sqlite/models/Mod.d.ts +45 -1
- package/dist/generated/sqlite/models/Mod.d.ts.map +1 -1
- package/dist/hosted-mod-license.d.ts.map +1 -1
- package/dist/hosted-mod-license.js +23 -7
- package/dist/hosted-mod-license.js.map +1 -1
- package/dist/hosted-oauth.d.ts +3 -0
- package/dist/hosted-oauth.d.ts.map +1 -1
- package/dist/hosted-oauth.js +75 -27
- package/dist/hosted-oauth.js.map +1 -1
- package/dist/hosted-policy.d.ts.map +1 -1
- package/dist/hosted-policy.js +3 -1
- package/dist/hosted-policy.js.map +1 -1
- package/dist/hosted-tool-catalog.js +1 -1
- package/dist/hosted-tool-catalog.js.map +1 -1
- package/dist/mod-reference.d.ts +24 -0
- package/dist/mod-reference.d.ts.map +1 -0
- package/dist/mod-reference.js +199 -0
- package/dist/mod-reference.js.map +1 -0
- package/dist/repositories/mod.d.ts +3 -0
- package/dist/repositories/mod.d.ts.map +1 -1
- package/dist/repositories/mod.js +19 -2
- package/dist/repositories/mod.js.map +1 -1
- package/dist/runtime/modlens-agent.jar +0 -0
- package/dist/server.js +41 -4
- package/dist/server.js.map +1 -1
- package/dist/sqlite-schema.d.ts.map +1 -1
- package/dist/sqlite-schema.js +7 -0
- package/dist/sqlite-schema.js.map +1 -1
- package/dist/tools/ingest.d.ts.map +1 -1
- package/dist/tools/ingest.js +3 -0
- package/dist/tools/ingest.js.map +1 -1
- package/dist/tools/mixin-scan.d.ts +1 -0
- package/dist/tools/mixin-scan.d.ts.map +1 -1
- package/package.json +1 -1
- package/prisma/backends/schema.sqlite.prisma +2 -0
- package/prisma/backends/template.db +0 -0
- package/prisma/schema.prisma +2 -0
- 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
|
package/prisma/schema.prisma
CHANGED
|
@@ -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' &&
|
|
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}`,
|
|
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, /^
|
|
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,
|
|
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
|
-
|
|
288
|
-
|
|
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));
|