mcp-grocy 1.10.0 โ 2.0.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/README.md +11 -11
- package/build/api/client.js +61 -94
- package/build/config/index.js +200 -0
- package/build/main.js +33 -18
- package/build/resources/README.md +11 -11
- package/build/resources/config.md +19 -155
- package/build/resources/examples.md +1 -1
- package/build/resources/installation.md +2 -2
- package/build/resources/response-format.md +2 -2
- package/build/server/http-server.js +189 -185
- package/build/server/mcp-server.js +91 -103
- package/build/tools/base.js +122 -37
- package/build/tools/household/definitions.js +157 -0
- package/build/tools/household/handlers.js +109 -0
- package/build/tools/household/index.js +25 -0
- package/build/tools/index.js +2 -494
- package/build/tools/inventory/definitions.js +379 -0
- package/build/tools/inventory/handlers.js +430 -0
- package/build/tools/inventory/index.js +32 -0
- package/build/tools/module-loader.js +146 -0
- package/build/tools/recipes/definitions.js +122 -52
- package/build/tools/recipes/handlers.js +309 -158
- package/build/tools/recipes/index.js +22 -11
- package/build/tools/recipes/validations.js +23 -0
- package/build/tools/shopping/definitions.js +71 -0
- package/build/tools/shopping/handlers.js +43 -0
- package/build/tools/shopping/index.js +9 -102
- package/build/tools/system/definitions.js +86 -0
- package/build/tools/system/handlers.js +94 -0
- package/build/tools/system/index.js +11 -225
- package/build/tools/validation-helpers.js +110 -0
- package/build/types/index.js +62 -0
- package/build/utils/errors.js +138 -0
- package/build/utils/logger.js +141 -0
- package/build/version.js +1 -1
- package/package.json +3 -1
- package/build/config/environment.js +0 -142
- package/build/resources/CHANGELOG.md +0 -352
- package/build/tools/products/definitions.js +0 -57
- package/build/tools/products/handlers.js +0 -78
- package/build/tools/products/index.js +0 -13
- package/build/tools/stock/definitions.js +0 -234
- package/build/tools/stock/handlers.js +0 -391
- package/build/tools/stock/index.js +0 -19
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ Transform your LLM into an intelligent household management assistant with focus
|
|
|
63
63
|
|
|
64
64
|
# Configure
|
|
65
65
|
cp .env.example .env
|
|
66
|
-
# Edit .env with your GROCY_BASE_URL and
|
|
66
|
+
# Edit .env with your GROCY_BASE_URL and GROCY_API_KEY
|
|
67
67
|
|
|
68
68
|
# Run
|
|
69
69
|
docker compose up -d
|
|
@@ -74,7 +74,7 @@ Test with mock data (no real Grocy instance needed):
|
|
|
74
74
|
```bash
|
|
75
75
|
# In .env file, any values work for mock mode
|
|
76
76
|
GROCY_BASE_URL=http://mock
|
|
77
|
-
|
|
77
|
+
GROCY_API_KEY=mock
|
|
78
78
|
|
|
79
79
|
npm install && npm run dev
|
|
80
80
|
```
|
|
@@ -93,7 +93,7 @@ npm run build
|
|
|
93
93
|
### Docker
|
|
94
94
|
|
|
95
95
|
```bash
|
|
96
|
-
docker run -e
|
|
96
|
+
docker run -e GROCY_API_KEY=your_api_key -e GROCY_BASE_URL=http://your-grocy-instance ghcr.io/miguelangel-nubla/mcp-grocy:latest
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
### Docker Compose (Recommended)
|
|
@@ -126,20 +126,20 @@ docker compose up -d
|
|
|
126
126
|
2. **Configure the server:**
|
|
127
127
|
```bash
|
|
128
128
|
cp .env.example .env
|
|
129
|
-
# Edit .env with your GROCY_BASE_URL and
|
|
129
|
+
# Edit .env with your GROCY_BASE_URL and GROCY_API_KEY
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
3. **Essential variables:**
|
|
133
133
|
- `GROCY_BASE_URL` - Your Grocy instance URL
|
|
134
|
-
- `
|
|
134
|
+
- `GROCY_API_KEY` - Your Grocy API key
|
|
135
135
|
|
|
136
136
|
### Configuration Options
|
|
137
137
|
|
|
138
138
|
| Method | Use Case | Command |
|
|
139
139
|
|--------|----------|---------|
|
|
140
140
|
| **`.env` file** | Recommended for most users | `cp .env.example .env` |
|
|
141
|
-
| **Environment variables** | CI/CD, containers | `GROCY_BASE_URL=...
|
|
142
|
-
| **Tool
|
|
141
|
+
| **Environment variables** | CI/CD, containers | `GROCY_BASE_URL=... GROCY_API_KEY=... mcp-grocy` |
|
|
142
|
+
| **Tool configuration** | Customize functionality | Edit `tools` section in `mcp-grocy.yaml` |
|
|
143
143
|
|
|
144
144
|
๐ **For complete configuration reference:** See [Configuration Guide](src/resources/config.md)
|
|
145
145
|
|
|
@@ -172,7 +172,7 @@ npm start
|
|
|
172
172
|
|----------|---------|-------------|
|
|
173
173
|
| [๐ API Reference](src/resources/api-reference.md) | Complete tool documentation | Tool usage and examples |
|
|
174
174
|
| [โ๏ธ Configuration Guide](src/resources/config.md) | Advanced configuration reference | Detailed setup, presets, troubleshooting |
|
|
175
|
-
| [๐ .env.example](.env.example) |
|
|
175
|
+
| [๐ .env.example](.env.example) | Environment configuration template | Copy and customize for your setup |
|
|
176
176
|
| [๐งช MCP Inspector](https://github.com/modelcontextprotocol/inspector) | Protocol debugging | Debug MCP interactions |
|
|
177
177
|
|
|
178
178
|
### ๐ Troubleshooting
|
|
@@ -185,17 +185,17 @@ npm start
|
|
|
185
185
|
- For HTTPS URLs, ensure SSL certificate is valid or disable verification with `GROCY_ENABLE_SSL_VERIFY=false`
|
|
186
186
|
|
|
187
187
|
**"Invalid API key" or "Authentication failed"**
|
|
188
|
-
- Verify your `
|
|
188
|
+
- Verify your `GROCY_API_KEY` is correct
|
|
189
189
|
- Check that the API key exists in your Grocy instance (User Settings โ API Keys)
|
|
190
190
|
- Ensure the API key has proper permissions
|
|
191
191
|
|
|
192
192
|
**"Tool not found" errors**
|
|
193
|
-
- Check if the tool is enabled in your
|
|
193
|
+
- Check if the tool is enabled in your `mcp-grocy.yaml` file
|
|
194
194
|
- Verify you're using the correct tool names from the API reference
|
|
195
195
|
|
|
196
196
|
**Large response errors**
|
|
197
197
|
- Increase `REST_RESPONSE_SIZE_LIMIT` if you have many products/stock entries
|
|
198
|
-
- Consider
|
|
198
|
+
- Consider disabling unused tools in `mcp-grocy.yaml`
|
|
199
199
|
|
|
200
200
|
#### Debug Mode
|
|
201
201
|
|
package/build/api/client.js
CHANGED
|
@@ -1,77 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplified API client for Grocy
|
|
3
|
+
*/
|
|
1
4
|
import axios from 'axios';
|
|
2
5
|
import https from 'https';
|
|
3
|
-
import config from '../config/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
response;
|
|
7
|
-
constructor(message, status, response) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.name = 'ApiError';
|
|
10
|
-
this.status = status;
|
|
11
|
-
this.response = response;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
6
|
+
import { config } from '../config/index.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
|
+
import { ApiError, ErrorHandler } from '../utils/errors.js';
|
|
14
9
|
export class GrocyApiClient {
|
|
15
10
|
axiosInstance;
|
|
16
11
|
API_KEY_HEADER = 'GROCY-API-KEY';
|
|
17
12
|
constructor() {
|
|
18
|
-
|
|
19
|
-
this.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
this.axiosInstance = this.createAxiosInstance();
|
|
14
|
+
this.setupInterceptors();
|
|
15
|
+
}
|
|
16
|
+
createAxiosInstance() {
|
|
17
|
+
const instance = axios.create({
|
|
18
|
+
baseURL: config.grocy.base_url,
|
|
19
|
+
validateStatus: () => true, // Handle all status codes manually
|
|
20
|
+
timeout: 30000,
|
|
21
|
+
httpsAgent: config.grocy.enable_ssl_verify ? undefined : new https.Agent({
|
|
24
22
|
rejectUnauthorized: false
|
|
25
23
|
})
|
|
26
24
|
});
|
|
27
|
-
// Set default authentication
|
|
28
|
-
if (
|
|
29
|
-
|
|
25
|
+
// Set default authentication
|
|
26
|
+
if (config.grocy.api_key) {
|
|
27
|
+
instance.defaults.headers.common[this.API_KEY_HEADER] = config.grocy.api_key;
|
|
30
28
|
}
|
|
31
|
-
|
|
29
|
+
return instance;
|
|
30
|
+
}
|
|
31
|
+
setupInterceptors() {
|
|
32
|
+
// Request logging
|
|
32
33
|
this.axiosInstance.interceptors.request.use((config) => {
|
|
33
|
-
|
|
34
|
-
if (config.data) {
|
|
35
|
-
console.error(`[API] Request body: ${JSON.stringify(config.data)}`);
|
|
36
|
-
}
|
|
34
|
+
logger.api(`${config.method?.toUpperCase()} ${config.url}`);
|
|
37
35
|
return config;
|
|
38
36
|
}, (error) => {
|
|
39
|
-
|
|
37
|
+
logger.error('Request error', 'API', { error: error.message });
|
|
40
38
|
return Promise.reject(error);
|
|
41
39
|
});
|
|
42
|
-
//
|
|
40
|
+
// Response logging
|
|
43
41
|
this.axiosInstance.interceptors.response.use((response) => {
|
|
44
42
|
if (response.status >= 400) {
|
|
45
|
-
|
|
43
|
+
logger.warn(`HTTP ${response.status}`, 'API', {
|
|
44
|
+
url: response.config?.url,
|
|
45
|
+
status: response.status
|
|
46
|
+
});
|
|
46
47
|
}
|
|
47
48
|
return response;
|
|
48
49
|
}, (error) => {
|
|
49
|
-
|
|
50
|
+
logger.error('Response error', 'API', { error: error.message });
|
|
50
51
|
return Promise.reject(error);
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
54
|
normalizeEndpoint(endpoint) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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;
|
|
55
|
+
if (endpoint.startsWith('/api/'))
|
|
56
|
+
return endpoint;
|
|
57
|
+
if (endpoint.startsWith('api/'))
|
|
58
|
+
return `/${endpoint}`;
|
|
59
|
+
if (endpoint.startsWith('/'))
|
|
60
|
+
return `/api${endpoint}`;
|
|
61
|
+
return `/api/${endpoint}`;
|
|
75
62
|
}
|
|
76
63
|
buildQueryString(params) {
|
|
77
64
|
return Object.entries(params)
|
|
@@ -80,57 +67,35 @@ export class GrocyApiClient {
|
|
|
80
67
|
}
|
|
81
68
|
async request(endpoint, options = {}) {
|
|
82
69
|
const { method = 'GET', body = null, headers = {}, queryParams = {}, timeout } = options;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
70
|
+
return ErrorHandler.handleAsync(async () => {
|
|
71
|
+
let url = this.normalizeEndpoint(endpoint);
|
|
72
|
+
if (Object.keys(queryParams).length > 0) {
|
|
73
|
+
url += `?${this.buildQueryString(queryParams)}`;
|
|
74
|
+
}
|
|
75
|
+
const requestConfig = {
|
|
76
|
+
method,
|
|
77
|
+
url,
|
|
78
|
+
headers: {
|
|
79
|
+
'Accept': 'application/json',
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
...config.getCustomHeaders(),
|
|
82
|
+
...headers
|
|
83
|
+
},
|
|
84
|
+
...(timeout && { timeout })
|
|
85
|
+
};
|
|
86
|
+
if (['POST', 'PUT', 'PATCH'].includes(method) && body !== null) {
|
|
87
|
+
requestConfig.data = body;
|
|
88
|
+
}
|
|
104
89
|
const response = await this.axiosInstance.request(requestConfig);
|
|
105
90
|
if (response.status >= 400) {
|
|
106
|
-
throw new ApiError(`
|
|
91
|
+
throw new ApiError(response.data?.message || `HTTP ${response.status} error`, response.status, `${method} ${url}`, { responseData: response.data });
|
|
107
92
|
}
|
|
108
93
|
return {
|
|
109
94
|
data: response.data,
|
|
110
95
|
status: response.status,
|
|
111
96
|
headers: response.headers
|
|
112
97
|
};
|
|
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
|
-
}
|
|
98
|
+
}, `API ${method} ${endpoint}`);
|
|
134
99
|
}
|
|
135
100
|
// Convenience methods
|
|
136
101
|
async get(endpoint, options = {}) {
|
|
@@ -149,6 +114,8 @@ export class GrocyApiClient {
|
|
|
149
114
|
return this.request(endpoint, { ...options, method: 'PATCH', body });
|
|
150
115
|
}
|
|
151
116
|
}
|
|
152
|
-
// Export
|
|
117
|
+
// Export singleton instance
|
|
153
118
|
export const apiClient = new GrocyApiClient();
|
|
154
119
|
export default apiClient;
|
|
120
|
+
// Re-export ApiError for convenience
|
|
121
|
+
export { ApiError };
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
// Environment schema
|
|
13
|
+
const EnvironmentSchema = z.object({
|
|
14
|
+
// Grocy Configuration
|
|
15
|
+
GROCY_BASE_URL: z.string().url().optional(),
|
|
16
|
+
GROCY_API_KEY: z.string().optional(),
|
|
17
|
+
GROCY_ENABLE_SSL_VERIFY: z.enum(['true', 'false']).optional(),
|
|
18
|
+
// Server Configuration
|
|
19
|
+
REST_RESPONSE_SIZE_LIMIT: z.string().regex(/^\d+$/).optional(),
|
|
20
|
+
ENABLE_HTTP_SERVER: z.enum(['true', 'false']).optional(),
|
|
21
|
+
HTTP_SERVER_PORT: z.string().regex(/^\d+$/).optional(),
|
|
22
|
+
// Logging Configuration
|
|
23
|
+
LOG_LEVEL: z.enum(['DEBUG', 'INFO', 'WARN', 'ERROR']).optional(),
|
|
24
|
+
LOG_CATEGORIES: z.string().optional(),
|
|
25
|
+
// Build Configuration
|
|
26
|
+
RELEASE_VERSION: z.string().optional(),
|
|
27
|
+
NODE_ENV: z.enum(['development', 'production', 'test']).optional(),
|
|
28
|
+
});
|
|
29
|
+
// YAML configuration schema
|
|
30
|
+
const YamlConfigSchema = z.object({
|
|
31
|
+
server: z.object({
|
|
32
|
+
enable_http_server: z.boolean().default(false),
|
|
33
|
+
http_server_port: z.number().min(1).max(65535).default(8080),
|
|
34
|
+
}).default({}),
|
|
35
|
+
grocy: z.object({
|
|
36
|
+
base_url: z.string().url().default('http://localhost:9283'),
|
|
37
|
+
api_key: z.string().optional(),
|
|
38
|
+
enable_ssl_verify: z.boolean().default(true),
|
|
39
|
+
response_size_limit: z.number().positive().default(10000),
|
|
40
|
+
}).default({}),
|
|
41
|
+
tools: z.record(z.string(), z.object({
|
|
42
|
+
enabled: z.boolean().default(false),
|
|
43
|
+
ack_token: z.string().optional(),
|
|
44
|
+
}).catchall(z.unknown())).default({}),
|
|
45
|
+
});
|
|
46
|
+
export class ConfigManager {
|
|
47
|
+
static instance;
|
|
48
|
+
config;
|
|
49
|
+
// Unified config properties - final resolved values
|
|
50
|
+
grocy;
|
|
51
|
+
server;
|
|
52
|
+
tools;
|
|
53
|
+
constructor(configPath) {
|
|
54
|
+
this.config = this.loadConfig(configPath);
|
|
55
|
+
// Expose final resolved values
|
|
56
|
+
this.grocy = {
|
|
57
|
+
base_url: this.config.yaml.grocy.base_url,
|
|
58
|
+
...(this.config.yaml.grocy.api_key !== undefined && { api_key: this.config.yaml.grocy.api_key }),
|
|
59
|
+
enable_ssl_verify: this.config.yaml.grocy.enable_ssl_verify,
|
|
60
|
+
response_size_limit: this.config.yaml.grocy.response_size_limit
|
|
61
|
+
};
|
|
62
|
+
this.server = {
|
|
63
|
+
enable_http_server: this.config.yaml.server.enable_http_server,
|
|
64
|
+
http_server_port: this.config.yaml.server.http_server_port
|
|
65
|
+
};
|
|
66
|
+
this.tools = this.config.yaml.tools;
|
|
67
|
+
}
|
|
68
|
+
static getInstance() {
|
|
69
|
+
if (!ConfigManager.instance) {
|
|
70
|
+
ConfigManager.instance = new ConfigManager();
|
|
71
|
+
}
|
|
72
|
+
return ConfigManager.instance;
|
|
73
|
+
}
|
|
74
|
+
loadConfig(configPath) {
|
|
75
|
+
// Load environment variables
|
|
76
|
+
const env = this.loadEnvironment();
|
|
77
|
+
// Load YAML configuration
|
|
78
|
+
const yaml = this.loadYamlConfig(configPath);
|
|
79
|
+
// Apply environment variable overrides
|
|
80
|
+
this.applyEnvironmentOverrides(yaml, env);
|
|
81
|
+
return { env, yaml };
|
|
82
|
+
}
|
|
83
|
+
loadEnvironment() {
|
|
84
|
+
try {
|
|
85
|
+
return EnvironmentSchema.parse(process.env);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (error instanceof z.ZodError) {
|
|
89
|
+
logger.error('Invalid environment variables', 'CONFIG');
|
|
90
|
+
error.errors.forEach(err => {
|
|
91
|
+
logger.error(`${err.path.join('.')}: ${err.message}`, 'CONFIG');
|
|
92
|
+
});
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
loadYamlConfig(configPath) {
|
|
99
|
+
const yamlPath = this.findConfigFile(configPath);
|
|
100
|
+
try {
|
|
101
|
+
let configData = {};
|
|
102
|
+
if (existsSync(yamlPath)) {
|
|
103
|
+
const yamlContent = readFileSync(yamlPath, 'utf8');
|
|
104
|
+
configData = YAML.parse(yamlContent) || {};
|
|
105
|
+
logger.config(`Loaded YAML config from: ${yamlPath}`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
logger.config('No YAML config found, using defaults');
|
|
109
|
+
}
|
|
110
|
+
return YamlConfigSchema.parse(configData);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (error instanceof z.ZodError) {
|
|
114
|
+
logger.error('Invalid YAML configuration', 'CONFIG');
|
|
115
|
+
error.errors.forEach(err => {
|
|
116
|
+
logger.error(`${err.path.join('.')}: ${err.message}`, 'CONFIG');
|
|
117
|
+
});
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
findConfigFile(configPath) {
|
|
124
|
+
if (configPath)
|
|
125
|
+
return configPath;
|
|
126
|
+
// Look for config files in the following order:
|
|
127
|
+
// 1. Current working directory (for development)
|
|
128
|
+
// 2. Project root (relative to the compiled main.js)
|
|
129
|
+
const projectRoot = resolve(__dirname, '../..');
|
|
130
|
+
const possiblePaths = [
|
|
131
|
+
resolve(process.cwd(), 'mcp-grocy.yaml'),
|
|
132
|
+
resolve(process.cwd(), 'mcp-grocy.yml'),
|
|
133
|
+
resolve(projectRoot, 'mcp-grocy.yaml'),
|
|
134
|
+
resolve(projectRoot, 'mcp-grocy.yml'),
|
|
135
|
+
];
|
|
136
|
+
return possiblePaths.find(path => existsSync(path)) ?? possiblePaths[0];
|
|
137
|
+
}
|
|
138
|
+
// Public getters
|
|
139
|
+
getConfig() {
|
|
140
|
+
return this.config;
|
|
141
|
+
}
|
|
142
|
+
getApiUrl() {
|
|
143
|
+
return this.grocy.base_url.endsWith('/') ? `${this.grocy.base_url}api` : `${this.grocy.base_url}/api`;
|
|
144
|
+
}
|
|
145
|
+
getCustomHeaders() {
|
|
146
|
+
const headers = {};
|
|
147
|
+
if (this.grocy.api_key) {
|
|
148
|
+
headers['GROCY-API-KEY'] = this.grocy.api_key;
|
|
149
|
+
}
|
|
150
|
+
return headers;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Apply environment variable overrides to YAML configuration
|
|
154
|
+
*/
|
|
155
|
+
applyEnvironmentOverrides(yaml, env) {
|
|
156
|
+
// Grocy configuration overrides
|
|
157
|
+
if (env.GROCY_BASE_URL) {
|
|
158
|
+
yaml.grocy.base_url = env.GROCY_BASE_URL;
|
|
159
|
+
}
|
|
160
|
+
if (env.GROCY_API_KEY) {
|
|
161
|
+
yaml.grocy.api_key = env.GROCY_API_KEY;
|
|
162
|
+
}
|
|
163
|
+
if (env.GROCY_ENABLE_SSL_VERIFY !== undefined) {
|
|
164
|
+
yaml.grocy.enable_ssl_verify = env.GROCY_ENABLE_SSL_VERIFY === 'true';
|
|
165
|
+
}
|
|
166
|
+
if (env.REST_RESPONSE_SIZE_LIMIT !== undefined) {
|
|
167
|
+
yaml.grocy.response_size_limit = parseInt(env.REST_RESPONSE_SIZE_LIMIT, 10);
|
|
168
|
+
}
|
|
169
|
+
// Server configuration overrides
|
|
170
|
+
if (env.ENABLE_HTTP_SERVER !== undefined) {
|
|
171
|
+
yaml.server.enable_http_server = env.ENABLE_HTTP_SERVER === 'true';
|
|
172
|
+
}
|
|
173
|
+
if (env.HTTP_SERVER_PORT !== undefined) {
|
|
174
|
+
yaml.server.http_server_port = parseInt(env.HTTP_SERVER_PORT, 10);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
parseToolConfiguration() {
|
|
178
|
+
const enabledTools = new Set();
|
|
179
|
+
const toolSubConfigs = new Map();
|
|
180
|
+
for (const [toolName, toolConfig] of Object.entries(this.config.yaml.tools)) {
|
|
181
|
+
if (toolConfig.enabled) {
|
|
182
|
+
enabledTools.add(toolName);
|
|
183
|
+
// Extract sub-configs (everything except standard fields)
|
|
184
|
+
const subConfigs = new Map();
|
|
185
|
+
for (const [key, value] of Object.entries(toolConfig)) {
|
|
186
|
+
if (!['enabled', 'ack_token'].includes(key)) {
|
|
187
|
+
subConfigs.set(key, value);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (subConfigs.size > 0) {
|
|
191
|
+
toolSubConfigs.set(toolName, subConfigs);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return { enabledTools, toolSubConfigs };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Export singleton instance
|
|
199
|
+
export const config = ConfigManager.getInstance();
|
|
200
|
+
export default config;
|
package/build/main.js
CHANGED
|
@@ -1,29 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Simplified main entry point
|
|
4
|
+
* Demonstrates the refactored architecture
|
|
5
|
+
*/
|
|
6
|
+
// Load environment variables
|
|
3
7
|
import 'dotenv/config';
|
|
4
8
|
import { GrocyMcpServer } from './server/mcp-server.js';
|
|
5
|
-
import config from './config/
|
|
9
|
+
import { config } from './config/index.js';
|
|
6
10
|
import { VERSION, PACKAGE_NAME as SERVER_NAME } from './version.js';
|
|
7
|
-
|
|
8
|
-
|
|
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');
|
|
9
15
|
async function main() {
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (!config.hasApiKeyAuth()) {
|
|
15
|
-
console.error('[WARNING] No GROCY_APIKEY_VALUE configured. Some API calls may fail.');
|
|
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');
|
|
16
20
|
}
|
|
17
|
-
//
|
|
18
|
-
|
|
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();
|
|
19
27
|
await server.start();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
console.error('[ERROR] Failed to start server:', error);
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
28
|
+
logger.info('Server started successfully', 'SERVER');
|
|
29
|
+
}, 'server startup');
|
|
25
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
|
|
26
41
|
main().catch((error) => {
|
|
27
|
-
|
|
42
|
+
logger.error('Failed to start server', 'SERVER', { error });
|
|
28
43
|
process.exit(1);
|
|
29
44
|
});
|
|
@@ -63,7 +63,7 @@ Transform your LLM into an intelligent household management assistant with focus
|
|
|
63
63
|
|
|
64
64
|
# Configure
|
|
65
65
|
cp .env.example .env
|
|
66
|
-
# Edit .env with your GROCY_BASE_URL and
|
|
66
|
+
# Edit .env with your GROCY_BASE_URL and GROCY_API_KEY
|
|
67
67
|
|
|
68
68
|
# Run
|
|
69
69
|
docker compose up -d
|
|
@@ -74,7 +74,7 @@ Test with mock data (no real Grocy instance needed):
|
|
|
74
74
|
```bash
|
|
75
75
|
# In .env file, any values work for mock mode
|
|
76
76
|
GROCY_BASE_URL=http://mock
|
|
77
|
-
|
|
77
|
+
GROCY_API_KEY=mock
|
|
78
78
|
|
|
79
79
|
npm install && npm run dev
|
|
80
80
|
```
|
|
@@ -93,7 +93,7 @@ npm run build
|
|
|
93
93
|
### Docker
|
|
94
94
|
|
|
95
95
|
```bash
|
|
96
|
-
docker run -e
|
|
96
|
+
docker run -e GROCY_API_KEY=your_api_key -e GROCY_BASE_URL=http://your-grocy-instance ghcr.io/miguelangel-nubla/mcp-grocy:latest
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
### Docker Compose (Recommended)
|
|
@@ -126,20 +126,20 @@ docker compose up -d
|
|
|
126
126
|
2. **Configure the server:**
|
|
127
127
|
```bash
|
|
128
128
|
cp .env.example .env
|
|
129
|
-
# Edit .env with your GROCY_BASE_URL and
|
|
129
|
+
# Edit .env with your GROCY_BASE_URL and GROCY_API_KEY
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
3. **Essential variables:**
|
|
133
133
|
- `GROCY_BASE_URL` - Your Grocy instance URL
|
|
134
|
-
- `
|
|
134
|
+
- `GROCY_API_KEY` - Your Grocy API key
|
|
135
135
|
|
|
136
136
|
### Configuration Options
|
|
137
137
|
|
|
138
138
|
| Method | Use Case | Command |
|
|
139
139
|
|--------|----------|---------|
|
|
140
140
|
| **`.env` file** | Recommended for most users | `cp .env.example .env` |
|
|
141
|
-
| **Environment variables** | CI/CD, containers | `GROCY_BASE_URL=...
|
|
142
|
-
| **Tool
|
|
141
|
+
| **Environment variables** | CI/CD, containers | `GROCY_BASE_URL=... GROCY_API_KEY=... mcp-grocy` |
|
|
142
|
+
| **Tool configuration** | Customize functionality | Edit `tools` section in `mcp-grocy.yaml` |
|
|
143
143
|
|
|
144
144
|
๐ **For complete configuration reference:** See [Configuration Guide](src/resources/config.md)
|
|
145
145
|
|
|
@@ -172,7 +172,7 @@ npm start
|
|
|
172
172
|
|----------|---------|-------------|
|
|
173
173
|
| [๐ API Reference](src/resources/api-reference.md) | Complete tool documentation | Tool usage and examples |
|
|
174
174
|
| [โ๏ธ Configuration Guide](src/resources/config.md) | Advanced configuration reference | Detailed setup, presets, troubleshooting |
|
|
175
|
-
| [๐ .env.example](.env.example) |
|
|
175
|
+
| [๐ .env.example](.env.example) | Environment configuration template | Copy and customize for your setup |
|
|
176
176
|
| [๐งช MCP Inspector](https://github.com/modelcontextprotocol/inspector) | Protocol debugging | Debug MCP interactions |
|
|
177
177
|
|
|
178
178
|
### ๐ Troubleshooting
|
|
@@ -185,17 +185,17 @@ npm start
|
|
|
185
185
|
- For HTTPS URLs, ensure SSL certificate is valid or disable verification with `GROCY_ENABLE_SSL_VERIFY=false`
|
|
186
186
|
|
|
187
187
|
**"Invalid API key" or "Authentication failed"**
|
|
188
|
-
- Verify your `
|
|
188
|
+
- Verify your `GROCY_API_KEY` is correct
|
|
189
189
|
- Check that the API key exists in your Grocy instance (User Settings โ API Keys)
|
|
190
190
|
- Ensure the API key has proper permissions
|
|
191
191
|
|
|
192
192
|
**"Tool not found" errors**
|
|
193
|
-
- Check if the tool is enabled in your
|
|
193
|
+
- Check if the tool is enabled in your `mcp-grocy.yaml` file
|
|
194
194
|
- Verify you're using the correct tool names from the API reference
|
|
195
195
|
|
|
196
196
|
**Large response errors**
|
|
197
197
|
- Increase `REST_RESPONSE_SIZE_LIMIT` if you have many products/stock entries
|
|
198
|
-
- Consider
|
|
198
|
+
- Consider disabling unused tools in `mcp-grocy.yaml`
|
|
199
199
|
|
|
200
200
|
#### Debug Mode
|
|
201
201
|
|