@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.
- package/LICENSE +21 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +37 -0
- package/dist/bin.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +16 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +37 -0
- package/dist/context.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +15 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +26 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +57 -0
- package/dist/logger.js.map +1 -0
- package/dist/probe.d.ts +19 -0
- package/dist/probe.d.ts.map +1 -0
- package/dist/probe.js +181 -0
- package/dist/probe.js.map +1 -0
- package/dist/resources.d.ts +11 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +37 -0
- package/dist/resources.js.map +1 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +32 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/action.d.ts +6 -0
- package/dist/tools/action.d.ts.map +1 -0
- package/dist/tools/action.js +158 -0
- package/dist/tools/action.js.map +1 -0
- package/dist/tools/execute.d.ts +6 -0
- package/dist/tools/execute.d.ts.map +1 -0
- package/dist/tools/execute.js +156 -0
- package/dist/tools/execute.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +13 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/introspect.d.ts +6 -0
- package/dist/tools/introspect.d.ts.map +1 -0
- package/dist/tools/introspect.js +155 -0
- package/dist/tools/introspect.js.map +1 -0
- package/dist/tools/orm.d.ts +16 -0
- package/dist/tools/orm.d.ts.map +1 -0
- package/dist/tools/orm.js +139 -0
- package/dist/tools/orm.js.map +1 -0
- package/dist/tools/report.d.ts +6 -0
- package/dist/tools/report.d.ts.map +1 -0
- package/dist/tools/report.js +99 -0
- package/dist/tools/report.js.map +1 -0
- package/dist/tools/schemas.d.ts +202 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +77 -0
- package/dist/tools/schemas.js.map +1 -0
- 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 @@
|
|
|
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
|
package/dist/bin.js.map
ADDED
|
@@ -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"}
|
package/dist/config.d.ts
ADDED
|
@@ -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"}
|
package/dist/context.js
ADDED
|
@@ -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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/logger.d.ts
ADDED
|
@@ -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"}
|
package/dist/probe.d.ts
ADDED
|
@@ -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"}
|