@supericons/mcp
Advanced tools
| import { createHash } from 'node:crypto'; | ||
| const DEFAULT_PAGE_SIZE = 1000; | ||
| export function exactSiSetSummary(refs) { | ||
| const values = [...new Set((refs || []).map((value) => String(value || '').trim()).filter(Boolean))].sort(); | ||
| return { | ||
| count: values.length, | ||
| sha256: createHash('sha256').update(values.join('\n')).digest('hex'), | ||
| refs: values, | ||
| }; | ||
| } | ||
| export function compareSiSets(actualRefs, expectedRefs) { | ||
| const actual = exactSiSetSummary(actualRefs); | ||
| const expected = exactSiSetSummary(expectedRefs); | ||
| const actualSet = new Set(actual.refs); | ||
| const expectedSet = new Set(expected.refs); | ||
| return { | ||
| count: actual.count, | ||
| sha256: actual.sha256, | ||
| matches_expected: actual.sha256 === expected.sha256 && actual.count === expected.count, | ||
| missing: expected.refs.filter((ref) => !actualSet.has(ref)), | ||
| extra: actual.refs.filter((ref) => !expectedSet.has(ref)), | ||
| }; | ||
| } | ||
| async function fetchRows({ | ||
| supabaseUrl, | ||
| serviceRoleKey, | ||
| table, | ||
| filterName, | ||
| filterValue, | ||
| fetchImpl, | ||
| pageSize, | ||
| timeoutMs, | ||
| }) { | ||
| const rows = []; | ||
| for (let offset = 0; ; offset += pageSize) { | ||
| const url = new URL(`${String(supabaseUrl).replace(/\/+$/, '')}/rest/v1/${table}`); | ||
| url.searchParams.set('select', 'icon_id'); | ||
| url.searchParams.set(filterName, filterValue); | ||
| url.searchParams.set('order', 'icon_id.asc'); | ||
| url.searchParams.set('limit', String(pageSize)); | ||
| url.searchParams.set('offset', String(offset)); | ||
| const response = await fetchImpl(url, { | ||
| headers: { | ||
| apikey: serviceRoleKey, | ||
| Authorization: `Bearer ${serviceRoleKey}`, | ||
| }, | ||
| signal: AbortSignal.timeout(timeoutMs), | ||
| }); | ||
| if (!response.ok) { | ||
| const detail = await response.text().catch(() => ''); | ||
| throw new Error(`Supabase read failed for ${table} (${response.status}): ${detail}`); | ||
| } | ||
| const page = await response.json(); | ||
| if (!Array.isArray(page)) throw new Error(`Supabase returned a non-array payload for ${table}.`); | ||
| rows.push(...page); | ||
| if (page.length < pageSize) return rows; | ||
| } | ||
| } | ||
| export async function readHostedSiCatalogStatus({ | ||
| supabaseUrl, | ||
| serviceRoleKey, | ||
| expectedRefs, | ||
| fetchImpl = fetch, | ||
| pageSize = DEFAULT_PAGE_SIZE, | ||
| timeoutMs = 5000, | ||
| } = {}) { | ||
| const expected = exactSiSetSummary(expectedRefs); | ||
| if (!supabaseUrl || !serviceRoleKey) { | ||
| return { | ||
| available: false, | ||
| reason: 'service_role_unavailable', | ||
| expected: { count: expected.count, sha256: expected.sha256 }, | ||
| }; | ||
| } | ||
| const [catalogRows, registryRows] = await Promise.all([ | ||
| fetchRows({ | ||
| supabaseUrl, | ||
| serviceRoleKey, | ||
| table: 'icon_catalog', | ||
| filterName: 'source_library', | ||
| filterValue: 'eq.si', | ||
| fetchImpl, | ||
| pageSize, | ||
| timeoutMs, | ||
| }), | ||
| fetchRows({ | ||
| supabaseUrl, | ||
| serviceRoleKey, | ||
| table: 'icon_search_public_registry_metadata', | ||
| filterName: 'icon_id', | ||
| filterValue: 'like.si:*', | ||
| fetchImpl, | ||
| pageSize, | ||
| timeoutMs, | ||
| }), | ||
| ]); | ||
| const catalog = compareSiSets(catalogRows.map((row) => row.icon_id), expected.refs); | ||
| const registry = compareSiSets(registryRows.map((row) => row.icon_id), expected.refs); | ||
| return { | ||
| available: true, | ||
| matches_bundle: catalog.matches_expected && registry.matches_expected, | ||
| expected: { count: expected.count, sha256: expected.sha256 }, | ||
| icon_catalog: catalog, | ||
| public_registry: registry, | ||
| }; | ||
| } |
| import { createHash } from 'node:crypto'; | ||
| import { readFileSync } from 'node:fs'; | ||
| import { join } from 'node:path'; | ||
| export const SEARCH_RUNTIME_FILES = Object.freeze([ | ||
| 'hosted-si-catalog-status.js', | ||
| 'railway-local-search.js', | ||
| 'remote-server.js', | ||
| 'search-release-fingerprint.js', | ||
| 'search.js', | ||
| 'semantic-registry.js', | ||
| 'public/icon-index.json', | ||
| 'public/synonyms.json', | ||
| 'runtime/search-pipeline.js', | ||
| ]); | ||
| export function buildSearchReleaseFingerprint({ | ||
| packageRoot, | ||
| version, | ||
| siIconSetSha256, | ||
| releaseCommit = '', | ||
| } = {}) { | ||
| const runtimeHash = createHash('sha256'); | ||
| for (const relativePath of SEARCH_RUNTIME_FILES) { | ||
| runtimeHash.update(relativePath); | ||
| runtimeHash.update('\0'); | ||
| runtimeHash.update(readFileSync(join(packageRoot, relativePath))); | ||
| runtimeHash.update('\0'); | ||
| } | ||
| const runtimeSha256 = runtimeHash.digest('hex'); | ||
| const configuredCommit = String(releaseCommit || '').trim(); | ||
| const releaseCommitSha = /^[0-9a-f]{7,64}$/i.test(configuredCommit) | ||
| ? configuredCommit.toLowerCase() | ||
| : null; | ||
| const releaseSha256 = createHash('sha256') | ||
| .update([String(version || ''), String(siIconSetSha256 || ''), runtimeSha256].join('\n')) | ||
| .digest('hex'); | ||
| return { | ||
| release_commit_sha: releaseCommitSha, | ||
| runtime_sha256: runtimeSha256, | ||
| release_sha256: releaseSha256, | ||
| }; | ||
| } |
+14
-0
| # Changelog | ||
| ## 0.4.31 - 2026-08-10 | ||
| ### Fixed | ||
| - made exact SI DNA synonyms and semantic tags searchable across local and hosted routes | ||
| - kept exact SI identity after hosted result fusion, including XAI and SpaceXAI aliases | ||
| - linked website, hosted catalog, Railway runtime, and npm release checks | ||
| ### Verified | ||
| - added full-library identity and alternate DNA search coverage | ||
| - added exact hosted SI catalog and content-bearing runtime fingerprints | ||
| - added a bounded release runner with rollback for Supabase, Railway, Netlify, and npm latest | ||
| ## 0.4.30 - 2026-08-10 | ||
@@ -4,0 +18,0 @@ |
+4
-2
| { | ||
| "name": "@supericons/mcp", | ||
| "version": "0.4.30", | ||
| "version": "0.4.31", | ||
| "mcpName": "io.github.curlymolelabs/supericons", | ||
@@ -37,2 +37,3 @@ "description": "MCP server for Supericons: multilingual semantic SVG icon search and recommendations for AI coding agents.", | ||
| "hosted-search-resilience.js", | ||
| "hosted-si-catalog-status.js", | ||
| "index.js", | ||
@@ -62,2 +63,3 @@ "library-capabilities.js", | ||
| "search-query-normalization.js", | ||
| "search-release-fingerprint.js", | ||
| "search-tool-shell.js", | ||
@@ -96,3 +98,3 @@ "server.json", | ||
| "verify:search-v2-shell": "node ../scripts/verify-search-v2-one-call-contract.mjs --package-root . && node ../scripts/verify-mcp-agent-friendly-errors.mjs && node ../scripts/verify-search-v2-packaged-query-frame.mjs --package-root . && node ../scripts/verify-search-v2-429-propagation.mjs && node ../scripts/verify-recommend-icons-clarification.mjs", | ||
| "prepublishOnly": "npm run verify:si-search-surface-parity && npm run verify:public-safety && node ../scripts/verify-mcp-preview-icons-image.mjs && npm run verify:preview-exact-ref && npm run verify:synchronized-surfaces && npm run verify:route-package && npm run verify:search-v2-shell && npm run verify:package", | ||
| "prepublishOnly": "npm run verify:si-search-surface-parity && node ../scripts/verify-si-dna-search-coverage.mjs && node ../scripts/verify-si-search-release-guards.mjs && npm run verify:public-safety && node ../scripts/verify-mcp-preview-icons-image.mjs && npm run verify:preview-exact-ref && npm run verify:synchronized-surfaces && npm run verify:route-package && npm run verify:search-v2-shell && npm run verify:package", | ||
| "verify:package": "node ../scripts/verify-motion-lab-mcp-package.mjs", | ||
@@ -99,0 +101,0 @@ "verify:si-search-surface-parity": "node ../scripts/verify-si-search-surface-parity.mjs" |
| { | ||
| "generatedAt": "2026-08-09T15:12:04.283Z", | ||
| "freeIconCount": 21541, | ||
| "generatedAt": "2026-08-09T20:16:05.277Z", | ||
| "freeIconCount": 21542, | ||
| "freeLibraryCount": 11, | ||
@@ -9,3 +9,3 @@ "premiumCollectionCount": 9, | ||
| "mcpFreeToolCount": 5, | ||
| "mcpPackageVersion": "0.4.30", | ||
| "mcpPackageVersion": "0.4.31", | ||
| "display": { | ||
@@ -12,0 +12,0 @@ "freeIconsRounded": "20,000+", |
@@ -449,3 +449,3 @@ /** | ||
| const { name, id, fullId, tokens, compactPrimaryValues } = getIconSearchMetadata(icon); | ||
| const { name, id, fullId, tokens, primaryTokens, compactPrimaryValues } = getIconSearchMetadata(icon); | ||
| const meaningfulQueryWords = getMeaningfulQueryWords(queryWords); | ||
@@ -463,2 +463,10 @@ | ||
| const hasBrandIdentityInflection = | ||
| isLikelyBrandIdentityIcon(icon) && | ||
| meaningfulQueryWords.length > 0 && | ||
| meaningfulQueryWords.every((word) => | ||
| [...primaryTokens].some((token) => token === word || isSafeInflectionalTokenMatch(word, token)), | ||
| ); | ||
| if (hasBrandIdentityInflection) return 280; | ||
| const singleQueryWord = queryWords.length === 1 ? queryWords[0] : null; | ||
@@ -950,2 +958,36 @@ const hasLongTokenPrefix = | ||
| function getExactMetadataValueAnchors(query, icons, synonyms, options = {}) { | ||
| const normalizedQuery = normalizeSemanticText(query); | ||
| if (!normalizedQuery) return []; | ||
| const library = String(options.library || '').toLowerCase(); | ||
| const libraryMode = normalizeSearchLibraryMode(options.libraryMode); | ||
| const normalizedStyle = normalizeRequestedStyle(options.style || 'any'); | ||
| const withoutLogoIntent = tokenizeSemanticText(query) | ||
| .filter((word) => !LOGO_INTENT_TOKENS.has(word)) | ||
| .join(' '); | ||
| const exactQueries = new Set([normalizedQuery, withoutLogoIntent].filter(Boolean)); | ||
| return getIndexedCandidatePool(icons, query, synonyms) | ||
| .filter((icon) => { | ||
| if (icon.lib !== 'si') return false; | ||
| if (library && libraryMode === 'strict' && icon.lib !== library) return false; | ||
| if (!iconMatchesRequestedStyle(icon, normalizedStyle)) return false; | ||
| const values = [ | ||
| ...(icon.semanticTags || []), | ||
| ...(icon.synonyms || []), | ||
| ...(icon.aliases || []), | ||
| ...(icon.searchTerms || []), | ||
| ]; | ||
| return values.some((value) => exactQueries.has(normalizeSemanticText(value))); | ||
| }) | ||
| .sort((a, b) => { | ||
| const aBrand = getBrandRankAdjustment(query, a); | ||
| const bBrand = getBrandRankAdjustment(query, b); | ||
| if (bBrand.boost !== aBrand.boost) return bBrand.boost - aBrand.boost; | ||
| if (aBrand.penalty !== bBrand.penalty) return aBrand.penalty - bBrand.penalty; | ||
| const rankDiff = getIconJobRank(a) - getIconJobRank(b); | ||
| return rankDiff || a.name.localeCompare(b.name); | ||
| }); | ||
| } | ||
| export function prioritizeExactIconMatches(query, icons, synonyms, results = [], options = {}) { | ||
@@ -962,2 +1004,3 @@ const requestedLibrary = String(options.library || '').toLowerCase(); | ||
| const anchors = getExactIconIdentityAnchors(query, icons, synonyms, options); | ||
| const metadataAnchors = getExactMetadataValueAnchors(query, icons, synonyms, options); | ||
| const exact = []; | ||
@@ -980,3 +1023,3 @@ const remaining = []; | ||
| const seen = new Set(); | ||
| const merged = [...anchors, ...exact, ...remaining].filter((icon) => { | ||
| const merged = [...anchors, ...exact, ...metadataAnchors, ...remaining].filter((icon) => { | ||
| const key = iconKey(icon); | ||
@@ -983,0 +1026,0 @@ if (seen.has(key)) return false; |
@@ -62,2 +62,18 @@ import { GENERATED_SEARCH_RANKING_POLICY } from './generated-search-ranking-policy.js'; | ||
| function isSafeBrandIdentityInflection(query, identity) { | ||
| const queryTokens = tokenize(query); | ||
| const identityTokens = tokenize(identity); | ||
| if (queryTokens.length === 0 || queryTokens.length !== identityTokens.length) return false; | ||
| let changed = false; | ||
| const matches = queryTokens.every((queryToken, index) => { | ||
| const identityToken = identityTokens[index]; | ||
| if (queryToken === identityToken) return true; | ||
| if (identityToken.length < 3) return false; | ||
| const inflected = queryToken === `${identityToken}s` || queryToken === `${identityToken}es`; | ||
| if (inflected) changed = true; | ||
| return inflected; | ||
| }); | ||
| return matches && changed; | ||
| } | ||
| function unique(values = []) { | ||
@@ -304,3 +320,10 @@ return [...new Set(values.filter(Boolean))]; | ||
| } | ||
| if (identity && (identity === meaningfulQuery || compactIdentity === compactQuery)) { | ||
| if ( | ||
| identity | ||
| && ( | ||
| identity === meaningfulQuery | ||
| || compactIdentity === compactQuery | ||
| || isSafeBrandIdentityInflection(meaningfulQuery, identity) | ||
| ) | ||
| ) { | ||
| return { boost: 160, penalty: 0, match_class: 'distinctive_exact' }; | ||
@@ -307,0 +330,0 @@ } |
+2
-2
@@ -10,3 +10,3 @@ { | ||
| }, | ||
| "version": "0.4.30", | ||
| "version": "0.4.31", | ||
| "remotes": [ | ||
@@ -22,3 +22,3 @@ { | ||
| "identifier": "@supericons/mcp", | ||
| "version": "0.4.30", | ||
| "version": "0.4.31", | ||
| "transport": { | ||
@@ -25,0 +25,0 @@ "type": "stdio" |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
26262810
0.05%73
2.82%123280
0.2%71
4.41%14
7.69%