@deerdesign/deertools 1.0.3 → 1.2.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.
@@ -0,0 +1,157 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { homeDir } = require('../credentials');
5
+
6
+ const CACHE_FILE = path.join(__dirname, 'schema-cache.json');
7
+ const USER_CACHE_FILE = path.join(homeDir(), 'baselinker-schema.json');
8
+ const INDEX_FILE = path.join(__dirname, 'methods-index.json');
9
+
10
+ function loadSchemas() {
11
+ if (fs.existsSync(USER_CACHE_FILE)) {
12
+ try {
13
+ return JSON.parse(fs.readFileSync(USER_CACHE_FILE, 'utf8'));
14
+ } catch (_) {}
15
+ }
16
+ if (fs.existsSync(CACHE_FILE)) {
17
+ try {
18
+ return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
19
+ } catch (_) {}
20
+ }
21
+ if (fs.existsSync(INDEX_FILE)) {
22
+ try {
23
+ return JSON.parse(fs.readFileSync(INDEX_FILE, 'utf8'));
24
+ } catch (_) {}
25
+ }
26
+ return {};
27
+ }
28
+
29
+ function parseType(rawType) {
30
+ const t = (rawType || '').toLowerCase();
31
+ if (t.includes('int')) return 'integer';
32
+ if (t.includes('bool')) return 'boolean';
33
+ if (t.includes('float') || t.includes('decimal')) return 'number';
34
+ if (t.includes('array')) return 'array';
35
+ return 'string';
36
+ }
37
+
38
+ async function fetchMethodDetails(methodName) {
39
+ try {
40
+ const res = await fetch(`https://api.baselinker.com/index.php?method=${methodName}`);
41
+ if (!res.ok) return null;
42
+ const html = await res.text();
43
+ const tableMatch = html.match(/<div class=[\'\"]header[\'\"]>\s*Input parameters\s*<\/div>\s*<table class=[\'\"]table[\'\"]>(.*?)<\/table>/s);
44
+ if (!tableMatch) return { properties: {}, required: [] };
45
+
46
+ const rows = [...tableMatch[1].matchAll(/<tr><td[^>]*><B>(.*?)<\/B><\/td><td>(.*?)<\/td><td>(.*?)<\/td><\/tr>/gs)];
47
+ const properties = {};
48
+ const required = [];
49
+
50
+ for (const r of rows) {
51
+ const name = r[1].replace(/<[^>]+>/g, '').trim();
52
+ const rawType = r[2].replace(/<[^>]+>/g, '').trim();
53
+ const desc = r[3].replace(/<[^>]+>/g, '').trim();
54
+ const isOptional = desc.toLowerCase().includes('optional');
55
+
56
+ properties[name] = {
57
+ type: parseType(rawType),
58
+ description: desc
59
+ };
60
+ if (!isOptional && !name.includes('|')) {
61
+ required.push(name);
62
+ }
63
+ }
64
+ return { properties, required };
65
+ } catch (err) {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ async function syncSchema(onProgress = null) {
71
+ const res = await fetch('https://api.baselinker.com/');
72
+ const html = await res.text();
73
+
74
+ const sections = html.split(/<div class=[\'\"]header[\'\"]>/i);
75
+ const catalog = {};
76
+
77
+ for (let i = 1; i < sections.length; i++) {
78
+ const sec = sections[i];
79
+ const titleEnd = sec.indexOf('</div>');
80
+ if (titleEnd === -1) continue;
81
+ const categoryName = sec.substring(0, titleEnd).trim();
82
+ const re = /method=([a-zA-Z0-9_]+)[\'\"]>([a-zA-Z0-9_]+)<\/B><\/a><\/td><td>(.*?)<\/td>/g;
83
+ let m;
84
+ while ((m = re.exec(sec)) !== null) {
85
+ const methodName = m[1];
86
+ const desc = m[3].replace(/<[^>]+>/g, '').trim();
87
+ catalog[methodName] = {
88
+ name: methodName,
89
+ category: categoryName,
90
+ description: desc,
91
+ parameters: {}
92
+ };
93
+ }
94
+ }
95
+
96
+ const methodNames = Object.keys(catalog);
97
+ let done = 0;
98
+ const concurrency = 6;
99
+ const queue = [...methodNames];
100
+
101
+ async function worker() {
102
+ while (queue.length > 0) {
103
+ const m = queue.shift();
104
+ const schema = await fetchMethodDetails(m);
105
+ if (schema) {
106
+ catalog[m].parameters = schema.properties;
107
+ catalog[m].required = schema.required;
108
+ }
109
+ done++;
110
+ if (onProgress) onProgress(done, methodNames.length, m);
111
+ }
112
+ }
113
+
114
+ const workers = Array.from({ length: concurrency }, () => worker());
115
+ await Promise.all(workers);
116
+
117
+ const dir = homeDir();
118
+ fs.mkdirSync(dir, { recursive: true });
119
+ fs.writeFileSync(USER_CACHE_FILE, JSON.stringify(catalog, null, 2));
120
+
121
+ return catalog;
122
+ }
123
+
124
+ function searchMethods(query, category = null) {
125
+ const schemas = loadSchemas();
126
+ const q = (query || '').toLowerCase().trim();
127
+ const cat = (category || '').toLowerCase().trim();
128
+
129
+ return Object.values(schemas).filter(item => {
130
+ if (cat && !item.category.toLowerCase().includes(cat)) return false;
131
+ const hasParam = item.parameters && Object.keys(item.parameters).some(p => p.toLowerCase().includes(q));
132
+ return (
133
+ item.name.toLowerCase().includes(q) ||
134
+ (item.description && item.description.toLowerCase().includes(q)) ||
135
+ (item.category && item.category.toLowerCase().includes(q)) ||
136
+ hasParam
137
+ );
138
+ });
139
+ }
140
+
141
+ function getMethodSchema(methodName) {
142
+ const schemas = loadSchemas();
143
+ return schemas[methodName] || null;
144
+ }
145
+
146
+ function getAllCatalogMethods() {
147
+ const schemas = loadSchemas();
148
+ return Object.values(schemas).filter(s => s.category === 'Product catalog');
149
+ }
150
+
151
+ module.exports = {
152
+ loadSchemas,
153
+ syncSchema,
154
+ searchMethods,
155
+ getMethodSchema,
156
+ getAllCatalogMethods
157
+ };
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+ const { BaseLinkerClient, getClient } = require('./client');
3
+ const { CATALOG_MCP_TOOLS, handleCatalogToolCall } = require('./catalog-tools');
4
+ const { buildToolsList, handleMcpMessage, runBaseLinkerMcpServer } = require('./mcp-server');
5
+ const { startBaseLinkerServer } = require('./ui');
6
+ const { loadSchemas, syncSchema, searchMethods, getMethodSchema, getAllCatalogMethods } = require('./crawler');
7
+ const { loginBaseLinkerBrowser } = require('./browser-login');
8
+
9
+ module.exports = {
10
+ BaseLinkerClient,
11
+ getClient,
12
+ CATALOG_MCP_TOOLS,
13
+ handleCatalogToolCall,
14
+ buildToolsList,
15
+ handleMcpMessage,
16
+ runBaseLinkerMcpServer,
17
+ startBaseLinkerServer,
18
+ loadSchemas,
19
+ syncSchema,
20
+ searchMethods,
21
+ getMethodSchema,
22
+ getAllCatalogMethods,
23
+ loginBaseLinkerBrowser
24
+ };
@@ -0,0 +1,153 @@
1
+ 'use strict';
2
+ const readline = require('readline');
3
+ const { CATALOG_MCP_TOOLS, handleCatalogToolCall } = require('./catalog-tools');
4
+ const { getClient } = require('./client');
5
+ const { getAllCatalogMethods, getMethodSchema, loadSchemas } = require('./crawler');
6
+
7
+ function buildToolsList(mode = 'catalog') {
8
+ if (mode === 'all') {
9
+ const all = loadSchemas();
10
+ const tools = [...CATALOG_MCP_TOOLS];
11
+ for (const [name, def] of Object.entries(all)) {
12
+ if (tools.some(t => t.name === `bl_${name}` || t.name === name)) continue;
13
+ tools.push({
14
+ name: `bl_${name}`,
15
+ description: def.description || `Metoda BaseLinker ${name} (${def.category})`,
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: def.parameters || {},
19
+ required: def.required || []
20
+ }
21
+ });
22
+ }
23
+ return tools;
24
+ }
25
+ return CATALOG_MCP_TOOLS;
26
+ }
27
+
28
+ async function handleMcpMessage(msg, client = null, mode = 'catalog') {
29
+ if (!msg || typeof msg !== 'object') return null;
30
+ const { id, method, params } = msg;
31
+
32
+ if (method === 'initialize') {
33
+ return {
34
+ jsonrpc: '2.0',
35
+ id,
36
+ result: {
37
+ protocolVersion: '2024-11-05',
38
+ capabilities: { tools: {} },
39
+ serverInfo: {
40
+ name: 'deer-baselinker-mcp',
41
+ version: '1.0.0'
42
+ }
43
+ }
44
+ };
45
+ }
46
+
47
+ if (method === 'notifications/initialized' || method === 'initialized') {
48
+ return null;
49
+ }
50
+
51
+ if (method === 'ping') {
52
+ return { jsonrpc: '2.0', id, result: {} };
53
+ }
54
+
55
+ if (method === 'tools/list') {
56
+ return {
57
+ jsonrpc: '2.0',
58
+ id,
59
+ result: {
60
+ tools: buildToolsList(mode)
61
+ }
62
+ };
63
+ }
64
+
65
+ if (method === 'tools/call') {
66
+ const { name, arguments: args } = params || {};
67
+ try {
68
+ let resultData;
69
+ if (name.startsWith('bl_catalog_') || name === 'bl_search_methods' || name === 'bl_call_api') {
70
+ resultData = await handleCatalogToolCall(name, args || {}, client);
71
+ } else if (name.startsWith('bl_') || loadSchemas()[name]) {
72
+ const rawMethod = name.startsWith('bl_') ? name.slice(3) : name;
73
+ const bl = client || getClient();
74
+ resultData = await bl.call(rawMethod, args || {});
75
+ } else {
76
+ throw new Error(`Nieznane narzędzie: ${name}`);
77
+ }
78
+
79
+ return {
80
+ jsonrpc: '2.0',
81
+ id,
82
+ result: {
83
+ content: [
84
+ {
85
+ type: 'text',
86
+ text: JSON.stringify(resultData, null, 2)
87
+ }
88
+ ]
89
+ }
90
+ };
91
+ } catch (err) {
92
+ return {
93
+ jsonrpc: '2.0',
94
+ id,
95
+ isError: true,
96
+ result: {
97
+ content: [
98
+ {
99
+ type: 'text',
100
+ text: `Błąd wykonania ${name}: ${err.message}`
101
+ }
102
+ ]
103
+ }
104
+ };
105
+ }
106
+ }
107
+
108
+ return {
109
+ jsonrpc: '2.0',
110
+ id,
111
+ error: {
112
+ code: -32601,
113
+ message: `Nieznana metoda: ${method}`
114
+ }
115
+ };
116
+ }
117
+
118
+ function runBaseLinkerMcpServer(options = {}) {
119
+ const { mode = 'catalog' } = options;
120
+ const rl = readline.createInterface({
121
+ input: process.stdin,
122
+ output: process.stdout,
123
+ terminal: false
124
+ });
125
+
126
+ rl.on('line', async line => {
127
+ const trimmed = line.trim();
128
+ if (!trimmed) return;
129
+ try {
130
+ const msg = JSON.parse(trimmed);
131
+ const res = await handleMcpMessage(msg, null, mode);
132
+ if (res) {
133
+ process.stdout.write(JSON.stringify(res) + '\n');
134
+ }
135
+ } catch (err) {
136
+ const errRes = {
137
+ jsonrpc: '2.0',
138
+ id: null,
139
+ error: { code: -32700, message: 'Parse error: ' + err.message }
140
+ };
141
+ process.stdout.write(JSON.stringify(errRes) + '\n');
142
+ }
143
+ });
144
+
145
+ process.on('SIGINT', () => process.exit(0));
146
+ process.on('SIGTERM', () => process.exit(0));
147
+ }
148
+
149
+ module.exports = {
150
+ buildToolsList,
151
+ handleMcpMessage,
152
+ runBaseLinkerMcpServer
153
+ };