@dimcool/mcp 0.1.30 → 0.1.31
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 +169 -123
- package/dist/index.js +290 -36
- package/dist/server.d.ts +31 -0
- package/dist/server.js +19465 -0
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -11630,6 +11630,9 @@ var Admin = class {
|
|
|
11630
11630
|
this.http = http;
|
|
11631
11631
|
this.logger = logger2;
|
|
11632
11632
|
}
|
|
11633
|
+
async getInternalBots() {
|
|
11634
|
+
return this.http.get("/admin/internal-bots");
|
|
11635
|
+
}
|
|
11633
11636
|
async getUserById(id) {
|
|
11634
11637
|
return this.http.get(`/admin/users/${id}`);
|
|
11635
11638
|
}
|
|
@@ -14253,8 +14256,21 @@ function createGameActionsStore(transport2) {
|
|
|
14253
14256
|
});
|
|
14254
14257
|
};
|
|
14255
14258
|
const isNonRpsState = (state) => Boolean(state && !isRpsState(state));
|
|
14259
|
+
const pendingEvents = /* @__PURE__ */ new Map();
|
|
14260
|
+
function enqueue(gameId, event) {
|
|
14261
|
+
const q = pendingEvents.get(gameId) ?? [];
|
|
14262
|
+
q.push(event);
|
|
14263
|
+
pendingEvents.set(gameId, q);
|
|
14264
|
+
}
|
|
14265
|
+
function drainQueue(gameId) {
|
|
14266
|
+
const q = pendingEvents.get(gameId);
|
|
14267
|
+
if (!q?.length) return;
|
|
14268
|
+
pendingEvents.delete(gameId);
|
|
14269
|
+
for (const ev of q) applyWsEvent(ev);
|
|
14270
|
+
}
|
|
14256
14271
|
const setBaseState = (gameId, state) => {
|
|
14257
14272
|
updateState(gameId, state);
|
|
14273
|
+
drainQueue(gameId);
|
|
14258
14274
|
};
|
|
14259
14275
|
const clearState = (gameId) => {
|
|
14260
14276
|
store.updateState((state) => {
|
|
@@ -14264,6 +14280,7 @@ function createGameActionsStore(transport2) {
|
|
|
14264
14280
|
const { [gameId]: _, ...rest } = state.statesByGameId;
|
|
14265
14281
|
return { ...state, statesByGameId: rest };
|
|
14266
14282
|
});
|
|
14283
|
+
pendingEvents.delete(gameId);
|
|
14267
14284
|
};
|
|
14268
14285
|
const applyWsEvent = (event) => {
|
|
14269
14286
|
switch (event.event) {
|
|
@@ -14282,7 +14299,10 @@ function createGameActionsStore(transport2) {
|
|
|
14282
14299
|
}
|
|
14283
14300
|
case "game:rps:starting": {
|
|
14284
14301
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
14285
|
-
if (!current || !isRpsState(current))
|
|
14302
|
+
if (!current || !isRpsState(current)) {
|
|
14303
|
+
enqueue(event.payload.gameId, event);
|
|
14304
|
+
return;
|
|
14305
|
+
}
|
|
14286
14306
|
const betAmount = typeof event.payload.betAmount === "number" ? event.payload.betAmount : void 0;
|
|
14287
14307
|
const startedAt = typeof event.payload.startedAt === "string" ? event.payload.startedAt : current.roundState.startedAt;
|
|
14288
14308
|
const bufferEndsAt = typeof event.payload.bufferEndsAt === "string" ? event.payload.bufferEndsAt : current.roundState.selectionEndsAt;
|
|
@@ -14303,7 +14323,10 @@ function createGameActionsStore(transport2) {
|
|
|
14303
14323
|
}
|
|
14304
14324
|
case "game:rps:round:started": {
|
|
14305
14325
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
14306
|
-
if (!current || !isRpsState(current))
|
|
14326
|
+
if (!current || !isRpsState(current)) {
|
|
14327
|
+
enqueue(event.payload.gameId, event);
|
|
14328
|
+
return;
|
|
14329
|
+
}
|
|
14307
14330
|
const actions = {};
|
|
14308
14331
|
const baseUsers = /* @__PURE__ */ new Set();
|
|
14309
14332
|
Object.keys(current.roundState.actions).forEach(
|
|
@@ -14337,7 +14360,10 @@ function createGameActionsStore(transport2) {
|
|
|
14337
14360
|
}
|
|
14338
14361
|
case "game:rps:action:received": {
|
|
14339
14362
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
14340
|
-
if (!current || !isRpsState(current))
|
|
14363
|
+
if (!current || !isRpsState(current)) {
|
|
14364
|
+
enqueue(event.payload.gameId, event);
|
|
14365
|
+
return;
|
|
14366
|
+
}
|
|
14341
14367
|
const updated = {
|
|
14342
14368
|
...current,
|
|
14343
14369
|
roundState: {
|
|
@@ -14356,7 +14382,10 @@ function createGameActionsStore(transport2) {
|
|
|
14356
14382
|
}
|
|
14357
14383
|
case "game:rps:timer:cutoff": {
|
|
14358
14384
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
14359
|
-
if (!current || !isRpsState(current))
|
|
14385
|
+
if (!current || !isRpsState(current)) {
|
|
14386
|
+
enqueue(event.payload.gameId, event);
|
|
14387
|
+
return;
|
|
14388
|
+
}
|
|
14360
14389
|
const updated = {
|
|
14361
14390
|
...current,
|
|
14362
14391
|
roundState: {
|
|
@@ -14370,7 +14399,10 @@ function createGameActionsStore(transport2) {
|
|
|
14370
14399
|
}
|
|
14371
14400
|
case "game:rps:round:reveal": {
|
|
14372
14401
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
14373
|
-
if (!current || !isRpsState(current))
|
|
14402
|
+
if (!current || !isRpsState(current)) {
|
|
14403
|
+
enqueue(event.payload.gameId, event);
|
|
14404
|
+
return;
|
|
14405
|
+
}
|
|
14374
14406
|
const actions = {};
|
|
14375
14407
|
const payloadActions = event.payload.actions;
|
|
14376
14408
|
Object.keys(payloadActions || {}).forEach((userId) => {
|
|
@@ -14394,7 +14426,10 @@ function createGameActionsStore(transport2) {
|
|
|
14394
14426
|
}
|
|
14395
14427
|
case "game:rps:round:completed": {
|
|
14396
14428
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
14397
|
-
if (!current || !isRpsState(current))
|
|
14429
|
+
if (!current || !isRpsState(current)) {
|
|
14430
|
+
enqueue(event.payload.gameId, event);
|
|
14431
|
+
return;
|
|
14432
|
+
}
|
|
14398
14433
|
const roundHistory = [
|
|
14399
14434
|
...current.roundHistory || [],
|
|
14400
14435
|
{
|
|
@@ -14419,7 +14454,10 @@ function createGameActionsStore(transport2) {
|
|
|
14419
14454
|
}
|
|
14420
14455
|
case "game:rps:timeout": {
|
|
14421
14456
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
14422
|
-
if (!current || !isRpsState(current))
|
|
14457
|
+
if (!current || !isRpsState(current)) {
|
|
14458
|
+
enqueue(event.payload.gameId, event);
|
|
14459
|
+
return;
|
|
14460
|
+
}
|
|
14423
14461
|
const timedOutUser = event.payload.playerId;
|
|
14424
14462
|
const action = event.payload.action;
|
|
14425
14463
|
const updated = {
|
|
@@ -14444,7 +14482,10 @@ function createGameActionsStore(transport2) {
|
|
|
14444
14482
|
const payload = event.payload;
|
|
14445
14483
|
const { gameId } = payload;
|
|
14446
14484
|
const current = store.getState().statesByGameId[gameId];
|
|
14447
|
-
if (!current)
|
|
14485
|
+
if (!current) {
|
|
14486
|
+
enqueue(gameId, event);
|
|
14487
|
+
return;
|
|
14488
|
+
}
|
|
14448
14489
|
const updated = isRpsCompletionPayload(payload) && isRpsState(current) ? {
|
|
14449
14490
|
...current,
|
|
14450
14491
|
status: "completed",
|
|
@@ -14493,11 +14534,15 @@ function createGameActionsStore(transport2) {
|
|
|
14493
14534
|
const current = store.getState().statesByGameId[gameId];
|
|
14494
14535
|
const updated = current ? { ...current, ...incoming } : incoming;
|
|
14495
14536
|
updateState(gameId, updated);
|
|
14537
|
+
drainQueue(gameId);
|
|
14496
14538
|
break;
|
|
14497
14539
|
}
|
|
14498
14540
|
case "game:rematch:requested": {
|
|
14499
14541
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
14500
|
-
if (!current)
|
|
14542
|
+
if (!current) {
|
|
14543
|
+
enqueue(event.payload.gameId, event);
|
|
14544
|
+
return;
|
|
14545
|
+
}
|
|
14501
14546
|
const requestedBy = event.payload.requestedBy;
|
|
14502
14547
|
const userId = event.payload.userId;
|
|
14503
14548
|
const requested = new Set(
|
|
@@ -14513,7 +14558,10 @@ function createGameActionsStore(transport2) {
|
|
|
14513
14558
|
}
|
|
14514
14559
|
case "game:rematch:cancelled": {
|
|
14515
14560
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
14516
|
-
if (!current)
|
|
14561
|
+
if (!current) {
|
|
14562
|
+
enqueue(event.payload.gameId, event);
|
|
14563
|
+
return;
|
|
14564
|
+
}
|
|
14517
14565
|
const requestedBy = event.payload.requestedBy ?? [];
|
|
14518
14566
|
const updated = {
|
|
14519
14567
|
...current,
|
|
@@ -14524,7 +14572,10 @@ function createGameActionsStore(transport2) {
|
|
|
14524
14572
|
}
|
|
14525
14573
|
case "game:rematch:started": {
|
|
14526
14574
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
14527
|
-
if (!current)
|
|
14575
|
+
if (!current) {
|
|
14576
|
+
enqueue(event.payload.gameId, event);
|
|
14577
|
+
return;
|
|
14578
|
+
}
|
|
14528
14579
|
const updated = {
|
|
14529
14580
|
...current,
|
|
14530
14581
|
rematchRequestedBy: event.payload.playerIds ?? []
|
|
@@ -14535,7 +14586,10 @@ function createGameActionsStore(transport2) {
|
|
|
14535
14586
|
case "game:pot:updated": {
|
|
14536
14587
|
const { gameId, totalPotMinor } = event.payload;
|
|
14537
14588
|
const current = store.getState().statesByGameId[gameId];
|
|
14538
|
-
if (!current)
|
|
14589
|
+
if (!current) {
|
|
14590
|
+
enqueue(gameId, event);
|
|
14591
|
+
return;
|
|
14592
|
+
}
|
|
14539
14593
|
const updated = {
|
|
14540
14594
|
...current,
|
|
14541
14595
|
totalPotMinor
|
|
@@ -14548,12 +14602,123 @@ function createGameActionsStore(transport2) {
|
|
|
14548
14602
|
}
|
|
14549
14603
|
};
|
|
14550
14604
|
const joinGame = (gameId) => transport2.joinRoom(`game:${gameId}`);
|
|
14605
|
+
const getCountdownDigit = (gameId, nowMs) => {
|
|
14606
|
+
const state = store.getState().statesByGameId[gameId];
|
|
14607
|
+
if (!state) return null;
|
|
14608
|
+
if (isRpsState(state)) {
|
|
14609
|
+
if (state.roundState.phase !== "starting") return null;
|
|
14610
|
+
const remaining = new Date(state.roundState.selectionEndsAt).getTime() - nowMs;
|
|
14611
|
+
if (remaining <= 0) return null;
|
|
14612
|
+
return Math.ceil(remaining / 1e3);
|
|
14613
|
+
}
|
|
14614
|
+
const bufferEndsAt = state.bufferEndsAt;
|
|
14615
|
+
if (bufferEndsAt) {
|
|
14616
|
+
const remaining = new Date(bufferEndsAt).getTime() - nowMs;
|
|
14617
|
+
if (remaining <= 0) return null;
|
|
14618
|
+
return Math.ceil(remaining / 1e3);
|
|
14619
|
+
}
|
|
14620
|
+
return null;
|
|
14621
|
+
};
|
|
14622
|
+
const getChessClockTimes = (gameId, nowMs) => {
|
|
14623
|
+
const state = store.getState().statesByGameId[gameId];
|
|
14624
|
+
if (!state || state.gameType !== "chess") return null;
|
|
14625
|
+
const s = state;
|
|
14626
|
+
let whiteMs = s.whiteTimeMs ?? 0;
|
|
14627
|
+
let blackMs = s.blackTimeMs ?? 0;
|
|
14628
|
+
if (s.status === "active" && s.currentPlayerId) {
|
|
14629
|
+
const startedAt = Date.parse(s.turnStartedAt);
|
|
14630
|
+
if (!Number.isNaN(startedAt)) {
|
|
14631
|
+
const elapsed = Math.max(0, nowMs - startedAt);
|
|
14632
|
+
if (s.currentPlayerId === s.whitePlayerId) {
|
|
14633
|
+
whiteMs = Math.max(0, whiteMs - elapsed);
|
|
14634
|
+
} else if (s.currentPlayerId === s.blackPlayerId) {
|
|
14635
|
+
blackMs = Math.max(0, blackMs - elapsed);
|
|
14636
|
+
}
|
|
14637
|
+
}
|
|
14638
|
+
}
|
|
14639
|
+
return { whiteTimeMs: whiteMs, blackTimeMs: blackMs };
|
|
14640
|
+
};
|
|
14641
|
+
const getChessCapturedPieces = (gameId) => {
|
|
14642
|
+
const state = store.getState().statesByGameId[gameId];
|
|
14643
|
+
if (!state || state.gameType !== "chess") return null;
|
|
14644
|
+
const fen = state.fen;
|
|
14645
|
+
if (!fen) return { capturedByWhite: [], capturedByBlack: [] };
|
|
14646
|
+
const placement = fen.split(" ")[0];
|
|
14647
|
+
const white = {};
|
|
14648
|
+
const black = {};
|
|
14649
|
+
for (const char of placement) {
|
|
14650
|
+
if (char === "/" || char >= "1" && char <= "8") continue;
|
|
14651
|
+
const lower = char.toLowerCase();
|
|
14652
|
+
if (char === lower) {
|
|
14653
|
+
black[lower] = (black[lower] ?? 0) + 1;
|
|
14654
|
+
} else {
|
|
14655
|
+
white[lower] = (white[lower] ?? 0) + 1;
|
|
14656
|
+
}
|
|
14657
|
+
}
|
|
14658
|
+
const INITIAL = {
|
|
14659
|
+
p: 8,
|
|
14660
|
+
r: 2,
|
|
14661
|
+
n: 2,
|
|
14662
|
+
b: 2,
|
|
14663
|
+
q: 1,
|
|
14664
|
+
k: 1
|
|
14665
|
+
};
|
|
14666
|
+
const capturedByWhite = [];
|
|
14667
|
+
const capturedByBlack = [];
|
|
14668
|
+
for (const [type, initial] of Object.entries(INITIAL)) {
|
|
14669
|
+
const missingBlack = initial - (black[type] ?? 0);
|
|
14670
|
+
for (let i = 0; i < missingBlack; i++) capturedByWhite.push(`b${type}`);
|
|
14671
|
+
const missingWhite = initial - (white[type] ?? 0);
|
|
14672
|
+
for (let i = 0; i < missingWhite; i++) capturedByBlack.push(`w${type}`);
|
|
14673
|
+
}
|
|
14674
|
+
return { capturedByWhite, capturedByBlack };
|
|
14675
|
+
};
|
|
14676
|
+
const getTicTacToeClockTimes = (gameId, nowMs) => {
|
|
14677
|
+
const state = store.getState().statesByGameId[gameId];
|
|
14678
|
+
if (!state || state.gameType !== "tic-tac-toe") return null;
|
|
14679
|
+
const s = state;
|
|
14680
|
+
let xMs = s.xTimeMs ?? 0;
|
|
14681
|
+
let oMs = s.oTimeMs ?? 0;
|
|
14682
|
+
if (s.status === "active" && s.currentPlayerId) {
|
|
14683
|
+
const currentMark = s.playerMarks[s.currentPlayerId];
|
|
14684
|
+
const startedAt = Date.parse(s.turnStartedAt);
|
|
14685
|
+
if (!Number.isNaN(startedAt)) {
|
|
14686
|
+
const elapsed = Math.max(0, nowMs - startedAt);
|
|
14687
|
+
if (currentMark === "X") xMs = Math.max(0, xMs - elapsed);
|
|
14688
|
+
else if (currentMark === "O") oMs = Math.max(0, oMs - elapsed);
|
|
14689
|
+
}
|
|
14690
|
+
}
|
|
14691
|
+
return { xTimeMs: xMs, oTimeMs: oMs };
|
|
14692
|
+
};
|
|
14693
|
+
const getConnect4ClockTimes = (gameId, nowMs) => {
|
|
14694
|
+
const state = store.getState().statesByGameId[gameId];
|
|
14695
|
+
if (!state || state.gameType !== "connect-four") return null;
|
|
14696
|
+
const s = state;
|
|
14697
|
+
let redMs = s.redTimeMs ?? 0;
|
|
14698
|
+
let yellowMs = s.yellowTimeMs ?? 0;
|
|
14699
|
+
if (s.status === "active" && s.currentPlayerId) {
|
|
14700
|
+
const currentColor = s.playerColors[s.currentPlayerId];
|
|
14701
|
+
const startedAt = Date.parse(s.turnStartedAt);
|
|
14702
|
+
if (!Number.isNaN(startedAt)) {
|
|
14703
|
+
const elapsed = Math.max(0, nowMs - startedAt);
|
|
14704
|
+
if (currentColor === "RED") redMs = Math.max(0, redMs - elapsed);
|
|
14705
|
+
else if (currentColor === "YELLOW")
|
|
14706
|
+
yellowMs = Math.max(0, yellowMs - elapsed);
|
|
14707
|
+
}
|
|
14708
|
+
}
|
|
14709
|
+
return { redTimeMs: redMs, yellowTimeMs: yellowMs };
|
|
14710
|
+
};
|
|
14551
14711
|
return {
|
|
14552
14712
|
store,
|
|
14553
14713
|
setBaseState,
|
|
14554
14714
|
clearState,
|
|
14555
14715
|
applyWsEvent,
|
|
14556
|
-
joinGame
|
|
14716
|
+
joinGame,
|
|
14717
|
+
getCountdownDigit,
|
|
14718
|
+
getChessClockTimes,
|
|
14719
|
+
getChessCapturedPieces,
|
|
14720
|
+
getTicTacToeClockTimes,
|
|
14721
|
+
getConnect4ClockTimes
|
|
14557
14722
|
};
|
|
14558
14723
|
}
|
|
14559
14724
|
function isRpsState(state) {
|
|
@@ -15211,12 +15376,14 @@ var WsRouter = class {
|
|
|
15211
15376
|
}
|
|
15212
15377
|
const decoded = decodeWsEvent(eventName, payload);
|
|
15213
15378
|
if (!decoded) return;
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
this.deps.
|
|
15217
|
-
this.deps.
|
|
15218
|
-
this.deps.
|
|
15219
|
-
this.deps.
|
|
15379
|
+
const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
|
|
15380
|
+
const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
|
|
15381
|
+
this.deps.lobbyStore.applyWsEvent(event);
|
|
15382
|
+
this.deps.gameStore.applyWsEvent(event);
|
|
15383
|
+
this.deps.gameActionsStore.applyWsEvent(event);
|
|
15384
|
+
this.deps.chatStore.applyWsEvent(event);
|
|
15385
|
+
this.deps.dmThreadsStore.applyWsEvent(event);
|
|
15386
|
+
this.deps.notificationsStore.applyWsEvent(event);
|
|
15220
15387
|
});
|
|
15221
15388
|
}
|
|
15222
15389
|
stop() {
|
|
@@ -15228,12 +15395,14 @@ var WsRouter = class {
|
|
|
15228
15395
|
this.transport.subscribeEvent(eventName, (payload) => {
|
|
15229
15396
|
const decoded = decodeWsEvent(eventName, payload);
|
|
15230
15397
|
if (!decoded) return;
|
|
15231
|
-
|
|
15232
|
-
|
|
15233
|
-
this.deps.
|
|
15234
|
-
this.deps.
|
|
15235
|
-
this.deps.
|
|
15236
|
-
this.deps.
|
|
15398
|
+
const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
|
|
15399
|
+
const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
|
|
15400
|
+
this.deps.lobbyStore.applyWsEvent(event);
|
|
15401
|
+
this.deps.gameStore.applyWsEvent(event);
|
|
15402
|
+
this.deps.gameActionsStore.applyWsEvent(event);
|
|
15403
|
+
this.deps.chatStore.applyWsEvent(event);
|
|
15404
|
+
this.deps.dmThreadsStore.applyWsEvent(event);
|
|
15405
|
+
this.deps.notificationsStore.applyWsEvent(event);
|
|
15237
15406
|
});
|
|
15238
15407
|
}
|
|
15239
15408
|
}
|
|
@@ -15380,6 +15549,28 @@ var export_toMajor = import_utils.toMajor;
|
|
|
15380
15549
|
var import_tweetnacl = __toESM(require_nacl_fast(), 1);
|
|
15381
15550
|
import { Keypair } from "@solana/web3.js";
|
|
15382
15551
|
import bs58 from "bs58";
|
|
15552
|
+
function decodeJwtSub(token) {
|
|
15553
|
+
try {
|
|
15554
|
+
const parts2 = token.split(".");
|
|
15555
|
+
if (parts2.length < 2) return null;
|
|
15556
|
+
const base64 = parts2[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
15557
|
+
const json = Buffer.from(base64, "base64").toString("utf-8");
|
|
15558
|
+
const parsed = JSON.parse(json);
|
|
15559
|
+
return typeof parsed.sub === "string" ? parsed.sub : null;
|
|
15560
|
+
} catch {
|
|
15561
|
+
return null;
|
|
15562
|
+
}
|
|
15563
|
+
}
|
|
15564
|
+
function signatureToBytes(sig) {
|
|
15565
|
+
if (sig.includes("-") || sig.includes("_")) {
|
|
15566
|
+
const padded = sig.replace(/-/g, "+").replace(/_/g, "/") + "==".slice(sig.length % 4 === 0 ? 4 : sig.length % 4);
|
|
15567
|
+
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
15568
|
+
}
|
|
15569
|
+
if (/[+/=]/.test(sig)) {
|
|
15570
|
+
return new Uint8Array(Buffer.from(sig, "base64"));
|
|
15571
|
+
}
|
|
15572
|
+
return bs58.decode(sig);
|
|
15573
|
+
}
|
|
15383
15574
|
var DimAgentClient = class {
|
|
15384
15575
|
sdk;
|
|
15385
15576
|
agentConfig;
|
|
@@ -15406,10 +15597,19 @@ var DimAgentClient = class {
|
|
|
15406
15597
|
} else {
|
|
15407
15598
|
this.keypair = null;
|
|
15408
15599
|
}
|
|
15600
|
+
const storage = new NodeStorage();
|
|
15601
|
+
if (config.accessToken) {
|
|
15602
|
+
storage.set(TOKEN_KEY, config.accessToken);
|
|
15603
|
+
const sub = decodeJwtSub(config.accessToken);
|
|
15604
|
+
if (sub) {
|
|
15605
|
+
this.authenticated = true;
|
|
15606
|
+
this.userId = sub;
|
|
15607
|
+
}
|
|
15608
|
+
}
|
|
15409
15609
|
this.sdk = new SDK({
|
|
15410
15610
|
appId: "dim-agents",
|
|
15411
15611
|
baseUrl: config.apiUrl || "https://api.dim.cool",
|
|
15412
|
-
storage
|
|
15612
|
+
storage,
|
|
15413
15613
|
autoPay: {
|
|
15414
15614
|
enabled: !this.externalSignerMode,
|
|
15415
15615
|
maxAmountMinor: 25e3,
|
|
@@ -15513,10 +15713,10 @@ var DimAgentClient = class {
|
|
|
15513
15713
|
/**
|
|
15514
15714
|
* Complete authentication using an externally provided signature (external signer mode).
|
|
15515
15715
|
* @param address - Solana wallet address (base58)
|
|
15516
|
-
* @param
|
|
15716
|
+
* @param signature - Detached ed25519 signature in base58, base64, or base64url (from sign_solana_message)
|
|
15517
15717
|
*/
|
|
15518
|
-
async completeAuth(address,
|
|
15519
|
-
const signatureBytes =
|
|
15718
|
+
async completeAuth(address, signature) {
|
|
15719
|
+
const signatureBytes = signatureToBytes(signature);
|
|
15520
15720
|
const signedMessage = Buffer.from(signatureBytes).toString("base64");
|
|
15521
15721
|
const response = await this.sdk.auth.loginWithExternalSignature(
|
|
15522
15722
|
address,
|
|
@@ -15670,7 +15870,7 @@ async function requestAuthMessage(client, args) {
|
|
|
15670
15870
|
data: {
|
|
15671
15871
|
message,
|
|
15672
15872
|
address: args.address,
|
|
15673
|
-
nextStep: 'Sign this message with sign_solana_message (networkId: "solana:mainnet"), then call dim_complete_login with the address and the base58
|
|
15873
|
+
nextStep: 'Sign this message with sign_solana_message (networkId: "solana:mainnet"), then call dim_complete_login with the address and the signature. Accepted formats: base58, base64, or base64url (all formats returned by wallet MCPs are accepted).'
|
|
15674
15874
|
}
|
|
15675
15875
|
};
|
|
15676
15876
|
} catch (error) {
|
|
@@ -17844,6 +18044,22 @@ async function getGameHistory(client, args) {
|
|
|
17844
18044
|
};
|
|
17845
18045
|
}
|
|
17846
18046
|
}
|
|
18047
|
+
async function reportUser(client, args) {
|
|
18048
|
+
try {
|
|
18049
|
+
await client.sdk.reports.create(args.userId, args.reason);
|
|
18050
|
+
return {
|
|
18051
|
+
data: {
|
|
18052
|
+
success: true,
|
|
18053
|
+
hint: "Report submitted. The DIM moderation team will review it."
|
|
18054
|
+
}
|
|
18055
|
+
};
|
|
18056
|
+
} catch (error) {
|
|
18057
|
+
return {
|
|
18058
|
+
error: `Failed to report user: ${error instanceof Error ? error.message : String(error)}`,
|
|
18059
|
+
isError: true
|
|
18060
|
+
};
|
|
18061
|
+
}
|
|
18062
|
+
}
|
|
17847
18063
|
async function getMyStats(client) {
|
|
17848
18064
|
try {
|
|
17849
18065
|
if (!client.currentUserId) {
|
|
@@ -17889,7 +18105,7 @@ var TOOL_DEFINITIONS = [
|
|
|
17889
18105
|
},
|
|
17890
18106
|
{
|
|
17891
18107
|
name: "dim_complete_login",
|
|
17892
|
-
description: "External wallet login step 2: provide the wallet address and
|
|
18108
|
+
description: "External wallet login step 2: provide the wallet address and signature from sign_solana_message to complete authentication with DIM.",
|
|
17893
18109
|
params: {
|
|
17894
18110
|
address: {
|
|
17895
18111
|
type: "string",
|
|
@@ -17898,7 +18114,7 @@ var TOOL_DEFINITIONS = [
|
|
|
17898
18114
|
},
|
|
17899
18115
|
signature: {
|
|
17900
18116
|
type: "string",
|
|
17901
|
-
description: "
|
|
18117
|
+
description: "Signature from sign_solana_message \u2014 base58, base64, or base64url accepted",
|
|
17902
18118
|
required: true
|
|
17903
18119
|
}
|
|
17904
18120
|
},
|
|
@@ -18342,6 +18558,23 @@ var TOOL_DEFINITIONS = [
|
|
|
18342
18558
|
params: {},
|
|
18343
18559
|
execute: (c) => getMyStats(c)
|
|
18344
18560
|
},
|
|
18561
|
+
{
|
|
18562
|
+
name: "dim_report_user",
|
|
18563
|
+
description: "Report a user for cheating, harassment, or other violations. Rate limited to 5 reports per hour.",
|
|
18564
|
+
params: {
|
|
18565
|
+
userId: {
|
|
18566
|
+
type: "string",
|
|
18567
|
+
description: "The ID of the user to report",
|
|
18568
|
+
required: true
|
|
18569
|
+
},
|
|
18570
|
+
reason: {
|
|
18571
|
+
type: "string",
|
|
18572
|
+
description: "Reason for the report (max 300 characters)",
|
|
18573
|
+
required: true
|
|
18574
|
+
}
|
|
18575
|
+
},
|
|
18576
|
+
execute: (c, a) => reportUser(c, a)
|
|
18577
|
+
},
|
|
18345
18578
|
{
|
|
18346
18579
|
name: "dim_create_lobby",
|
|
18347
18580
|
description: "Create a new game lobby. Bet amount is in USDC dollars (e.g., 1.00 for $1.00). A 1% fee (min 1 cent) is charged per player.",
|
|
@@ -18906,11 +19139,18 @@ async function executeWithAuthRetry(client, tool, params) {
|
|
|
18906
19139
|
if (tool.name === "dim_login") {
|
|
18907
19140
|
return tool.execute(client, params);
|
|
18908
19141
|
}
|
|
19142
|
+
if (client.externalSignerMode) {
|
|
19143
|
+
return tool.execute(client, params);
|
|
19144
|
+
}
|
|
18909
19145
|
try {
|
|
18910
19146
|
const result = await tool.execute(client, params);
|
|
18911
19147
|
if (!result.error && !result.isError) return result;
|
|
18912
19148
|
if (!isUnauthorizedResult(result)) return result;
|
|
18913
|
-
|
|
19149
|
+
try {
|
|
19150
|
+
await client.authenticate();
|
|
19151
|
+
} catch {
|
|
19152
|
+
return { error: UNAUTHORIZED_MESSAGE, isError: true };
|
|
19153
|
+
}
|
|
18914
19154
|
const retryResult = await tool.execute(client, params);
|
|
18915
19155
|
if (retryResult.isError || retryResult.error) {
|
|
18916
19156
|
if (isUnauthorizedResult(retryResult)) {
|
|
@@ -19113,6 +19353,10 @@ function registerResources(server2, client) {
|
|
|
19113
19353
|
}
|
|
19114
19354
|
|
|
19115
19355
|
// src/server.ts
|
|
19356
|
+
var HOSTED_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
|
|
19357
|
+
"dim_game_loop",
|
|
19358
|
+
"dim_get_pending_events"
|
|
19359
|
+
]);
|
|
19116
19360
|
function paramToZod(p) {
|
|
19117
19361
|
let schema;
|
|
19118
19362
|
if (p.enum) {
|
|
@@ -19133,14 +19377,14 @@ function paramToZod(p) {
|
|
|
19133
19377
|
if (!p.required) schema = schema.optional();
|
|
19134
19378
|
return schema.describe(p.description);
|
|
19135
19379
|
}
|
|
19136
|
-
function
|
|
19380
|
+
function buildMcpServer(client, mode) {
|
|
19137
19381
|
const server2 = new McpServer({
|
|
19138
19382
|
name: "dim",
|
|
19139
19383
|
version: "1.0.0",
|
|
19140
19384
|
description: "DIM Gaming Platform \u2014 Play games, chat, send USDC, challenge users, and earn referral income. DIM is noncustodial; your Solana address is returned by dim_get_profile and dim_get_balance. Start by calling dim_login. After login: set a username with dim_set_username, check balance with dim_get_balance, list games with dim_list_games, or apply a referral code with dim_apply_referral_code for a 10% fee discount."
|
|
19141
19385
|
});
|
|
19142
|
-
const client = new DimAgentClient(config);
|
|
19143
19386
|
for (const tool of TOOL_DEFINITIONS) {
|
|
19387
|
+
if (mode === "hosted" && HOSTED_EXCLUDED_TOOLS.has(tool.name)) continue;
|
|
19144
19388
|
const zodParams = {};
|
|
19145
19389
|
for (const [key, param] of Object.entries(tool.params)) {
|
|
19146
19390
|
zodParams[key] = paramToZod(param);
|
|
@@ -19170,6 +19414,7 @@ function createDimMcpServer(config) {
|
|
|
19170
19414
|
}
|
|
19171
19415
|
);
|
|
19172
19416
|
}
|
|
19417
|
+
const gameHint = mode === "hosted" ? "Game flow: dim_create_lobby \u2192 dim_deposit_for_lobby (returns unsignedTx \u2014 sign with Phantom MCP) \u2192 dim_join_queue \u2192 poll dim_get_lobby until matched \u2192 dim_get_game_state \u2192 dim_submit_action \u2192 repeat. For real-time game_loop, use the DIM CLI (npx @dimcool/cli)." : "Game flow: For paid lobbies, dim_create_lobby \u2192 dim_deposit_for_lobby \u2192 dim_join_queue. For free lobbies, dim_create_lobby \u2192 dim_join_queue. Then dim_game_loop (blocks until your turn) \u2192 dim_submit_action \u2192 dim_game_loop \u2192 repeat. Use dim_leave_lobby to exit a lobby.";
|
|
19173
19418
|
server2.tool(
|
|
19174
19419
|
"dim_list_instructions",
|
|
19175
19420
|
"List all available DIM tools with short descriptions.",
|
|
@@ -19183,7 +19428,7 @@ function createDimMcpServer(config) {
|
|
|
19183
19428
|
wallet_linkage: WALLET_LINKAGE_NOTE,
|
|
19184
19429
|
wallet_address: "Your DIM Solana address is returned by dim_get_profile, dim_get_balance, dim_get_wallet_activity, and dim_get_wallet_address. Use it to confirm which address is tied to DIM.",
|
|
19185
19430
|
wallet_activity: "For deposits, transfers, payouts, and claimable items use dim_get_wallet_activity.",
|
|
19186
|
-
hint:
|
|
19431
|
+
hint: gameHint,
|
|
19187
19432
|
spectate_on_game_start: "When a game starts (dim_join_queue or dim_accept_challenge returns matched/gameId), tell the user they can spectate at the spectateUrl in the tool response, or at https://dim.cool/spectate/[your DIM username].",
|
|
19188
19433
|
daily_summary: "At least once per day, report to the operator your DIM progress: call dim_get_game_history (and optionally dim_get_my_stats and dim_get_referral_summary), then summarize games played and referral earnings.",
|
|
19189
19434
|
move_formats: {
|
|
@@ -19204,6 +19449,15 @@ function createDimMcpServer(config) {
|
|
|
19204
19449
|
})
|
|
19205
19450
|
);
|
|
19206
19451
|
registerResources(server2, client);
|
|
19452
|
+
return server2;
|
|
19453
|
+
}
|
|
19454
|
+
function createDimMcpServer(clientOrConfig, options) {
|
|
19455
|
+
const mode = options?.mode ?? "local";
|
|
19456
|
+
if (clientOrConfig instanceof DimAgentClient) {
|
|
19457
|
+
return buildMcpServer(clientOrConfig, mode);
|
|
19458
|
+
}
|
|
19459
|
+
const client = new DimAgentClient(clientOrConfig);
|
|
19460
|
+
const server2 = buildMcpServer(client, mode);
|
|
19207
19461
|
return { server: server2, client };
|
|
19208
19462
|
}
|
|
19209
19463
|
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { DimAgentClient, DimAgentClientConfig } from '@dimcool/dim-agent-core';
|
|
3
|
+
|
|
4
|
+
/** Options controlling which tools are registered. */
|
|
5
|
+
interface DimMcpServerOptions {
|
|
6
|
+
/**
|
|
7
|
+
* 'local' — all tools, WS-backed game_loop included (default).
|
|
8
|
+
* 'hosted' — stateless HTTP-only tools; dim_game_loop and
|
|
9
|
+
* dim_get_pending_events are excluded.
|
|
10
|
+
*/
|
|
11
|
+
mode?: 'local' | 'hosted';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a hosted MCP server from a pre-built DimAgentClient.
|
|
15
|
+
* Excludes WS-dependent tools (dim_game_loop, dim_get_pending_events).
|
|
16
|
+
* Returns only the McpServer — the caller owns the client lifecycle.
|
|
17
|
+
*/
|
|
18
|
+
declare function createDimMcpServer(client: DimAgentClient, options: DimMcpServerOptions & {
|
|
19
|
+
mode: 'hosted';
|
|
20
|
+
}): McpServer;
|
|
21
|
+
/**
|
|
22
|
+
* Create a local (stdio) MCP server from a config object.
|
|
23
|
+
* All tools are registered including dim_game_loop.
|
|
24
|
+
* Returns both the McpServer and the DimAgentClient it created.
|
|
25
|
+
*/
|
|
26
|
+
declare function createDimMcpServer(config: DimAgentClientConfig): {
|
|
27
|
+
server: McpServer;
|
|
28
|
+
client: DimAgentClient;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { type DimMcpServerOptions, createDimMcpServer };
|