mcp-grocy 2.7.1 → 2.7.2

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 (39) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +15 -5
  3. package/build/api/client.js +119 -0
  4. package/build/config/index.js +283 -0
  5. package/build/main.js +44 -0
  6. package/build/resources/CHANGELOG.md +9 -3
  7. package/build/resources/README.md +15 -5
  8. package/build/resources/api-reference.md +2 -1
  9. package/build/resources/response-format.md +1 -3
  10. package/build/server/http-server.js +398 -0
  11. package/build/server/mcp-server.js +230 -0
  12. package/build/server/resources.js +73 -0
  13. package/build/server/tool-input-zod.js +86 -0
  14. package/build/tools/base.js +157 -0
  15. package/build/tools/household/definitions.js +161 -0
  16. package/build/tools/household/handlers.js +109 -0
  17. package/build/tools/household/index.js +25 -0
  18. package/build/tools/index.js +2 -0
  19. package/build/tools/inventory/definitions.js +416 -0
  20. package/build/tools/inventory/handlers.js +443 -0
  21. package/build/tools/inventory/index.js +32 -0
  22. package/build/tools/module-loader.js +154 -0
  23. package/build/tools/recipes/definitions.js +278 -0
  24. package/build/tools/recipes/handlers.js +518 -0
  25. package/build/tools/recipes/index.js +33 -0
  26. package/build/tools/recipes/validations.js +29 -0
  27. package/build/tools/shopping/definitions.js +147 -0
  28. package/build/tools/shopping/handlers.js +198 -0
  29. package/build/tools/shopping/index.js +17 -0
  30. package/build/tools/system/definitions.js +89 -0
  31. package/build/tools/system/handlers.js +156 -0
  32. package/build/tools/system/index.js +16 -0
  33. package/build/tools/types.js +1 -0
  34. package/build/tools/validation-helpers.js +39 -0
  35. package/build/types/index.js +64 -0
  36. package/build/utils/errors.js +143 -0
  37. package/build/utils/logger.js +142 -0
  38. package/build/version.js +1 -1
  39. package/package.json +27 -25
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [2.7.2](https://github.com/miguelangel-nubla/mcp-grocy/compare/v2.7.1...v2.7.2) (2026-09-10)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add prepack script, update release configs, and fix docker workflows ([c464c05](https://github.com/miguelangel-nubla/mcp-grocy/commit/c464c054ab4f3f420647196ded7454c60f3702fc))
7
+
1
8
  ## [2.7.1](https://github.com/miguelangel-nubla/mcp-grocy/compare/v2.7.0...v2.7.1) (2026-09-10)
2
9
 
3
10
  ### Bug Fixes
package/README.md CHANGED
@@ -6,6 +6,20 @@
6
6
  [![Configuration Status](https://github.com/miguelangel-nubla/mcp-grocy/actions/workflows/validate-config.yml/badge.svg)](https://github.com/miguelangel-nubla/mcp-grocy/actions/workflows/validate-config.yml)
7
7
  [![CI/CD Pipeline](https://github.com/miguelangel-nubla/mcp-grocy/actions/workflows/pipeline.yml/badge.svg)](https://github.com/miguelangel-nubla/mcp-grocy/actions/workflows/pipeline.yml)
8
8
  [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-blue)](https://modelcontextprotocol.io)
9
+ [![Recommended Backend: grocy-next](https://img.shields.io/badge/Recommended%20Backend-grocy--next-brightgreen?logo=docker)](https://github.com/miguelangel-nubla/grocy-next)
10
+
11
+ > ### 🌟 Recommended Companion: [grocy-next](https://github.com/miguelangel-nubla/grocy-next)
12
+ >
13
+ > While **mcp-grocy** works great with standard upstream [Grocy](https://grocy.info/) (v4.x+), it is primarily developed and daily-driven with **[grocy-next](https://github.com/miguelangel-nubla/grocy-next)**.
14
+ >
15
+ > **grocy-next** is an enhanced, 100% compatible drop-in Docker distribution of Grocy featuring:
16
+ >
17
+ > - 📦 **Container Inventory Management:** Barcode-driven container packing, location hierarchies, and rapid transfers.
18
+ > - ⚖️ **Hardware & Home Assistant Integration:** Native helper integration for smart scales and wireless barcode scanners.
19
+ > - 🖥️ **Live Screens & Pending Scans:** Dedicated live inventory dashboards and async scan queues.
20
+ > - 🥗 **Enhanced Meal Planning:** Calorie breakdown per serving and streamlined meal plan workflows.
21
+ >
22
+ > 👉 If you want the most seamless experience with this MCP, deploy **[miguelangel-nubla/grocy-next](https://github.com/miguelangel-nubla/grocy-next)**!
9
23
 
10
24
  > **🍴 Opinionated Fork Notice**
11
25
  >
@@ -26,10 +40,6 @@
26
40
  >
27
41
  > If you need complete API access, use the [original fork](https://github.com/saya6k/mcp-grocy-api). This version trades flexibility for focused, dependable grocery management workflows.
28
42
 
29
- > **⚡ Supported Grocy Backends**
30
- >
31
- > This MCP server is compatible with standard [Grocy](https://grocy.info/) (v4.x+) as well as [grocy-next](https://github.com/miguelangel-nubla/grocy-next) (an enhanced fork with container inventory management and live workflows). The primary environment used by the author is `grocy-next`.
32
-
33
43
  ## 🎯 What This MCP Does
34
44
 
35
45
  Transform your LLM into an intelligent household management assistant with focused tools for:
@@ -64,7 +74,7 @@ Transform your LLM into an intelligent household management assistant with focus
64
74
 
65
75
  ## ⚡ Quick Start
66
76
 
67
- 1. **Get your Grocy API key** from your Grocy instance (User Settings → API Keys)
77
+ 1. **Get your Grocy API key** from your Grocy or [grocy-next](https://github.com/miguelangel-nubla/grocy-next) instance (User Settings → API Keys)
68
78
  2. **Set up with Docker Compose:**
69
79
 
70
80
  ```bash
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Simplified API client for Grocy
3
+ */
4
+ import axios from 'axios';
5
+ import https from 'https';
6
+ import { config } from '../config/index.js';
7
+ import { logger } from '../utils/logger.js';
8
+ import { ApiError, ErrorHandler } from '../utils/errors.js';
9
+ export class GrocyApiClient {
10
+ axiosInstance;
11
+ constructor() {
12
+ this.axiosInstance = this.createAxiosInstance();
13
+ this.setupInterceptors();
14
+ }
15
+ createAxiosInstance() {
16
+ const instance = axios.create({
17
+ baseURL: config.grocy.base_url,
18
+ validateStatus: () => true, // Handle all status codes manually
19
+ timeout: 30000,
20
+ maxContentLength: config.grocy.max_response_bytes,
21
+ httpsAgent: config.grocy.enable_ssl_verify
22
+ ? undefined
23
+ : new https.Agent({
24
+ rejectUnauthorized: false,
25
+ }),
26
+ });
27
+ return instance;
28
+ }
29
+ setupInterceptors() {
30
+ // Request logging
31
+ this.axiosInstance.interceptors.request.use((config) => {
32
+ logger.api(`${config.method?.toUpperCase()} ${config.url}`);
33
+ return config;
34
+ }, (error) => {
35
+ logger.error('Request error', 'API', { error: error.message });
36
+ return Promise.reject(error);
37
+ });
38
+ // Response logging
39
+ this.axiosInstance.interceptors.response.use((response) => {
40
+ if (response.status >= 400) {
41
+ logger.warn(`HTTP ${response.status}`, 'API', {
42
+ url: response.config?.url,
43
+ status: response.status,
44
+ });
45
+ }
46
+ return response;
47
+ }, (error) => {
48
+ logger.error('Response error', 'API', { error: error.message });
49
+ return Promise.reject(error);
50
+ });
51
+ }
52
+ normalizeEndpoint(endpoint) {
53
+ if (endpoint.startsWith('/api/'))
54
+ return endpoint;
55
+ if (endpoint.startsWith('api/'))
56
+ return `/${endpoint}`;
57
+ if (endpoint.startsWith('/'))
58
+ return `/api${endpoint}`;
59
+ return `/api/${endpoint}`;
60
+ }
61
+ buildQueryString(params) {
62
+ return Object.entries(params)
63
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
64
+ .join('&');
65
+ }
66
+ async request(endpoint, options = {}) {
67
+ const { method = 'GET', body = null, headers = {}, queryParams = {}, timeout } = options;
68
+ return ErrorHandler.handleAsync(async () => {
69
+ let url = this.normalizeEndpoint(endpoint);
70
+ if (Object.keys(queryParams).length > 0) {
71
+ url += `?${this.buildQueryString(queryParams)}`;
72
+ }
73
+ const requestConfig = {
74
+ method,
75
+ url,
76
+ headers: {
77
+ Accept: 'application/json',
78
+ 'Content-Type': 'application/json',
79
+ ...config.getCustomHeaders(),
80
+ ...headers,
81
+ },
82
+ ...(timeout && { timeout }),
83
+ };
84
+ if (['POST', 'PUT', 'PATCH'].includes(method) && body !== null) {
85
+ requestConfig.data = body;
86
+ }
87
+ const response = await this.axiosInstance.request(requestConfig);
88
+ if (response.status >= 400) {
89
+ throw new ApiError(response.data?.message || `HTTP ${response.status} error`, response.status, `${method} ${url}`, { responseData: response.data });
90
+ }
91
+ return {
92
+ data: response.data,
93
+ status: response.status,
94
+ headers: response.headers,
95
+ };
96
+ }, `API ${method} ${endpoint}`);
97
+ }
98
+ // Convenience methods
99
+ async get(endpoint, options = {}) {
100
+ return this.request(endpoint, { ...options, method: 'GET' });
101
+ }
102
+ async post(endpoint, body, options = {}) {
103
+ return this.request(endpoint, { ...options, method: 'POST', body });
104
+ }
105
+ async put(endpoint, body, options = {}) {
106
+ return this.request(endpoint, { ...options, method: 'PUT', body });
107
+ }
108
+ async delete(endpoint, options = {}) {
109
+ return this.request(endpoint, { ...options, method: 'DELETE' });
110
+ }
111
+ async patch(endpoint, body, options = {}) {
112
+ return this.request(endpoint, { ...options, method: 'PATCH', body });
113
+ }
114
+ }
115
+ // Export singleton instance
116
+ export const apiClient = new GrocyApiClient();
117
+ export default apiClient;
118
+ // Re-export ApiError for convenience
119
+ export { ApiError };
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Unified configuration system
3
+ * Combines environment variables and YAML configuration
4
+ */
5
+ import { z } from 'zod';
6
+ import { readFileSync, existsSync } from 'fs';
7
+ import { resolve, dirname } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import YAML from 'yaml';
10
+ import { logger } from '../utils/logger.js';
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ /** Default cap for Grocy HTTP response bodies (axios maxContentLength), in bytes */
13
+ export const DEFAULT_MAX_RESPONSE_BYTES = 52_428_800; // 50 MiB
14
+ // Environment schema
15
+ const EnvironmentSchema = z.object({
16
+ // Grocy Configuration
17
+ GROCY_BASE_URL: z.string().url().optional(),
18
+ GROCY_API_KEY: z.string().optional(),
19
+ GROCY_ENABLE_SSL_VERIFY: z.enum(['true', 'false']).optional(),
20
+ GROCY_MAX_RESPONSE_BYTES: z.string().regex(/^\d+$/).optional(),
21
+ // Server Configuration
22
+ REST_RESPONSE_SIZE_LIMIT: z.string().regex(/^\d+$/).optional(),
23
+ ENABLE_HTTP_SERVER: z.enum(['true', 'false']).optional(),
24
+ HTTP_SERVER_PORT: z.string().regex(/^\d+$/).optional(),
25
+ HTTP_CORS_ORIGIN: z.string().optional(),
26
+ MCP_HTTP_ACCESS_TOKEN: z.string().optional(),
27
+ MCP_SESSION_IDLE_TIMEOUT_MS: z.string().regex(/^\d+$/).optional(),
28
+ MCP_SESSION_SWEEP_INTERVAL_MS: z.string().regex(/^\d+$/).optional(),
29
+ SERIALIZE_STRUCTURED_TO_CONTENT: z.enum(['true', 'false']).optional(),
30
+ // Logging Configuration
31
+ LOG_LEVEL: z.enum(['DEBUG', 'INFO', 'WARN', 'ERROR']).optional(),
32
+ LOG_CATEGORIES: z.string().optional(),
33
+ // Build Configuration
34
+ RELEASE_VERSION: z.string().optional(),
35
+ NODE_ENV: z.enum(['development', 'production', 'test']).optional(),
36
+ });
37
+ // YAML configuration schema
38
+ const YamlConfigSchema = z
39
+ .object({
40
+ server: z
41
+ .object({
42
+ enable_http_server: z.boolean().default(false),
43
+ http_server_port: z.number().min(1).max(65535).default(8080),
44
+ /** CORS `Access-Control-Allow-Origin` for HTTP MCP endpoints (`*` or a single origin URL) */
45
+ http_cors_origin: z.string().min(1).default('*'),
46
+ /** When set, MCP HTTP/SSE routes require `Authorization: Bearer <token>`, `X-MCP-Access-Token`, or `access_token` query (GET only). */
47
+ http_access_token: z.string().optional(),
48
+ /** Reap an idle streamable-HTTP MCP session after this many ms with no requests. Bounds memory when clients reconnect without sending `DELETE /mcp`. */
49
+ session_idle_timeout_ms: z.number().int().positive().default(300_000),
50
+ /** How often, in ms, to sweep for idle MCP sessions to reap. */
51
+ session_sweep_interval_ms: z.number().int().positive().default(60_000),
52
+ /** When true, tools also serialize structured JSON payload into the content text block for backwards-compatible MCP clients. Default: false. */
53
+ serialize_structured_to_content: z.boolean().default(false),
54
+ })
55
+ .strict()
56
+ .default({
57
+ enable_http_server: false,
58
+ http_server_port: 8080,
59
+ http_cors_origin: '*',
60
+ session_idle_timeout_ms: 300_000,
61
+ session_sweep_interval_ms: 60_000,
62
+ serialize_structured_to_content: false,
63
+ }),
64
+ grocy: z
65
+ .object({
66
+ base_url: z.string().url().default('http://localhost:9283'),
67
+ api_key: z.string().optional(),
68
+ enable_ssl_verify: z.boolean().default(true),
69
+ response_size_limit: z.number().positive().default(10000),
70
+ /** Max Grocy API response body size in bytes (all tools); larger responses fail fast */
71
+ max_response_bytes: z.number().positive().default(DEFAULT_MAX_RESPONSE_BYTES),
72
+ })
73
+ .strict()
74
+ .default({
75
+ base_url: 'http://localhost:9283',
76
+ enable_ssl_verify: true,
77
+ response_size_limit: 10000,
78
+ max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES,
79
+ }),
80
+ tools: z
81
+ .record(z.string(), z
82
+ .object({
83
+ enabled: z.boolean().default(false),
84
+ ack_token: z.string().optional(),
85
+ })
86
+ .catchall(z.unknown()))
87
+ .default({}),
88
+ })
89
+ .strict();
90
+ export class ConfigManager {
91
+ static instance;
92
+ config;
93
+ // Unified config properties - final resolved values
94
+ grocy;
95
+ server;
96
+ tools;
97
+ constructor(configPath) {
98
+ this.config = this.loadConfig(configPath);
99
+ // Expose final resolved values
100
+ this.grocy = {
101
+ base_url: this.config.yaml.grocy.base_url,
102
+ ...(this.config.yaml.grocy.api_key !== undefined && {
103
+ api_key: this.config.yaml.grocy.api_key,
104
+ }),
105
+ enable_ssl_verify: this.config.yaml.grocy.enable_ssl_verify,
106
+ response_size_limit: this.config.yaml.grocy.response_size_limit,
107
+ max_response_bytes: this.config.yaml.grocy.max_response_bytes,
108
+ };
109
+ this.server = {
110
+ enable_http_server: this.config.yaml.server.enable_http_server,
111
+ http_server_port: this.config.yaml.server.http_server_port,
112
+ http_cors_origin: this.config.yaml.server.http_cors_origin,
113
+ session_idle_timeout_ms: this.config.yaml.server.session_idle_timeout_ms,
114
+ session_sweep_interval_ms: this.config.yaml.server.session_sweep_interval_ms,
115
+ serialize_structured_to_content: this.config.yaml.server.serialize_structured_to_content,
116
+ ...(this.config.yaml.server.http_access_token !== undefined &&
117
+ this.config.yaml.server.http_access_token !== '' && {
118
+ http_access_token: this.config.yaml.server.http_access_token,
119
+ }),
120
+ };
121
+ this.tools = this.config.yaml.tools;
122
+ }
123
+ static getInstance() {
124
+ if (!ConfigManager.instance) {
125
+ ConfigManager.instance = new ConfigManager();
126
+ }
127
+ return ConfigManager.instance;
128
+ }
129
+ loadConfig(configPath) {
130
+ // Load environment variables
131
+ const env = this.loadEnvironment();
132
+ // Load YAML configuration
133
+ const yaml = this.loadYamlConfig(configPath);
134
+ // Apply environment variable overrides
135
+ this.applyEnvironmentOverrides(yaml, env);
136
+ return { env, yaml };
137
+ }
138
+ loadEnvironment() {
139
+ try {
140
+ return EnvironmentSchema.parse(process.env);
141
+ }
142
+ catch (error) {
143
+ if (error instanceof z.ZodError) {
144
+ logger.error('Invalid environment variables', 'CONFIG');
145
+ for (const issue of error.issues) {
146
+ const path = issue.path?.length ? issue.path.join('.') : '(root)';
147
+ logger.error(`${path}: ${issue.message}`, 'CONFIG');
148
+ }
149
+ process.exit(1);
150
+ }
151
+ throw error;
152
+ }
153
+ }
154
+ loadYamlConfig(configPath) {
155
+ const yamlPath = this.findConfigFile(configPath);
156
+ try {
157
+ let configData = {};
158
+ if (existsSync(yamlPath)) {
159
+ const yamlContent = readFileSync(yamlPath, 'utf8');
160
+ configData = YAML.parse(yamlContent) || {};
161
+ logger.config(`Loaded YAML config from: ${yamlPath}`);
162
+ }
163
+ else {
164
+ logger.config('No YAML config found, using defaults');
165
+ }
166
+ return YamlConfigSchema.parse(configData);
167
+ }
168
+ catch (error) {
169
+ if (error instanceof z.ZodError) {
170
+ logger.error('Invalid YAML configuration', 'CONFIG');
171
+ for (const issue of error.issues) {
172
+ const path = issue.path?.length ? issue.path.join('.') : '(root)';
173
+ logger.error(`${path}: ${issue.message}`, 'CONFIG');
174
+ }
175
+ process.exit(1);
176
+ }
177
+ throw error;
178
+ }
179
+ }
180
+ findConfigFile(configPath) {
181
+ if (configPath)
182
+ return configPath;
183
+ // Look for config files in the following order:
184
+ // 1. Current working directory (for development)
185
+ // 2. Project root (relative to the compiled main.js)
186
+ const projectRoot = resolve(__dirname, '../..');
187
+ const possiblePaths = [
188
+ resolve(process.cwd(), 'mcp-grocy.yaml'),
189
+ resolve(process.cwd(), 'mcp-grocy.yml'),
190
+ resolve(projectRoot, 'mcp-grocy.yaml'),
191
+ resolve(projectRoot, 'mcp-grocy.yml'),
192
+ ];
193
+ return possiblePaths.find((path) => existsSync(path)) ?? possiblePaths[0];
194
+ }
195
+ // Public getters
196
+ getConfig() {
197
+ return this.config;
198
+ }
199
+ getApiUrl() {
200
+ return this.grocy.base_url.endsWith('/')
201
+ ? `${this.grocy.base_url}api`
202
+ : `${this.grocy.base_url}/api`;
203
+ }
204
+ getCustomHeaders() {
205
+ const headers = {};
206
+ if (this.grocy.api_key) {
207
+ headers['GROCY-API-KEY'] = this.grocy.api_key;
208
+ }
209
+ return headers;
210
+ }
211
+ /**
212
+ * Apply environment variable overrides to YAML configuration
213
+ */
214
+ applyEnvironmentOverrides(yaml, env) {
215
+ // Grocy configuration overrides
216
+ if (env.GROCY_BASE_URL) {
217
+ yaml.grocy.base_url = env.GROCY_BASE_URL;
218
+ }
219
+ if (env.GROCY_API_KEY) {
220
+ yaml.grocy.api_key = env.GROCY_API_KEY;
221
+ }
222
+ if (env.GROCY_ENABLE_SSL_VERIFY !== undefined) {
223
+ yaml.grocy.enable_ssl_verify = env.GROCY_ENABLE_SSL_VERIFY === 'true';
224
+ }
225
+ if (env.REST_RESPONSE_SIZE_LIMIT !== undefined) {
226
+ yaml.grocy.response_size_limit = parseInt(env.REST_RESPONSE_SIZE_LIMIT, 10);
227
+ }
228
+ if (env.GROCY_MAX_RESPONSE_BYTES !== undefined) {
229
+ yaml.grocy.max_response_bytes = parseInt(env.GROCY_MAX_RESPONSE_BYTES, 10);
230
+ }
231
+ // Server configuration overrides
232
+ if (env.ENABLE_HTTP_SERVER !== undefined) {
233
+ yaml.server.enable_http_server = env.ENABLE_HTTP_SERVER === 'true';
234
+ }
235
+ if (env.HTTP_SERVER_PORT !== undefined) {
236
+ yaml.server.http_server_port = parseInt(env.HTTP_SERVER_PORT, 10);
237
+ }
238
+ if (env.HTTP_CORS_ORIGIN !== undefined && env.HTTP_CORS_ORIGIN.length > 0) {
239
+ yaml.server.http_cors_origin = env.HTTP_CORS_ORIGIN;
240
+ }
241
+ if (env.MCP_HTTP_ACCESS_TOKEN !== undefined) {
242
+ yaml.server.http_access_token =
243
+ env.MCP_HTTP_ACCESS_TOKEN.length > 0 ? env.MCP_HTTP_ACCESS_TOKEN : undefined;
244
+ }
245
+ if (env.MCP_SESSION_IDLE_TIMEOUT_MS !== undefined) {
246
+ yaml.server.session_idle_timeout_ms = parseInt(env.MCP_SESSION_IDLE_TIMEOUT_MS, 10);
247
+ }
248
+ if (env.MCP_SESSION_SWEEP_INTERVAL_MS !== undefined) {
249
+ yaml.server.session_sweep_interval_ms = parseInt(env.MCP_SESSION_SWEEP_INTERVAL_MS, 10);
250
+ }
251
+ if (env.SERIALIZE_STRUCTURED_TO_CONTENT !== undefined) {
252
+ yaml.server.serialize_structured_to_content = env.SERIALIZE_STRUCTURED_TO_CONTENT === 'true';
253
+ }
254
+ }
255
+ parseToolConfiguration() {
256
+ const enabledTools = new Set();
257
+ const toolSubConfigs = new Map();
258
+ const toolAckTokens = new Map();
259
+ for (const [toolName, toolConfig] of Object.entries(this.config.yaml.tools)) {
260
+ if (toolConfig.enabled) {
261
+ enabledTools.add(toolName);
262
+ // Store ack_token separately if configured
263
+ if (toolConfig.ack_token && typeof toolConfig.ack_token === 'string') {
264
+ toolAckTokens.set(toolName, toolConfig.ack_token);
265
+ }
266
+ // Extract sub-configs (everything except standard fields)
267
+ const subConfigs = new Map();
268
+ for (const [key, value] of Object.entries(toolConfig)) {
269
+ if (!['enabled', 'ack_token'].includes(key)) {
270
+ subConfigs.set(key, value);
271
+ }
272
+ }
273
+ if (subConfigs.size > 0) {
274
+ toolSubConfigs.set(toolName, subConfigs);
275
+ }
276
+ }
277
+ }
278
+ return { enabledTools, toolSubConfigs, toolAckTokens };
279
+ }
280
+ }
281
+ // Export singleton instance
282
+ export const config = ConfigManager.getInstance();
283
+ export default config;
package/build/main.js ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Simplified main entry point
4
+ * Demonstrates the refactored architecture
5
+ */
6
+ // Load environment variables
7
+ import 'dotenv/config';
8
+ import { GrocyMcpServer } from './server/mcp-server.js';
9
+ import { config } from './config/index.js';
10
+ import { VERSION, PACKAGE_NAME as SERVER_NAME } from './version.js';
11
+ import { logger } from './utils/logger.js';
12
+ import { ErrorHandler } from './utils/errors.js';
13
+ // Startup banner
14
+ logger.info(`Starting ${SERVER_NAME} v${VERSION}`, 'SERVER');
15
+ async function main() {
16
+ return ErrorHandler.handleAsync(async () => {
17
+ // Check API key
18
+ if (!config.grocy.api_key) {
19
+ logger.warn('No API key configured. Some operations may fail.', 'CONFIG');
20
+ }
21
+ // Log configuration summary
22
+ logger.config(`Grocy URL: ${config.grocy.base_url}`);
23
+ logger.config(`SSL Verify: ${config.grocy.enable_ssl_verify}`);
24
+ logger.config(`HTTP Server: ${config.server.enable_http_server}`);
25
+ // Create and start server
26
+ const server = await GrocyMcpServer.create();
27
+ await server.start();
28
+ logger.info('Server started successfully', 'SERVER');
29
+ }, 'server startup');
30
+ }
31
+ // Error handling
32
+ process.on('uncaughtException', (error) => {
33
+ logger.error('Uncaught exception', 'PROCESS', { error });
34
+ process.exit(1);
35
+ });
36
+ process.on('unhandledRejection', (reason, promise) => {
37
+ logger.error('Unhandled rejection', 'PROCESS', { reason, promise });
38
+ process.exit(1);
39
+ });
40
+ // Start the application
41
+ main().catch((error) => {
42
+ logger.error('Failed to start server', 'SERVER', { error });
43
+ process.exit(1);
44
+ });
@@ -1,10 +1,16 @@
1
- ## [2.7.1](https://github.com/miguelangel-nubla/mcp-grocy/compare/v2.7.0...v2.7.1) (2026-09-10)
1
+ ## [2.7.2](https://github.com/miguelangel-nubla/mcp-grocy/compare/v2.7.1...v2.7.2) (2026-09-10)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add prepack script, update release configs, and fix docker workflows ([c464c05](https://github.com/miguelangel-nubla/mcp-grocy/commit/c464c054ab4f3f420647196ded7454c60f3702fc))
2
7
 
8
+ ## [2.7.1](https://github.com/miguelangel-nubla/mcp-grocy/compare/v2.7.0...v2.7.1) (2026-09-10)
3
9
 
4
10
  ### Bug Fixes
5
11
 
6
- * refuse non-recipe meal plan entries in recipes_cooking_complete before writing ([172d1f0](https://github.com/miguelangel-nubla/mcp-grocy/commit/172d1f0089b1e401736b258cec652a30d90e0215)), closes [#2](https://github.com/miguelangel-nubla/mcp-grocy/issues/2)
7
- * send correct meal plan payload in recipes_mealplan_add_recipe ([2b180b0](https://github.com/miguelangel-nubla/mcp-grocy/commit/2b180b02b58ab03db69efab727ee53ab88caa0d0)), closes [#2](https://github.com/miguelangel-nubla/mcp-grocy/issues/2)
12
+ - refuse non-recipe meal plan entries in recipes_cooking_complete before writing ([172d1f0](https://github.com/miguelangel-nubla/mcp-grocy/commit/172d1f0089b1e401736b258cec652a30d90e0215)), closes [#2](https://github.com/miguelangel-nubla/mcp-grocy/issues/2)
13
+ - send correct meal plan payload in recipes_mealplan_add_recipe ([2b180b0](https://github.com/miguelangel-nubla/mcp-grocy/commit/2b180b02b58ab03db69efab727ee53ab88caa0d0)), closes [#2](https://github.com/miguelangel-nubla/mcp-grocy/issues/2)
8
14
 
9
15
  # [2.7.0](https://github.com/miguelangel-nubla/mcp-grocy/compare/v2.6.0...v2.7.0) (2026-08-03)
10
16
 
@@ -6,6 +6,20 @@
6
6
  [![Configuration Status](https://github.com/miguelangel-nubla/mcp-grocy/actions/workflows/validate-config.yml/badge.svg)](https://github.com/miguelangel-nubla/mcp-grocy/actions/workflows/validate-config.yml)
7
7
  [![CI/CD Pipeline](https://github.com/miguelangel-nubla/mcp-grocy/actions/workflows/pipeline.yml/badge.svg)](https://github.com/miguelangel-nubla/mcp-grocy/actions/workflows/pipeline.yml)
8
8
  [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-blue)](https://modelcontextprotocol.io)
9
+ [![Recommended Backend: grocy-next](https://img.shields.io/badge/Recommended%20Backend-grocy--next-brightgreen?logo=docker)](https://github.com/miguelangel-nubla/grocy-next)
10
+
11
+ > ### 🌟 Recommended Companion: [grocy-next](https://github.com/miguelangel-nubla/grocy-next)
12
+ >
13
+ > While **mcp-grocy** works great with standard upstream [Grocy](https://grocy.info/) (v4.x+), it is primarily developed and daily-driven with **[grocy-next](https://github.com/miguelangel-nubla/grocy-next)**.
14
+ >
15
+ > **grocy-next** is an enhanced, 100% compatible drop-in Docker distribution of Grocy featuring:
16
+ >
17
+ > - 📦 **Container Inventory Management:** Barcode-driven container packing, location hierarchies, and rapid transfers.
18
+ > - ⚖️ **Hardware & Home Assistant Integration:** Native helper integration for smart scales and wireless barcode scanners.
19
+ > - 🖥️ **Live Screens & Pending Scans:** Dedicated live inventory dashboards and async scan queues.
20
+ > - 🥗 **Enhanced Meal Planning:** Calorie breakdown per serving and streamlined meal plan workflows.
21
+ >
22
+ > 👉 If you want the most seamless experience with this MCP, deploy **[miguelangel-nubla/grocy-next](https://github.com/miguelangel-nubla/grocy-next)**!
9
23
 
10
24
  > **🍴 Opinionated Fork Notice**
11
25
  >
@@ -26,10 +40,6 @@
26
40
  >
27
41
  > If you need complete API access, use the [original fork](https://github.com/saya6k/mcp-grocy-api). This version trades flexibility for focused, dependable grocery management workflows.
28
42
 
29
- > **⚡ Supported Grocy Backends**
30
- >
31
- > This MCP server is compatible with standard [Grocy](https://grocy.info/) (v4.x+) as well as [grocy-next](https://github.com/miguelangel-nubla/grocy-next) (an enhanced fork with container inventory management and live workflows). The primary environment used by the author is `grocy-next`.
32
-
33
43
  ## 🎯 What This MCP Does
34
44
 
35
45
  Transform your LLM into an intelligent household management assistant with focused tools for:
@@ -64,7 +74,7 @@ Transform your LLM into an intelligent household management assistant with focus
64
74
 
65
75
  ## ⚡ Quick Start
66
76
 
67
- 1. **Get your Grocy API key** from your Grocy instance (User Settings → API Keys)
77
+ 1. **Get your Grocy API key** from your Grocy or [grocy-next](https://github.com/miguelangel-nubla/grocy-next) instance (User Settings → API Keys)
68
78
  2. **Set up with Docker Compose:**
69
79
 
70
80
  ```bash
@@ -1,6 +1,6 @@
1
1
  # Grocy API Reference
2
2
 
3
- > Auto-generated from https://demo.grocy.info/api/openapi/specification on 2025-05-17T16:56:10.327Z
3
+ > Auto-generated from https://demo.grocy.info/api/openapi/specification on 2026-09-10T20:59:54.572Z
4
4
 
5
5
  | Path | Method | Summary |
6
6
  | -------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -51,6 +51,7 @@
51
51
  | /stock/products/{productId} | GET | Returns details of the given product |
52
52
  | /stock/products/{productId}/add | POST | Adds the given amount of the given product to stock |
53
53
  | /stock/products/{productId}/consume | POST | Removes the given amount of the given product from stock |
54
+ | /stock/products/{productId}/copy | POST | Copies a product |
54
55
  | /stock/products/{productId}/entries | GET | Returns all stock entries of the given product in order of next use (Opened first, then first due first, then first in first out) |
55
56
  | /stock/products/{productId}/inventory | POST | Inventories the given product (adds/removes based on the given new amount) |
56
57
  | /stock/products/{productId}/locations | GET | Returns all locations where the given product currently has stock |
@@ -159,9 +159,7 @@ If **`system_dev_test_request`** encounters an API error (e.g., authentication f
159
159
  "statusCode": 401,
160
160
  "statusText": "Unauthorized",
161
161
  "timing": "50ms",
162
- "headers": {
163
- /* ... headers ... */
164
- },
162
+ "headers": {/* ... headers ... */},
165
163
  "body": {
166
164
  "error_message": "API key is missing or invalid."
167
165
  }