@netlinksinc/odoo-mcp 0.1.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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bin.d.ts +3 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +37 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/config.d.ts +7 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +59 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/context.d.ts +16 -0
  11. package/dist/context.d.ts.map +1 -0
  12. package/dist/context.js +37 -0
  13. package/dist/context.js.map +1 -0
  14. package/dist/errors.d.ts +10 -0
  15. package/dist/errors.d.ts.map +1 -0
  16. package/dist/errors.js +15 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +2 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/logger.d.ts +26 -0
  23. package/dist/logger.d.ts.map +1 -0
  24. package/dist/logger.js +57 -0
  25. package/dist/logger.js.map +1 -0
  26. package/dist/probe.d.ts +19 -0
  27. package/dist/probe.d.ts.map +1 -0
  28. package/dist/probe.js +181 -0
  29. package/dist/probe.js.map +1 -0
  30. package/dist/resources.d.ts +11 -0
  31. package/dist/resources.d.ts.map +1 -0
  32. package/dist/resources.js +37 -0
  33. package/dist/resources.js.map +1 -0
  34. package/dist/server.d.ts +20 -0
  35. package/dist/server.d.ts.map +1 -0
  36. package/dist/server.js +32 -0
  37. package/dist/server.js.map +1 -0
  38. package/dist/tools/action.d.ts +6 -0
  39. package/dist/tools/action.d.ts.map +1 -0
  40. package/dist/tools/action.js +158 -0
  41. package/dist/tools/action.js.map +1 -0
  42. package/dist/tools/execute.d.ts +6 -0
  43. package/dist/tools/execute.d.ts.map +1 -0
  44. package/dist/tools/execute.js +156 -0
  45. package/dist/tools/execute.js.map +1 -0
  46. package/dist/tools/index.d.ts +5 -0
  47. package/dist/tools/index.d.ts.map +1 -0
  48. package/dist/tools/index.js +13 -0
  49. package/dist/tools/index.js.map +1 -0
  50. package/dist/tools/introspect.d.ts +6 -0
  51. package/dist/tools/introspect.d.ts.map +1 -0
  52. package/dist/tools/introspect.js +155 -0
  53. package/dist/tools/introspect.js.map +1 -0
  54. package/dist/tools/orm.d.ts +16 -0
  55. package/dist/tools/orm.d.ts.map +1 -0
  56. package/dist/tools/orm.js +139 -0
  57. package/dist/tools/orm.js.map +1 -0
  58. package/dist/tools/report.d.ts +6 -0
  59. package/dist/tools/report.d.ts.map +1 -0
  60. package/dist/tools/report.js +99 -0
  61. package/dist/tools/report.js.map +1 -0
  62. package/dist/tools/schemas.d.ts +202 -0
  63. package/dist/tools/schemas.d.ts.map +1 -0
  64. package/dist/tools/schemas.js +77 -0
  65. package/dist/tools/schemas.js.map +1 -0
  66. package/package.json +56 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NETLINKS Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { loadConfig } from './config.js';
4
+ import { createOdooMcpServer } from './server.js';
5
+ (async () => {
6
+ try {
7
+ // 1. Load and validate configuration (exits 1 itself on invalid env).
8
+ const config = loadConfig();
9
+ // 2. Wire all subsystems together.
10
+ const { server, logger } = await createOdooMcpServer({
11
+ odooConfig: config.odoo,
12
+ logFile: config.logFile,
13
+ });
14
+ // 3. Log startup info before connecting (AC-3 — startup BEFORE connect).
15
+ logger.startup({
16
+ odoo_url: config.odoo.url,
17
+ odoo_db: config.odoo.db,
18
+ odoo_username: config.odoo.username,
19
+ });
20
+ // 4. Register signal handlers BEFORE connecting so they are armed in time.
21
+ const shutdown = () => {
22
+ logger.shutdown();
23
+ process.exit(0);
24
+ };
25
+ process.on('SIGTERM', shutdown);
26
+ process.on('SIGINT', shutdown);
27
+ // 5. Connect the transport — blocks until the transport closes.
28
+ await server.connect(new StdioServerTransport());
29
+ }
30
+ catch (err) {
31
+ const message = err instanceof Error ? err.message : String(err);
32
+ const errorType = err instanceof Error ? err.constructor.name : 'UnknownError';
33
+ process.stderr.write(`${JSON.stringify({ event: 'startup_error', error_type: errorType, message })}\n`);
34
+ process.exit(1);
35
+ }
36
+ })();
37
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAWlD,CAAC,KAAK,IAAI,EAAE;IACV,IAAI,CAAC;QACH,sEAAsE;QACtE,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAE5B,mCAAmC;QACnC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,mBAAmB,CAAC;YACnD,UAAU,EAAE,MAAM,CAAC,IAAI;YACvB,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC,CAAC;QAEH,yEAAyE;QACzE,MAAM,CAAC,OAAO,CAAC;YACb,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG;YACzB,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE;YACvB,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ;SACpC,CAAC,CAAC;QAEH,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,GAAG,EAAE;YACpB,MAAM,CAAC,QAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC;QACF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAChC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAE/B,gEAAgE;QAChE,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,SAAS,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC;QAC/E,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,IAAI,CAClF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,EAAE,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { OdooConfig } from '@netlinksinc/odoo-client';
2
+ export interface AppConfig {
3
+ odoo: OdooConfig;
4
+ logFile?: string;
5
+ }
6
+ export declare function loadConfig(env?: Record<string, string | undefined>): AppConfig;
7
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAU3D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAaD,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,SAAS,CAkD3F"}
package/dist/config.js ADDED
@@ -0,0 +1,59 @@
1
+ // @ts-ignore — node:fs has no types without @types/node (matches logger.ts pattern)
2
+ import { closeSync, openSync } from 'node:fs';
3
+ import { z } from 'zod';
4
+ const configSchema = z.object({
5
+ ODOO_URL: z
6
+ .string()
7
+ .url()
8
+ .transform((u) => u.replace(/\/$/, '')),
9
+ ODOO_DB: z.string().min(1),
10
+ ODOO_USERNAME: z.string().min(1),
11
+ ODOO_API_KEY: z.string().min(1),
12
+ ODOO_MCP_LOG_FILE: z.string().optional(),
13
+ });
14
+ export function loadConfig(env = process.env) {
15
+ const result = configSchema.safeParse(env);
16
+ if (!result.success) {
17
+ const missing = [];
18
+ const invalid = [];
19
+ for (const issue of result.error.issues) {
20
+ // path[0] is the env var name
21
+ const name = String(issue.path[0]);
22
+ if (issue.code === 'invalid_type' && issue.received === 'undefined') {
23
+ missing.push(name);
24
+ }
25
+ else {
26
+ if (!invalid.includes(name)) {
27
+ invalid.push(name);
28
+ }
29
+ }
30
+ }
31
+ // CRITICAL: Do NOT include any values from env — only names (AC-6 / US-1 AC-6)
32
+ process.stderr.write(`${JSON.stringify({ event: 'config_error', missing, invalid })}\n`);
33
+ process.exit(1);
34
+ }
35
+ // TypeScript discriminated union: result.success is true here, so result.data is defined.
36
+ const parsed = result.data;
37
+ if (parsed.ODOO_MCP_LOG_FILE !== undefined) {
38
+ const logFile = parsed.ODOO_MCP_LOG_FILE;
39
+ try {
40
+ const fd = openSync(logFile, 'a');
41
+ closeSync(fd);
42
+ }
43
+ catch (err) {
44
+ const message = err instanceof Error ? err.message : String(err);
45
+ process.stderr.write(`${JSON.stringify({ event: 'log_file_error', path: logFile, message })}\n`);
46
+ process.exit(1);
47
+ }
48
+ }
49
+ return {
50
+ odoo: {
51
+ url: parsed.ODOO_URL,
52
+ db: parsed.ODOO_DB,
53
+ username: parsed.ODOO_USERNAME,
54
+ apiKey: parsed.ODOO_API_KEY,
55
+ },
56
+ logFile: parsed.ODOO_MCP_LOG_FILE,
57
+ };
58
+ }
59
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { CompanyContext, Context, OdooSession } from '@netlinksinc/odoo-client';
2
+ /**
3
+ * Build an Odoo RPC context from a session, company args, and optional caller-supplied extras.
4
+ *
5
+ * Threat-model enforcement (US-7 AC-7): session-authoritative fields (uid,
6
+ * allowed_company_ids, company_id) are applied LAST so no caller-supplied
7
+ * extraContext value can override them.
8
+ */
9
+ export declare function buildContext(session: OdooSession, companyArgs: CompanyContext, extraContext?: Context): Context;
10
+ /**
11
+ * Assert that every caller-requested company ID is present in the session's
12
+ * allowed set. Throws OdooError with errorType 'InputValidationError' on
13
+ * failure so that formatMcpError surfaces the right error_type to Claude.
14
+ */
15
+ export declare function validateCompanySubset(callerIds: number[], sessionIds: number[]): void;
16
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGrF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,WAAW,EACpB,WAAW,EAAE,cAAc,EAC3B,YAAY,CAAC,EAAE,OAAO,GACrB,OAAO,CAiBT;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CASrF"}
@@ -0,0 +1,37 @@
1
+ import { OdooError } from '@netlinksinc/odoo-client';
2
+ /**
3
+ * Build an Odoo RPC context from a session, company args, and optional caller-supplied extras.
4
+ *
5
+ * Threat-model enforcement (US-7 AC-7): session-authoritative fields (uid,
6
+ * allowed_company_ids, company_id) are applied LAST so no caller-supplied
7
+ * extraContext value can override them.
8
+ */
9
+ export function buildContext(session, companyArgs, extraContext) {
10
+ // Resolve authoritative company/user values from session + companyArgs.
11
+ const allowed_company_ids = companyArgs.allowed_company_ids ?? session.allowedCompanyIds;
12
+ const company_id = companyArgs.active_company_id ?? session.companyId;
13
+ const uid = session.uid;
14
+ // Merge: session userContext → optional extraContext → authoritative fields.
15
+ const base = { ...session.userContext };
16
+ const merged = extraContext ? { ...base, ...extraContext } : base;
17
+ return {
18
+ ...merged,
19
+ // Re-apply authoritative fields last — cannot be overridden by extraContext.
20
+ uid,
21
+ allowed_company_ids,
22
+ company_id,
23
+ };
24
+ }
25
+ /**
26
+ * Assert that every caller-requested company ID is present in the session's
27
+ * allowed set. Throws OdooError with errorType 'InputValidationError' on
28
+ * failure so that formatMcpError surfaces the right error_type to Claude.
29
+ */
30
+ export function validateCompanySubset(callerIds, sessionIds) {
31
+ const sessionSet = new Set(sessionIds);
32
+ const missing = callerIds.filter((id) => !sessionSet.has(id));
33
+ if (missing.length > 0) {
34
+ throw new OdooError('InputValidationError', `InputValidationError: company ID not in session allowedCompanyIds: ${missing.join(', ')}`);
35
+ }
36
+ }
37
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAoB,EACpB,WAA2B,EAC3B,YAAsB;IAEtB,wEAAwE;IACxE,MAAM,mBAAmB,GAAG,WAAW,CAAC,mBAAmB,IAAI,OAAO,CAAC,iBAAiB,CAAC;IACzF,MAAM,UAAU,GAAG,WAAW,CAAC,iBAAiB,IAAI,OAAO,CAAC,SAAS,CAAC;IACtE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAExB,6EAA6E;IAC7E,MAAM,IAAI,GAAY,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACjD,MAAM,MAAM,GAAY,YAAY,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAE3E,OAAO;QACL,GAAG,MAAM;QACT,6EAA6E;QAC7E,GAAG;QACH,mBAAmB;QACnB,UAAU;KACX,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAmB,EAAE,UAAoB;IAC7E,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9D,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,SAAS,CACjB,sBAAsB,EACtB,sEAAsE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3F,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { OdooError } from '@netlinksinc/odoo-client';
2
+ export interface McpToolError {
3
+ error_type: string;
4
+ message: string;
5
+ model?: string;
6
+ method?: string;
7
+ traceback?: string;
8
+ }
9
+ export declare function formatMcpError(error: OdooError): McpToolError;
10
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAI1D,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,YAAY,CAW7D"}
package/dist/errors.js ADDED
@@ -0,0 +1,15 @@
1
+ export function formatMcpError(error) {
2
+ const result = {
3
+ error_type: error.errorType,
4
+ message: error.message,
5
+ };
6
+ if (error.model !== undefined)
7
+ result.model = error.model;
8
+ if (error.method !== undefined)
9
+ result.method = error.method;
10
+ if (process.env.ODOO_MCP_DEBUG === '1' && error.traceback !== undefined) {
11
+ result.traceback = error.traceback;
12
+ }
13
+ return result;
14
+ }
15
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAYA,MAAM,UAAU,cAAc,CAAC,KAAgB;IAC7C,MAAM,MAAM,GAAiB;QAC3B,UAAU,EAAE,KAAK,CAAC,SAAS;QAC3B,OAAO,EAAE,KAAK,CAAC,OAAO;KACvB,CAAC;IACF,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAC1D,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC7D,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,GAAG,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACxE,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;IACrC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { createOdooMcpServer } from './server.js';
2
+ export type { McpServerConfig } from './server.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { createOdooMcpServer } from './server.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,26 @@
1
+ export interface Logger {
2
+ toolCall(entry: {
3
+ tool: string;
4
+ args_sanitized: Record<string, unknown>;
5
+ latency_ms: number;
6
+ status: 'ok' | 'error';
7
+ error?: string;
8
+ }): void;
9
+ startup(info: {
10
+ odoo_url: string;
11
+ odoo_db: string;
12
+ odoo_username: string;
13
+ }): void;
14
+ shutdown(): void;
15
+ }
16
+ /**
17
+ * Creates a structured JSON logger that writes to stderr (always) and
18
+ * optionally to a file.
19
+ *
20
+ * File lifecycle: when logFile is supplied the fd is opened once with
21
+ * O_APPEND | O_CREAT and mode 0o600, then kept open for the logger's
22
+ * lifetime. This avoids per-write open/close overhead and lets the OS
23
+ * buffer writes correctly.
24
+ */
25
+ export declare function createLogger(logFile?: string): Logger;
26
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,KAAK,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;QACb,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACxC,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;QACvB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,IAAI,CAAC;IACT,OAAO,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClF,QAAQ,IAAI,IAAI,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAgDrD"}
package/dist/logger.js ADDED
@@ -0,0 +1,57 @@
1
+ // @ts-ignore — @types/node is not installed; this resolves correctly at Node.js runtime
2
+ import { openSync, writeSync } from 'node:fs';
3
+ /**
4
+ * Creates a structured JSON logger that writes to stderr (always) and
5
+ * optionally to a file.
6
+ *
7
+ * File lifecycle: when logFile is supplied the fd is opened once with
8
+ * O_APPEND | O_CREAT and mode 0o600, then kept open for the logger's
9
+ * lifetime. This avoids per-write open/close overhead and lets the OS
10
+ * buffer writes correctly.
11
+ */
12
+ export function createLogger(logFile) {
13
+ // Open the log file once; fd is -1 when no file is requested.
14
+ const fd = logFile !== undefined ? openSync(logFile, 'a', 0o600) : -1;
15
+ function emit(line) {
16
+ process.stderr.write(`${line}\n`);
17
+ if (fd !== -1) {
18
+ writeSync(fd, `${line}\n`);
19
+ }
20
+ }
21
+ return {
22
+ toolCall(entry) {
23
+ const obj = {
24
+ ts: new Date().toISOString(),
25
+ event: 'tool_call',
26
+ tool: entry.tool,
27
+ args_sanitized: entry.args_sanitized,
28
+ latency_ms: entry.latency_ms,
29
+ status: entry.status,
30
+ };
31
+ // Omit error key entirely when undefined
32
+ if (entry.error !== undefined) {
33
+ obj.error = entry.error;
34
+ }
35
+ emit(JSON.stringify(obj));
36
+ },
37
+ startup(info) {
38
+ // MUST NOT include odoo_api_key or any key matching /api_key/i (US-9 AC-2)
39
+ const obj = {
40
+ ts: new Date().toISOString(),
41
+ event: 'startup',
42
+ odoo_url: info.odoo_url,
43
+ odoo_db: info.odoo_db,
44
+ odoo_username: info.odoo_username,
45
+ };
46
+ emit(JSON.stringify(obj));
47
+ },
48
+ shutdown() {
49
+ const obj = {
50
+ ts: new Date().toISOString(),
51
+ event: 'shutdown',
52
+ };
53
+ emit(JSON.stringify(obj));
54
+ },
55
+ };
56
+ }
57
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAIA,wFAAwF;AACxF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAc9C;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,8DAA8D;IAC9D,MAAM,EAAE,GAAW,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE9E,SAAS,IAAI,CAAC,IAAY;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;QAClC,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;YACd,SAAS,CAAC,EAAE,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,OAAO;QACL,QAAQ,CAAC,KAAK;YACZ,MAAM,GAAG,GAA4B;gBACnC,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC5B,KAAK,EAAE,WAAW;gBAClB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,cAAc,EAAE,KAAK,CAAC,cAAc;gBACpC,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,MAAM,EAAE,KAAK,CAAC,MAAM;aACrB,CAAC;YACF,yCAAyC;YACzC,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC9B,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;YAC1B,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,CAAC,IAAI;YACV,2EAA2E;YAC3E,MAAM,GAAG,GAAG;gBACV,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC5B,KAAK,EAAE,SAAS;gBAChB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,aAAa,EAAE,IAAI,CAAC,aAAa;aAClC,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5B,CAAC;QAED,QAAQ;YACN,MAAM,GAAG,GAAG;gBACV,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC5B,KAAK,EAAE,UAAU;aAClB,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { OdooClient, ProbeResult } from '@netlinksinc/odoo-client';
2
+ /**
3
+ * Runs 7 sub-queries in parallel via Promise.allSettled and assembles a
4
+ * ProbeResult. Requires that client.authenticate() has already been called.
5
+ *
6
+ * Sub-queries (7 promises → 8 ProbeResult fields):
7
+ * 1. modules — ir.module.module
8
+ * 2. reports — ir.actions.report
9
+ * 3. serverActions — ir.actions.server
10
+ * 4. companies — res.company
11
+ * 5. currencies — res.currency
12
+ * 6. fiscalYear — account.fiscal.year (falls back to current year)
13
+ * 7. userContext — res.users.context_get → language + locale
14
+ *
15
+ * On total failure (all 7 promises rejected) writes a JSON warning to stderr.
16
+ * Never throws.
17
+ */
18
+ export declare function runProbe(client: OdooClient): Promise<ProbeResult>;
19
+ //# sourceMappingURL=probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAwBxE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA8LvE"}
package/dist/probe.js ADDED
@@ -0,0 +1,181 @@
1
+ import { OdooMissingError } from '@netlinksinc/odoo-client';
2
+ // Upper bound on the modules-installed query. Typical Odoo deployments have
3
+ // 100-300 installed modules; 500 covers the largest production instances
4
+ // while still avoiding unbounded payloads if Odoo's search_read default ever
5
+ // changes.
6
+ const MODULE_PROBE_LIMIT = 500;
7
+ // ---------------------------------------------------------------------------
8
+ // Helpers
9
+ // ---------------------------------------------------------------------------
10
+ function extractMessage(reason) {
11
+ if (reason instanceof Error)
12
+ return reason.message;
13
+ return String(reason);
14
+ }
15
+ // ---------------------------------------------------------------------------
16
+ // runProbe
17
+ // ---------------------------------------------------------------------------
18
+ /**
19
+ * Runs 7 sub-queries in parallel via Promise.allSettled and assembles a
20
+ * ProbeResult. Requires that client.authenticate() has already been called.
21
+ *
22
+ * Sub-queries (7 promises → 8 ProbeResult fields):
23
+ * 1. modules — ir.module.module
24
+ * 2. reports — ir.actions.report
25
+ * 3. serverActions — ir.actions.server
26
+ * 4. companies — res.company
27
+ * 5. currencies — res.currency
28
+ * 6. fiscalYear — account.fiscal.year (falls back to current year)
29
+ * 7. userContext — res.users.context_get → language + locale
30
+ *
31
+ * On total failure (all 7 promises rejected) writes a JSON warning to stderr.
32
+ * Never throws.
33
+ */
34
+ export async function runProbe(client) {
35
+ const currentYear = new Date().getFullYear();
36
+ // -------------------------------------------------------------------
37
+ // Promise 6: fiscalYear with internal fallback for missing model
38
+ // -------------------------------------------------------------------
39
+ const fiscalYearPromise = (async () => {
40
+ try {
41
+ const rows = await client.searchRead('account.fiscal.year', [], ['date_from', 'date_to'], {
42
+ limit: 1,
43
+ });
44
+ const first = rows[0];
45
+ if (first) {
46
+ return {
47
+ date_from: first.date_from,
48
+ date_to: first.date_to,
49
+ };
50
+ }
51
+ // Empty result — fall back to synthetic year
52
+ return {
53
+ date_from: `${currentYear}-01-01`,
54
+ date_to: `${currentYear}-12-31`,
55
+ };
56
+ }
57
+ catch (err) {
58
+ if (err instanceof OdooMissingError) {
59
+ // Model doesn't exist on this Odoo instance — return synthetic year
60
+ return {
61
+ date_from: `${currentYear}-01-01`,
62
+ date_to: `${currentYear}-12-31`,
63
+ };
64
+ }
65
+ throw err;
66
+ }
67
+ })();
68
+ // -------------------------------------------------------------------
69
+ // Promise 7: userContext — provides language + locale
70
+ // -------------------------------------------------------------------
71
+ const userContextPromise = client.execute('res.users', 'context_get', [], {}).then((result) => {
72
+ const ctx = result;
73
+ return {
74
+ lang: ctx.lang ?? 'en_US',
75
+ tz: ctx.tz ?? 'UTC',
76
+ };
77
+ });
78
+ // -------------------------------------------------------------------
79
+ // Fan-out: 7 promises
80
+ // -------------------------------------------------------------------
81
+ const [modulesResult, reportsResult, serverActionsResult, companiesResult, currenciesResult, fiscalYearResult, userContextResult,] = await Promise.allSettled([
82
+ // 1. modules
83
+ client
84
+ .searchRead('ir.module.module', [['state', '=', 'installed']], ['name', 'version'], {
85
+ limit: MODULE_PROBE_LIMIT,
86
+ })
87
+ .then((rows) => rows.map((r) => ({
88
+ name: r.name,
89
+ version: r.version,
90
+ }))),
91
+ // 2. reports
92
+ client
93
+ .searchRead('ir.actions.report', [], ['report_name', 'model', 'report_type'])
94
+ .then((rows) => rows.map((r) => ({
95
+ report_name: r.report_name,
96
+ model: r.model,
97
+ report_type: r.report_type,
98
+ }))),
99
+ // 3. serverActions
100
+ client
101
+ .searchRead('ir.actions.server', [], ['name', 'model_id', 'type'])
102
+ .then((rows) => rows.map((r) => ({
103
+ name: r.name,
104
+ model: r.model_id[1],
105
+ type: r.type,
106
+ }))),
107
+ // 4. companies
108
+ client
109
+ .searchRead('res.company', [], ['id', 'name', 'currency_id'])
110
+ .then((rows) => rows.map((r) => ({
111
+ id: r.id,
112
+ name: r.name,
113
+ currency_id: r.currency_id,
114
+ }))),
115
+ // 5. currencies
116
+ client
117
+ .searchRead('res.currency', [['active', '=', true]], ['id', 'name', 'symbol'])
118
+ .then((rows) => rows.map((r) => ({
119
+ id: r.id,
120
+ name: r.name,
121
+ symbol: r.symbol,
122
+ }))),
123
+ // 6. fiscalYear (internal fallback for OdooMissingError)
124
+ fiscalYearPromise,
125
+ // 7. userContext (language + locale)
126
+ userContextPromise,
127
+ ]);
128
+ // -------------------------------------------------------------------
129
+ // Threat-model US-3 AC-6: warn if all 7 promises failed
130
+ // -------------------------------------------------------------------
131
+ const succeeded = [
132
+ modulesResult,
133
+ reportsResult,
134
+ serverActionsResult,
135
+ companiesResult,
136
+ currenciesResult,
137
+ fiscalYearResult,
138
+ userContextResult,
139
+ ].filter((r) => r.status === 'fulfilled').length;
140
+ if (succeeded === 0) {
141
+ process.stderr.write(`${JSON.stringify({ event: 'warning', message: 'All probe sub-queries failed' })}\n`);
142
+ }
143
+ // -------------------------------------------------------------------
144
+ // Assemble ProbeResult
145
+ // -------------------------------------------------------------------
146
+ // language and locale share the userContext promise
147
+ let language;
148
+ let locale;
149
+ if (userContextResult.status === 'fulfilled') {
150
+ language = userContextResult.value.lang;
151
+ locale = userContextResult.value.tz;
152
+ }
153
+ else {
154
+ const errMsg = extractMessage(userContextResult.reason);
155
+ language = { error: errMsg };
156
+ locale = { error: errMsg };
157
+ }
158
+ return {
159
+ modules: modulesResult.status === 'fulfilled'
160
+ ? modulesResult.value
161
+ : { error: extractMessage(modulesResult.reason) },
162
+ reports: reportsResult.status === 'fulfilled'
163
+ ? reportsResult.value
164
+ : { error: extractMessage(reportsResult.reason) },
165
+ serverActions: serverActionsResult.status === 'fulfilled'
166
+ ? serverActionsResult.value
167
+ : { error: extractMessage(serverActionsResult.reason) },
168
+ companies: companiesResult.status === 'fulfilled'
169
+ ? companiesResult.value
170
+ : { error: extractMessage(companiesResult.reason) },
171
+ currencies: currenciesResult.status === 'fulfilled'
172
+ ? currenciesResult.value
173
+ : { error: extractMessage(currenciesResult.reason) },
174
+ fiscalYear: fiscalYearResult.status === 'fulfilled'
175
+ ? fiscalYearResult.value
176
+ : { error: extractMessage(fiscalYearResult.reason) },
177
+ language,
178
+ locale,
179
+ };
180
+ }
181
+ //# sourceMappingURL=probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ProbeResult } from '@netlinksinc/odoo-client';
3
+ /**
4
+ * Registers 7 MCP resources on the given server, each backed by a closure
5
+ * over the already-resolved `probe` — no re-query to Odoo (US-3 AC-5).
6
+ *
7
+ * If a probe field is `{ error: string }`, the resource returns that object
8
+ * as JSON content (US-3 AC-3 — error transparency, no throw).
9
+ */
10
+ export declare function registerResources(server: McpServer, probe: ProbeResult): void;
11
+ //# sourceMappingURL=resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAkC7E"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Registers 7 MCP resources on the given server, each backed by a closure
3
+ * over the already-resolved `probe` — no re-query to Odoo (US-3 AC-5).
4
+ *
5
+ * If a probe field is `{ error: string }`, the resource returns that object
6
+ * as JSON content (US-3 AC-3 — error transparency, no throw).
7
+ */
8
+ export function registerResources(server, probe) {
9
+ const resources = [
10
+ { name: 'modules', uri: 'odoo://modules', data: probe.modules },
11
+ { name: 'reports', uri: 'odoo://reports', data: probe.reports },
12
+ { name: 'server-actions', uri: 'odoo://server-actions', data: probe.serverActions },
13
+ { name: 'companies', uri: 'odoo://companies', data: probe.companies },
14
+ { name: 'currencies', uri: 'odoo://currencies', data: probe.currencies },
15
+ { name: 'fiscal-year', uri: 'odoo://fiscal-year', data: probe.fiscalYear },
16
+ {
17
+ name: 'user-context',
18
+ uri: 'odoo://user-context',
19
+ data: { language: probe.language, locale: probe.locale },
20
+ },
21
+ ];
22
+ for (const { name, uri, data } of resources) {
23
+ // Capture uri and data in closure — no re-query on each read.
24
+ const capturedUri = uri;
25
+ const capturedData = data;
26
+ server.resource(name, capturedUri, (_url) => ({
27
+ contents: [
28
+ {
29
+ uri: capturedUri,
30
+ mimeType: 'application/json',
31
+ text: JSON.stringify(capturedData),
32
+ },
33
+ ],
34
+ }));
35
+ }
36
+ }
37
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAiB,EAAE,KAAkB;IACrE,MAAM,SAAS,GAIV;QACH,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE;QAC/D,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE;QAC/D,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,uBAAuB,EAAE,IAAI,EAAE,KAAK,CAAC,aAAa,EAAE;QACnF,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,kBAAkB,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,EAAE;QACrE,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,mBAAmB,EAAE,IAAI,EAAE,KAAK,CAAC,UAAU,EAAE;QACxE,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,oBAAoB,EAAE,IAAI,EAAE,KAAK,CAAC,UAAU,EAAE;QAC1E;YACE,IAAI,EAAE,cAAc;YACpB,GAAG,EAAE,qBAAqB;YAC1B,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;SACzD;KACF,CAAC;IAEF,KAAK,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,SAAS,EAAE,CAAC;QAC5C,8DAA8D;QAC9D,MAAM,WAAW,GAAG,GAAG,CAAC;QACxB,MAAM,YAAY,GAAG,IAAI,CAAC;QAE1B,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC5C,QAAQ,EAAE;gBACR;oBACE,GAAG,EAAE,WAAW;oBAChB,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;iBACnC;aACF;SACF,CAAC,CAAC,CAAC;IACN,CAAC;AACH,CAAC"}