@public-ui/mcp 3.0.7-rc.3 → 4.0.0-alpha.8
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/README.md +339 -140
- package/dist/cli.cjs +37 -42
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +36 -42
- package/dist/data.cjs +88 -0
- package/dist/data.d.cts +34 -0
- package/dist/data.d.mts +34 -0
- package/dist/data.d.ts +34 -0
- package/dist/data.mjs +83 -0
- package/dist/mcp.cjs +298 -0
- package/dist/mcp.d.cts +9 -0
- package/dist/mcp.d.mts +9 -0
- package/dist/mcp.d.ts +9 -0
- package/dist/mcp.mjs +291 -0
- package/dist/search.cjs +52 -0
- package/dist/search.d.cts +16 -0
- package/dist/search.d.mts +16 -0
- package/dist/search.d.ts +16 -0
- package/dist/search.mjs +46 -0
- package/package.json +48 -6
- package/shared/.gitkeep +1 -0
- package/shared/sample-index.json +1860 -0
- package/dist/api-handler.cjs +0 -269
- package/dist/api-handler.mjs +0 -265
- package/dist/chunks/sample-index-runtime.cjs +0 -327
- package/dist/chunks/sample-index-runtime.mjs +0 -320
- package/dist/index.cjs +0 -71
- package/dist/index.mjs +0 -66
- package/dist/sample-index.cjs +0 -104
- package/dist/sample-index.mjs +0 -96
- package/dist/samples.json +0 -1410
- package/dist/samples.mjs +0 -1412
package/dist/mcp.mjs
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { getSampleIndexMetadata, getAllEntries, getEntryById } from './data.mjs';
|
|
7
|
+
import { searchEntries } from './search.mjs';
|
|
8
|
+
import 'node:fs';
|
|
9
|
+
import 'node:url';
|
|
10
|
+
import 'fuse.js';
|
|
11
|
+
|
|
12
|
+
const KIND_OPTIONS = ["doc", "sample", "scenario", "spec"];
|
|
13
|
+
function isValidKind(value) {
|
|
14
|
+
return typeof value === "string" && KIND_OPTIONS.includes(value);
|
|
15
|
+
}
|
|
16
|
+
function normalizeTags(tags) {
|
|
17
|
+
return Array.isArray(tags) ? tags : [];
|
|
18
|
+
}
|
|
19
|
+
function formatTagsForText(tags) {
|
|
20
|
+
const normalized = normalizeTags(tags);
|
|
21
|
+
return normalized.length > 0 ? normalized.join(", ") : "none";
|
|
22
|
+
}
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
const {
|
|
25
|
+
version: PACKAGE_VERSION = "0.0.0",
|
|
26
|
+
name: PACKAGE_NAME = "@public-ui/mcp",
|
|
27
|
+
description: PACKAGE_DESCRIPTION
|
|
28
|
+
} = require("../package.json");
|
|
29
|
+
const ENABLE_LOGGING = process.env.MCP_LOGGING === "true" || process.env.MCP_LOGGING === "1";
|
|
30
|
+
function log(type, message, data) {
|
|
31
|
+
if (!ENABLE_LOGGING) return;
|
|
32
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
33
|
+
const prefix = `[${timestamp}] [${type.toUpperCase()}]`;
|
|
34
|
+
if (data) {
|
|
35
|
+
console.error(`${prefix} ${message}`, JSON.stringify(data, null, 2));
|
|
36
|
+
} else {
|
|
37
|
+
console.error(`${prefix} ${message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function createKolibriMcpServer() {
|
|
41
|
+
const server = new McpServer({
|
|
42
|
+
name: PACKAGE_NAME,
|
|
43
|
+
version: PACKAGE_VERSION
|
|
44
|
+
});
|
|
45
|
+
return configureServer(server);
|
|
46
|
+
}
|
|
47
|
+
function configureServer(server) {
|
|
48
|
+
server.registerTool(
|
|
49
|
+
"search",
|
|
50
|
+
{
|
|
51
|
+
title: "Search KoliBri Samples and Docs",
|
|
52
|
+
description: 'Search for KoliBri component samples, scenarios, specifications, and documentation using fuzzy search. Parameters: query (optional string), kind (optional select: "doc", "sample", "scenario", or "spec"), limit (optional number, default 10).',
|
|
53
|
+
inputSchema: {
|
|
54
|
+
query: z.string().optional().default(""),
|
|
55
|
+
kind: z.enum(KIND_OPTIONS).optional(),
|
|
56
|
+
limit: z.number().optional()
|
|
57
|
+
},
|
|
58
|
+
outputSchema: {
|
|
59
|
+
query: z.string(),
|
|
60
|
+
totalResults: z.number()
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
({ query, kind, limit }) => {
|
|
64
|
+
log("tool", "search called", { query, kind, limit });
|
|
65
|
+
const queryStr = typeof query === "string" ? query : "";
|
|
66
|
+
const allEntries = getAllEntries();
|
|
67
|
+
const searchOptions = {
|
|
68
|
+
limit: typeof limit === "number" ? limit : 10
|
|
69
|
+
};
|
|
70
|
+
if (isValidKind(kind)) {
|
|
71
|
+
searchOptions.kind = kind;
|
|
72
|
+
}
|
|
73
|
+
const results = searchEntries(allEntries, queryStr, searchOptions);
|
|
74
|
+
log("tool", "search completed", {
|
|
75
|
+
query: queryStr,
|
|
76
|
+
resultCount: results.length,
|
|
77
|
+
options: searchOptions
|
|
78
|
+
});
|
|
79
|
+
const structuredContent = {
|
|
80
|
+
query: queryStr,
|
|
81
|
+
totalResults: results.length,
|
|
82
|
+
results: results.map(({ item, score }) => ({
|
|
83
|
+
id: item.id,
|
|
84
|
+
kind: item.kind,
|
|
85
|
+
name: item.name,
|
|
86
|
+
group: item.group ?? "N/A",
|
|
87
|
+
description: item.description ?? "N/A",
|
|
88
|
+
tags: normalizeTags(item.tags),
|
|
89
|
+
score: score ?? 1,
|
|
90
|
+
path: item.path ?? "N/A"
|
|
91
|
+
}))
|
|
92
|
+
};
|
|
93
|
+
const resultText = results.length ? results.map(({ item, score }, index) => {
|
|
94
|
+
const matchScore = ((score ?? 1) * 100).toFixed(1);
|
|
95
|
+
const pathLine = item.path ? `
|
|
96
|
+
Path: ${item.path}` : "";
|
|
97
|
+
return `${index + 1}. [${item.kind}] ${item.id}: ${item.name}
|
|
98
|
+
Description: ${item.description ?? "N/A"}
|
|
99
|
+
Match score: ${matchScore}%
|
|
100
|
+
Tags: ${formatTagsForText(item.tags)}${pathLine}`;
|
|
101
|
+
}).join("\n\n") : "No matches found.";
|
|
102
|
+
const tipText = results.length ? "\n\n\u{1F4A1} Tip: Use 'fetch' with any ID above to see full entry details." : "";
|
|
103
|
+
return {
|
|
104
|
+
content: [
|
|
105
|
+
{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: `Found ${results.length} result(s) for "${queryStr}":
|
|
108
|
+
|
|
109
|
+
${resultText}${tipText}`
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
structuredContent
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
server.registerTool(
|
|
117
|
+
"fetch",
|
|
118
|
+
{
|
|
119
|
+
title: "Get Sample or Doc Entry",
|
|
120
|
+
description: 'Get a specific sample, specification, or documentation entry by its ID. Parameter: id (required string, e.g. "button/basic", "spec/button", or "docs/getting-started")',
|
|
121
|
+
inputSchema: {
|
|
122
|
+
id: z.string()
|
|
123
|
+
},
|
|
124
|
+
outputSchema: {
|
|
125
|
+
id: z.string(),
|
|
126
|
+
kind: z.string(),
|
|
127
|
+
name: z.string()
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
({ id }) => {
|
|
131
|
+
log("tool", "fetch called", { id });
|
|
132
|
+
const idStr = String(id ?? "");
|
|
133
|
+
if (!idStr) {
|
|
134
|
+
log("error", "fetch failed: empty id");
|
|
135
|
+
throw new Error("ID parameter is required");
|
|
136
|
+
}
|
|
137
|
+
const entry = getEntryById(idStr);
|
|
138
|
+
if (!entry) {
|
|
139
|
+
log("error", "fetch failed: entry not found", { id: idStr });
|
|
140
|
+
throw new Error(`Entry with ID "${idStr}" not found`);
|
|
141
|
+
}
|
|
142
|
+
log("tool", "fetch completed", { id: idStr, kind: entry.kind });
|
|
143
|
+
const output = {
|
|
144
|
+
id: entry.id,
|
|
145
|
+
kind: entry.kind,
|
|
146
|
+
name: entry.name,
|
|
147
|
+
group: entry.group ?? "N/A",
|
|
148
|
+
description: entry.description ?? "N/A",
|
|
149
|
+
tags: entry.tags ?? [],
|
|
150
|
+
code: entry.code ?? "No code available",
|
|
151
|
+
path: entry.path ?? "N/A"
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: "text",
|
|
157
|
+
text: `# ${entry.name}
|
|
158
|
+
|
|
159
|
+
ID: ${entry.id}
|
|
160
|
+
Kind: ${entry.kind}
|
|
161
|
+
Group: ${entry.group ?? "N/A"}
|
|
162
|
+
Description: ${entry.description ?? "N/A"}
|
|
163
|
+
Tags: ${entry.tags?.join(", ") ?? "none"}
|
|
164
|
+
|
|
165
|
+
## Code
|
|
166
|
+
|
|
167
|
+
\`\`\`
|
|
168
|
+
${entry.code ?? "No code available"}
|
|
169
|
+
\`\`\``
|
|
170
|
+
}
|
|
171
|
+
],
|
|
172
|
+
structuredContent: output
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
server.registerResource(
|
|
177
|
+
"info",
|
|
178
|
+
new ResourceTemplate("kolibri://info", { list: void 0 }),
|
|
179
|
+
{
|
|
180
|
+
title: "KoliBri MCP Server Info",
|
|
181
|
+
description: "Get information about the KoliBri MCP Server and available samples"
|
|
182
|
+
},
|
|
183
|
+
(uri) => {
|
|
184
|
+
log("resource", "info accessed", { uri: uri.href });
|
|
185
|
+
const metadata = getSampleIndexMetadata();
|
|
186
|
+
const infoText = `# KoliBri MCP Server v${PACKAGE_VERSION}
|
|
187
|
+
|
|
188
|
+
${PACKAGE_DESCRIPTION ?? ""}
|
|
189
|
+
|
|
190
|
+
## Sample Index
|
|
191
|
+
- Generated: ${metadata.generatedAt ?? "unknown"}
|
|
192
|
+
- Build mode: ${metadata.buildMode}
|
|
193
|
+
- Total entries: ${metadata.counts.total}
|
|
194
|
+
- Documentation: ${metadata.counts.totalDocs}
|
|
195
|
+
- Specifications: ${metadata.counts.totalSpecs ?? 0}
|
|
196
|
+
- Samples: ${metadata.counts.totalSamples}
|
|
197
|
+
- Scenarios: ${metadata.counts.totalScenarios ?? 0}
|
|
198
|
+
|
|
199
|
+
## Repository
|
|
200
|
+
- Branch: ${metadata.repo.branch ?? "N/A"}
|
|
201
|
+
- Commit: ${metadata.repo.commit ?? "N/A"}
|
|
202
|
+
- URL: ${metadata.repo.repoUrl ?? "N/A"}
|
|
203
|
+
`;
|
|
204
|
+
return {
|
|
205
|
+
contents: [
|
|
206
|
+
{
|
|
207
|
+
uri: uri.href,
|
|
208
|
+
mimeType: "text/markdown",
|
|
209
|
+
text: infoText
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
server.registerResource(
|
|
216
|
+
"best-practices",
|
|
217
|
+
new ResourceTemplate("kolibri://best-practices", { list: void 0 }),
|
|
218
|
+
{
|
|
219
|
+
title: "KoliBri Best Practices",
|
|
220
|
+
description: "Essential guidelines for working with KoliBri Web Components"
|
|
221
|
+
},
|
|
222
|
+
(uri) => {
|
|
223
|
+
log("resource", "best-practices accessed", { uri: uri.href });
|
|
224
|
+
const practicesText = `# KoliBri Web Components - Best Practices
|
|
225
|
+
|
|
226
|
+
## Essential Guidelines
|
|
227
|
+
|
|
228
|
+
1. **Component Registration**
|
|
229
|
+
Always register KoliBri Web Components in the browser runtime before rendering them.
|
|
230
|
+
|
|
231
|
+
2. **Integration Setup**
|
|
232
|
+
Choose the integration guide that matches your project setup to load and bundle the components correctly.
|
|
233
|
+
|
|
234
|
+
3. **Icon Font Assets**
|
|
235
|
+
Bundle the KoliBri icon font assets (for example codicon.css and codicon.ttf) so kol-icon glyphs can render.
|
|
236
|
+
|
|
237
|
+
4. **Form Validation**
|
|
238
|
+
Wrap input elements with <kol-form> and feed its _errorList to surface validation issues via the generated error summary.
|
|
239
|
+
|
|
240
|
+
## Additional Resources
|
|
241
|
+
|
|
242
|
+
Use the 'search' tool to find specific component examples and implementation details.
|
|
243
|
+
Use the 'fetch' tool to retrieve full code samples for specific components.
|
|
244
|
+
`;
|
|
245
|
+
return {
|
|
246
|
+
contents: [
|
|
247
|
+
{
|
|
248
|
+
uri: uri.href,
|
|
249
|
+
mimeType: "text/markdown",
|
|
250
|
+
text: practicesText
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
);
|
|
256
|
+
return server;
|
|
257
|
+
}
|
|
258
|
+
if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("/mcp.ts") || process.argv[1]?.endsWith("/mcp.cjs") || process.argv[1]?.endsWith("/mcp.mjs")) {
|
|
259
|
+
const server = createKolibriMcpServer();
|
|
260
|
+
const app = express();
|
|
261
|
+
app.use(express.json());
|
|
262
|
+
app.post("/mcp", async (req, res) => {
|
|
263
|
+
const transport = new StreamableHTTPServerTransport({
|
|
264
|
+
sessionIdGenerator: void 0,
|
|
265
|
+
enableJsonResponse: true
|
|
266
|
+
});
|
|
267
|
+
res.on("close", () => {
|
|
268
|
+
void transport.close();
|
|
269
|
+
});
|
|
270
|
+
await server.connect(transport);
|
|
271
|
+
await transport.handleRequest(req, res, req.body);
|
|
272
|
+
});
|
|
273
|
+
const port = parseInt(process.env.PORT || "3000");
|
|
274
|
+
void app.listen(port, () => {
|
|
275
|
+
console.log(`KoliBri MCP Server v${PACKAGE_VERSION} running on http://localhost:${port}/mcp`);
|
|
276
|
+
const metadata = getSampleIndexMetadata();
|
|
277
|
+
console.log(
|
|
278
|
+
`Loaded ${metadata.counts.total} entries (${metadata.counts.totalDocs} docs, ${metadata.counts.totalSamples} samples, ${metadata.counts.totalScenarios ?? 0} scenarios)`
|
|
279
|
+
);
|
|
280
|
+
if (ENABLE_LOGGING) {
|
|
281
|
+
console.log("\u{1F50D} Logging is ENABLED (MCP_LOGGING=true)");
|
|
282
|
+
} else {
|
|
283
|
+
console.log("\u{1F4A1} Logging is disabled. Set MCP_LOGGING=true to enable request logging");
|
|
284
|
+
}
|
|
285
|
+
}).on("error", (error) => {
|
|
286
|
+
console.error("Server error:", error);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export { createKolibriMcpServer };
|
package/dist/search.cjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Fuse = require('fuse.js');
|
|
4
|
+
|
|
5
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
6
|
+
|
|
7
|
+
const Fuse__default = /*#__PURE__*/_interopDefaultCompat(Fuse);
|
|
8
|
+
|
|
9
|
+
const FUSE_OPTIONS = {
|
|
10
|
+
includeScore: true,
|
|
11
|
+
shouldSort: true,
|
|
12
|
+
threshold: 0.4,
|
|
13
|
+
// Default threshold for single-word queries
|
|
14
|
+
minMatchCharLength: 2,
|
|
15
|
+
// useExtendedSearch is set dynamically based on query type
|
|
16
|
+
keys: [
|
|
17
|
+
{ name: "id", weight: 0.2 },
|
|
18
|
+
{ name: "name", weight: 0.2 },
|
|
19
|
+
{ name: "group", weight: 0.15 },
|
|
20
|
+
{ name: "description", weight: 0.1 },
|
|
21
|
+
{ name: "tags", weight: 0.05 }
|
|
22
|
+
]
|
|
23
|
+
};
|
|
24
|
+
function searchEntries(entries, query, options = {}) {
|
|
25
|
+
const normalizedQuery = query.trim();
|
|
26
|
+
const filteredEntries = options.kind ? entries.filter((e) => e.kind === options.kind) : entries;
|
|
27
|
+
if (!normalizedQuery) {
|
|
28
|
+
return filteredEntries.map((item) => ({ item, score: 1 }));
|
|
29
|
+
}
|
|
30
|
+
const words = normalizedQuery.split(/\s+/).filter((w) => w.length > 0);
|
|
31
|
+
const isMultiWord = words.length > 1;
|
|
32
|
+
const fuseOptions = {
|
|
33
|
+
...FUSE_OPTIONS,
|
|
34
|
+
// Use higher threshold for multi-word queries to find more results
|
|
35
|
+
threshold: options.threshold ?? (isMultiWord ? 0.6 : FUSE_OPTIONS.threshold),
|
|
36
|
+
// Only use extended search for multi-word queries
|
|
37
|
+
useExtendedSearch: isMultiWord
|
|
38
|
+
};
|
|
39
|
+
const fuse = new Fuse__default(filteredEntries, fuseOptions);
|
|
40
|
+
const searchQuery = isMultiWord ? words.map((w) => `'${w}`).join(" | ") : normalizedQuery;
|
|
41
|
+
const results = fuse.search(searchQuery);
|
|
42
|
+
const searchResults = results.map((result) => ({
|
|
43
|
+
item: result.item,
|
|
44
|
+
score: result.score ?? 1
|
|
45
|
+
}));
|
|
46
|
+
if (options.limit && options.limit > 0) {
|
|
47
|
+
return searchResults.slice(0, options.limit);
|
|
48
|
+
}
|
|
49
|
+
return searchResults;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
exports.searchEntries = searchEntries;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SampleEntry } from './data.cjs';
|
|
2
|
+
|
|
3
|
+
type EntryKind = SampleEntry['kind'];
|
|
4
|
+
interface SearchOptions {
|
|
5
|
+
threshold?: number;
|
|
6
|
+
limit?: number;
|
|
7
|
+
kind?: EntryKind;
|
|
8
|
+
}
|
|
9
|
+
interface SearchResult {
|
|
10
|
+
item: SampleEntry;
|
|
11
|
+
score: number;
|
|
12
|
+
}
|
|
13
|
+
declare function searchEntries(entries: SampleEntry[], query: string, options?: SearchOptions): SearchResult[];
|
|
14
|
+
|
|
15
|
+
export { searchEntries };
|
|
16
|
+
export type { SearchOptions };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SampleEntry } from './data.mjs';
|
|
2
|
+
|
|
3
|
+
type EntryKind = SampleEntry['kind'];
|
|
4
|
+
interface SearchOptions {
|
|
5
|
+
threshold?: number;
|
|
6
|
+
limit?: number;
|
|
7
|
+
kind?: EntryKind;
|
|
8
|
+
}
|
|
9
|
+
interface SearchResult {
|
|
10
|
+
item: SampleEntry;
|
|
11
|
+
score: number;
|
|
12
|
+
}
|
|
13
|
+
declare function searchEntries(entries: SampleEntry[], query: string, options?: SearchOptions): SearchResult[];
|
|
14
|
+
|
|
15
|
+
export { searchEntries };
|
|
16
|
+
export type { SearchOptions };
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SampleEntry } from './data.js';
|
|
2
|
+
|
|
3
|
+
type EntryKind = SampleEntry['kind'];
|
|
4
|
+
interface SearchOptions {
|
|
5
|
+
threshold?: number;
|
|
6
|
+
limit?: number;
|
|
7
|
+
kind?: EntryKind;
|
|
8
|
+
}
|
|
9
|
+
interface SearchResult {
|
|
10
|
+
item: SampleEntry;
|
|
11
|
+
score: number;
|
|
12
|
+
}
|
|
13
|
+
declare function searchEntries(entries: SampleEntry[], query: string, options?: SearchOptions): SearchResult[];
|
|
14
|
+
|
|
15
|
+
export { searchEntries };
|
|
16
|
+
export type { SearchOptions };
|
package/dist/search.mjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import Fuse from 'fuse.js';
|
|
2
|
+
|
|
3
|
+
const FUSE_OPTIONS = {
|
|
4
|
+
includeScore: true,
|
|
5
|
+
shouldSort: true,
|
|
6
|
+
threshold: 0.4,
|
|
7
|
+
// Default threshold for single-word queries
|
|
8
|
+
minMatchCharLength: 2,
|
|
9
|
+
// useExtendedSearch is set dynamically based on query type
|
|
10
|
+
keys: [
|
|
11
|
+
{ name: "id", weight: 0.2 },
|
|
12
|
+
{ name: "name", weight: 0.2 },
|
|
13
|
+
{ name: "group", weight: 0.15 },
|
|
14
|
+
{ name: "description", weight: 0.1 },
|
|
15
|
+
{ name: "tags", weight: 0.05 }
|
|
16
|
+
]
|
|
17
|
+
};
|
|
18
|
+
function searchEntries(entries, query, options = {}) {
|
|
19
|
+
const normalizedQuery = query.trim();
|
|
20
|
+
const filteredEntries = options.kind ? entries.filter((e) => e.kind === options.kind) : entries;
|
|
21
|
+
if (!normalizedQuery) {
|
|
22
|
+
return filteredEntries.map((item) => ({ item, score: 1 }));
|
|
23
|
+
}
|
|
24
|
+
const words = normalizedQuery.split(/\s+/).filter((w) => w.length > 0);
|
|
25
|
+
const isMultiWord = words.length > 1;
|
|
26
|
+
const fuseOptions = {
|
|
27
|
+
...FUSE_OPTIONS,
|
|
28
|
+
// Use higher threshold for multi-word queries to find more results
|
|
29
|
+
threshold: options.threshold ?? (isMultiWord ? 0.6 : FUSE_OPTIONS.threshold),
|
|
30
|
+
// Only use extended search for multi-word queries
|
|
31
|
+
useExtendedSearch: isMultiWord
|
|
32
|
+
};
|
|
33
|
+
const fuse = new Fuse(filteredEntries, fuseOptions);
|
|
34
|
+
const searchQuery = isMultiWord ? words.map((w) => `'${w}`).join(" | ") : normalizedQuery;
|
|
35
|
+
const results = fuse.search(searchQuery);
|
|
36
|
+
const searchResults = results.map((result) => ({
|
|
37
|
+
item: result.item,
|
|
38
|
+
score: result.score ?? 1
|
|
39
|
+
}));
|
|
40
|
+
if (options.limit && options.limit > 0) {
|
|
41
|
+
return searchResults.slice(0, options.limit);
|
|
42
|
+
}
|
|
43
|
+
return searchResults;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { searchEntries };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@public-ui/mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-alpha.8",
|
|
4
4
|
"license": "EUPL-1.2",
|
|
5
5
|
"homepage": "https://public-ui.github.io",
|
|
6
6
|
"repository": {
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"main": "dist/index.cjs",
|
|
33
33
|
"module": "dist/index.mjs",
|
|
34
34
|
"bin": {
|
|
35
|
-
"
|
|
35
|
+
"@public-ui/mcp": "dist/cli.cjs",
|
|
36
|
+
"kolibri-mcp": "dist/cli.cjs"
|
|
36
37
|
},
|
|
37
38
|
"exports": {
|
|
38
39
|
".": {
|
|
@@ -40,18 +41,59 @@
|
|
|
40
41
|
"require": "./dist/index.cjs"
|
|
41
42
|
}
|
|
42
43
|
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@modelcontextprotocol/sdk": "1.24.3",
|
|
46
|
+
"express": "5.2.1",
|
|
47
|
+
"fuse.js": "7.1.0",
|
|
48
|
+
"zod": "3.25.76",
|
|
49
|
+
"@public-ui/components": "4.0.0-alpha.8"
|
|
50
|
+
},
|
|
43
51
|
"devDependencies": {
|
|
44
|
-
"
|
|
52
|
+
"@modelcontextprotocol/inspector": "0.17.5",
|
|
53
|
+
"@types/express": "5.0.6",
|
|
54
|
+
"@types/node": "24.10.1",
|
|
55
|
+
"@typescript-eslint/eslint-plugin": "7.18.0",
|
|
56
|
+
"@typescript-eslint/parser": "7.18.0",
|
|
57
|
+
"eslint": "8.57.1",
|
|
58
|
+
"eslint-config-prettier": "9.1.2",
|
|
59
|
+
"eslint-plugin-html": "8.1.3",
|
|
60
|
+
"eslint-plugin-jsdoc": "50.8.0",
|
|
61
|
+
"eslint-plugin-json": "3.1.0",
|
|
62
|
+
"knip": "5.71.0",
|
|
63
|
+
"nodemon": "3.1.11",
|
|
64
|
+
"prettier": "3.7.4",
|
|
65
|
+
"prettier-plugin-organize-imports": "4.3.0",
|
|
66
|
+
"tsx": "4.21.0",
|
|
67
|
+
"typescript": "5.9.3",
|
|
45
68
|
"unbuild": "3.6.1"
|
|
46
69
|
},
|
|
47
70
|
"files": [
|
|
48
71
|
"dist/",
|
|
72
|
+
"shared/",
|
|
49
73
|
"README.md"
|
|
50
74
|
],
|
|
51
75
|
"scripts": {
|
|
52
|
-
"build": "
|
|
53
|
-
"
|
|
76
|
+
"build:deps": "pnpm --filter @public-ui/mcp^... build",
|
|
77
|
+
"generate-index": "node ./scripts/generate-sample-index.mjs",
|
|
78
|
+
"prebuild": "pnpm generate-index",
|
|
79
|
+
"build": "unbuild",
|
|
80
|
+
"predev": "pnpm generate-index",
|
|
81
|
+
"dev": "nodemon",
|
|
82
|
+
"format": "prettier -c src test public",
|
|
83
|
+
"lint": "pnpm lint:eslint && pnpm lint:tsc",
|
|
84
|
+
"lint:eslint": "eslint src test 'public/**/*.html'",
|
|
85
|
+
"lint:tsc": "tsc --noemit",
|
|
86
|
+
"preinspect": "pnpm generate-index",
|
|
87
|
+
"inspect": "mcp-inspector tsx src/cli.ts",
|
|
54
88
|
"prestart": "pnpm build",
|
|
55
|
-
"start": "node dist/
|
|
89
|
+
"start": "node dist/mcp.cjs",
|
|
90
|
+
"start:stdio": "node dist/cli.cjs",
|
|
91
|
+
"pretest": "pnpm build",
|
|
92
|
+
"test": "node --test test/*.test.js",
|
|
93
|
+
"test:api": "node test-vercel-api.mjs",
|
|
94
|
+
"unused": "knip",
|
|
95
|
+
"vercel:dev": "vercel dev",
|
|
96
|
+
"vercel:deploy": "vercel",
|
|
97
|
+
"vercel:deploy:prod": "vercel --prod"
|
|
56
98
|
}
|
|
57
99
|
}
|
package/shared/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Keep this directory in git, but exclude generated JSON files
|