mcp-grocy 1.9.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 ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Christian Pedersen
4
+ Copyright (c) 2024 saya6k
5
+ Copyright (c) 2025 miguelangel-nubla
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,285 @@
1
+ # MCP Grocy
2
+
3
+ [![npm version](https://img.shields.io/npm/v/mcp-grocy.svg)](https://www.npmjs.com/package/mcp-grocy)
4
+ [![Docker Image](https://img.shields.io/badge/docker%20image-ghcr.io-blue)](https://github.com/miguelangel-nubla/mcp-grocy/pkgs/container/mcp-grocy)
5
+ [![License](https://img.shields.io/github/license/miguelangel-nubla/mcp-grocy)](LICENSE)
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
+ [![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
+ [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-blue)](https://modelcontextprotocol.io)
9
+
10
+ > **🍴 Opinionated Fork Notice**
11
+ >
12
+ > This is a heavily opinionated fork of [saya6k/mcp-grocy-api](https://github.com/saya6k/mcp-grocy-api) that has diverged significantly to warrant a separate identity. This MCP prioritizes **usability over features**.
13
+ >
14
+ > **Why This Fork Exists:**
15
+ > - The original wrapper exposes the entire Grocy API unprocessed, leading to context overload and LLM confusion
16
+ > - Grocy's API design choices and limitations cause error-prone interactions
17
+ > - Generic API exposure increases hallucination and near-miss results
18
+ >
19
+ > **This Fork's Philosophy:**
20
+ > - **Filters and augments data** with relevant context for better LLM comprehension
21
+ > - **Reduces API calls** by combining common operations to minimize error chains
22
+ > - **Optimizes for reliability and repeatability** over feature completeness
23
+ > - **Opinionated workflows** that may not match everyone's preferences
24
+ >
25
+ > 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.
26
+
27
+ ## 🎯 What This MCP Does
28
+
29
+ Transform your LLM into an intelligent household management assistant with focused tools for:
30
+
31
+ ### 📦 **Stock Management**
32
+ - Track inventory across multiple locations with precision
33
+ - Record purchases and consumption with automatic stock updates
34
+ - Monitor expiry dates and get volatile stock alerts
35
+ - Transfer products between storage locations
36
+
37
+ ### 🛒 **Smart Shopping & Planning**
38
+ - Maintain shopping lists with intelligent quantity management
39
+ - Plan meals with recipe scheduling and fulfillment checking
40
+ - Automatically add missing ingredients to shopping lists
41
+ - Track shopping locations and optimize store visits
42
+
43
+ ### 🍽️ **Recipe & Meal Workflows**
44
+ - Find recipes with fuzzy search capabilities
45
+ - Check if recipes can be made with current stock
46
+ - Complete cooking workflows with portion control
47
+ - Integrate meal planning with inventory consumption
48
+
49
+ ### 🏠 **Household Management**
50
+ - Manage chores, tasks, and battery tracking
51
+ - Get product price history for budgeting
52
+ - Organize products by groups and categories
53
+ - Print labels for stock entries
54
+
55
+ ## ⚡ Quick Start
56
+
57
+ 1. **Get your Grocy API key** from your Grocy instance (User Settings → API Keys)
58
+ 2. **Set up with Docker Compose:**
59
+ ```bash
60
+ # Get the project
61
+ git clone https://github.com/miguelangel-nubla/mcp-grocy.git
62
+ cd mcp-grocy
63
+
64
+ # Configure
65
+ cp .env.example .env
66
+ # Edit .env with your GROCY_BASE_URL and GROCY_APIKEY_VALUE
67
+
68
+ # Run
69
+ docker compose up -d
70
+ ```
71
+
72
+ ### Try Without Grocy
73
+ Test with mock data (no real Grocy instance needed):
74
+ ```bash
75
+ # In .env file, any values work for mock mode
76
+ GROCY_BASE_URL=http://mock
77
+ GROCY_APIKEY_VALUE=mock
78
+
79
+ npm install && npm run dev
80
+ ```
81
+
82
+ ## Installation
83
+
84
+ ### NPM
85
+
86
+ ```bash
87
+ git clone -b main https://github.com/miguelangel-nubla/mcp-grocy.git
88
+ cd mcp-grocy
89
+ npm install
90
+ npm run build
91
+ ```
92
+
93
+ ### Docker
94
+
95
+ ```bash
96
+ docker run -e GROCY_APIKEY_VALUE=your_api_key -e GROCY_BASE_URL=http://your-grocy-instance ghcr.io/miguelangel-nubla/mcp-grocy:latest
97
+ ```
98
+
99
+ ### Docker Compose (Recommended)
100
+
101
+ Create a `docker-compose.yml`:
102
+ ```yaml
103
+ services:
104
+ mcp-grocy:
105
+ image: ghcr.io/miguelangel-nubla/mcp-grocy:latest
106
+ env_file:
107
+ - .env
108
+ restart: unless-stopped
109
+ ```
110
+
111
+ Then:
112
+ ```bash
113
+ cp .env.example .env
114
+ # Edit .env with your configuration
115
+ docker compose up -d
116
+ ```
117
+
118
+ ## ⚙️ Configuration
119
+
120
+ ### Quick Setup
121
+
122
+ 1. **Get your Grocy API key:**
123
+ - Open your Grocy instance → **User Settings** → **API Keys**
124
+ - Create a new API key and copy it
125
+
126
+ 2. **Configure the server:**
127
+ ```bash
128
+ cp .env.example .env
129
+ # Edit .env with your GROCY_BASE_URL and GROCY_APIKEY_VALUE
130
+ ```
131
+
132
+ 3. **Essential variables:**
133
+ - `GROCY_BASE_URL` - Your Grocy instance URL
134
+ - `GROCY_APIKEY_VALUE` - Your Grocy API key
135
+
136
+ ### Configuration Options
137
+
138
+ | Method | Use Case | Command |
139
+ |--------|----------|---------|
140
+ | **`.env` file** | Recommended for most users | `cp .env.example .env` |
141
+ | **Environment variables** | CI/CD, containers | `GROCY_BASE_URL=... GROCY_APIKEY_VALUE=... mcp-grocy` |
142
+ | **Tool toggles** | Customize functionality | Edit `TOOL__*` variables in `.env` |
143
+
144
+ 📖 **For complete configuration reference:** See [Configuration Guide](src/resources/config.md)
145
+
146
+ ## 🚀 Usage Modes
147
+
148
+ ### Production Mode
149
+ Start with your real Grocy instance:
150
+ ```bash
151
+ npm start
152
+ ```
153
+
154
+ ### Development/Testing Mode
155
+ Use mock data (no Grocy instance required):
156
+ ```bash
157
+ npm run dev
158
+ ```
159
+
160
+ ### HTTP Server Mode
161
+ Enable web-based access via HTTP/SSE:
162
+ ```bash
163
+ # In .env: ENABLE_HTTP_SERVER=true
164
+ npm start
165
+ # Access via http://localhost:8080/mcp
166
+ ```
167
+
168
+
169
+ ## 📚 Documentation & Resources
170
+
171
+ | Resource | Purpose | When to Use |
172
+ |----------|---------|-------------|
173
+ | [📖 API Reference](src/resources/api-reference.md) | Complete tool documentation | Tool usage and examples |
174
+ | [⚙️ Configuration Guide](src/resources/config.md) | Advanced configuration reference | Detailed setup, presets, troubleshooting |
175
+ | [📋 .env.example](.env.example) | Configuration template with ALL tools | Copy and customize for your setup |
176
+ | [🧪 MCP Inspector](https://github.com/modelcontextprotocol/inspector) | Protocol debugging | Debug MCP interactions |
177
+
178
+ ### 🆘 Troubleshooting
179
+
180
+ #### Common Issues
181
+
182
+ **"Connection refused" or "Cannot connect to Grocy"**
183
+ - Verify `GROCY_BASE_URL` is correct and accessible
184
+ - Check that your Grocy instance is running
185
+ - For HTTPS URLs, ensure SSL certificate is valid or disable verification with `GROCY_ENABLE_SSL_VERIFY=false`
186
+
187
+ **"Invalid API key" or "Authentication failed"**
188
+ - Verify your `GROCY_APIKEY_VALUE` is correct
189
+ - Check that the API key exists in your Grocy instance (User Settings → API Keys)
190
+ - Ensure the API key has proper permissions
191
+
192
+ **"Tool not found" errors**
193
+ - Check if the tool is enabled in your `.env` file (tool toggles)
194
+ - Verify you're using the correct tool names from the API reference
195
+
196
+ **Large response errors**
197
+ - Increase `REST_RESPONSE_SIZE_LIMIT` if you have many products/stock entries
198
+ - Consider using tool toggles to disable unused functionality
199
+
200
+ #### Debug Mode
201
+
202
+ Enable detailed logging and use the MCP inspector:
203
+ ```bash
204
+ # Launch MCP inspector for protocol debugging
205
+ npm run inspector
206
+
207
+ # Run with mock data for testing
208
+ npm run dev
209
+ ```
210
+
211
+ ## 🛠️ Development
212
+
213
+ ### Prerequisites
214
+
215
+ - Node.js 18 or higher
216
+ - Grocy instance (optional with mock mode)
217
+
218
+ ### Development Setup
219
+
220
+ ```bash
221
+ # Clone and install
222
+ git clone https://github.com/miguelangel-nubla/mcp-grocy.git
223
+ cd mcp-grocy
224
+ npm install
225
+
226
+ # Configure for development
227
+ cp .env.example .env
228
+ # Edit .env with your settings (or use mock values)
229
+
230
+ # Build and run
231
+ npm run build
232
+ npm start
233
+ ```
234
+
235
+ ### Development Commands
236
+
237
+ | Command | Description |
238
+ |---------|-------------|
239
+ | `npm run build` | Build TypeScript to JavaScript |
240
+ | `npm run watch` | Watch mode for development |
241
+ | `npm run dev` | Start with mock data (no Grocy needed) |
242
+ | `npm test` | Run test suite |
243
+ | `npm run test:watch` | Run tests in watch mode |
244
+ | `npm run inspector` | Launch MCP protocol inspector |
245
+
246
+ ### Debugging
247
+
248
+ Use the MCP inspector to debug protocol interactions:
249
+ ```bash
250
+ npm run inspector
251
+ ```
252
+
253
+ This launches a web interface for testing MCP tools and viewing protocol messages.
254
+
255
+ ## 🤝 Contributing
256
+
257
+ This is an **opinionated fork** focused on LLM usability and workflow reliability. Contributions are welcome but must align with the core philosophy:
258
+
259
+ ### ✅ Welcome Contributions
260
+ - Bug fixes and reliability improvements
261
+ - Better error handling and validation
262
+ - Documentation improvements
263
+ - Test coverage enhancements
264
+ - Performance optimizations
265
+
266
+ ### ❌ Contributions Requiring Discussion
267
+ - New tool additions (must demonstrate clear LLM workflow benefits)
268
+ - API design changes that increase complexity
269
+ - Features that expose raw Grocy API behavior
270
+
271
+ ### Development Workflow
272
+ 1. Fork the repository
273
+ 2. Create a feature branch
274
+ 3. Make your changes with tests
275
+ 4. Run `npm test` and ensure all tests pass
276
+ 5. Submit a pull request with clear description
277
+
278
+ ## 📄 License
279
+
280
+ This project is licensed under the [MIT License](LICENSE).
281
+
282
+ ---
283
+
284
+ **🏠 Made for reliable household management with LLMs**
285
+ *Prioritizing workflow efficiency over feature completeness*
@@ -0,0 +1,154 @@
1
+ import axios from 'axios';
2
+ import https from 'https';
3
+ import config from '../config/environment.js';
4
+ export class ApiError extends Error {
5
+ status;
6
+ response;
7
+ constructor(message, status, response) {
8
+ super(message);
9
+ this.name = 'ApiError';
10
+ this.status = status;
11
+ this.response = response;
12
+ }
13
+ }
14
+ export class GrocyApiClient {
15
+ axiosInstance;
16
+ API_KEY_HEADER = 'GROCY-API-KEY';
17
+ constructor() {
18
+ const { GROCY_ENABLE_SSL_VERIFY, GROCY_APIKEY_VALUE } = config.get();
19
+ this.axiosInstance = axios.create({
20
+ baseURL: config.getGrocyBaseUrl(),
21
+ validateStatus: () => true, // Allow any status code
22
+ timeout: 30000, // 30 seconds timeout
23
+ httpsAgent: GROCY_ENABLE_SSL_VERIFY ? undefined : new https.Agent({
24
+ rejectUnauthorized: false
25
+ })
26
+ });
27
+ // Set default authentication if available
28
+ if (GROCY_APIKEY_VALUE) {
29
+ this.axiosInstance.defaults.headers.common[this.API_KEY_HEADER] = GROCY_APIKEY_VALUE;
30
+ }
31
+ // Add request interceptor for logging
32
+ this.axiosInstance.interceptors.request.use((config) => {
33
+ console.error(`[API] ${config.method?.toUpperCase()} ${config.url}`);
34
+ if (config.data) {
35
+ console.error(`[API] Request body: ${JSON.stringify(config.data)}`);
36
+ }
37
+ return config;
38
+ }, (error) => {
39
+ console.error('[API] Request error:', error);
40
+ return Promise.reject(error);
41
+ });
42
+ // Add response interceptor for error handling
43
+ this.axiosInstance.interceptors.response.use((response) => {
44
+ if (response.status >= 400) {
45
+ console.error(`[API] Error response (${response.status}): ${JSON.stringify(response.data)}`);
46
+ }
47
+ return response;
48
+ }, (error) => {
49
+ console.error('[API] Response error:', error);
50
+ return Promise.reject(error);
51
+ });
52
+ }
53
+ normalizeEndpoint(endpoint) {
54
+ // Standardize path handling
55
+ let normalizedEndpoint = endpoint;
56
+ // Check if endpoint explicitly starts with /api/ - use it as is
57
+ if (endpoint.startsWith('/api/')) {
58
+ normalizedEndpoint = endpoint;
59
+ }
60
+ // Handle endpoints that start with api/ without leading slash
61
+ else if (endpoint.startsWith('api/')) {
62
+ normalizedEndpoint = `/${endpoint}`;
63
+ }
64
+ // All other endpoints - ensure they start with /api/
65
+ else {
66
+ if (endpoint.startsWith('/')) {
67
+ normalizedEndpoint = `/api${endpoint}`;
68
+ }
69
+ else {
70
+ normalizedEndpoint = `/api/${endpoint}`;
71
+ }
72
+ }
73
+ console.error(`[API] Normalized endpoint: ${normalizedEndpoint}`);
74
+ return normalizedEndpoint;
75
+ }
76
+ buildQueryString(params) {
77
+ return Object.entries(params)
78
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
79
+ .join('&');
80
+ }
81
+ async request(endpoint, options = {}) {
82
+ const { method = 'GET', body = null, headers = {}, queryParams = {}, timeout } = options;
83
+ let url = this.normalizeEndpoint(endpoint);
84
+ // Add query parameters
85
+ if (Object.keys(queryParams).length > 0) {
86
+ const queryString = this.buildQueryString(queryParams);
87
+ url += `?${queryString}`;
88
+ }
89
+ const requestConfig = {
90
+ method,
91
+ url,
92
+ headers: {
93
+ 'Accept': 'application/json',
94
+ 'Content-Type': 'application/json',
95
+ ...config.getCustomHeaders(),
96
+ ...headers
97
+ },
98
+ timeout
99
+ };
100
+ if (['POST', 'PUT', 'PATCH'].includes(method) && body !== null) {
101
+ requestConfig.data = body;
102
+ }
103
+ try {
104
+ const response = await this.axiosInstance.request(requestConfig);
105
+ if (response.status >= 400) {
106
+ throw new ApiError(`API error (${response.status}): ${JSON.stringify(response.data)}`, response.status, response.data);
107
+ }
108
+ return {
109
+ data: response.data,
110
+ status: response.status,
111
+ headers: response.headers
112
+ };
113
+ }
114
+ catch (error) {
115
+ if (axios.isAxiosError(error)) {
116
+ const axiosError = error;
117
+ if (axiosError.code === 'ECONNABORTED') {
118
+ throw new ApiError('Connection timeout: The server took too long to respond');
119
+ }
120
+ else if (axiosError.code === 'ECONNRESET' || axiosError.message.includes('socket hang up')) {
121
+ throw new ApiError('Connection reset: The server unexpectedly closed the connection');
122
+ }
123
+ else if (!axiosError.response) {
124
+ throw new ApiError('Network error: Unable to reach the Grocy server');
125
+ }
126
+ }
127
+ // Re-throw ApiError instances
128
+ if (error instanceof ApiError) {
129
+ throw error;
130
+ }
131
+ // Wrap other errors
132
+ throw new ApiError(`Request failed: ${error.message || error}`);
133
+ }
134
+ }
135
+ // Convenience methods
136
+ async get(endpoint, options = {}) {
137
+ return this.request(endpoint, { ...options, method: 'GET' });
138
+ }
139
+ async post(endpoint, body, options = {}) {
140
+ return this.request(endpoint, { ...options, method: 'POST', body });
141
+ }
142
+ async put(endpoint, body, options = {}) {
143
+ return this.request(endpoint, { ...options, method: 'PUT', body });
144
+ }
145
+ async delete(endpoint, options = {}) {
146
+ return this.request(endpoint, { ...options, method: 'DELETE' });
147
+ }
148
+ async patch(endpoint, body, options = {}) {
149
+ return this.request(endpoint, { ...options, method: 'PATCH', body });
150
+ }
151
+ }
152
+ // Export a singleton instance
153
+ export const apiClient = new GrocyApiClient();
154
+ export default apiClient;
@@ -0,0 +1,142 @@
1
+ import { z } from 'zod';
2
+ // Environment variable schema with validation and defaults
3
+ const EnvironmentSchema = z.object({
4
+ // Grocy Configuration
5
+ GROCY_BASE_URL: z.string().url().default('http://localhost:9283'),
6
+ GROCY_APIKEY_VALUE: z.string().optional(),
7
+ GROCY_ENABLE_SSL_VERIFY: z.string().default('true').transform(val => val !== 'false'),
8
+ // Server Configuration
9
+ ENABLE_HTTP_SERVER: z.string().default('false').transform(val => ['true', 'yes', '1', 'on', 'enabled'].includes(val.toLowerCase())),
10
+ HTTP_SERVER_PORT: z.string().default('8080').transform(val => parseInt(val, 10)),
11
+ // API Configuration
12
+ REST_RESPONSE_SIZE_LIMIT: z.string().default('10000').transform(val => {
13
+ const parsed = parseInt(val, 10);
14
+ if (isNaN(parsed) || parsed <= 0) {
15
+ throw new Error('REST_RESPONSE_SIZE_LIMIT must be a positive number');
16
+ }
17
+ return parsed;
18
+ }),
19
+ // Build Configuration
20
+ RELEASE_VERSION: z.string().optional(),
21
+ });
22
+ export class ConfigManager {
23
+ static instance;
24
+ config;
25
+ constructor() {
26
+ try {
27
+ this.config = EnvironmentSchema.parse(process.env);
28
+ this.validateConfiguration();
29
+ }
30
+ catch (error) {
31
+ if (error instanceof z.ZodError) {
32
+ console.error('[CONFIG ERROR] Invalid environment variables:');
33
+ error.errors.forEach(err => {
34
+ console.error(` - ${err.path.join('.')}: ${err.message}`);
35
+ });
36
+ process.exit(1);
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+ static getInstance() {
42
+ if (!ConfigManager.instance) {
43
+ ConfigManager.instance = new ConfigManager();
44
+ }
45
+ return ConfigManager.instance;
46
+ }
47
+ validateConfiguration() {
48
+ // Custom validation logic
49
+ if (this.config.HTTP_SERVER_PORT < 1 || this.config.HTTP_SERVER_PORT > 65535) {
50
+ throw new Error('HTTP_SERVER_PORT must be between 1 and 65535');
51
+ }
52
+ // Log important configuration
53
+ console.error(`[CONFIG] Grocy Base URL: ${this.config.GROCY_BASE_URL}`);
54
+ console.error(`[CONFIG] SSL Verification: ${this.config.GROCY_ENABLE_SSL_VERIFY ? 'enabled' : 'disabled'}`);
55
+ console.error(`[CONFIG] HTTP Server: ${this.config.ENABLE_HTTP_SERVER ? `enabled on port ${this.config.HTTP_SERVER_PORT}` : 'disabled'}`);
56
+ console.error(`[CONFIG] Response Size Limit: ${this.config.REST_RESPONSE_SIZE_LIMIT} bytes`);
57
+ }
58
+ get() {
59
+ return this.config;
60
+ }
61
+ getGrocyBaseUrl() {
62
+ return this.config.GROCY_BASE_URL.replace(/\/+$/, '');
63
+ }
64
+ getApiUrl() {
65
+ return `${this.getGrocyBaseUrl()}/api`;
66
+ }
67
+ hasApiKeyAuth() {
68
+ return !!this.config.GROCY_APIKEY_VALUE;
69
+ }
70
+ getCustomHeaders() {
71
+ const headers = {};
72
+ const headerPrefix = /^header_/i;
73
+ for (const [key, value] of Object.entries(process.env)) {
74
+ if (headerPrefix.test(key) && value !== undefined) {
75
+ const headerName = key.replace(headerPrefix, '');
76
+ headers[headerName] = value;
77
+ }
78
+ }
79
+ return headers;
80
+ }
81
+ parseToolConfiguration() {
82
+ const enabledTools = new Set();
83
+ const disabledTools = new Set();
84
+ const toolSubConfigs = new Map();
85
+ const errors = [];
86
+ // Scan all environment variables for TOOL__ patterns
87
+ for (const [key, value] of Object.entries(process.env)) {
88
+ if (!key.startsWith('TOOL__'))
89
+ continue;
90
+ const parts = key.split('__');
91
+ if (parts.length === 3) {
92
+ // TOOL__tool_name__sub_config
93
+ const toolName = parts[1];
94
+ const subConfig = parts[2];
95
+ if (!toolSubConfigs.has(toolName)) {
96
+ toolSubConfigs.set(toolName, new Map());
97
+ }
98
+ toolSubConfigs.get(toolName).set(subConfig, value === 'true');
99
+ }
100
+ else if (parts.length === 2) {
101
+ // TOOL__tool_name
102
+ const toolName = parts[1];
103
+ if (value === 'true') {
104
+ enabledTools.add(toolName);
105
+ }
106
+ else if (value === 'false') {
107
+ disabledTools.add(toolName);
108
+ }
109
+ else {
110
+ errors.push(`${key}=${value} (must be 'true' or 'false')`);
111
+ }
112
+ }
113
+ }
114
+ // Error out if invalid values found
115
+ if (errors.length > 0) {
116
+ console.error('[CONFIG ERROR] Invalid TOOL_ configuration values:');
117
+ errors.forEach(error => console.error(` - ${error}`));
118
+ console.error('All TOOL_ variables must be set to either "true" or "false"');
119
+ process.exit(1);
120
+ }
121
+ if (enabledTools.size > 0) {
122
+ console.error(`[CONFIG] Enabled tools (${enabledTools.size}): ${Array.from(enabledTools).sort().join(', ')}`);
123
+ }
124
+ else {
125
+ console.error('[CONFIG] No tools enabled - all tools are disabled by default');
126
+ }
127
+ if (disabledTools.size > 0) {
128
+ console.error(`[CONFIG] Explicitly disabled tools (${disabledTools.size}): ${Array.from(disabledTools).sort().join(', ')}`);
129
+ }
130
+ // Log sub-configurations
131
+ if (toolSubConfigs.size > 0) {
132
+ console.error('[CONFIG] Tool sub-configurations:');
133
+ for (const [toolName, subConfigs] of toolSubConfigs) {
134
+ const subConfigList = Array.from(subConfigs.entries()).map(([key, value]) => `${key}=${value}`);
135
+ console.error(` - ${toolName}: ${subConfigList.join(', ')}`);
136
+ }
137
+ }
138
+ return { enabledTools, toolSubConfigs };
139
+ }
140
+ }
141
+ export const config = ConfigManager.getInstance();
142
+ export default config;
package/build/main.js ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ // Load environment variables from .env file
3
+ import 'dotenv/config';
4
+ import { GrocyMcpServer } from './server/mcp-server.js';
5
+ import config from './config/environment.js';
6
+ import { VERSION, PACKAGE_NAME as SERVER_NAME } from './version.js';
7
+ // Debug output to help identify version and naming issues
8
+ console.error(`Starting ${SERVER_NAME} server version ${VERSION}`);
9
+ async function main() {
10
+ try {
11
+ // Validate configuration early
12
+ const envConfig = config.get();
13
+ // Ensure required environment variables
14
+ if (!config.hasApiKeyAuth()) {
15
+ console.error('[WARNING] No GROCY_APIKEY_VALUE configured. Some API calls may fail.');
16
+ }
17
+ // Create and start the server
18
+ const server = new GrocyMcpServer();
19
+ await server.start();
20
+ }
21
+ catch (error) {
22
+ console.error('[ERROR] Failed to start server:', error);
23
+ process.exit(1);
24
+ }
25
+ }
26
+ main().catch((error) => {
27
+ console.error('[FATAL] Unhandled error:', error);
28
+ process.exit(1);
29
+ });