@danielsimonjr/memory-mcp 0.41.0 → 0.48.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.
@@ -0,0 +1,117 @@
1
+ /**
2
+ * MCP Tool Handlers
3
+ *
4
+ * Extracted from MCPServer.ts to reduce file size and improve maintainability.
5
+ * Contains handler functions for all 45 Knowledge Graph tools.
6
+ *
7
+ * @module server/toolHandlers
8
+ */
9
+ import { formatToolResponse, formatTextResponse, formatRawResponse } from '../utils/responseFormatter.js';
10
+ /**
11
+ * Registry of all tool handlers keyed by tool name.
12
+ */
13
+ export const toolHandlers = {
14
+ // ==================== ENTITY HANDLERS ====================
15
+ create_entities: async (manager, args) => formatToolResponse(await manager.createEntities(args.entities)),
16
+ delete_entities: async (manager, args) => {
17
+ await manager.deleteEntities(args.entityNames);
18
+ return formatTextResponse(`Deleted ${args.entityNames.length} entities`);
19
+ },
20
+ read_graph: async (manager) => formatToolResponse(await manager.readGraph()),
21
+ open_nodes: async (manager, args) => formatToolResponse(await manager.openNodes(args.names)),
22
+ // ==================== RELATION HANDLERS ====================
23
+ create_relations: async (manager, args) => formatToolResponse(await manager.createRelations(args.relations)),
24
+ delete_relations: async (manager, args) => {
25
+ await manager.deleteRelations(args.relations);
26
+ return formatTextResponse(`Deleted ${args.relations.length} relations`);
27
+ },
28
+ // ==================== OBSERVATION HANDLERS ====================
29
+ add_observations: async (manager, args) => formatToolResponse(await manager.addObservations(args.observations)),
30
+ delete_observations: async (manager, args) => {
31
+ await manager.deleteObservations(args.deletions);
32
+ return formatTextResponse('Observations deleted successfully');
33
+ },
34
+ // ==================== SEARCH HANDLERS ====================
35
+ search_nodes: async (manager, args) => formatToolResponse(await manager.searchNodes(args.query, args.tags, args.minImportance, args.maxImportance)),
36
+ search_by_date_range: async (manager, args) => formatToolResponse(await manager.searchByDateRange(args.startDate, args.endDate, args.entityType, args.tags)),
37
+ search_nodes_ranked: async (manager, args) => formatToolResponse(await manager.searchNodesRanked(args.query, args.tags, args.minImportance, args.maxImportance, args.limit)),
38
+ boolean_search: async (manager, args) => formatToolResponse(await manager.booleanSearch(args.query, args.tags, args.minImportance, args.maxImportance)),
39
+ fuzzy_search: async (manager, args) => formatToolResponse(await manager.fuzzySearch(args.query, args.threshold, args.tags, args.minImportance, args.maxImportance)),
40
+ get_search_suggestions: async (manager, args) => formatToolResponse(await manager.getSearchSuggestions(args.query, args.maxSuggestions)),
41
+ // ==================== SAVED SEARCH HANDLERS ====================
42
+ save_search: async (manager, args) => formatToolResponse(await manager.saveSearch(args)),
43
+ execute_saved_search: async (manager, args) => formatToolResponse(await manager.executeSavedSearch(args.name)),
44
+ list_saved_searches: async (manager) => formatToolResponse(await manager.listSavedSearches()),
45
+ delete_saved_search: async (manager, args) => {
46
+ const deleted = await manager.deleteSavedSearch(args.name);
47
+ return formatTextResponse(deleted
48
+ ? `Saved search "${args.name}" deleted successfully`
49
+ : `Saved search "${args.name}" not found`);
50
+ },
51
+ update_saved_search: async (manager, args) => formatToolResponse(await manager.updateSavedSearch(args.name, args.updates)),
52
+ // ==================== TAG HANDLERS ====================
53
+ add_tags: async (manager, args) => formatToolResponse(await manager.addTags(args.entityName, args.tags)),
54
+ remove_tags: async (manager, args) => formatToolResponse(await manager.removeTags(args.entityName, args.tags)),
55
+ set_importance: async (manager, args) => formatToolResponse(await manager.setImportance(args.entityName, args.importance)),
56
+ add_tags_to_multiple_entities: async (manager, args) => formatToolResponse(await manager.addTagsToMultipleEntities(args.entityNames, args.tags)),
57
+ replace_tag: async (manager, args) => formatToolResponse(await manager.replaceTag(args.oldTag, args.newTag)),
58
+ merge_tags: async (manager, args) => formatToolResponse(await manager.mergeTags(args.tag1, args.tag2, args.targetTag)),
59
+ // ==================== TAG ALIAS HANDLERS ====================
60
+ add_tag_alias: async (manager, args) => formatToolResponse(await manager.addTagAlias(args.alias, args.canonical, args.description)),
61
+ list_tag_aliases: async (manager) => formatToolResponse(await manager.listTagAliases()),
62
+ remove_tag_alias: async (manager, args) => {
63
+ const removed = await manager.removeTagAlias(args.alias);
64
+ return formatTextResponse(removed
65
+ ? `Tag alias "${args.alias}" removed successfully`
66
+ : `Tag alias "${args.alias}" not found`);
67
+ },
68
+ get_aliases_for_tag: async (manager, args) => formatToolResponse(await manager.getAliasesForTag(args.canonicalTag)),
69
+ resolve_tag: async (manager, args) => formatToolResponse({
70
+ tag: args.tag,
71
+ resolved: await manager.resolveTag(args.tag),
72
+ }),
73
+ // ==================== HIERARCHY HANDLERS ====================
74
+ set_entity_parent: async (manager, args) => formatToolResponse(await manager.setEntityParent(args.entityName, args.parentName)),
75
+ get_children: async (manager, args) => formatToolResponse(await manager.getChildren(args.entityName)),
76
+ get_parent: async (manager, args) => formatToolResponse(await manager.getParent(args.entityName)),
77
+ get_ancestors: async (manager, args) => formatToolResponse(await manager.getAncestors(args.entityName)),
78
+ get_descendants: async (manager, args) => formatToolResponse(await manager.getDescendants(args.entityName)),
79
+ get_subtree: async (manager, args) => formatToolResponse(await manager.getSubtree(args.entityName)),
80
+ get_root_entities: async (manager) => formatToolResponse(await manager.getRootEntities()),
81
+ get_entity_depth: async (manager, args) => formatToolResponse({
82
+ entityName: args.entityName,
83
+ depth: await manager.getEntityDepth(args.entityName),
84
+ }),
85
+ move_entity: async (manager, args) => formatToolResponse(await manager.moveEntity(args.entityName, args.newParentName)),
86
+ // ==================== ANALYTICS HANDLERS ====================
87
+ get_graph_stats: async (manager) => formatToolResponse(await manager.getGraphStats()),
88
+ validate_graph: async (manager) => formatToolResponse(await manager.validateGraph()),
89
+ // ==================== COMPRESSION HANDLERS ====================
90
+ find_duplicates: async (manager, args) => formatToolResponse(await manager.findDuplicates(args.threshold)),
91
+ merge_entities: async (manager, args) => formatToolResponse(await manager.mergeEntities(args.entityNames, args.targetName)),
92
+ compress_graph: async (manager, args) => formatToolResponse(await manager.compressGraph(args.threshold, args.dryRun)),
93
+ archive_entities: async (manager, args) => formatToolResponse(await manager.archiveEntities({
94
+ olderThan: args.olderThan,
95
+ importanceLessThan: args.importanceLessThan,
96
+ tags: args.tags,
97
+ }, args.dryRun)),
98
+ // ==================== IMPORT/EXPORT HANDLERS ====================
99
+ import_graph: async (manager, args) => formatToolResponse(await manager.importGraph(args.format, args.data, args.mergeStrategy, args.dryRun)),
100
+ export_graph: async (manager, args) => formatRawResponse(await manager.exportGraph(args.format, args.filter)),
101
+ };
102
+ /**
103
+ * Handle a tool call by dispatching to the appropriate handler.
104
+ *
105
+ * @param name - Tool name to call
106
+ * @param args - Tool arguments
107
+ * @param manager - Knowledge graph manager instance
108
+ * @returns Tool response
109
+ * @throws Error if tool name is unknown
110
+ */
111
+ export async function handleToolCall(name, args, manager) {
112
+ const handler = toolHandlers[name];
113
+ if (!handler) {
114
+ throw new Error(`Unknown tool: ${name}`);
115
+ }
116
+ return handler(manager, args);
117
+ }
@@ -2,6 +2,6 @@
2
2
  * Import/Export Types
3
3
  *
4
4
  * Type definitions for import and export operations, including
5
- * result summaries and compression results.
5
+ * result summaries, compression results, and export filters.
6
6
  */
7
7
  export {};
@@ -59,6 +59,7 @@ export const LOG_PREFIXES = {
59
59
  * Similarity scoring weights for duplicate detection.
60
60
  * These weights determine the relative importance of each factor
61
61
  * when calculating entity similarity for duplicate detection.
62
+ * Total weights must sum to 1.0 (100%).
62
63
  */
63
64
  export const SIMILARITY_WEIGHTS = {
64
65
  /** Name similarity weight (40%) - Uses Levenshtein distance */
@@ -66,9 +67,9 @@ export const SIMILARITY_WEIGHTS = {
66
67
  /** Entity type match weight (20%) - Exact match required */
67
68
  TYPE: 0.2,
68
69
  /** Observation overlap weight (30%) - Uses Jaccard similarity */
69
- OBSERVATION: 0.3,
70
+ OBSERVATIONS: 0.3,
70
71
  /** Tag overlap weight (10%) - Uses Jaccard similarity */
71
- TAG: 0.1,
72
+ TAGS: 0.1,
72
73
  };
73
74
  /**
74
75
  * Default threshold for duplicate detection (80% similarity required).
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Entity Lookup and Utility Functions
3
+ *
4
+ * Centralizes entity lookup patterns to eliminate redundant code across managers.
5
+ * Provides type-safe entity finding with optional error throwing.
6
+ */
7
+ import { EntityNotFoundError } from './errors.js';
8
+ export function findEntityByName(graph, name, throwIfNotFound = true) {
9
+ const entity = graph.entities.find(e => e.name === name);
10
+ if (!entity && throwIfNotFound) {
11
+ throw new EntityNotFoundError(name);
12
+ }
13
+ return entity ?? null;
14
+ }
15
+ /**
16
+ * Finds multiple entities by name.
17
+ *
18
+ * @param graph - The knowledge graph to search
19
+ * @param names - Array of entity names to find
20
+ * @param throwIfAnyNotFound - Whether to throw if any entity doesn't exist (default: true)
21
+ * @returns Array of found entities (may be shorter than names if throwIfAnyNotFound is false)
22
+ * @throws EntityNotFoundError if any entity not found and throwIfAnyNotFound is true
23
+ */
24
+ export function findEntitiesByNames(graph, names, throwIfAnyNotFound = true) {
25
+ const entities = [];
26
+ for (const name of names) {
27
+ const entity = findEntityByName(graph, name, false);
28
+ if (entity) {
29
+ entities.push(entity);
30
+ }
31
+ else if (throwIfAnyNotFound) {
32
+ throw new EntityNotFoundError(name);
33
+ }
34
+ }
35
+ return entities;
36
+ }
37
+ /**
38
+ * Checks if an entity exists in the graph.
39
+ *
40
+ * @param graph - The knowledge graph to search
41
+ * @param name - The entity name to check
42
+ * @returns true if entity exists, false otherwise
43
+ */
44
+ export function entityExists(graph, name) {
45
+ return graph.entities.some(e => e.name === name);
46
+ }
47
+ /**
48
+ * Gets the index of an entity in the graph's entities array.
49
+ *
50
+ * @param graph - The knowledge graph to search
51
+ * @param name - The entity name to find
52
+ * @returns The index if found, -1 otherwise
53
+ */
54
+ export function getEntityIndex(graph, name) {
55
+ return graph.entities.findIndex(e => e.name === name);
56
+ }
57
+ /**
58
+ * Removes an entity from the graph by name.
59
+ * Mutates the graph's entities array in place.
60
+ *
61
+ * @param graph - The knowledge graph to modify
62
+ * @param name - The entity name to remove
63
+ * @returns true if entity was removed, false if not found
64
+ */
65
+ export function removeEntityByName(graph, name) {
66
+ const index = getEntityIndex(graph, name);
67
+ if (index === -1)
68
+ return false;
69
+ graph.entities.splice(index, 1);
70
+ return true;
71
+ }
72
+ /**
73
+ * Gets all entity names as a Set for fast lookup.
74
+ *
75
+ * @param graph - The knowledge graph
76
+ * @returns Set of all entity names
77
+ */
78
+ export function getEntityNameSet(graph) {
79
+ return new Set(graph.entities.map(e => e.name));
80
+ }
81
+ /**
82
+ * Groups entities by their type.
83
+ *
84
+ * @param entities - Array of entities to group
85
+ * @returns Map of entity type to array of entities
86
+ */
87
+ export function groupEntitiesByType(entities) {
88
+ const groups = new Map();
89
+ for (const entity of entities) {
90
+ const type = entity.entityType;
91
+ if (!groups.has(type)) {
92
+ groups.set(type, []);
93
+ }
94
+ groups.get(type).push(entity);
95
+ }
96
+ return groups;
97
+ }
98
+ /**
99
+ * Updates the lastModified timestamp on an entity.
100
+ * Mutates the entity in place.
101
+ *
102
+ * @param entity - The entity to update
103
+ * @returns The updated entity (same reference)
104
+ */
105
+ export function touchEntity(entity) {
106
+ entity.lastModified = new Date().toISOString();
107
+ return entity;
108
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Entity Filtering Utilities
3
+ *
4
+ * Centralizes common filtering logic for importance, date ranges, and other
5
+ * entity properties to eliminate duplicate patterns across search implementations.
6
+ */
7
+ /**
8
+ * Checks if an entity's importance is within the specified range.
9
+ * Entities without importance are treated as not matching if any filter is set.
10
+ *
11
+ * @param importance - The entity's importance value (may be undefined)
12
+ * @param minImportance - Minimum importance filter (inclusive)
13
+ * @param maxImportance - Maximum importance filter (inclusive)
14
+ * @returns true if importance is within range or no filters are set
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * // Check if entity passes importance filter
19
+ * if (isWithinImportanceRange(entity.importance, 5, 10)) {
20
+ * // Entity has importance between 5 and 10
21
+ * }
22
+ * ```
23
+ */
24
+ export function isWithinImportanceRange(importance, minImportance, maxImportance) {
25
+ // If no filters set, always pass
26
+ if (minImportance === undefined && maxImportance === undefined) {
27
+ return true;
28
+ }
29
+ // Check minimum importance
30
+ if (minImportance !== undefined) {
31
+ if (importance === undefined || importance < minImportance) {
32
+ return false;
33
+ }
34
+ }
35
+ // Check maximum importance
36
+ if (maxImportance !== undefined) {
37
+ if (importance === undefined || importance > maxImportance) {
38
+ return false;
39
+ }
40
+ }
41
+ return true;
42
+ }
43
+ /**
44
+ * Filters entities by importance range.
45
+ * Returns all entities if no importance filters are specified.
46
+ *
47
+ * @param entities - Array of entities to filter
48
+ * @param minImportance - Minimum importance filter (inclusive)
49
+ * @param maxImportance - Maximum importance filter (inclusive)
50
+ * @returns Filtered entities within the importance range
51
+ */
52
+ export function filterByImportance(entities, minImportance, maxImportance) {
53
+ if (minImportance === undefined && maxImportance === undefined) {
54
+ return entities;
55
+ }
56
+ return entities.filter(e => isWithinImportanceRange(e.importance, minImportance, maxImportance));
57
+ }
58
+ /**
59
+ * Checks if a date value is within the specified range.
60
+ * Handles undefined values appropriately.
61
+ *
62
+ * @param dateValue - ISO 8601 date string to check (may be undefined)
63
+ * @param startDate - Start of date range (inclusive)
64
+ * @param endDate - End of date range (inclusive)
65
+ * @returns true if date is within range or no filters are set
66
+ */
67
+ export function isWithinDateRange(dateValue, startDate, endDate) {
68
+ // If no filters set, always pass
69
+ if (!startDate && !endDate) {
70
+ return true;
71
+ }
72
+ // If date value is undefined but we have filters, fail
73
+ if (!dateValue) {
74
+ return false;
75
+ }
76
+ const date = new Date(dateValue);
77
+ if (startDate && date < new Date(startDate)) {
78
+ return false;
79
+ }
80
+ if (endDate && date > new Date(endDate)) {
81
+ return false;
82
+ }
83
+ return true;
84
+ }
85
+ /**
86
+ * Filters entities by creation date range.
87
+ *
88
+ * @param entities - Array of entities to filter
89
+ * @param startDate - Start of date range (inclusive)
90
+ * @param endDate - End of date range (inclusive)
91
+ * @returns Filtered entities created within the date range
92
+ */
93
+ export function filterByCreatedDate(entities, startDate, endDate) {
94
+ if (!startDate && !endDate) {
95
+ return entities;
96
+ }
97
+ return entities.filter(e => isWithinDateRange(e.createdAt, startDate, endDate));
98
+ }
99
+ /**
100
+ * Filters entities by last modified date range.
101
+ *
102
+ * @param entities - Array of entities to filter
103
+ * @param startDate - Start of date range (inclusive)
104
+ * @param endDate - End of date range (inclusive)
105
+ * @returns Filtered entities modified within the date range
106
+ */
107
+ export function filterByModifiedDate(entities, startDate, endDate) {
108
+ if (!startDate && !endDate) {
109
+ return entities;
110
+ }
111
+ return entities.filter(e => isWithinDateRange(e.lastModified, startDate, endDate));
112
+ }
113
+ /**
114
+ * Filters entities by entity type.
115
+ *
116
+ * @param entities - Array of entities to filter
117
+ * @param entityType - Entity type to filter by (case-sensitive)
118
+ * @returns Filtered entities of the specified type
119
+ */
120
+ export function filterByEntityType(entities, entityType) {
121
+ if (!entityType) {
122
+ return entities;
123
+ }
124
+ return entities.filter(e => e.entityType === entityType);
125
+ }
126
+ /**
127
+ * Checks if an entity passes all the specified filters.
128
+ * Short-circuits on first failing filter for performance.
129
+ *
130
+ * Note: Tag filtering should be handled separately using tagUtils.hasMatchingTag
131
+ * as it requires special normalization logic.
132
+ *
133
+ * @param entity - Entity to check
134
+ * @param filters - Filters to apply
135
+ * @returns true if entity passes all filters
136
+ */
137
+ export function entityPassesFilters(entity, filters) {
138
+ // Importance filter
139
+ if (!isWithinImportanceRange(entity.importance, filters.minImportance, filters.maxImportance)) {
140
+ return false;
141
+ }
142
+ // Entity type filter
143
+ if (filters.entityType && entity.entityType !== filters.entityType) {
144
+ return false;
145
+ }
146
+ // Created date filter
147
+ if (!isWithinDateRange(entity.createdAt, filters.createdAfter, filters.createdBefore)) {
148
+ return false;
149
+ }
150
+ // Modified date filter
151
+ if (!isWithinDateRange(entity.lastModified, filters.modifiedAfter, filters.modifiedBefore)) {
152
+ return false;
153
+ }
154
+ return true;
155
+ }
@@ -1,13 +1,39 @@
1
1
  /**
2
2
  * Utilities Module Barrel Export
3
+ *
4
+ * Centralizes all utility exports for convenient importing.
5
+ * Sprint 1 additions: responseFormatter, tagUtils, entityUtils,
6
+ * validationHelper, paginationUtils, filterUtils
3
7
  */
8
+ // Error types
4
9
  export { KnowledgeGraphError, EntityNotFoundError, RelationNotFoundError, DuplicateEntityError, ValidationError, CycleDetectedError, InvalidImportanceError, FileOperationError, ImportError, ExportError, InsufficientEntitiesError, } from './errors.js';
10
+ // String utilities
5
11
  export { levenshteinDistance } from './levenshtein.js';
6
12
  export { calculateTF, calculateIDF, calculateTFIDF, tokenize } from './tfidf.js';
13
+ // Logging
7
14
  export { logger } from './logger.js';
15
+ // Date utilities
8
16
  export { isWithinDateRange, parseDateRange, isValidISODate, getCurrentTimestamp } from './dateUtils.js';
17
+ // Validation utilities
9
18
  export { validateEntity, validateRelation, validateImportance, validateTags } from './validationUtils.js';
19
+ // Path utilities
10
20
  export { defaultMemoryPath, ensureMemoryFilePath, validateFilePath } from './pathUtils.js';
21
+ // Constants
11
22
  export { FILE_EXTENSIONS, FILE_SUFFIXES, DEFAULT_FILE_NAMES, ENV_VARS, DEFAULT_BASE_DIR, LOG_PREFIXES, SIMILARITY_WEIGHTS, DEFAULT_DUPLICATE_THRESHOLD, SEARCH_LIMITS, IMPORTANCE_RANGE, } from './constants.js';
23
+ // Zod schemas
12
24
  export { EntitySchema, CreateEntitySchema, UpdateEntitySchema, RelationSchema, CreateRelationSchema, SearchQuerySchema, DateRangeSchema, TagAliasSchema, ExportFormatSchema, BatchCreateEntitiesSchema, BatchCreateRelationsSchema, EntityNamesSchema, DeleteRelationsSchema, } from './schemas.js';
25
+ // Search cache
13
26
  export { SearchCache, searchCaches, clearAllSearchCaches, getAllCacheStats, cleanupAllCaches, } from './searchCache.js';
27
+ // === Sprint 1: New Utility Exports ===
28
+ // MCP Response formatting (Task 1.1)
29
+ export { formatToolResponse, formatTextResponse, formatRawResponse, formatErrorResponse, } from './responseFormatter.js';
30
+ // Tag utilities (Task 1.2)
31
+ export { normalizeTag, normalizeTags, hasMatchingTag, hasAllTags, filterByTags, addUniqueTags, removeTags, } from './tagUtils.js';
32
+ // Entity utilities (Task 1.3)
33
+ export { findEntityByName, findEntitiesByNames, entityExists, getEntityIndex, removeEntityByName, getEntityNameSet, groupEntitiesByType, touchEntity, } from './entityUtils.js';
34
+ // Zod validation helpers (Task 1.4)
35
+ export { formatZodErrors, validateWithSchema, validateSafe, validateArrayWithSchema, } from './validationHelper.js';
36
+ // Pagination utilities (Task 1.5)
37
+ export { validatePagination, applyPagination, paginateArray, getPaginationMeta, } from './paginationUtils.js';
38
+ // Filter utilities (Task 1.6)
39
+ export { isWithinImportanceRange, filterByImportance, filterByCreatedDate, filterByModifiedDate, filterByEntityType, entityPassesFilters, } from './filterUtils.js';
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Pagination Utilities
3
+ *
4
+ * Centralizes pagination validation and application logic
5
+ * to eliminate duplicate patterns across search implementations.
6
+ */
7
+ import { SEARCH_LIMITS } from './constants.js';
8
+ /**
9
+ * Validates and normalizes pagination parameters.
10
+ * Ensures offset is non-negative and limit is within configured bounds.
11
+ *
12
+ * @param offset - Starting position (default: 0)
13
+ * @param limit - Maximum results to return (default: SEARCH_LIMITS.DEFAULT)
14
+ * @returns Validated pagination parameters with helper methods
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const pagination = validatePagination(10, 50);
19
+ * const results = items.slice(pagination.offset, pagination.offset + pagination.limit);
20
+ * if (pagination.hasMore(items.length)) {
21
+ * console.log('More results available');
22
+ * }
23
+ * ```
24
+ */
25
+ export function validatePagination(offset = 0, limit = SEARCH_LIMITS.DEFAULT) {
26
+ const validatedOffset = Math.max(0, offset);
27
+ const validatedLimit = Math.min(Math.max(SEARCH_LIMITS.MIN, limit), SEARCH_LIMITS.MAX);
28
+ return {
29
+ offset: validatedOffset,
30
+ limit: validatedLimit,
31
+ hasMore: (totalCount) => validatedOffset + validatedLimit < totalCount,
32
+ };
33
+ }
34
+ /**
35
+ * Applies pagination to an array of items.
36
+ *
37
+ * @param items - Array to paginate
38
+ * @param pagination - Validated pagination parameters
39
+ * @returns Paginated slice of the array
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const pagination = validatePagination(offset, limit);
44
+ * const pageResults = applyPagination(allResults, pagination);
45
+ * ```
46
+ */
47
+ export function applyPagination(items, pagination) {
48
+ return items.slice(pagination.offset, pagination.offset + pagination.limit);
49
+ }
50
+ /**
51
+ * Applies pagination using raw offset and limit values.
52
+ * Combines validation and application in one call.
53
+ *
54
+ * @param items - Array to paginate
55
+ * @param offset - Starting position
56
+ * @param limit - Maximum results
57
+ * @returns Paginated slice of the array
58
+ */
59
+ export function paginateArray(items, offset = 0, limit = SEARCH_LIMITS.DEFAULT) {
60
+ const pagination = validatePagination(offset, limit);
61
+ return applyPagination(items, pagination);
62
+ }
63
+ /**
64
+ * Calculates pagination metadata for a result set.
65
+ *
66
+ * @param totalCount - Total number of items
67
+ * @param offset - Current offset
68
+ * @param limit - Current limit
69
+ * @returns Pagination metadata
70
+ */
71
+ export function getPaginationMeta(totalCount, offset = 0, limit = SEARCH_LIMITS.DEFAULT) {
72
+ const pagination = validatePagination(offset, limit);
73
+ return {
74
+ totalCount,
75
+ offset: pagination.offset,
76
+ limit: pagination.limit,
77
+ hasMore: pagination.hasMore(totalCount),
78
+ pageNumber: Math.floor(pagination.offset / pagination.limit) + 1,
79
+ totalPages: Math.ceil(totalCount / pagination.limit),
80
+ };
81
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * MCP Tool Response Formatter Utilities
3
+ *
4
+ * Centralizes response formatting for MCP tool calls to eliminate
5
+ * redundant JSON.stringify patterns across the codebase.
6
+ */
7
+ /**
8
+ * Formats data as an MCP tool response with JSON content.
9
+ * Centralizes the response format to ensure consistency and reduce duplication.
10
+ *
11
+ * @param data - Any data to be JSON stringified
12
+ * @returns Formatted MCP tool response
13
+ */
14
+ export function formatToolResponse(data) {
15
+ return {
16
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
17
+ };
18
+ }
19
+ /**
20
+ * Formats a simple text message as an MCP tool response.
21
+ * Use for success messages that don't need JSON formatting.
22
+ *
23
+ * @param message - Plain text message
24
+ * @returns Formatted MCP tool response
25
+ */
26
+ export function formatTextResponse(message) {
27
+ return {
28
+ content: [{ type: 'text', text: message }],
29
+ };
30
+ }
31
+ /**
32
+ * Formats raw string content as an MCP tool response.
33
+ * Use for export formats that return pre-formatted strings (markdown, CSV, etc.)
34
+ *
35
+ * @param content - Raw string content
36
+ * @returns Formatted MCP tool response
37
+ */
38
+ export function formatRawResponse(content) {
39
+ return {
40
+ content: [{ type: 'text', text: content }],
41
+ };
42
+ }
43
+ /**
44
+ * Formats an error as an MCP tool response with isError flag.
45
+ *
46
+ * @param error - Error object or message string
47
+ * @returns Formatted MCP tool error response
48
+ */
49
+ export function formatErrorResponse(error) {
50
+ const message = error instanceof Error ? error.message : error;
51
+ return {
52
+ content: [{ type: 'text', text: message }],
53
+ isError: true,
54
+ };
55
+ }