mcp-grocy 2.2.0 → 2.5.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +112 -32
  3. package/build/api/client.js +11 -13
  4. package/build/config/index.js +74 -20
  5. package/build/resources/CHANGELOG.md +31 -0
  6. package/build/resources/DOCS.md +8 -3
  7. package/build/resources/README.md +112 -32
  8. package/build/resources/api-reference.md +88 -88
  9. package/build/resources/config.md +30 -17
  10. package/build/resources/examples.md +120 -221
  11. package/build/resources/installation.md +9 -14
  12. package/build/resources/response-format.md +18 -13
  13. package/build/server/http-server.js +154 -42
  14. package/build/server/mcp-server.js +143 -96
  15. package/build/server/resources.js +37 -24
  16. package/build/server/tool-input-zod.js +86 -0
  17. package/build/tools/base.js +19 -15
  18. package/build/tools/household/definitions.js +47 -43
  19. package/build/tools/household/handlers.js +2 -2
  20. package/build/tools/household/index.js +2 -2
  21. package/build/tools/inventory/definitions.js +150 -113
  22. package/build/tools/inventory/handlers.js +55 -42
  23. package/build/tools/inventory/index.js +2 -2
  24. package/build/tools/module-loader.js +15 -12
  25. package/build/tools/recipes/definitions.js +103 -86
  26. package/build/tools/recipes/handlers.js +44 -38
  27. package/build/tools/recipes/index.js +3 -3
  28. package/build/tools/recipes/validations.js +8 -2
  29. package/build/tools/shopping/definitions.js +22 -20
  30. package/build/tools/shopping/handlers.js +1 -1
  31. package/build/tools/shopping/index.js +2 -2
  32. package/build/tools/system/definitions.js +25 -22
  33. package/build/tools/system/handlers.js +74 -12
  34. package/build/tools/system/index.js +2 -2
  35. package/build/tools/validation-helpers.js +10 -7
  36. package/build/types/index.js +12 -10
  37. package/build/utils/errors.js +10 -5
  38. package/build/utils/logger.js +16 -15
  39. package/build/version.js +2 -2
  40. package/package.json +38 -23
@@ -2,11 +2,12 @@ export const shoppingToolDefinitions = [
2
2
  {
3
3
  name: 'shopping_list_get',
4
4
  description: '[SHOPPING/LIST] Get your current shopping list items.',
5
+ annotations: { readOnlyHint: true },
5
6
  inputSchema: {
6
7
  type: 'object',
7
8
  properties: {},
8
- required: []
9
- }
9
+ required: [],
10
+ },
10
11
  },
11
12
  {
12
13
  name: 'shopping_list_add_item',
@@ -16,25 +17,25 @@ export const shoppingToolDefinitions = [
16
17
  properties: {
17
18
  productId: {
18
19
  type: 'number',
19
- description: 'ID of the product to add. Use inventory_products_get tool to find the correct product ID by searching for the product name in the results.'
20
+ description: 'ID of the product to add. Use inventory_products_get tool to find the correct product ID by searching for the product name in the results.',
20
21
  },
21
22
  amount: {
22
23
  type: 'number',
23
- description: 'Amount to add to shopping list in the product\'s stock unit (e.g., 2 pieces, 1.5 kg, 750 ml). Ensure you know the product\'s unit before specifying amount. Default: 1',
24
- default: 1
24
+ description: "Amount to add to shopping list in the product's stock unit (e.g., 2 pieces, 1.5 kg, 750 ml). Ensure you know the product's unit before specifying amount. Default: 1",
25
+ default: 1,
25
26
  },
26
27
  shoppingListId: {
27
28
  type: 'number',
28
29
  description: 'ID of the shopping list to add to (default: 1). Most users have only one shopping list with ID 1.',
29
- default: 1
30
+ default: 1,
30
31
  },
31
32
  note: {
32
33
  type: 'string',
33
- description: 'Optional note for the shopping list item'
34
- }
34
+ description: 'Optional note for the shopping list item',
35
+ },
35
36
  },
36
- required: ['productId']
37
- }
37
+ required: ['productId'],
38
+ },
38
39
  },
39
40
  {
40
41
  name: 'shopping_list_remove_item',
@@ -44,20 +45,21 @@ export const shoppingToolDefinitions = [
44
45
  properties: {
45
46
  shoppingListItemId: {
46
47
  type: 'number',
47
- description: 'ID of the shopping list item to remove. Use shopping_list_get tool to find the correct shopping list item ID by looking at the "id" field in the results.'
48
- }
48
+ description: 'ID of the shopping list item to remove. Use shopping_list_get tool to find the correct shopping list item ID by looking at the "id" field in the results.',
49
+ },
49
50
  },
50
- required: ['shoppingListItemId']
51
- }
51
+ required: ['shoppingListItemId'],
52
+ },
52
53
  },
53
54
  {
54
55
  name: 'shopping_locations_get',
55
- description: '[SHOPPING/LOCATIONS] Get all shopping locations (stores) from your Grocy instance. Use this to find store IDs and names when working with tools that require storeId parameters.',
56
+ description: '[SHOPPING/LOCATIONS] Get **retail store / shop** locations where you buy groceries (Grocy shopping locations). NOT pantry or home storage—use system_locations_get for storage location IDs (locationId). Use shopping_locations_get for storeId when adding shopping-list items or store-specific workflows.',
57
+ annotations: { readOnlyHint: true },
56
58
  inputSchema: {
57
59
  type: 'object',
58
60
  properties: {},
59
- required: []
60
- }
61
+ required: [],
62
+ },
61
63
  },
62
64
  {
63
65
  name: 'shopping_list_print_thermal',
@@ -65,7 +67,7 @@ export const shoppingToolDefinitions = [
65
67
  inputSchema: {
66
68
  type: 'object',
67
69
  properties: {},
68
- required: []
69
- }
70
- }
70
+ required: [],
71
+ },
72
+ },
71
73
  ];
@@ -14,7 +14,7 @@ export class ShoppingToolHandlers extends BaseToolHandler {
14
14
  product_id: productId,
15
15
  amount,
16
16
  shopping_list_id: shoppingListId,
17
- note
17
+ note,
18
18
  };
19
19
  const result = await this.apiCall('/objects/shopping_list', 'POST', body);
20
20
  return this.createSuccess(result, 'Shopping list item added successfully');
@@ -8,7 +8,7 @@ export const shoppingModule = {
8
8
  shopping_list_add_item: handlers.addShoppingListItem,
9
9
  shopping_list_remove_item: handlers.removeShoppingListItem,
10
10
  shopping_locations_get: handlers.getShoppingLocations,
11
- shopping_list_print_thermal: handlers.printShoppingListThermal
12
- }
11
+ shopping_list_print_thermal: handlers.printShoppingListThermal,
12
+ },
13
13
  };
14
14
  export * from './definitions.js';
@@ -3,30 +3,33 @@ export const systemToolDefinitions = [
3
3
  // ==================== CORE SYSTEM UTILITIES ====================
4
4
  {
5
5
  name: 'system_locations_get',
6
- description: '[SYSTEM/LOCATIONS] Get all storage locations from your Grocy instance. Use this to find location IDs and names when working with other tools that require locationId parameters.',
6
+ description: '[SYSTEM/LOCATIONS] Get **home storage / stock** locations (freezer, pantry, fridge—where inventory lives). NOT retail stores—use shopping_locations_get for shop/store IDs (storeId). Use this for locationId on stock, transfers, and purchases.',
7
+ annotations: { readOnlyHint: true },
7
8
  inputSchema: {
8
9
  type: 'object',
9
10
  properties: {},
10
- required: []
11
- }
11
+ required: [],
12
+ },
12
13
  },
13
14
  {
14
15
  name: 'system_units_get',
15
16
  description: '[SYSTEM/UNITS] Get all quantity units from your Grocy instance.',
17
+ annotations: { readOnlyHint: true },
16
18
  inputSchema: {
17
19
  type: 'object',
18
20
  properties: {},
19
- required: []
20
- }
21
+ required: [],
22
+ },
21
23
  },
22
24
  {
23
25
  name: 'system_users_get',
24
26
  description: '[SYSTEM/USERS] Get all users from your Grocy instance.',
27
+ annotations: { readOnlyHint: true },
25
28
  inputSchema: {
26
29
  type: 'object',
27
30
  properties: {},
28
- required: []
29
- }
31
+ required: [],
32
+ },
30
33
  },
31
34
  // ==================== DEVELOPER UTILITIES ====================
32
35
  {
@@ -37,21 +40,21 @@ export const systemToolDefinitions = [
37
40
  properties: {
38
41
  endpoint: {
39
42
  type: 'string',
40
- description: 'Grocy API endpoint to call (e.g., "objects/products"). Do not include /api/ prefix.'
43
+ description: 'Grocy API endpoint to call (e.g., "objects/products"). Do not include /api/ prefix.',
41
44
  },
42
45
  method: {
43
46
  type: 'string',
44
47
  enum: ['GET', 'POST', 'PUT', 'DELETE'],
45
48
  description: 'HTTP method to use',
46
- default: 'GET'
49
+ default: 'GET',
47
50
  },
48
51
  body: {
49
52
  type: 'object',
50
- description: 'Optional request body for POST/PUT requests'
51
- }
53
+ description: 'Optional request body for POST/PUT requests',
54
+ },
52
55
  },
53
- required: ['endpoint']
54
- }
56
+ required: ['endpoint'],
57
+ },
55
58
  },
56
59
  {
57
60
  name: 'system_dev_test_request',
@@ -62,25 +65,25 @@ export const systemToolDefinitions = [
62
65
  method: {
63
66
  type: 'string',
64
67
  enum: ['GET', 'POST', 'PUT', 'DELETE'],
65
- description: 'HTTP method to use'
68
+ description: 'HTTP method to use',
66
69
  },
67
70
  endpoint: {
68
71
  type: 'string',
69
- description: 'Endpoint path (e.g. "/users"). Do not include full URLs - only the path.'
72
+ description: 'Endpoint path (e.g. "/users"). Do not include full URLs - only the path.',
70
73
  },
71
74
  body: {
72
75
  type: 'object',
73
- description: 'Optional request body for POST/PUT requests'
76
+ description: 'Optional request body for POST/PUT requests',
74
77
  },
75
78
  headers: {
76
79
  type: 'object',
77
80
  description: 'Optional request headers for one-time use.',
78
81
  additionalProperties: {
79
- type: 'string'
80
- }
81
- }
82
+ type: 'string',
83
+ },
84
+ },
82
85
  },
83
- required: ['method', 'endpoint']
84
- }
85
- }
86
+ required: ['method', 'endpoint'],
87
+ },
88
+ },
86
89
  ];
@@ -4,6 +4,64 @@ import apiClient from '../../api/client.js';
4
4
  import { config } from '../../config/index.js';
5
5
  import { logger } from '../../utils/logger.js';
6
6
  export class SystemToolHandlers extends BaseToolHandler {
7
+ redactHeaders(headers) {
8
+ const redacted = { ...headers };
9
+ const sensitiveHeaders = new Set([
10
+ 'grocy-api-key',
11
+ 'authorization',
12
+ 'x-api-key',
13
+ 'proxy-authorization',
14
+ ]);
15
+ for (const key of Object.keys(redacted)) {
16
+ if (sensitiveHeaders.has(key.toLowerCase())) {
17
+ redacted[key] = '[REDACTED]';
18
+ }
19
+ }
20
+ return redacted;
21
+ }
22
+ /**
23
+ * Truncate a string to at most `maxBytes` UTF-8 bytes without splitting a codepoint.
24
+ */
25
+ truncateStringToUtf8Bytes(str, maxBytes) {
26
+ const buf = Buffer.from(str, 'utf8');
27
+ if (buf.length <= maxBytes) {
28
+ return { text: str, byteLength: buf.length };
29
+ }
30
+ let end = maxBytes;
31
+ while (end > 0) {
32
+ while (end > 0 && (buf[end - 1] & 0xc0) === 0x80) {
33
+ end--;
34
+ }
35
+ if (end === 0) {
36
+ break;
37
+ }
38
+ const text = buf.subarray(0, end).toString('utf8');
39
+ if (Buffer.byteLength(text, 'utf8') === end) {
40
+ return { text, byteLength: end };
41
+ }
42
+ end--;
43
+ }
44
+ return { text: '', byteLength: 0 };
45
+ }
46
+ applyResponseSizeLimit(body) {
47
+ const sizeLimit = config.grocy.response_size_limit;
48
+ const jsonBody = JSON.stringify(body);
49
+ const originalSize = Buffer.byteLength(jsonBody, 'utf8');
50
+ if (originalSize <= sizeLimit) {
51
+ return { body };
52
+ }
53
+ const { text: truncatedJsonBody, byteLength: prefixBytes } = this.truncateStringToUtf8Bytes(jsonBody, sizeLimit);
54
+ const returnedSize = Buffer.byteLength(truncatedJsonBody, 'utf8');
55
+ return {
56
+ body: `${truncatedJsonBody}...[TRUNCATED]`,
57
+ truncated: {
58
+ originalSize,
59
+ returnedSize,
60
+ truncationPoint: prefixBytes,
61
+ sizeLimit,
62
+ },
63
+ };
64
+ }
7
65
  // ==================== CORE SYSTEM UTILITIES ====================
8
66
  getLocations = async () => {
9
67
  return this.executeToolHandler(async () => {
@@ -34,7 +92,7 @@ export class SystemToolHandlers extends BaseToolHandler {
34
92
  try {
35
93
  const response = await apiClient.request(`/${cleanEndpoint}`, {
36
94
  method,
37
- body
95
+ body,
38
96
  });
39
97
  return this.createSuccess(response.data);
40
98
  }
@@ -49,34 +107,38 @@ export class SystemToolHandlers extends BaseToolHandler {
49
107
  throw new McpError(ErrorCode.InvalidParams, 'method and endpoint are required');
50
108
  }
51
109
  const normalizedEndpoint = `/${endpoint.replace(/^\/+|\/+$/g, '')}`;
110
+ const requestHeaders = { ...config.getCustomHeaders(), ...headers };
111
+ const safeRequestHeaders = this.redactHeaders(requestHeaders);
52
112
  try {
53
113
  const startTime = Date.now();
54
114
  const response = await apiClient.request(normalizedEndpoint, {
55
115
  method,
56
116
  body,
57
- headers: { ...config.getCustomHeaders(), ...headers }
117
+ headers: requestHeaders,
58
118
  });
59
119
  const endTime = Date.now();
120
+ const responseWithLimit = this.applyResponseSizeLimit(response.data);
60
121
  const responseObj = {
61
122
  request: {
62
123
  url: `${config.grocy.base_url}${normalizedEndpoint}`,
63
124
  method,
64
- headers: { ...config.getCustomHeaders(), ...headers },
125
+ headers: safeRequestHeaders,
65
126
  body,
66
- authMethod: config.grocy.api_key ? 'apikey' : 'none'
127
+ authMethod: config.grocy.api_key ? 'apikey' : 'none',
67
128
  },
68
129
  response: {
69
130
  statusCode: response.status,
70
131
  timing: `${endTime - startTime}ms`,
71
132
  headers: response.headers,
72
- body: response.data
133
+ body: responseWithLimit.body,
73
134
  },
74
135
  validation: {
75
136
  isError: response.status >= 400,
76
- messages: response.status >= 400 ?
77
- [`Request failed with status ${response.status}`] :
78
- ['Request completed successfully']
79
- }
137
+ messages: response.status >= 400
138
+ ? [`Request failed with status ${response.status}`]
139
+ : ['Request completed successfully'],
140
+ ...(responseWithLimit.truncated ? { truncated: responseWithLimit.truncated } : {}),
141
+ },
80
142
  };
81
143
  return this.createSuccess(responseObj);
82
144
  }
@@ -85,9 +147,9 @@ export class SystemToolHandlers extends BaseToolHandler {
85
147
  request: {
86
148
  url: `${config.grocy.base_url}${normalizedEndpoint}`,
87
149
  method,
88
- headers: { ...config.getCustomHeaders(), ...headers },
89
- body
90
- }
150
+ headers: safeRequestHeaders,
151
+ body,
152
+ },
91
153
  });
92
154
  }
93
155
  };
@@ -10,7 +10,7 @@ export const systemModule = {
10
10
  system_users_get: handlers.getUsers,
11
11
  // Developer Utilities
12
12
  system_dev_call_api: handlers.callGrocyApi,
13
- system_dev_test_request: handlers.testRequest
14
- }
13
+ system_dev_test_request: handlers.testRequest,
14
+ },
15
15
  };
16
16
  export * from './definitions.js';
@@ -1,35 +1,38 @@
1
1
  /**
2
2
  * Common validation utility functions
3
3
  */
4
+ import { ValidationError } from '../utils/errors.js';
4
5
  export class ValidationHelpers {
5
6
  static validateBoolean(value, fieldName) {
6
7
  if (value !== undefined && typeof value !== 'boolean') {
7
- throw new Error(`${fieldName} must be a boolean`);
8
+ throw new ValidationError(`${fieldName} must be a boolean`, 'sub-config validation');
8
9
  }
9
10
  }
10
11
  static validateString(value, fieldName) {
11
12
  if (value !== undefined && typeof value !== 'string') {
12
- throw new Error(`${fieldName} must be a string`);
13
+ throw new ValidationError(`${fieldName} must be a string`, 'sub-config validation');
13
14
  }
14
15
  }
15
16
  static validateNumber(value, fieldName, options) {
16
17
  if (value !== undefined) {
17
18
  if (typeof value !== 'number') {
18
- throw new Error(`${fieldName} must be a number`);
19
+ throw new ValidationError(`${fieldName} must be a number`, 'sub-config validation');
19
20
  }
20
21
  if (options?.min !== undefined && value < options.min) {
21
- throw new Error(`${fieldName} must be at least ${options.min}`);
22
+ throw new ValidationError(`${fieldName} must be at least ${options.min}`, 'sub-config validation');
22
23
  }
23
24
  if (options?.max !== undefined && value > options.max) {
24
- throw new Error(`${fieldName} must be at most ${options.max}`);
25
+ throw new ValidationError(`${fieldName} must be at most ${options.max}`, 'sub-config validation');
25
26
  }
26
27
  }
27
28
  }
28
29
  static validateKnownOptions(subConfigs, knownOptions, toolName) {
29
30
  for (const [key] of subConfigs) {
30
31
  if (!knownOptions.has(key)) {
31
- const validOptions = Array.from(knownOptions).filter(k => k !== 'ack_token').join(', ');
32
- throw new Error(`Unknown sub-configuration option '${key}' for ${toolName} tool. Valid options are: ${validOptions}`);
32
+ const validOptions = Array.from(knownOptions)
33
+ .filter((k) => k !== 'ack_token')
34
+ .join(', ');
35
+ throw new ValidationError(`Unknown sub-configuration option '${key}' for ${toolName} tool. Valid options are: ${validOptions}`, 'sub-config validation');
33
36
  }
34
37
  }
35
38
  }
@@ -19,11 +19,13 @@ export const ConfigSchema = z.object({
19
19
  enable_ssl_verify: z.boolean().default(true),
20
20
  response_size_limit: z.number().positive().default(10000),
21
21
  }),
22
- tools: z.record(z.string(), z.object({
22
+ tools: z.record(z.string(), z
23
+ .object({
23
24
  enabled: z.boolean().default(false),
24
25
  ack_token: z.string().optional(),
25
- }).catchall(z.unknown())),
26
- })
26
+ })
27
+ .catchall(z.unknown())),
28
+ }),
27
29
  });
28
30
  // Validation schemas for runtime type checking
29
31
  export const ToolResultSchema = z.object({
@@ -31,20 +33,20 @@ export const ToolResultSchema = z.object({
31
33
  data: z.any().optional(),
32
34
  error: z.string().optional(),
33
35
  message: z.string().optional(),
34
- details: z.any().optional()
36
+ details: z.any().optional(),
35
37
  });
36
38
  export const ToolDefinitionSchema = z.object({
37
39
  name: z.string(),
38
40
  description: z.string(),
39
41
  inputSchema: z.object({
40
42
  type: z.literal('object'),
41
- properties: z.record(z.any()),
42
- required: z.array(z.string()).optional()
43
- })
43
+ properties: z.record(z.string(), z.any()),
44
+ required: z.array(z.string()).optional(),
45
+ }),
44
46
  });
45
47
  export const ToolModuleSchema = z.object({
46
48
  definitions: z.array(ToolDefinitionSchema),
47
- handlers: z.record(z.function())
49
+ handlers: z.record(z.string(), z.custom((v) => typeof v === 'function')),
48
50
  });
49
51
  // Type guards
50
52
  export function isToolResult(obj) {
@@ -54,9 +56,9 @@ export function isToolDefinition(obj) {
54
56
  return ToolDefinitionSchema.safeParse(obj).success;
55
57
  }
56
58
  export function isToolModule(obj) {
57
- return obj &&
59
+ return (obj &&
58
60
  typeof obj === 'object' &&
59
61
  Array.isArray(obj.definitions) &&
60
62
  typeof obj.handlers === 'object' &&
61
- obj.definitions.every((def) => isToolDefinition(def));
63
+ obj.definitions.every((def) => isToolDefinition(def)));
62
64
  }
@@ -27,15 +27,18 @@ export class AppError extends Error {
27
27
  }
28
28
  getErrorCode() {
29
29
  switch (this.category) {
30
- case 'VALIDATION': return ErrorCode.InvalidParams;
31
- case 'AUTH': return ErrorCode.InvalidRequest;
30
+ case 'VALIDATION':
31
+ return ErrorCode.InvalidParams;
32
+ case 'AUTH':
33
+ return ErrorCode.InvalidRequest;
32
34
  case 'API':
33
35
  if (this.statusCode === 404)
34
36
  return ErrorCode.InvalidRequest;
35
37
  if (this.statusCode === 401 || this.statusCode === 403)
36
38
  return ErrorCode.InvalidRequest;
37
39
  return ErrorCode.InternalError;
38
- default: return ErrorCode.InternalError;
40
+ default:
41
+ return ErrorCode.InternalError;
39
42
  }
40
43
  }
41
44
  }
@@ -52,7 +55,9 @@ export class ApiError extends AppError {
52
55
  return new ApiError(error.response.data?.message || `HTTP ${error.response.status}`, error.response.status, operation, { url: error.config?.url, method: error.config?.method });
53
56
  }
54
57
  if (error.request) {
55
- return new ApiError('Network error - unable to reach server', undefined, operation, { url: error.config?.url });
58
+ return new ApiError('Network error - unable to reach server', undefined, operation, {
59
+ url: error.config?.url,
60
+ });
56
61
  }
57
62
  return new ApiError(error.message || 'Request error', undefined, operation);
58
63
  }
@@ -102,7 +107,7 @@ export class ErrorHandler {
102
107
  category: error.category,
103
108
  statusCode: error.statusCode,
104
109
  operation: error.operation,
105
- details: error.details
110
+ details: error.details,
106
111
  });
107
112
  }
108
113
  else {
@@ -19,7 +19,7 @@ export class Logger {
19
19
  // Allow filtering by categories
20
20
  const categories = process.env.LOG_CATEGORIES;
21
21
  if (categories) {
22
- this.enabledCategories = new Set(categories.split(',').map(c => c.trim().toUpperCase()));
22
+ this.enabledCategories = new Set(categories.split(',').map((c) => c.trim().toUpperCase()));
23
23
  }
24
24
  }
25
25
  static getInstance() {
@@ -31,12 +31,18 @@ export class Logger {
31
31
  parseLogLevel(level) {
32
32
  const upperLevel = level.toUpperCase();
33
33
  switch (upperLevel) {
34
- case 'ERROR': return LogLevel.ERROR;
35
- case 'WARN': return LogLevel.WARN;
36
- case 'INFO': return LogLevel.INFO;
37
- case 'DEBUG': return LogLevel.DEBUG;
38
- case 'TRACE': return LogLevel.TRACE;
39
- default: return LogLevel.INFO;
34
+ case 'ERROR':
35
+ return LogLevel.ERROR;
36
+ case 'WARN':
37
+ return LogLevel.WARN;
38
+ case 'INFO':
39
+ return LogLevel.INFO;
40
+ case 'DEBUG':
41
+ return LogLevel.DEBUG;
42
+ case 'TRACE':
43
+ return LogLevel.TRACE;
44
+ default:
45
+ return LogLevel.INFO;
40
46
  }
41
47
  }
42
48
  shouldLog(level, category) {
@@ -66,16 +72,11 @@ export class Logger {
66
72
  message,
67
73
  category: category || undefined,
68
74
  data,
69
- timestamp: new Date()
75
+ timestamp: new Date(),
70
76
  };
71
77
  const formattedMessage = this.formatMessage(entry);
72
78
  // Use stderr for all logs to avoid interfering with MCP stdio protocol
73
- if (level <= LogLevel.ERROR) {
74
- console.error(formattedMessage);
75
- }
76
- else {
77
- console.error(formattedMessage);
78
- }
79
+ console.error(formattedMessage);
79
80
  }
80
81
  error(message, category, data) {
81
82
  this.log(LogLevel.ERROR, message, category, data);
@@ -113,7 +114,7 @@ export class Logger {
113
114
  this.logLevel = level;
114
115
  }
115
116
  setEnabledCategories(categories) {
116
- this.enabledCategories = categories ? new Set(categories.map(c => c.toUpperCase())) : null;
117
+ this.enabledCategories = categories ? new Set(categories.map((c) => c.toUpperCase())) : null;
117
118
  }
118
119
  }
119
120
  // Export singleton instance
package/build/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // Auto-generated file - DO NOT MODIFY
2
- export const VERSION = '2.2.0';
2
+ export const VERSION = '2.5.0';
3
3
  export const PACKAGE_NAME = 'mcp-grocy';
4
- export const SERVER_NAME = 'grocy-api';
4
+ export const SERVER_NAME = 'mcp-grocy';