mcp-grocy 2.2.0 → 2.6.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 (42) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +125 -32
  3. package/build/resources/CHANGELOG.md +47 -0
  4. package/build/resources/DOCS.md +8 -3
  5. package/build/resources/README.md +125 -32
  6. package/build/resources/api-reference.md +88 -88
  7. package/build/resources/config.md +45 -17
  8. package/build/resources/examples.md +120 -221
  9. package/build/resources/installation.md +9 -14
  10. package/build/resources/response-format.md +29 -20
  11. package/build/version.js +2 -2
  12. package/package.json +50 -24
  13. package/build/api/client.js +0 -121
  14. package/build/config/index.js +0 -205
  15. package/build/main.js +0 -44
  16. package/build/server/http-server.js +0 -218
  17. package/build/server/mcp-server.js +0 -163
  18. package/build/server/resources.js +0 -60
  19. package/build/tools/base.js +0 -141
  20. package/build/tools/household/definitions.js +0 -157
  21. package/build/tools/household/handlers.js +0 -109
  22. package/build/tools/household/index.js +0 -25
  23. package/build/tools/index.js +0 -2
  24. package/build/tools/inventory/definitions.js +0 -379
  25. package/build/tools/inventory/handlers.js +0 -430
  26. package/build/tools/inventory/index.js +0 -32
  27. package/build/tools/module-loader.js +0 -151
  28. package/build/tools/recipes/definitions.js +0 -261
  29. package/build/tools/recipes/handlers.js +0 -471
  30. package/build/tools/recipes/index.js +0 -33
  31. package/build/tools/recipes/validations.js +0 -23
  32. package/build/tools/shopping/definitions.js +0 -71
  33. package/build/tools/shopping/handlers.js +0 -43
  34. package/build/tools/shopping/index.js +0 -14
  35. package/build/tools/system/definitions.js +0 -86
  36. package/build/tools/system/handlers.js +0 -94
  37. package/build/tools/system/index.js +0 -16
  38. package/build/tools/types.js +0 -1
  39. package/build/tools/validation-helpers.js +0 -36
  40. package/build/types/index.js +0 -62
  41. package/build/utils/errors.js +0 -138
  42. package/build/utils/logger.js +0 -141
@@ -1,8 +1,8 @@
1
1
  # Grocy API Response Format Documentation
2
2
 
3
- The Grocy API testing tool (`test_request`) returns a comprehensive JSON response containing request details, response information, and validation results. Other specialized Grocy tools (e.g., `get_stock`, `add_shopping_list_item`) return the direct JSON response from the Grocy API, which is then stringified.
3
+ The dev tool **`system_dev_test_request`** returns a JSON payload with request details, response information, and validation results. Other tools (e.g. `inventory_stock_get_all`, `shopping_list_add_item`) return the Grocy API body as text content (typically stringified JSON).
4
4
 
5
- ## `test_request` Tool Response Structure
5
+ ## `system_dev_test_request` tool response structure
6
6
 
7
7
  ```json
8
8
  {
@@ -29,7 +29,7 @@ The Grocy API testing tool (`test_request`) returns a comprehensive JSON respons
29
29
  "id": "1",
30
30
  "name": "Cookies",
31
31
  "description": null,
32
- "product_group_id": "1",
32
+ "product_group_id": "1"
33
33
  // ... other product fields ...
34
34
  }
35
35
  },
@@ -72,9 +72,10 @@ GROCY_API_KEY=your-private-api-key
72
72
 
73
73
  These values can be set in your `.env` file for local development or in your project configuration for production use.
74
74
 
75
- ## Response Fields for `test_request`
75
+ ## Response Fields for `system_dev_test_request`
76
76
 
77
77
  ### Request Details (`request`)
78
+
78
79
  - `url`: Full URL of the Grocy API endpoint called, including base URL and path.
79
80
  - `method`: HTTP method used (e.g., GET, POST, PUT, DELETE).
80
81
  - `headers`: Request headers sent to the Grocy API. Sensitive headers like `GROCY-API-KEY` will have their values redacted.
@@ -82,6 +83,7 @@ These values can be set in your `.env` file for local development or in your pro
82
83
  - `authMethod`: Authentication method used. For Grocy, this will typically be `apikey` if `GROCY_API_KEY` is configured, or `none`.
83
84
 
84
85
  ### Response Details (`response`)
86
+
85
87
  - `statusCode`: HTTP status code returned by the Grocy API (e.g., 200, 400, 401).
86
88
  - `statusText`: HTTP status message (e.g., "OK", "Bad Request").
87
89
  - `timing`: Duration of the API request in milliseconds.
@@ -89,6 +91,7 @@ These values can be set in your `.env` file for local development or in your pro
89
91
  - `body`: Response body content from the Grocy API. This will be the JSON data returned by Grocy.
90
92
 
91
93
  ### Validation (`validation`)
94
+
92
95
  - `isError`: Boolean, `true` if the HTTP status code is 400 or higher, indicating an error.
93
96
  - `messages`: Array of messages, including success messages or error details.
94
97
  - `truncated` (optional): If the response body exceeds `REST_RESPONSE_SIZE_LIMIT`, this object will contain details about the truncation.
@@ -99,26 +102,32 @@ These values can be set in your `.env` file for local development or in your pro
99
102
 
100
103
  ## Specialized Grocy Tools Response Format
101
104
 
102
- Tools like `get_stock`, `get_products`, `add_shopping_list_item`, etc., directly return the JSON response from the Grocy API, stringified within the MCP tool response content.
105
+ Tools like `inventory_stock_get_all`, `inventory_products_get`, `shopping_list_add_item`, etc., return successful Grocy API responses as structured MCP tool output:
106
+
107
+ - `structuredContent.data`: the parsed Grocy response.
108
+ - `content`: short human-readable status text.
109
+
110
+ Example shape for a product-related read (illustrative):
103
111
 
104
- Example for `get_product` (if it existed as a specialized tool for a single product):
105
112
  ```json
106
113
  {
107
114
  "content": [
108
115
  {
109
116
  "type": "text",
110
- "text": "{\n \"id\": \"1\",\n \"name\": \"Cookies\",\n \"description\": null,\n \"product_group_id\": \"1\",\n \"qu_id_purchase\": \"2\",\n \"qu_id_stock\": \"2\",\n \"qu_factor_purchase_to_stock\": \"1.0\",\n \"barcode\": null,\n \"min_stock_amount\": \"0\",\n \"default_best_before_days\": \"0\",\n \"default_best_before_days_after_open\": \"0\",\n \"default_best_before_days_after_freezing\": \"0\",\n \"default_best_before_days_after_thawing\": \"0\",\n \"picture_file_name\": null,\n \"allow_partial_units_in_stock\": \"0\",\n \"row_created_timestamp\": \"2023-01-01 10:00:00\",\n \"show_in_recipes_list\": \"1\",\n \"has_sub_products\": \"0\",\n \"active\": \"1\",\n \"calories\": null,\n \"cumulate_min_stock_amount_of_sub_products\": \"0\",\n \"due_type\": \"1\",\n \"quick_consume_amount\": \"1.0\",\n \"hide_on_stock_overview\": \"0\",\n \"default_stock_label_type\": \"0\",\n \"should_not_be_frozen\": \"0\",\n \"treat_opened_as_out_of_stock\": \"1\",
111
- \"no_own_stock\": \"0\",
112
- \"default_consume_location_id\": null,
113
- \"move_on_open\": \"0\",
114
- \"userfields\": null
115
- }"
117
+ "text": "Product retrieved successfully"
116
118
  }
117
- ]
119
+ ],
120
+ "structuredContent": {
121
+ "data": {
122
+ "id": "1",
123
+ "name": "Cookies"
124
+ }
125
+ }
118
126
  }
119
127
  ```
120
128
 
121
129
  If an error occurs with a specialized tool, the response will typically look like:
130
+
122
131
  ```json
123
132
  {
124
133
  "content": [
@@ -131,9 +140,10 @@ If an error occurs with a specialized tool, the response will typically look lik
131
140
  }
132
141
  ```
133
142
 
134
- ## Error Response Example for `test_request`
143
+ ## Error response example for `system_dev_test_request`
144
+
145
+ If **`system_dev_test_request`** encounters an API error (e.g., authentication failure):
135
146
 
136
- If the `test_request` tool encounters an API error (e.g., authentication failure):
137
147
  ```json
138
148
  {
139
149
  "request": {
@@ -149,17 +159,16 @@ If the `test_request` tool encounters an API error (e.g., authentication failure
149
159
  "statusCode": 401,
150
160
  "statusText": "Unauthorized",
151
161
  "timing": "50ms",
152
- "headers": { /* ... headers ... */ },
162
+ "headers": {
163
+ /* ... headers ... */
164
+ },
153
165
  "body": {
154
166
  "error_message": "API key is missing or invalid."
155
167
  }
156
168
  },
157
169
  "validation": {
158
170
  "isError": true,
159
- "messages": [
160
- "Request failed with status 401",
161
- "API key is missing or invalid."
162
- ]
171
+ "messages": ["Request failed with status 401", "API key is missing or invalid."]
163
172
  }
164
173
  }
165
174
  ```
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-grocy",
3
- "version": "2.2.0",
3
+ "version": "2.6.0",
4
4
  "description": "Model Context Protocol (MCP) server for Grocy integration",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,16 +14,28 @@
14
14
  "files": [
15
15
  "build",
16
16
  "README.md",
17
+ "CHANGELOG.md",
17
18
  "LICENSE"
18
19
  ],
19
20
  "scripts": {
20
21
  "prebuild": "node scripts/build.js",
21
22
  "build": "tsc",
22
- "prepare": "npm run build",
23
+ "prepare": "husky",
24
+ "start": "node build/main.js",
25
+ "dev": "npm run build && node build/main.js",
23
26
  "watch": "tsc --watch",
24
27
  "inspector": "npx @modelcontextprotocol/inspector build/main.js",
25
28
  "test": "vitest run",
29
+ "test:mcp-validator": "bash scripts/run-mcp-validator.sh",
30
+ "dev:mcp-tef": "bash scripts/run-mcp-tef.sh",
31
+ "report:mcp-tef": "bash scripts/mcp-tef-report.sh",
32
+ "start:mcp-http-test": "bash scripts/start-mcp-http-for-tests.sh",
33
+ "stop:mcp-http-test": "bash scripts/stop-mcp-http-for-tests.sh",
26
34
  "test:watch": "vitest",
35
+ "lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\"",
36
+ "lint:fix": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" --fix",
37
+ "format": "prettier --write .",
38
+ "format:check": "prettier --check .",
27
39
  "check-tools": "node scripts/check-tools.js",
28
40
  "validate-release": "node scripts/check-tools.js --validate-release",
29
41
  "update-api-docs": "node scripts/fetch-grocy-api.js",
@@ -35,33 +47,38 @@
35
47
  "prepare-dev-release:dryrun": "node scripts/prepare-dev-release.js --dry-run"
36
48
  },
37
49
  "dependencies": {
38
- "@modelcontextprotocol/sdk": "^1.11.4",
39
- "axios": "^1.6.7",
40
- "cors": "^2.8.5",
41
- "dotenv": "^16.5.0",
42
- "express": "^5.1.0",
43
- "fuse.js": "^7.0.0",
44
- "uuid": "^11.1.0",
45
- "yaml": "^2.8.1",
46
- "zod": "^3.22.0"
50
+ "@modelcontextprotocol/sdk": "^1.28.0",
51
+ "axios": "^1.13.6",
52
+ "cors": "^2.8.6",
53
+ "dotenv": "^17.3.1",
54
+ "express": "^5.2.1",
55
+ "fuse.js": "^7.1.0",
56
+ "yaml": "^2.8.3",
57
+ "zod": "^4.3.6"
47
58
  },
48
59
  "devDependencies": {
49
- "@commitlint/cli": "^19.6.1",
50
- "@commitlint/config-conventional": "^19.6.0",
51
- "@commitlint/types": "^19.6.1",
60
+ "@commitlint/cli": "^20.5.0",
61
+ "@commitlint/config-conventional": "^20.5.0",
62
+ "@commitlint/types": "^20.5.0",
63
+ "@eslint/js": "^9.39.4",
52
64
  "@semantic-release/changelog": "^6.0.3",
53
- "@semantic-release/exec": "^6.0.3",
65
+ "@semantic-release/exec": "^7.1.0",
54
66
  "@semantic-release/git": "^10.0.1",
55
- "@types/cors": "^2.8.18",
56
- "@types/express": "^5.0.2",
57
- "@types/node": "^20.11.20",
58
- "@types/uuid": "^10.0.0",
67
+ "@types/cors": "^2.8.19",
68
+ "@types/express": "^5.0.6",
69
+ "@types/node": "^22.15.0",
59
70
  "@types/yaml": "^1.9.6",
60
- "fs-extra": "^11.2.0",
61
- "semantic-release": "^22.0.12",
71
+ "eslint": "^9.39.4",
72
+ "eslint-config-prettier": "^10.1.8",
73
+ "fs-extra": "^11.3.4",
74
+ "husky": "^9.1.7",
75
+ "lint-staged": "^17.0.8",
76
+ "prettier": "^3.8.1",
77
+ "semantic-release": "^25.0.3",
62
78
  "ts-node": "^10.9.1",
63
- "typescript": "^5.3.3",
64
- "vitest": "^3.1.3"
79
+ "typescript": "^5.9.3",
80
+ "typescript-eslint": "^8.57.2",
81
+ "vitest": "^4.1.2"
65
82
  },
66
83
  "keywords": [
67
84
  "grocy",
@@ -84,9 +101,18 @@
84
101
  },
85
102
  "homepage": "https://github.com/miguelangel-nubla/mcp-grocy#readme",
86
103
  "engines": {
87
- "node": ">=18.0.0"
104
+ "node": ">=22.0.0"
88
105
  },
89
106
  "config": {
90
107
  "supportedGrocyVersion": "4.5.0"
108
+ },
109
+ "lint-staged": {
110
+ "*.{ts,js,mjs,cjs}": [
111
+ "eslint --fix",
112
+ "prettier --write"
113
+ ],
114
+ "*.{json,yaml,yml,md}": [
115
+ "prettier --write"
116
+ ]
91
117
  }
92
118
  }
@@ -1,121 +0,0 @@
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
- API_KEY_HEADER = 'GROCY-API-KEY';
12
- constructor() {
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({
22
- rejectUnauthorized: false
23
- })
24
- });
25
- // Set default authentication
26
- if (config.grocy.api_key) {
27
- instance.defaults.headers.common[this.API_KEY_HEADER] = config.grocy.api_key;
28
- }
29
- return instance;
30
- }
31
- setupInterceptors() {
32
- // Request logging
33
- this.axiosInstance.interceptors.request.use((config) => {
34
- logger.api(`${config.method?.toUpperCase()} ${config.url}`);
35
- return config;
36
- }, (error) => {
37
- logger.error('Request error', 'API', { error: error.message });
38
- return Promise.reject(error);
39
- });
40
- // Response logging
41
- this.axiosInstance.interceptors.response.use((response) => {
42
- if (response.status >= 400) {
43
- logger.warn(`HTTP ${response.status}`, 'API', {
44
- url: response.config?.url,
45
- status: response.status
46
- });
47
- }
48
- return response;
49
- }, (error) => {
50
- logger.error('Response error', 'API', { error: error.message });
51
- return Promise.reject(error);
52
- });
53
- }
54
- normalizeEndpoint(endpoint) {
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}`;
62
- }
63
- buildQueryString(params) {
64
- return Object.entries(params)
65
- .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
66
- .join('&');
67
- }
68
- async request(endpoint, options = {}) {
69
- const { method = 'GET', body = null, headers = {}, queryParams = {}, timeout } = options;
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
- }
89
- const response = await this.axiosInstance.request(requestConfig);
90
- if (response.status >= 400) {
91
- throw new ApiError(response.data?.message || `HTTP ${response.status} error`, response.status, `${method} ${url}`, { responseData: response.data });
92
- }
93
- return {
94
- data: response.data,
95
- status: response.status,
96
- headers: response.headers
97
- };
98
- }, `API ${method} ${endpoint}`);
99
- }
100
- // Convenience methods
101
- async get(endpoint, options = {}) {
102
- return this.request(endpoint, { ...options, method: 'GET' });
103
- }
104
- async post(endpoint, body, options = {}) {
105
- return this.request(endpoint, { ...options, method: 'POST', body });
106
- }
107
- async put(endpoint, body, options = {}) {
108
- return this.request(endpoint, { ...options, method: 'PUT', body });
109
- }
110
- async delete(endpoint, options = {}) {
111
- return this.request(endpoint, { ...options, method: 'DELETE' });
112
- }
113
- async patch(endpoint, body, options = {}) {
114
- return this.request(endpoint, { ...options, method: 'PATCH', body });
115
- }
116
- }
117
- // Export singleton instance
118
- export const apiClient = new GrocyApiClient();
119
- export default apiClient;
120
- // Re-export ApiError for convenience
121
- export { ApiError };
@@ -1,205 +0,0 @@
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
- const toolAckTokens = new Map();
181
- for (const [toolName, toolConfig] of Object.entries(this.config.yaml.tools)) {
182
- if (toolConfig.enabled) {
183
- enabledTools.add(toolName);
184
- // Store ack_token separately if configured
185
- if (toolConfig.ack_token && typeof toolConfig.ack_token === 'string') {
186
- toolAckTokens.set(toolName, toolConfig.ack_token);
187
- }
188
- // Extract sub-configs (everything except standard fields)
189
- const subConfigs = new Map();
190
- for (const [key, value] of Object.entries(toolConfig)) {
191
- if (!['enabled', 'ack_token'].includes(key)) {
192
- subConfigs.set(key, value);
193
- }
194
- }
195
- if (subConfigs.size > 0) {
196
- toolSubConfigs.set(toolName, subConfigs);
197
- }
198
- }
199
- }
200
- return { enabledTools, toolSubConfigs, toolAckTokens };
201
- }
202
- }
203
- // Export singleton instance
204
- export const config = ConfigManager.getInstance();
205
- export default config;
package/build/main.js DELETED
@@ -1,44 +0,0 @@
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
- });