run-dmcp 0.1.0
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/LICENSE +22 -0
- package/README.md +79 -0
- package/dist/__tests__/engineVocabulary.test.d.ts +1 -0
- package/dist/__tests__/engineVocabulary.test.js +147 -0
- package/dist/db/__tests__/connection.test.d.ts +1 -0
- package/dist/db/__tests__/connection.test.js +72 -0
- package/dist/db/__tests__/testDb.d.ts +33 -0
- package/dist/db/__tests__/testDb.js +41 -0
- package/dist/db/connection.d.ts +22 -0
- package/dist/db/connection.js +107 -0
- package/dist/db/schema.d.ts +1 -0
- package/dist/db/schema.js +725 -0
- package/dist/events/emitter.d.ts +22 -0
- package/dist/events/emitter.js +71 -0
- package/dist/http/server.d.ts +3 -0
- package/dist/http/server.js +649 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +92 -0
- package/dist/register/abilities.d.ts +2 -0
- package/dist/register/abilities.js +165 -0
- package/dist/register/audio.d.ts +2 -0
- package/dist/register/audio.js +326 -0
- package/dist/register/batch.d.ts +2 -0
- package/dist/register/batch.js +343 -0
- package/dist/register/character.d.ts +2 -0
- package/dist/register/character.js +324 -0
- package/dist/register/combat.d.ts +2 -0
- package/dist/register/combat.js +207 -0
- package/dist/register/core.d.ts +2 -0
- package/dist/register/core.js +1040 -0
- package/dist/register/display.d.ts +2 -0
- package/dist/register/display.js +263 -0
- package/dist/register/factions.d.ts +2 -0
- package/dist/register/factions.js +186 -0
- package/dist/register/images.d.ts +2 -0
- package/dist/register/images.js +400 -0
- package/dist/register/inventory.d.ts +2 -0
- package/dist/register/inventory.js +115 -0
- package/dist/register/mcp-prompts.d.ts +2 -0
- package/dist/register/mcp-prompts.js +684 -0
- package/dist/register/mcp-resources.d.ts +2 -0
- package/dist/register/mcp-resources.js +335 -0
- package/dist/register/narrative.d.ts +2 -0
- package/dist/register/narrative.js +242 -0
- package/dist/register/notes.d.ts +2 -0
- package/dist/register/notes.js +170 -0
- package/dist/register/pause.d.ts +2 -0
- package/dist/register/pause.js +580 -0
- package/dist/register/quests.d.ts +2 -0
- package/dist/register/quests.js +118 -0
- package/dist/register/relationships.d.ts +2 -0
- package/dist/register/relationships.js +147 -0
- package/dist/register/resources.d.ts +2 -0
- package/dist/register/resources.js +277 -0
- package/dist/register/secrets.d.ts +2 -0
- package/dist/register/secrets.js +192 -0
- package/dist/register/status.d.ts +2 -0
- package/dist/register/status.js +130 -0
- package/dist/register/tables.d.ts +2 -0
- package/dist/register/tables.js +146 -0
- package/dist/register/tags.d.ts +2 -0
- package/dist/register/tags.js +114 -0
- package/dist/register/time.d.ts +2 -0
- package/dist/register/time.js +281 -0
- package/dist/register/world.d.ts +2 -0
- package/dist/register/world.js +127 -0
- package/dist/schemas/index.d.ts +921 -0
- package/dist/schemas/index.js +121 -0
- package/dist/test-setup.d.ts +1 -0
- package/dist/test-setup.js +13 -0
- package/dist/tools/__tests__/audio.test.d.ts +1 -0
- package/dist/tools/__tests__/audio.test.js +59 -0
- package/dist/tools/__tests__/conserved.test.d.ts +1 -0
- package/dist/tools/__tests__/conserved.test.js +488 -0
- package/dist/tools/__tests__/constraint.test.d.ts +1 -0
- package/dist/tools/__tests__/constraint.test.js +212 -0
- package/dist/tools/__tests__/expiry-consequences.test.d.ts +1 -0
- package/dist/tools/__tests__/expiry-consequences.test.js +110 -0
- package/dist/tools/__tests__/images.test.d.ts +1 -0
- package/dist/tools/__tests__/images.test.js +59 -0
- package/dist/tools/__tests__/relationship.test.d.ts +1 -0
- package/dist/tools/__tests__/relationship.test.js +132 -0
- package/dist/tools/__tests__/resource-constraints.test.d.ts +1 -0
- package/dist/tools/__tests__/resource-constraints.test.js +131 -0
- package/dist/tools/__tests__/resource.test.d.ts +1 -0
- package/dist/tools/__tests__/resource.test.js +190 -0
- package/dist/tools/__tests__/time.test.d.ts +1 -0
- package/dist/tools/__tests__/time.test.js +404 -0
- package/dist/tools/__tests__/timers.test.d.ts +1 -0
- package/dist/tools/__tests__/timers.test.js +426 -0
- package/dist/tools/__tests__/world.test.d.ts +1 -0
- package/dist/tools/__tests__/world.test.js +70 -0
- package/dist/tools/ability.d.ts +48 -0
- package/dist/tools/ability.js +238 -0
- package/dist/tools/audio.d.ts +24 -0
- package/dist/tools/audio.js +365 -0
- package/dist/tools/character.d.ts +70 -0
- package/dist/tools/character.js +309 -0
- package/dist/tools/combat.d.ts +13 -0
- package/dist/tools/combat.js +195 -0
- package/dist/tools/constraint.d.ts +132 -0
- package/dist/tools/constraint.js +269 -0
- package/dist/tools/dice.d.ts +23 -0
- package/dist/tools/dice.js +111 -0
- package/dist/tools/display.d.ts +120 -0
- package/dist/tools/display.js +528 -0
- package/dist/tools/faction.d.ts +61 -0
- package/dist/tools/faction.js +269 -0
- package/dist/tools/game.d.ts +96 -0
- package/dist/tools/game.js +526 -0
- package/dist/tools/image-prompt.d.ts +49 -0
- package/dist/tools/image-prompt.js +479 -0
- package/dist/tools/images.d.ts +47 -0
- package/dist/tools/images.js +449 -0
- package/dist/tools/inventory.d.ts +20 -0
- package/dist/tools/inventory.js +145 -0
- package/dist/tools/narrative.d.ts +58 -0
- package/dist/tools/narrative.js +237 -0
- package/dist/tools/notes.d.ts +41 -0
- package/dist/tools/notes.js +220 -0
- package/dist/tools/pause.d.ts +110 -0
- package/dist/tools/pause.js +1254 -0
- package/dist/tools/quest.d.ts +34 -0
- package/dist/tools/quest.js +164 -0
- package/dist/tools/relationship.d.ts +74 -0
- package/dist/tools/relationship.js +324 -0
- package/dist/tools/resource.d.ts +93 -0
- package/dist/tools/resource.js +374 -0
- package/dist/tools/rules.d.ts +4 -0
- package/dist/tools/rules.js +30 -0
- package/dist/tools/secrets.d.ts +49 -0
- package/dist/tools/secrets.js +195 -0
- package/dist/tools/status.d.ts +36 -0
- package/dist/tools/status.js +218 -0
- package/dist/tools/tables.d.ts +33 -0
- package/dist/tools/tables.js +209 -0
- package/dist/tools/tags.d.ts +52 -0
- package/dist/tools/tags.js +176 -0
- package/dist/tools/time.d.ts +33 -0
- package/dist/tools/time.js +276 -0
- package/dist/tools/timers.d.ts +41 -0
- package/dist/tools/timers.js +215 -0
- package/dist/tools/world.d.ts +78 -0
- package/dist/tools/world.js +331 -0
- package/dist/types/index.d.ts +969 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/__tests__/json.test.d.ts +1 -0
- package/dist/utils/__tests__/json.test.js +55 -0
- package/dist/utils/__tests__/validation.test.d.ts +1 -0
- package/dist/utils/__tests__/validation.test.js +90 -0
- package/dist/utils/errors.d.ts +44 -0
- package/dist/utils/errors.js +121 -0
- package/dist/utils/json.d.ts +9 -0
- package/dist/utils/json.js +23 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.js +50 -0
- package/dist/utils/output-schemas.d.ts +594 -0
- package/dist/utils/output-schemas.js +331 -0
- package/dist/utils/tool-annotations.d.ts +147 -0
- package/dist/utils/tool-annotations.js +98 -0
- package/dist/utils/validation.d.ts +34 -0
- package/dist/utils/validation.js +52 -0
- package/dist/utils/verbosity.d.ts +57 -0
- package/dist/utils/verbosity.js +67 -0
- package/dist/utils/webui.d.ts +20 -0
- package/dist/utils/webui.js +35 -0
- package/package.json +75 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { safeJsonParse, safeJsonParseOrNull } from '../json.js';
|
|
3
|
+
describe('safeJsonParse', () => {
|
|
4
|
+
it('should parse valid JSON', () => {
|
|
5
|
+
const result = safeJsonParse('{"name": "test"}', {});
|
|
6
|
+
expect(result).toEqual({ name: 'test' });
|
|
7
|
+
});
|
|
8
|
+
it('should return fallback for invalid JSON', () => {
|
|
9
|
+
const fallback = { default: true };
|
|
10
|
+
const result = safeJsonParse('not valid json', fallback);
|
|
11
|
+
expect(result).toEqual(fallback);
|
|
12
|
+
});
|
|
13
|
+
it('should return fallback for empty string', () => {
|
|
14
|
+
const fallback = [];
|
|
15
|
+
const result = safeJsonParse('', fallback);
|
|
16
|
+
expect(result).toEqual(fallback);
|
|
17
|
+
});
|
|
18
|
+
it('should parse arrays', () => {
|
|
19
|
+
const result = safeJsonParse('["a", "b", "c"]', []);
|
|
20
|
+
expect(result).toEqual(['a', 'b', 'c']);
|
|
21
|
+
});
|
|
22
|
+
it('should parse primitive values', () => {
|
|
23
|
+
expect(safeJsonParse('42', 0)).toBe(42);
|
|
24
|
+
expect(safeJsonParse('"hello"', '')).toBe('hello');
|
|
25
|
+
expect(safeJsonParse('true', false)).toBe(true);
|
|
26
|
+
expect(safeJsonParse('null', 'fallback')).toBe(null);
|
|
27
|
+
});
|
|
28
|
+
it('should return fallback for undefined JSON text', () => {
|
|
29
|
+
const result = safeJsonParse(undefined, { fallback: true });
|
|
30
|
+
expect(result).toEqual({ fallback: true });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('safeJsonParseOrNull', () => {
|
|
34
|
+
it('should parse valid JSON', () => {
|
|
35
|
+
const result = safeJsonParseOrNull('{"name": "test"}');
|
|
36
|
+
expect(result).toEqual({ name: 'test' });
|
|
37
|
+
});
|
|
38
|
+
it('should return null for invalid JSON', () => {
|
|
39
|
+
const result = safeJsonParseOrNull('not valid json');
|
|
40
|
+
expect(result).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
it('should return null for empty string', () => {
|
|
43
|
+
const result = safeJsonParseOrNull('');
|
|
44
|
+
expect(result).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
it('should parse arrays', () => {
|
|
47
|
+
const result = safeJsonParseOrNull('[1, 2, 3]');
|
|
48
|
+
expect(result).toEqual([1, 2, 3]);
|
|
49
|
+
});
|
|
50
|
+
it('should return null for malformed JSON', () => {
|
|
51
|
+
expect(safeJsonParseOrNull('{name: "test"}')).toBeNull();
|
|
52
|
+
expect(safeJsonParseOrNull('{"name": test}')).toBeNull();
|
|
53
|
+
expect(safeJsonParseOrNull('{incomplete')).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { LIMITS, validatedSchemas, boundedString, boundedArray } from '../validation.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
describe('LIMITS', () => {
|
|
5
|
+
it('should have correct values', () => {
|
|
6
|
+
expect(LIMITS.NAME_MAX).toBe(200);
|
|
7
|
+
expect(LIMITS.DESCRIPTION_MAX).toBe(5000);
|
|
8
|
+
expect(LIMITS.CONTENT_MAX).toBe(50000);
|
|
9
|
+
expect(LIMITS.NARRATIVE_MAX).toBe(200000);
|
|
10
|
+
expect(LIMITS.ARRAY_MAX).toBe(100);
|
|
11
|
+
expect(LIMITS.MAX_DEPTH).toBe(10);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
describe('validatedSchemas', () => {
|
|
15
|
+
describe('name', () => {
|
|
16
|
+
it('should accept valid names', () => {
|
|
17
|
+
expect(validatedSchemas.name.parse('Test Name')).toBe('Test Name');
|
|
18
|
+
});
|
|
19
|
+
it('should reject empty names', () => {
|
|
20
|
+
expect(() => validatedSchemas.name.parse('')).toThrow();
|
|
21
|
+
});
|
|
22
|
+
it('should reject names exceeding max length', () => {
|
|
23
|
+
const longName = 'a'.repeat(LIMITS.NAME_MAX + 1);
|
|
24
|
+
expect(() => validatedSchemas.name.parse(longName)).toThrow();
|
|
25
|
+
});
|
|
26
|
+
it('should accept names at max length', () => {
|
|
27
|
+
const maxName = 'a'.repeat(LIMITS.NAME_MAX);
|
|
28
|
+
expect(validatedSchemas.name.parse(maxName)).toBe(maxName);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe('description', () => {
|
|
32
|
+
it('should accept valid descriptions', () => {
|
|
33
|
+
expect(validatedSchemas.description.parse('A description')).toBe('A description');
|
|
34
|
+
});
|
|
35
|
+
it('should accept empty descriptions', () => {
|
|
36
|
+
expect(validatedSchemas.description.parse('')).toBe('');
|
|
37
|
+
});
|
|
38
|
+
it('should reject descriptions exceeding max length', () => {
|
|
39
|
+
const longDesc = 'a'.repeat(LIMITS.DESCRIPTION_MAX + 1);
|
|
40
|
+
expect(() => validatedSchemas.description.parse(longDesc)).toThrow();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('id', () => {
|
|
44
|
+
it('should accept valid UUIDs', () => {
|
|
45
|
+
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
46
|
+
expect(validatedSchemas.id.parse(uuid)).toBe(uuid);
|
|
47
|
+
});
|
|
48
|
+
it('should reject IDs exceeding 100 characters', () => {
|
|
49
|
+
const longId = 'a'.repeat(101);
|
|
50
|
+
expect(() => validatedSchemas.id.parse(longId)).toThrow();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe('stringArray', () => {
|
|
54
|
+
it('should accept valid arrays', () => {
|
|
55
|
+
const arr = ['one', 'two', 'three'];
|
|
56
|
+
expect(validatedSchemas.stringArray.parse(arr)).toEqual(arr);
|
|
57
|
+
});
|
|
58
|
+
it('should accept empty arrays', () => {
|
|
59
|
+
expect(validatedSchemas.stringArray.parse([])).toEqual([]);
|
|
60
|
+
});
|
|
61
|
+
it('should reject arrays exceeding max items', () => {
|
|
62
|
+
const longArr = Array(LIMITS.ARRAY_MAX + 1).fill('item');
|
|
63
|
+
expect(() => validatedSchemas.stringArray.parse(longArr)).toThrow();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('boundedString', () => {
|
|
68
|
+
it('should create a schema with default max length', () => {
|
|
69
|
+
const schema = boundedString();
|
|
70
|
+
expect(schema.parse('test')).toBe('test');
|
|
71
|
+
expect(() => schema.parse('a'.repeat(LIMITS.NAME_MAX + 1))).toThrow();
|
|
72
|
+
});
|
|
73
|
+
it('should create a schema with custom max length', () => {
|
|
74
|
+
const schema = boundedString(10);
|
|
75
|
+
expect(schema.parse('test')).toBe('test');
|
|
76
|
+
expect(() => schema.parse('a'.repeat(11))).toThrow();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('boundedArray', () => {
|
|
80
|
+
it('should create an array schema with default max items', () => {
|
|
81
|
+
const schema = boundedArray(z.string());
|
|
82
|
+
expect(schema.parse(['a', 'b'])).toEqual(['a', 'b']);
|
|
83
|
+
expect(() => schema.parse(Array(LIMITS.ARRAY_MAX + 1).fill('x'))).toThrow();
|
|
84
|
+
});
|
|
85
|
+
it('should create an array schema with custom max items', () => {
|
|
86
|
+
const schema = boundedArray(z.number(), 3);
|
|
87
|
+
expect(schema.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
|
88
|
+
expect(() => schema.parse([1, 2, 3, 4])).toThrow();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured error response with suggestions for agents
|
|
3
|
+
*/
|
|
4
|
+
export interface AgentError {
|
|
5
|
+
isError: true;
|
|
6
|
+
errorCode: string;
|
|
7
|
+
message: string;
|
|
8
|
+
entityType?: string;
|
|
9
|
+
entityId?: string;
|
|
10
|
+
suggestions: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create a structured error response with suggestions
|
|
14
|
+
*/
|
|
15
|
+
export declare function createError(errorCode: string, message: string, options?: {
|
|
16
|
+
entityType?: string;
|
|
17
|
+
entityId?: string;
|
|
18
|
+
additionalSuggestions?: string[];
|
|
19
|
+
}): AgentError;
|
|
20
|
+
/**
|
|
21
|
+
* Format an AgentError for MCP tool response
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatErrorResponse(error: AgentError): {
|
|
24
|
+
content: {
|
|
25
|
+
type: "text";
|
|
26
|
+
text: string;
|
|
27
|
+
}[];
|
|
28
|
+
isError: boolean;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Convenience helpers for common error types
|
|
32
|
+
*/
|
|
33
|
+
export declare const errors: {
|
|
34
|
+
gameNotFound: (gameId: string) => AgentError;
|
|
35
|
+
characterNotFound: (characterId: string) => AgentError;
|
|
36
|
+
locationNotFound: (locationId: string) => AgentError;
|
|
37
|
+
questNotFound: (questId: string) => AgentError;
|
|
38
|
+
itemNotFound: (itemId: string) => AgentError;
|
|
39
|
+
factionNotFound: (factionId: string) => AgentError;
|
|
40
|
+
abilityNotFound: (abilityId: string) => AgentError;
|
|
41
|
+
combatNotActive: (gameId: string) => AgentError;
|
|
42
|
+
invalidInput: (message: string, additionalSuggestions?: string[]) => AgentError;
|
|
43
|
+
constraintViolation: (resourceId: string, message: string) => AgentError;
|
|
44
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common error codes and their suggestions
|
|
3
|
+
*/
|
|
4
|
+
const ERROR_SUGGESTIONS = {
|
|
5
|
+
GAME_NOT_FOUND: [
|
|
6
|
+
'Use get_game_menu to see available games',
|
|
7
|
+
'Create a new game with create_game',
|
|
8
|
+
],
|
|
9
|
+
CHARACTER_NOT_FOUND: [
|
|
10
|
+
'Use list_characters to see available characters',
|
|
11
|
+
'Create a character with create_character',
|
|
12
|
+
],
|
|
13
|
+
LOCATION_NOT_FOUND: [
|
|
14
|
+
'Use list_locations to see available locations',
|
|
15
|
+
'Create a location with create_location',
|
|
16
|
+
],
|
|
17
|
+
QUEST_NOT_FOUND: [
|
|
18
|
+
'Use list_quests to see available quests',
|
|
19
|
+
'Create a quest with create_quest',
|
|
20
|
+
],
|
|
21
|
+
ITEM_NOT_FOUND: [
|
|
22
|
+
'Use list_items or get_inventory to see available items',
|
|
23
|
+
'Create an item with create_item',
|
|
24
|
+
],
|
|
25
|
+
FACTION_NOT_FOUND: [
|
|
26
|
+
'Use list_factions to see available factions',
|
|
27
|
+
'Create a faction with create_faction',
|
|
28
|
+
],
|
|
29
|
+
ABILITY_NOT_FOUND: [
|
|
30
|
+
'Use list_abilities to see available abilities',
|
|
31
|
+
'Create an ability with create_ability',
|
|
32
|
+
],
|
|
33
|
+
COMBAT_NOT_ACTIVE: [
|
|
34
|
+
'Start combat with start_combat',
|
|
35
|
+
'Check if combat exists with get_active_combat',
|
|
36
|
+
],
|
|
37
|
+
INVALID_INPUT: [
|
|
38
|
+
'Check the input schema for required fields',
|
|
39
|
+
'Verify all IDs reference existing entities',
|
|
40
|
+
],
|
|
41
|
+
CONSTRAINT_VIOLATION: [
|
|
42
|
+
'Use get_resource to check the current value and its min/max bounds',
|
|
43
|
+
'Use list_resource_constraints to see what constraint is declared and why the write was rejected',
|
|
44
|
+
'Choose a value that satisfies the constraint, or remove it with remove_resource_constraint if it no longer applies',
|
|
45
|
+
],
|
|
46
|
+
PERMISSION_DENIED: [
|
|
47
|
+
'Check if the entity belongs to the current game',
|
|
48
|
+
'Verify the character has permission for this action',
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Create a structured error response with suggestions
|
|
53
|
+
*/
|
|
54
|
+
export function createError(errorCode, message, options) {
|
|
55
|
+
const baseSuggestions = ERROR_SUGGESTIONS[errorCode] || [];
|
|
56
|
+
const suggestions = [...baseSuggestions, ...(options?.additionalSuggestions || [])];
|
|
57
|
+
return {
|
|
58
|
+
isError: true,
|
|
59
|
+
errorCode,
|
|
60
|
+
message,
|
|
61
|
+
entityType: options?.entityType,
|
|
62
|
+
entityId: options?.entityId,
|
|
63
|
+
suggestions,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Format an AgentError for MCP tool response
|
|
68
|
+
*/
|
|
69
|
+
export function formatErrorResponse(error) {
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: 'text',
|
|
74
|
+
text: JSON.stringify(error, null, 2),
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
isError: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Convenience helpers for common error types
|
|
82
|
+
*/
|
|
83
|
+
export const errors = {
|
|
84
|
+
gameNotFound: (gameId) => createError('GAME_NOT_FOUND', `Game '${gameId}' not found`, {
|
|
85
|
+
entityType: 'game',
|
|
86
|
+
entityId: gameId,
|
|
87
|
+
}),
|
|
88
|
+
characterNotFound: (characterId) => createError('CHARACTER_NOT_FOUND', `Character '${characterId}' not found`, {
|
|
89
|
+
entityType: 'character',
|
|
90
|
+
entityId: characterId,
|
|
91
|
+
}),
|
|
92
|
+
locationNotFound: (locationId) => createError('LOCATION_NOT_FOUND', `Location '${locationId}' not found`, {
|
|
93
|
+
entityType: 'location',
|
|
94
|
+
entityId: locationId,
|
|
95
|
+
}),
|
|
96
|
+
questNotFound: (questId) => createError('QUEST_NOT_FOUND', `Quest '${questId}' not found`, {
|
|
97
|
+
entityType: 'quest',
|
|
98
|
+
entityId: questId,
|
|
99
|
+
}),
|
|
100
|
+
itemNotFound: (itemId) => createError('ITEM_NOT_FOUND', `Item '${itemId}' not found`, {
|
|
101
|
+
entityType: 'item',
|
|
102
|
+
entityId: itemId,
|
|
103
|
+
}),
|
|
104
|
+
factionNotFound: (factionId) => createError('FACTION_NOT_FOUND', `Faction '${factionId}' not found`, {
|
|
105
|
+
entityType: 'faction',
|
|
106
|
+
entityId: factionId,
|
|
107
|
+
}),
|
|
108
|
+
abilityNotFound: (abilityId) => createError('ABILITY_NOT_FOUND', `Ability '${abilityId}' not found`, {
|
|
109
|
+
entityType: 'ability',
|
|
110
|
+
entityId: abilityId,
|
|
111
|
+
}),
|
|
112
|
+
combatNotActive: (gameId) => createError('COMBAT_NOT_ACTIVE', 'No active combat in this game', {
|
|
113
|
+
entityType: 'game',
|
|
114
|
+
entityId: gameId,
|
|
115
|
+
}),
|
|
116
|
+
invalidInput: (message, additionalSuggestions) => createError('INVALID_INPUT', message, { additionalSuggestions }),
|
|
117
|
+
constraintViolation: (resourceId, message) => createError('CONSTRAINT_VIOLATION', message, {
|
|
118
|
+
entityType: 'resource',
|
|
119
|
+
entityId: resourceId,
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safely parse JSON with a fallback value.
|
|
3
|
+
* Prevents crashes from corrupted JSON data in the database.
|
|
4
|
+
*/
|
|
5
|
+
export declare function safeJsonParse<T>(json: string, fallback: T): T;
|
|
6
|
+
/**
|
|
7
|
+
* Safely parse JSON, returning null on error.
|
|
8
|
+
*/
|
|
9
|
+
export declare function safeJsonParseOrNull<T>(json: string): T | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safely parse JSON with a fallback value.
|
|
3
|
+
* Prevents crashes from corrupted JSON data in the database.
|
|
4
|
+
*/
|
|
5
|
+
export function safeJsonParse(json, fallback) {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(json);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Safely parse JSON, returning null on error.
|
|
15
|
+
*/
|
|
16
|
+
export function safeJsonParseOrNull(json) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(json);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface Logger {
|
|
2
|
+
debug: (message: string, data?: Record<string, unknown>) => void;
|
|
3
|
+
info: (message: string, data?: Record<string, unknown>) => void;
|
|
4
|
+
warn: (message: string, data?: Record<string, unknown>) => void;
|
|
5
|
+
error: (message: string, data?: Record<string, unknown>) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function createLogger(module: string): Logger;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const LOG_LEVELS = {
|
|
2
|
+
debug: 0,
|
|
3
|
+
info: 1,
|
|
4
|
+
warn: 2,
|
|
5
|
+
error: 3,
|
|
6
|
+
};
|
|
7
|
+
function getMinLevel() {
|
|
8
|
+
const envLevel = process.env.DMCP_LOG_LEVEL?.toLowerCase();
|
|
9
|
+
if (envLevel && envLevel in LOG_LEVELS) {
|
|
10
|
+
return envLevel;
|
|
11
|
+
}
|
|
12
|
+
return "info";
|
|
13
|
+
}
|
|
14
|
+
function shouldLog(level) {
|
|
15
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[getMinLevel()];
|
|
16
|
+
}
|
|
17
|
+
function formatLog(entry) {
|
|
18
|
+
const base = `[${entry.timestamp}] ${entry.level.toUpperCase().padEnd(5)} [${entry.module}] ${entry.message}`;
|
|
19
|
+
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
20
|
+
return `${base} ${JSON.stringify(entry.data)}`;
|
|
21
|
+
}
|
|
22
|
+
return base;
|
|
23
|
+
}
|
|
24
|
+
export function createLogger(module) {
|
|
25
|
+
const log = (level, message, data) => {
|
|
26
|
+
if (!shouldLog(level))
|
|
27
|
+
return;
|
|
28
|
+
const entry = {
|
|
29
|
+
timestamp: new Date().toISOString(),
|
|
30
|
+
level,
|
|
31
|
+
module,
|
|
32
|
+
message,
|
|
33
|
+
data,
|
|
34
|
+
};
|
|
35
|
+
const formatted = formatLog(entry);
|
|
36
|
+
// Write to stderr so it doesn't interfere with MCP stdio transport
|
|
37
|
+
if (level === "error") {
|
|
38
|
+
console.error(formatted);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.error(formatted);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
debug: (message, data) => log("debug", message, data),
|
|
46
|
+
info: (message, data) => log("info", message, data),
|
|
47
|
+
warn: (message, data) => log("warn", message, data),
|
|
48
|
+
error: (message, data) => log("error", message, data),
|
|
49
|
+
};
|
|
50
|
+
}
|