/**
* EVENT INBOX TOOLS
*
* MCP tools for interacting with the event inbox:
* - poll_events: Get unread events (marks them consumed)
* - push_event: Manually push an event (DM/system use)
* - get_event_history: View recent events with filters
*/
import { z } from 'zod';
import { getDb } from '../storage/index.js';
import { EventInboxRepository, EventType, SourceType } from '../storage/repos/event-inbox.repo.js';
import { SessionContext } from './types.js';
const EventTypeEnum = z.enum([
'npc_action', 'combat_update', 'world_change', 'quest_update',
'time_passage', 'environmental', 'system'
]);
const SourceTypeEnum = z.enum(['npc', 'combat', 'world', 'system', 'scheduler']);
// Tool Definitions
export const EventInboxTools = {
POLL_EVENTS: {
name: 'poll_events',
description: `Poll the event inbox for unread events. Returns events and marks them as consumed.
Events are generated by NPCs, combat, world systems, and schedulers. They represent
things that happened "while the player wasn't looking" - making NPCs feel autonomous.
Example response:
[
{ "eventType": "npc_action", "payload": { "npcName": "Bartender", "action": "wipes a glass nervously" }},
{ "eventType": "quest_update", "payload": { "questName": "Missing Merchant", "update": "New rumor heard" }}
]`,
inputSchema: z.object({
limit: z.number().int().min(1).max(50).default(20).describe('Maximum events to return')
})
},
PUSH_EVENT: {
name: 'push_event',
description: `Push an event to the inbox. Used by DM or internal systems to queue events.
NPCs "doing things" on their own, combat updates, world changes - all go here.
Frontend polls this to show what happened.`,
inputSchema: z.object({
eventType: EventTypeEnum,
payload: z.record(z.any()).describe('Event data (JSON object)'),
sourceType: SourceTypeEnum.optional(),
sourceId: z.string().optional().describe('ID of source NPC/entity'),
priority: z.number().int().min(0).max(10).default(0).describe('0=normal, 10=urgent'),
expiresAt: z.string().optional().describe('ISO timestamp when event expires')
})
},
GET_EVENT_HISTORY: {
name: 'get_event_history',
description: 'Get recent event history with optional filters.',
inputSchema: z.object({
limit: z.number().int().min(1).max(100).default(50),
eventType: EventTypeEnum.optional(),
sourceType: SourceTypeEnum.optional(),
includeConsumed: z.boolean().default(true)
})
},
GET_PENDING_COUNT: {
name: 'get_pending_event_count',
description: 'Get the count of unread events in the inbox.',
inputSchema: z.object({})
}
} as const;
// Tool Handlers
export async function handlePollEvents(
args: z.infer<typeof EventInboxTools.POLL_EVENTS.inputSchema>,
_ctx: SessionContext
) {
const repo = new EventInboxRepository(getDb());
const events = repo.pollAndConsume(args.limit);
return {
content: [{
type: 'text' as const,
text: JSON.stringify({
count: events.length,
events: events.map(e => ({
id: e.id,
eventType: e.eventType,
payload: e.payload,
sourceType: e.sourceType,
sourceId: e.sourceId,
priority: e.priority,
createdAt: e.createdAt
}))
}, null, 2)
}]
};
}
export async function handlePushEvent(
args: z.infer<typeof EventInboxTools.PUSH_EVENT.inputSchema>,
_ctx: SessionContext
) {
const repo = new EventInboxRepository(getDb());
const id = repo.push({
eventType: args.eventType as EventType,
payload: args.payload,
sourceType: args.sourceType as SourceType | undefined,
sourceId: args.sourceId,
priority: args.priority,
expiresAt: args.expiresAt
});
return {
content: [{
type: 'text' as const,
text: JSON.stringify({
success: true,
eventId: id,
message: `Event queued with ID ${id}`
})
}]
};
}
export async function handleGetEventHistory(
args: z.infer<typeof EventInboxTools.GET_EVENT_HISTORY.inputSchema>,
_ctx: SessionContext
) {
const repo = new EventInboxRepository(getDb());
const events = repo.getHistory({
limit: args.limit,
eventType: args.eventType as EventType | undefined,
sourceType: args.sourceType as SourceType | undefined,
includeConsumed: args.includeConsumed
});
return {
content: [{
type: 'text' as const,
text: JSON.stringify({
count: events.length,
events
}, null, 2)
}]
};
}
export async function handleGetPendingCount(
_args: z.infer<typeof EventInboxTools.GET_PENDING_COUNT.inputSchema>,
_ctx: SessionContext
) {
const repo = new EventInboxRepository(getDb());
const count = repo.getPendingCount();
return {
content: [{
type: 'text' as const,
text: JSON.stringify({ pendingEvents: count })
}]
};
}