@supericons/mcp 0.4.6
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/CHANGELOG.md +78 -0
- package/auth.js +69 -0
- package/converter.js +8 -0
- package/generated/mcp-output-locales.json +12436 -0
- package/generated/motion-lab-baseline.json +886 -0
- package/hosted-search-client.js +198 -0
- package/index.js +1240 -0
- package/material-export.js +174 -0
- package/mcp-output-localization.js +132 -0
- package/motion-lab-client.js +347 -0
- package/motion-lab.js +21 -0
- package/package.json +63 -0
- package/public/cjk-search-terms.json +63474 -0
- package/public/multilingual-search-aliases.json +4307 -0
- package/public/product-facts.json +25 -0
- package/recommend-icons.js +707 -0
- package/remote-server.js +465 -0
- package/runtime/cjk-search-core.js +82 -0
- package/runtime/converter-workflow.js +593 -0
- package/runtime/generated-search-intent-rules.js +1190 -0
- package/runtime/icon-semantic-aliases.js +330 -0
- package/runtime/icon-taxonomy-seed.js +461 -0
- package/runtime/public-metadata-sanitizer.js +171 -0
- package/runtime/search-intent-core.js +130 -0
- package/search.js +375 -0
- package/semantic-registry.js +212 -0
- package/server.json +27 -0
- package/telemetry.js +85 -0
- package/variant-support.js +236 -0
- package/workflow-access.js +65 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { getConfiguredApiKey, SUPABASE_ANON, SUPABASE_URL } from './auth.js';
|
|
6
|
+
import {
|
|
7
|
+
expandCjkQuery,
|
|
8
|
+
normalizeCjkSearchText,
|
|
9
|
+
} from './runtime/cjk-search-core.js';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const cjkTermsPath = join(__dirname, 'public', 'cjk-search-terms.json');
|
|
13
|
+
const multilingualAliasesPath = join(__dirname, 'public', 'multilingual-search-aliases.json');
|
|
14
|
+
const cjkSearchTerms = existsSync(cjkTermsPath)
|
|
15
|
+
? JSON.parse(readFileSync(cjkTermsPath, 'utf8')).terms || []
|
|
16
|
+
: [];
|
|
17
|
+
const multilingualSearchAliases = existsSync(multilingualAliasesPath)
|
|
18
|
+
? JSON.parse(readFileSync(multilingualAliasesPath, 'utf8')).aliases || []
|
|
19
|
+
: [];
|
|
20
|
+
const multilingualExpansionTerms = [...cjkSearchTerms, ...multilingualSearchAliases];
|
|
21
|
+
|
|
22
|
+
function looksLikeJwt(value) {
|
|
23
|
+
return /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(String(value || '').trim());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function shouldRequireJwt() {
|
|
27
|
+
const raw = String(process.env.SUPERICONS_SEARCH_ENGINE_REQUIRE_JWT || '').trim().toLowerCase();
|
|
28
|
+
return raw !== '0' && raw !== 'false' && raw !== 'off';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function shouldUseInternalHostedDebug() {
|
|
32
|
+
const raw = String(process.env.SUPERICONS_INTERNAL_HOSTED_DEBUG || '').trim().toLowerCase();
|
|
33
|
+
return raw === '1' || raw === 'true' || raw === 'on';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getPublicGatewayUrl() {
|
|
37
|
+
return (
|
|
38
|
+
process.env.SUPERICONS_MCP_SEARCH_URL
|
|
39
|
+
|| `${SUPABASE_URL}/functions/v1/mcp-search`
|
|
40
|
+
).replace(/\/+$/, '');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getDirectHostedSearchUrl() {
|
|
44
|
+
return (
|
|
45
|
+
process.env.SUPERICONS_SEARCH_ENGINE_URL
|
|
46
|
+
|| `${SUPABASE_URL}/functions/v1/search-icons`
|
|
47
|
+
).replace(/\/+$/, '');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hasSearchResults(payload) {
|
|
51
|
+
return Array.isArray(payload?.results) && payload.results.length > 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildLocalizedRetryQueries(query, locale) {
|
|
55
|
+
if (!locale || multilingualExpansionTerms.length === 0) return [];
|
|
56
|
+
|
|
57
|
+
const expanded = expandCjkQuery(query, {
|
|
58
|
+
locale,
|
|
59
|
+
terms: multilingualExpansionTerms,
|
|
60
|
+
});
|
|
61
|
+
const original = normalizeCjkSearchText(query);
|
|
62
|
+
const seen = new Set([original]);
|
|
63
|
+
const retryQueries = [];
|
|
64
|
+
|
|
65
|
+
for (const variant of expanded.variants || []) {
|
|
66
|
+
const normalized = normalizeCjkSearchText(variant);
|
|
67
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
68
|
+
seen.add(normalized);
|
|
69
|
+
retryQueries.push(normalized);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return retryQueries.slice(0, 8);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function postHostedSearch(url, headers, body) {
|
|
76
|
+
const response = await fetch(url, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers,
|
|
79
|
+
body: JSON.stringify(body),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
throw new Error(`hosted MCP search failed (${response.status})`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return response.json();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function postPublicSearch(url, headers, body) {
|
|
90
|
+
const response = await fetch(url, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers,
|
|
93
|
+
body: JSON.stringify(body),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
throw new Error(`public MCP search failed (${response.status})`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return response.json();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function retryLocalizedHostedSearch({ postSearch, url, headers, body, locale }) {
|
|
104
|
+
const retryQueries = buildLocalizedRetryQueries(body.query, locale);
|
|
105
|
+
|
|
106
|
+
for (const retryQuery of retryQueries) {
|
|
107
|
+
const retryPayload = await postSearch(url, headers, {
|
|
108
|
+
...body,
|
|
109
|
+
query: retryQuery,
|
|
110
|
+
locale: null,
|
|
111
|
+
localized_query: body.query,
|
|
112
|
+
localized_locale: locale,
|
|
113
|
+
});
|
|
114
|
+
if (hasSearchResults(retryPayload)) return retryPayload;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function searchIconsHostedMcp({
|
|
121
|
+
query,
|
|
122
|
+
library = null,
|
|
123
|
+
limit = 20,
|
|
124
|
+
style = 'any',
|
|
125
|
+
locale = null,
|
|
126
|
+
}) {
|
|
127
|
+
const apiKey = getConfiguredApiKey();
|
|
128
|
+
|
|
129
|
+
if (shouldUseInternalHostedDebug()) {
|
|
130
|
+
const baseUrl = getDirectHostedSearchUrl();
|
|
131
|
+
const anonKey = process.env.SUPERICONS_SEARCH_ENGINE_ANON_KEY || process.env.SUPABASE_ANON_KEY || SUPABASE_ANON;
|
|
132
|
+
const isJwt = looksLikeJwt(anonKey);
|
|
133
|
+
|
|
134
|
+
if (shouldRequireJwt() && !isJwt) {
|
|
135
|
+
throw new Error('hosted MCP search requires a legacy Supabase anon JWT; publishable keys are not valid bearer tokens');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const headers = {
|
|
139
|
+
'Content-Type': 'application/json',
|
|
140
|
+
apikey: anonKey,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
if (apiKey) {
|
|
144
|
+
headers['x-supericons-api-key'] = apiKey;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (isJwt) {
|
|
148
|
+
headers.Authorization = `Bearer ${anonKey}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const body = {
|
|
152
|
+
query,
|
|
153
|
+
library,
|
|
154
|
+
limit,
|
|
155
|
+
style,
|
|
156
|
+
locale,
|
|
157
|
+
source: 'mcp',
|
|
158
|
+
};
|
|
159
|
+
const payload = await postHostedSearch(baseUrl, headers, body);
|
|
160
|
+
if (hasSearchResults(payload) || !locale) return payload;
|
|
161
|
+
|
|
162
|
+
return await retryLocalizedHostedSearch({
|
|
163
|
+
postSearch: postHostedSearch,
|
|
164
|
+
url: baseUrl,
|
|
165
|
+
headers,
|
|
166
|
+
body,
|
|
167
|
+
locale,
|
|
168
|
+
}) || payload;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const headers = {
|
|
172
|
+
'Content-Type': 'application/json',
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
if (apiKey) {
|
|
176
|
+
headers['x-supericons-api-key'] = apiKey;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const url = getPublicGatewayUrl();
|
|
180
|
+
const body = {
|
|
181
|
+
query,
|
|
182
|
+
library,
|
|
183
|
+
limit,
|
|
184
|
+
style,
|
|
185
|
+
locale,
|
|
186
|
+
source: 'mcp',
|
|
187
|
+
};
|
|
188
|
+
const payload = await postPublicSearch(url, headers, body);
|
|
189
|
+
if (hasSearchResults(payload) || !locale) return payload;
|
|
190
|
+
|
|
191
|
+
return await retryLocalizedHostedSearch({
|
|
192
|
+
postSearch: postPublicSearch,
|
|
193
|
+
url,
|
|
194
|
+
headers,
|
|
195
|
+
body,
|
|
196
|
+
locale,
|
|
197
|
+
}) || payload;
|
|
198
|
+
}
|