@expcat/tigercat-mcp 2.0.4 → 2.0.19
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/dist/index.d.ts +30 -3
- package/dist/index.js +477 -162
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -2,12 +2,30 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
2
2
|
|
|
3
3
|
type TigercatFramework = 'react' | 'vue';
|
|
4
4
|
interface TigercatMcpOptions {
|
|
5
|
+
/** 本地 Tigercat 仓库根目录;提供时优先于 baseUrl,走文件系统读取。 */
|
|
5
6
|
root?: string;
|
|
7
|
+
/** 远程 skills 基地址(GitHub Pages /mcp/ 路由或其镜像);缺省用官方地址。 */
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
/** 远程 fetch 超时毫秒数。 */
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
type LoadSkillIndexOptions = TigercatMcpOptions;
|
|
13
|
+
interface SkillSource {
|
|
14
|
+
readonly kind: 'fs' | 'http';
|
|
15
|
+
/** fs: 绝对仓库根目录;http: 规范化(以 / 结尾)的 base URL。 */
|
|
16
|
+
readonly origin: string;
|
|
17
|
+
/** 读取 origin 下 repo-relative 路径的 UTF-8 文本;path 需已经 normalizeRelativePath。 */
|
|
18
|
+
readText(path: string): Promise<string>;
|
|
19
|
+
/** doctor 可读性探测,不可读时抛错。 */
|
|
20
|
+
probe(path: string): Promise<void>;
|
|
6
21
|
}
|
|
7
22
|
interface ReferenceSource {
|
|
8
23
|
path: string;
|
|
9
24
|
reason: string;
|
|
10
25
|
truncated: boolean;
|
|
26
|
+
/** 抽取的 `## <section>` 小节名;整文件读取时缺省。 */
|
|
27
|
+
section?: string;
|
|
28
|
+
/** 内联正文;指针型 source(建议每会话经 tigercat_reference 另读一次)不带 text。 */
|
|
11
29
|
text?: string;
|
|
12
30
|
}
|
|
13
31
|
interface ComponentReferences {
|
|
@@ -48,6 +66,8 @@ interface TigercatContext7 {
|
|
|
48
66
|
generated_by?: string;
|
|
49
67
|
component_count?: number;
|
|
50
68
|
reference_paths?: Record<string, unknown>;
|
|
69
|
+
/** 全部 skill markdown 的仓库相对路径清单(远程模式的 allow-list 契约)。 */
|
|
70
|
+
skill_files?: string[];
|
|
51
71
|
aliases?: Record<string, string | string[]>;
|
|
52
72
|
command_apis?: Record<string, CommandApiMetadata>;
|
|
53
73
|
topics?: Record<string, TopicMetadata>;
|
|
@@ -61,7 +81,9 @@ interface TigercatContext7 {
|
|
|
61
81
|
}>;
|
|
62
82
|
}
|
|
63
83
|
interface SkillIndex {
|
|
84
|
+
/** fs: 绝对仓库根目录;http: base URL。与 source.origin 一致,兼容既有读者。 */
|
|
64
85
|
root: string;
|
|
86
|
+
source: SkillSource;
|
|
65
87
|
context7: TigercatContext7;
|
|
66
88
|
components: Map<string, ComponentMetadata>;
|
|
67
89
|
componentsByNormalizedName: Map<string, ComponentMetadata>;
|
|
@@ -124,17 +146,22 @@ interface InventorySummary {
|
|
|
124
146
|
interface DoctorResult {
|
|
125
147
|
ok: boolean;
|
|
126
148
|
root: string;
|
|
149
|
+
mode: 'fs' | 'http';
|
|
127
150
|
componentCount: number;
|
|
128
151
|
aliasCount: number;
|
|
129
152
|
topicCount: number;
|
|
130
153
|
readableReferenceCount: number;
|
|
154
|
+
/** http 模式下 best-effort 读取的远程 version.json 版本。 */
|
|
155
|
+
remoteVersion?: string;
|
|
131
156
|
issues: string[];
|
|
132
157
|
}
|
|
133
158
|
|
|
134
159
|
declare function createTigercatMcpServer(options?: TigercatMcpOptions): Server;
|
|
135
160
|
|
|
136
|
-
declare function loadSkillIndex(
|
|
137
|
-
declare function diagnoseTigercatMcp(
|
|
161
|
+
declare function loadSkillIndex(options?: string | LoadSkillIndexOptions): Promise<SkillIndex>;
|
|
162
|
+
declare function diagnoseTigercatMcp(options?: string | LoadSkillIndexOptions): Promise<DoctorResult>;
|
|
163
|
+
|
|
164
|
+
declare const DEFAULT_REMOTE_BASE_URL = "https://expcat.github.io/Tigercat/mcp/";
|
|
138
165
|
|
|
139
166
|
interface RouteTaskInput {
|
|
140
167
|
task: string;
|
|
@@ -158,4 +185,4 @@ declare function routeTigercatTask(index: SkillIndex, input: RouteTaskInput): Pr
|
|
|
158
185
|
declare function getInventory(index: SkillIndex): InventorySummary;
|
|
159
186
|
declare function getCategoryComponents(index: SkillIndex, slugOrCategory: string): ComponentMetadata[];
|
|
160
187
|
|
|
161
|
-
export { type ComponentLookupResult, type ComponentMetadata, type ComponentRoute, type DoctorResult, type InventorySummary, type ReferenceSource, type SearchResponse, type SearchResult, type SkillIndex, type TaskRouteResult, type TigercatFramework, type TigercatMcpOptions, createTigercatMcpServer, diagnoseTigercatMcp, getCategoryComponents, getInventory, getTigercatComponent, loadSkillIndex, routeTigercatTask, searchTigercat };
|
|
188
|
+
export { type ComponentLookupResult, type ComponentMetadata, type ComponentRoute, DEFAULT_REMOTE_BASE_URL, type DoctorResult, type InventorySummary, type LoadSkillIndexOptions, type ReferenceSource, type SearchResponse, type SearchResult, type SkillIndex, type SkillSource, type TaskRouteResult, type TigercatFramework, type TigercatMcpOptions, createTigercatMcpServer, diagnoseTigercatMcp, getCategoryComponents, getInventory, getTigercatComponent, loadSkillIndex, routeTigercatTask, searchTigercat };
|
package/dist/index.js
CHANGED
|
@@ -1,25 +1,178 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
-
import { realpathSync, existsSync } from 'fs';
|
|
4
|
-
import { resolve, join, relative, isAbsolute
|
|
3
|
+
import { readFileSync, realpathSync, existsSync } from 'fs';
|
|
4
|
+
import { resolve, join, relative, isAbsolute } from 'path';
|
|
5
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
6
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
7
|
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
-
import {
|
|
8
|
+
import { readFile, readdir, access, realpath } from 'fs/promises';
|
|
9
9
|
|
|
10
|
+
var PACKAGE_VERSION = (() => {
|
|
11
|
+
try {
|
|
12
|
+
const raw = readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
13
|
+
const version = JSON.parse(raw).version;
|
|
14
|
+
return typeof version === "string" && version ? version : "0.0.0";
|
|
15
|
+
} catch {
|
|
16
|
+
return "0.0.0";
|
|
17
|
+
}
|
|
18
|
+
})();
|
|
19
|
+
|
|
20
|
+
// src/source.ts
|
|
21
|
+
var DEFAULT_REMOTE_BASE_URL = "https://expcat.github.io/Tigercat/mcp/";
|
|
22
|
+
var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
|
|
23
|
+
function createFsSource(root) {
|
|
24
|
+
return {
|
|
25
|
+
kind: "fs",
|
|
26
|
+
origin: root,
|
|
27
|
+
async readText(path) {
|
|
28
|
+
const rootPath = await realpath(root);
|
|
29
|
+
const filePath = await realpath(join(root, path));
|
|
30
|
+
const relativePath = relative(rootPath, filePath);
|
|
31
|
+
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
32
|
+
throw new Error(`Reference path escapes the Tigercat repo: ${path}`);
|
|
33
|
+
}
|
|
34
|
+
return readFile(filePath, "utf8");
|
|
35
|
+
},
|
|
36
|
+
async probe(path) {
|
|
37
|
+
await access(join(root, path));
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function createHttpSource(baseUrl, options = {}) {
|
|
42
|
+
const base = normalizeBaseUrl(baseUrl);
|
|
43
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) && (options.timeoutMs ?? 0) > 0 ? Math.floor(options.timeoutMs) : DEFAULT_FETCH_TIMEOUT_MS;
|
|
44
|
+
const cache = /* @__PURE__ */ new Map();
|
|
45
|
+
const fetchOnce = (url) => fetch(url, {
|
|
46
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
47
|
+
headers: {
|
|
48
|
+
accept: "text/markdown, application/json;q=0.9, */*;q=0.8",
|
|
49
|
+
"user-agent": `tigercat-mcp/${PACKAGE_VERSION}`
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const fetchText = async (path) => {
|
|
53
|
+
const url = new URL(path, base);
|
|
54
|
+
let response;
|
|
55
|
+
try {
|
|
56
|
+
try {
|
|
57
|
+
response = await fetchOnce(url);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error instanceof Error && error.name === "TimeoutError") throw error;
|
|
60
|
+
response = await fetchOnce(url);
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
fetchFailureMessage(url, error instanceof Error ? error.message : String(error)),
|
|
65
|
+
{ cause: error }
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error(fetchFailureMessage(url, `HTTP ${response.status}`));
|
|
70
|
+
}
|
|
71
|
+
return response.text();
|
|
72
|
+
};
|
|
73
|
+
const readText = (path) => {
|
|
74
|
+
let pending = cache.get(path);
|
|
75
|
+
if (!pending) {
|
|
76
|
+
pending = fetchText(path);
|
|
77
|
+
pending.catch(() => cache.delete(path));
|
|
78
|
+
cache.set(path, pending);
|
|
79
|
+
}
|
|
80
|
+
return pending;
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
kind: "http",
|
|
84
|
+
origin: base,
|
|
85
|
+
readText,
|
|
86
|
+
async probe(path) {
|
|
87
|
+
await readText(path);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function normalizeBaseUrl(input) {
|
|
92
|
+
let parsed;
|
|
93
|
+
try {
|
|
94
|
+
parsed = new URL(input);
|
|
95
|
+
} catch {
|
|
96
|
+
throw new Error(`Invalid Tigercat skills base URL: ${input}`);
|
|
97
|
+
}
|
|
98
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
99
|
+
throw new Error(`Tigercat skills base URL must use http or https: ${input}`);
|
|
100
|
+
}
|
|
101
|
+
if (!parsed.pathname.endsWith("/")) {
|
|
102
|
+
parsed.pathname = `${parsed.pathname}/`;
|
|
103
|
+
}
|
|
104
|
+
return parsed.toString();
|
|
105
|
+
}
|
|
106
|
+
function fetchFailureMessage(url, reason) {
|
|
107
|
+
return `Failed to fetch Tigercat skill source (${reason}): ${url}. Use --root <repo> for local mode, or --base-url <mirror> if GitHub Pages is unreachable.`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/skill-index.ts
|
|
10
111
|
var DEFAULT_MAX_BYTES = 12e3;
|
|
11
|
-
var SKILL_ROOT = "skills/tigercat";
|
|
12
112
|
var REFERENCES_ROOT = "skills/tigercat/references";
|
|
13
113
|
var DEFAULT_COMPONENT_INDEX = "skills/tigercat/references/component-index.md";
|
|
14
114
|
var DEFAULT_REACT_REFERENCE = "skills/tigercat/references/react/index.md";
|
|
15
115
|
var DEFAULT_VUE_REFERENCE = "skills/tigercat/references/vue/index.md";
|
|
16
|
-
async function loadSkillIndex(
|
|
17
|
-
const
|
|
116
|
+
async function loadSkillIndex(options) {
|
|
117
|
+
const resolved = typeof options === "string" ? { root: options } : options ?? {};
|
|
118
|
+
if (resolved.root) {
|
|
119
|
+
return loadFsSkillIndex(resolve(resolved.root));
|
|
120
|
+
}
|
|
121
|
+
return loadHttpSkillIndex(resolved.baseUrl ?? DEFAULT_REMOTE_BASE_URL, resolved.timeoutMs);
|
|
122
|
+
}
|
|
123
|
+
async function loadFsSkillIndex(resolvedRoot) {
|
|
18
124
|
const contextPath = join(resolvedRoot, "context7.json");
|
|
19
125
|
if (!existsSync(contextPath)) {
|
|
20
126
|
throw new Error(`Missing context7.json under ${resolvedRoot}`);
|
|
21
127
|
}
|
|
22
128
|
const context7 = JSON.parse(await readFile(contextPath, "utf8"));
|
|
129
|
+
const allowedReferencePaths = collectAllowedReferencePaths(context7);
|
|
130
|
+
if (existsSync(join(resolvedRoot, REFERENCES_ROOT))) {
|
|
131
|
+
for (const path of await collectMarkdownReferences(join(resolvedRoot, REFERENCES_ROOT))) {
|
|
132
|
+
allowedReferencePaths.add(normalizeRelativePath(relative(resolvedRoot, path)));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
retainMarkdownPaths(allowedReferencePaths);
|
|
136
|
+
for (const path of allowedReferencePaths) {
|
|
137
|
+
const absolutePath = join(resolvedRoot, path);
|
|
138
|
+
if (!existsSync(absolutePath)) {
|
|
139
|
+
throw new Error(`Missing Tigercat skill reference: ${path}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return buildSkillIndex(context7, createFsSource(resolvedRoot), allowedReferencePaths);
|
|
143
|
+
}
|
|
144
|
+
async function loadHttpSkillIndex(baseUrl, timeoutMs) {
|
|
145
|
+
const source = createHttpSource(baseUrl, { timeoutMs });
|
|
146
|
+
const raw = await source.readText("context7.json");
|
|
147
|
+
let context7;
|
|
148
|
+
try {
|
|
149
|
+
context7 = JSON.parse(raw);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Invalid context7.json from ${source.origin}context7.json: ${error instanceof Error ? error.message : String(error)}`,
|
|
153
|
+
{ cause: error }
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const allowedReferencePaths = retainMarkdownPaths(collectAllowedReferencePaths(context7));
|
|
157
|
+
return buildSkillIndex(context7, source, allowedReferencePaths);
|
|
158
|
+
}
|
|
159
|
+
function collectAllowedReferencePaths(context7) {
|
|
160
|
+
const allowedReferencePaths = /* @__PURE__ */ new Set();
|
|
161
|
+
for (const path of collectReferencePaths(context7)) {
|
|
162
|
+
allowedReferencePaths.add(normalizeRelativePath(path));
|
|
163
|
+
}
|
|
164
|
+
allowedReferencePaths.add("skills/tigercat/SKILL.md");
|
|
165
|
+
return allowedReferencePaths;
|
|
166
|
+
}
|
|
167
|
+
function retainMarkdownPaths(paths) {
|
|
168
|
+
for (const path of paths) {
|
|
169
|
+
if (!path.endsWith(".md")) {
|
|
170
|
+
paths.delete(path);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return paths;
|
|
174
|
+
}
|
|
175
|
+
function buildSkillIndex(context7, source, allowedReferencePaths) {
|
|
23
176
|
const components = buildComponentMap(context7);
|
|
24
177
|
const componentsByNormalizedName = /* @__PURE__ */ new Map();
|
|
25
178
|
const aliasTargetsByNormalizedName = /* @__PURE__ */ new Map();
|
|
@@ -36,24 +189,9 @@ async function loadSkillIndex(root) {
|
|
|
36
189
|
addAliasTarget(aliasTargetsByNormalizedName, alias, [entry.name]);
|
|
37
190
|
}
|
|
38
191
|
}
|
|
39
|
-
const allowedReferencePaths = /* @__PURE__ */ new Set();
|
|
40
|
-
for (const path of collectReferencePaths(context7)) {
|
|
41
|
-
allowedReferencePaths.add(normalizeRelativePath(path));
|
|
42
|
-
}
|
|
43
|
-
if (existsSync(join(resolvedRoot, REFERENCES_ROOT))) {
|
|
44
|
-
for (const path of await collectMarkdownReferences(join(resolvedRoot, REFERENCES_ROOT))) {
|
|
45
|
-
allowedReferencePaths.add(normalizeRelativePath(relative(resolvedRoot, path)));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
allowedReferencePaths.add("skills/tigercat/SKILL.md");
|
|
49
|
-
for (const path of allowedReferencePaths) {
|
|
50
|
-
const absolutePath = join(resolvedRoot, path);
|
|
51
|
-
if (!existsSync(absolutePath)) {
|
|
52
|
-
throw new Error(`Missing Tigercat skill reference: ${path}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
192
|
return {
|
|
56
|
-
root:
|
|
193
|
+
root: source.origin,
|
|
194
|
+
source,
|
|
57
195
|
context7,
|
|
58
196
|
components,
|
|
59
197
|
componentsByNormalizedName,
|
|
@@ -62,8 +200,8 @@ async function loadSkillIndex(root) {
|
|
|
62
200
|
allowedReferencePaths
|
|
63
201
|
};
|
|
64
202
|
}
|
|
65
|
-
async function diagnoseTigercatMcp(
|
|
66
|
-
const index = await loadSkillIndex(
|
|
203
|
+
async function diagnoseTigercatMcp(options) {
|
|
204
|
+
const index = await loadSkillIndex(options);
|
|
67
205
|
const issues = [];
|
|
68
206
|
let readableReferenceCount = 0;
|
|
69
207
|
if (index.context7.component_count !== void 0 && index.context7.component_count !== index.components.size) {
|
|
@@ -79,36 +217,53 @@ async function diagnoseTigercatMcp(root) {
|
|
|
79
217
|
}
|
|
80
218
|
for (const path of index.allowedReferencePaths) {
|
|
81
219
|
try {
|
|
82
|
-
await
|
|
220
|
+
await index.source.probe(path);
|
|
83
221
|
readableReferenceCount += 1;
|
|
84
222
|
} catch {
|
|
85
223
|
issues.push(`reference is not readable: ${path}`);
|
|
86
224
|
}
|
|
87
225
|
}
|
|
226
|
+
let remoteVersion;
|
|
227
|
+
if (index.source.kind === "http") {
|
|
228
|
+
try {
|
|
229
|
+
const parsed = JSON.parse(await index.source.readText("version.json"));
|
|
230
|
+
if (typeof parsed.version === "string" && parsed.version) {
|
|
231
|
+
remoteVersion = parsed.version;
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
}
|
|
88
236
|
return {
|
|
89
237
|
ok: issues.length === 0,
|
|
90
238
|
root: index.root,
|
|
239
|
+
mode: index.source.kind,
|
|
91
240
|
componentCount: index.components.size,
|
|
92
241
|
aliasCount: index.aliasTargetsByNormalizedName.size,
|
|
93
242
|
topicCount: index.topics.size,
|
|
94
243
|
readableReferenceCount,
|
|
244
|
+
...remoteVersion ? { remoteVersion } : {},
|
|
95
245
|
issues
|
|
96
246
|
};
|
|
97
247
|
}
|
|
98
|
-
async function readReferenceSource(index, path, reason, maxBytes = DEFAULT_MAX_BYTES) {
|
|
248
|
+
async function readReferenceSource(index, path, reason, maxBytes = DEFAULT_MAX_BYTES, section) {
|
|
99
249
|
const normalizedPath = normalizeRelativePath(path);
|
|
100
250
|
if (!index.allowedReferencePaths.has(normalizedPath)) {
|
|
101
251
|
throw new Error(`Reference path is not allowed: ${path}`);
|
|
102
252
|
}
|
|
103
|
-
const
|
|
104
|
-
const rootPath = await realpath(index.root);
|
|
105
|
-
const filePath = await realpath(absolutePath);
|
|
106
|
-
const relativePath = relative(rootPath, filePath);
|
|
107
|
-
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
108
|
-
throw new Error(`Reference path escapes the Tigercat repo: ${path}`);
|
|
109
|
-
}
|
|
110
|
-
const text = await readFile(filePath, "utf8");
|
|
253
|
+
const text = await index.source.readText(normalizedPath);
|
|
111
254
|
const limit = Number.isFinite(maxBytes) && maxBytes > 0 ? Math.floor(maxBytes) : DEFAULT_MAX_BYTES;
|
|
255
|
+
if (section) {
|
|
256
|
+
const extracted = extractMarkdownSection(text, section);
|
|
257
|
+
if (extracted !== void 0) {
|
|
258
|
+
return {
|
|
259
|
+
path: normalizedPath,
|
|
260
|
+
reason,
|
|
261
|
+
truncated: Buffer.byteLength(extracted, "utf8") > limit,
|
|
262
|
+
section,
|
|
263
|
+
text: truncateUtf8(extracted, limit)
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
112
267
|
const truncated = Buffer.byteLength(text, "utf8") > limit;
|
|
113
268
|
return {
|
|
114
269
|
path: normalizedPath,
|
|
@@ -117,21 +272,37 @@ async function readReferenceSource(index, path, reason, maxBytes = DEFAULT_MAX_B
|
|
|
117
272
|
text: truncateUtf8(text, limit)
|
|
118
273
|
};
|
|
119
274
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
275
|
+
function extractMarkdownSection(text, section) {
|
|
276
|
+
const lines = text.split("\n");
|
|
277
|
+
const heading = section.trim().toLowerCase();
|
|
278
|
+
let start = -1;
|
|
279
|
+
for (let index = 0; index < lines.length; index++) {
|
|
280
|
+
const match = /^##\s+(.+?)\s*$/.exec(lines[index]);
|
|
281
|
+
if (match && match[1].toLowerCase() === heading) {
|
|
282
|
+
start = index;
|
|
283
|
+
break;
|
|
125
284
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
285
|
+
}
|
|
286
|
+
if (start === -1) return void 0;
|
|
287
|
+
let end = lines.length;
|
|
288
|
+
for (let index = start + 1; index < lines.length; index++) {
|
|
289
|
+
if (/^##\s/.test(lines[index])) {
|
|
290
|
+
end = index;
|
|
291
|
+
break;
|
|
129
292
|
}
|
|
130
|
-
current = next;
|
|
131
293
|
}
|
|
294
|
+
return `${lines.slice(start, end).join("\n").trimEnd()}
|
|
295
|
+
`;
|
|
296
|
+
}
|
|
297
|
+
function createReferencePointer(index, path, reason) {
|
|
298
|
+
const normalizedPath = normalizeRelativePath(path);
|
|
299
|
+
if (!index.allowedReferencePaths.has(normalizedPath)) {
|
|
300
|
+
throw new Error(`Reference path is not allowed: ${path}`);
|
|
301
|
+
}
|
|
302
|
+
return { path: normalizedPath, reason, truncated: false };
|
|
132
303
|
}
|
|
133
304
|
function normalizeName(value) {
|
|
134
|
-
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
305
|
+
return value.toLowerCase().replace(/[^a-z0-9一-鿿]/g, "");
|
|
135
306
|
}
|
|
136
307
|
function normalizeRelativePath(path) {
|
|
137
308
|
const normalized = path.replaceAll("\\", "/").replace(/^\.?\//, "");
|
|
@@ -274,8 +445,10 @@ async function routeTigercatTask(index, input) {
|
|
|
274
445
|
task,
|
|
275
446
|
framework: input.framework,
|
|
276
447
|
intent: matches.length > 0 && topics.length > 0 ? "mixed" : matches.length > 0 ? "component" : "topic",
|
|
277
|
-
matches
|
|
278
|
-
|
|
448
|
+
// 正文只在顶层 sources 内联一次;matches/topics 里保留 path/reason 元数据,
|
|
449
|
+
// 否则同一份 reference 全文会在响应里出现两遍。
|
|
450
|
+
matches: matches.map((match) => ({ ...match, sources: match.sources.map(stripText) })),
|
|
451
|
+
topics: topics.map((topic) => ({ ...topic, sources: topic.sources.map(stripText) })),
|
|
279
452
|
candidates: [],
|
|
280
453
|
sources
|
|
281
454
|
};
|
|
@@ -298,14 +471,11 @@ async function routeTigercatTask(index, input) {
|
|
|
298
471
|
};
|
|
299
472
|
}
|
|
300
473
|
async function createComponentRoute(index, entry, framework, maxBytes) {
|
|
301
|
-
const
|
|
302
|
-
{
|
|
303
|
-
path: entry.references.componentIndex,
|
|
304
|
-
reason: "Canonical generated component inventory and package subpath map."
|
|
305
|
-
},
|
|
474
|
+
const inlineSpecs = dedupeByPath([
|
|
306
475
|
{
|
|
307
476
|
path: entry.references.props,
|
|
308
|
-
reason: `${entry.name} props, events, methods, and type source
|
|
477
|
+
reason: `${entry.name} props, events, methods, and type source.`,
|
|
478
|
+
section: entry.name
|
|
309
479
|
},
|
|
310
480
|
{ path: entry.references.examples, reason: `${entry.name} compact Vue/React example routes.` },
|
|
311
481
|
...framework ? [
|
|
@@ -316,36 +486,60 @@ async function createComponentRoute(index, entry, framework, maxBytes) {
|
|
|
316
486
|
] : [
|
|
317
487
|
{ path: entry.references.react, reason: "React binding and import notes." },
|
|
318
488
|
{ path: entry.references.vue, reason: "Vue binding and import notes." }
|
|
319
|
-
]
|
|
320
|
-
|
|
321
|
-
{ path: SHARED_GLOSSARY, reason: "Shared Tigercat terminology." }
|
|
322
|
-
];
|
|
489
|
+
]
|
|
490
|
+
]);
|
|
323
491
|
const sources = await Promise.all(
|
|
324
|
-
|
|
325
|
-
(spec) => readReferenceSource(
|
|
492
|
+
inlineSpecs.map(
|
|
493
|
+
(spec) => readReferenceSource(
|
|
494
|
+
index,
|
|
495
|
+
spec.path,
|
|
496
|
+
spec.reason,
|
|
497
|
+
maxBytes,
|
|
498
|
+
"section" in spec ? spec.section : void 0
|
|
499
|
+
)
|
|
326
500
|
)
|
|
327
501
|
);
|
|
502
|
+
const pointers = [
|
|
503
|
+
{
|
|
504
|
+
path: entry.references.componentIndex,
|
|
505
|
+
reason: "Full component inventory; read at most once per session via tigercat_reference."
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
path: SHARED_PATTERNS,
|
|
509
|
+
reason: "Cross-framework binding differences; read at most once per session via tigercat_reference."
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
path: SHARED_GLOSSARY,
|
|
513
|
+
reason: "Shared Tigercat terminology; read at most once per session via tigercat_reference."
|
|
514
|
+
}
|
|
515
|
+
].map((spec) => createReferencePointer(index, spec.path, spec.reason));
|
|
328
516
|
return {
|
|
329
517
|
component: entry,
|
|
330
|
-
sources
|
|
518
|
+
sources: dedupeByPath([...sources, ...pointers])
|
|
331
519
|
};
|
|
332
520
|
}
|
|
333
521
|
async function createTopicRoute(index, slug, maxBytes) {
|
|
334
522
|
const topic = index.topics.get(slug);
|
|
335
523
|
if (!topic) throw new Error(`Unknown Tigercat topic: ${slug}`);
|
|
336
524
|
const sources = await Promise.all(
|
|
337
|
-
dedupeByPath(
|
|
338
|
-
|
|
339
|
-
...topic.references.map((path) => ({
|
|
525
|
+
dedupeByPath(
|
|
526
|
+
topic.references.map((path) => ({
|
|
340
527
|
path,
|
|
341
528
|
reason: `${topic.title} reference.`
|
|
342
529
|
}))
|
|
343
|
-
|
|
530
|
+
).map((spec) => readReferenceSource(index, spec.path, spec.reason, maxBytes))
|
|
344
531
|
);
|
|
345
532
|
return {
|
|
346
533
|
slug,
|
|
347
534
|
title: topic.title,
|
|
348
|
-
sources
|
|
535
|
+
sources: dedupeByPath([
|
|
536
|
+
...sources,
|
|
537
|
+
createReferencePointer(
|
|
538
|
+
index,
|
|
539
|
+
SKILL_INDEX,
|
|
540
|
+
"Top-level skill route index; read via tigercat_reference only if the topic references are insufficient."
|
|
541
|
+
)
|
|
542
|
+
])
|
|
349
543
|
};
|
|
350
544
|
}
|
|
351
545
|
function getInventory(index) {
|
|
@@ -506,8 +700,9 @@ function dedupeByPath(items) {
|
|
|
506
700
|
const seen = /* @__PURE__ */ new Set();
|
|
507
701
|
const result = [];
|
|
508
702
|
for (const item of items) {
|
|
509
|
-
|
|
510
|
-
seen.
|
|
703
|
+
const key = `${item.path}#${item.section ?? ""}`;
|
|
704
|
+
if (seen.has(key)) continue;
|
|
705
|
+
seen.add(key);
|
|
511
706
|
result.push(item);
|
|
512
707
|
}
|
|
513
708
|
return result;
|
|
@@ -515,6 +710,10 @@ function dedupeByPath(items) {
|
|
|
515
710
|
function mergeSources(sources) {
|
|
516
711
|
return dedupeByPath(sources);
|
|
517
712
|
}
|
|
713
|
+
function stripText(source) {
|
|
714
|
+
const { text: _text, ...rest } = source;
|
|
715
|
+
return rest;
|
|
716
|
+
}
|
|
518
717
|
function keywordMatches(task, normalizedTask, keyword) {
|
|
519
718
|
const normalizedKeyword = normalizeName(keyword);
|
|
520
719
|
if (normalizedKeyword) return normalizedTask.includes(normalizedKeyword);
|
|
@@ -524,100 +723,147 @@ function keywordMatches(task, normalizedTask, keyword) {
|
|
|
524
723
|
// src/server.ts
|
|
525
724
|
var JSON_MIME = "application/json";
|
|
526
725
|
var MARKDOWN_MIME = "text/markdown";
|
|
726
|
+
var TIGERCAT_SERVER_INSTRUCTIONS = [
|
|
727
|
+
"Tigercat is a Tailwind CSS React + Vue 3 UI library (149 components, imported from",
|
|
728
|
+
"@expcat/tigercat-react / @expcat/tigercat-vue PascalCase subpaths such as",
|
|
729
|
+
"@expcat/tigercat-react/Button). Use this server BEFORE writing or reviewing any code",
|
|
730
|
+
"that touches Tigercat \u2014 it returns exact import subpaths, props/events, and",
|
|
731
|
+
"per-framework binding notes that differ from generic React/Vue knowledge.",
|
|
732
|
+
"",
|
|
733
|
+
"Tool order: call `tigercat_route` first for any natural-language task (always pass",
|
|
734
|
+
"`framework`; it roughly halves the payload). Call `tigercat_component` when you",
|
|
735
|
+
"already know the component name or alias. Use `tigercat_search` only for fuzzy",
|
|
736
|
+
"discovery, and `tigercat_reference` only to read a path that a previous result",
|
|
737
|
+
"pointed to.",
|
|
738
|
+
"",
|
|
739
|
+
"Responses inline the needed reference text once, as extra content blocks after the",
|
|
740
|
+
'JSON summary. Sources marked `"inlined": false` are session-level background',
|
|
741
|
+
"(inventory, glossary, patterns): read each at most once per session via",
|
|
742
|
+
"`tigercat_reference`, and only if actually needed. Do not re-read paths you already",
|
|
743
|
+
"received.",
|
|
744
|
+
"",
|
|
745
|
+
"Component matching works with English names and listed Chinese aliases (e.g. \u8868\u5355,",
|
|
746
|
+
"\u65E5\u671F\u9009\u62E9\u5668); prefer English component names for precision. `notification` is a",
|
|
747
|
+
"command API topic, not a component; `Message` is both."
|
|
748
|
+
].join("\n");
|
|
749
|
+
var FRAMEWORK_SCHEMA = {
|
|
750
|
+
type: "string",
|
|
751
|
+
enum: ["react", "vue"],
|
|
752
|
+
description: "Target framework. Omit to receive notes for both React and Vue (larger payload)."
|
|
753
|
+
};
|
|
754
|
+
var MAX_BYTES_SCHEMA = {
|
|
755
|
+
type: "number",
|
|
756
|
+
minimum: 200,
|
|
757
|
+
maximum: 5e4,
|
|
758
|
+
description: "Byte cap per inlined source (default 12000). Raise only when a source was truncated."
|
|
759
|
+
};
|
|
760
|
+
var TIGERCAT_TOOLS = [
|
|
761
|
+
{
|
|
762
|
+
name: "tigercat_search",
|
|
763
|
+
description: "Fuzzy-search Tigercat components, aliases, categories, topics, and command APIs. Returns ranked name/metadata matches only \u2014 no reference text, cheapest call. Use when unsure of the exact name; otherwise go straight to tigercat_component or tigercat_route.",
|
|
764
|
+
inputSchema: {
|
|
765
|
+
type: "object",
|
|
766
|
+
properties: {
|
|
767
|
+
query: {
|
|
768
|
+
type: "string",
|
|
769
|
+
description: 'Component, alias, category, topic, or use case, e.g. "table" or "\u8868\u683C".'
|
|
770
|
+
},
|
|
771
|
+
framework: FRAMEWORK_SCHEMA,
|
|
772
|
+
limit: {
|
|
773
|
+
type: "number",
|
|
774
|
+
minimum: 1,
|
|
775
|
+
maximum: 30,
|
|
776
|
+
description: "Max results (default 8)."
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
required: ["query"],
|
|
780
|
+
additionalProperties: false
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
name: "tigercat_component",
|
|
785
|
+
description: "Look up one Tigercat component by exact name or alias (e.g. Button, Grid, \u65E5\u671F\u9009\u62E9\u5668). Returns its import subpath, its own props/events section, compact category examples, and framework binding notes, inlined as content blocks after the JSON summary. Preferred over tigercat_route when you already know the component name; pass framework to shrink the payload.",
|
|
786
|
+
inputSchema: {
|
|
787
|
+
type: "object",
|
|
788
|
+
properties: {
|
|
789
|
+
component: {
|
|
790
|
+
type: "string",
|
|
791
|
+
description: "Component name or alias, such as Button, Grid, or \u8868\u683C."
|
|
792
|
+
},
|
|
793
|
+
framework: FRAMEWORK_SCHEMA,
|
|
794
|
+
maxBytes: MAX_BYTES_SCHEMA
|
|
795
|
+
},
|
|
796
|
+
required: ["component"],
|
|
797
|
+
additionalProperties: false
|
|
798
|
+
}
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
name: "tigercat_route",
|
|
802
|
+
description: 'Route a natural-language Tigercat task to the smallest useful component/topic references. Preferred FIRST call for any Tigercat coding task. Returns matched components with import paths plus each needed reference inlined exactly once; sources marked "inlined": false are session-level background to read at most once via tigercat_reference. Always pass framework. Name components in English or a listed Chinese alias.',
|
|
803
|
+
inputSchema: {
|
|
804
|
+
type: "object",
|
|
805
|
+
properties: {
|
|
806
|
+
task: {
|
|
807
|
+
type: "string",
|
|
808
|
+
description: 'Natural-language Tigercat task, e.g. "add a DatePicker with Form validation" or "\u7ED9\u8868\u5355\u52A0\u65E5\u671F\u9009\u62E9\u5668".'
|
|
809
|
+
},
|
|
810
|
+
framework: FRAMEWORK_SCHEMA,
|
|
811
|
+
maxBytes: MAX_BYTES_SCHEMA,
|
|
812
|
+
limit: {
|
|
813
|
+
type: "number",
|
|
814
|
+
minimum: 1,
|
|
815
|
+
maximum: 30,
|
|
816
|
+
description: "Max fallback candidates when nothing matches directly (default 8)."
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
required: ["task"],
|
|
820
|
+
additionalProperties: false
|
|
821
|
+
}
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
name: "tigercat_reference",
|
|
825
|
+
description: 'Read one allow-listed Tigercat skill reference file verbatim. Use only for paths returned by other tigercat tools \u2014 typically to read an "inlined": false pointer source once per session.',
|
|
826
|
+
inputSchema: {
|
|
827
|
+
type: "object",
|
|
828
|
+
properties: {
|
|
829
|
+
path: {
|
|
830
|
+
type: "string",
|
|
831
|
+
description: 'Repo-relative path under skills/tigercat/, exactly as returned in a source "path" field, e.g. skills/tigercat/references/shared/glossary.md.'
|
|
832
|
+
},
|
|
833
|
+
maxBytes: MAX_BYTES_SCHEMA
|
|
834
|
+
},
|
|
835
|
+
required: ["path"],
|
|
836
|
+
additionalProperties: false
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
];
|
|
527
840
|
function createTigercatMcpServer(options = {}) {
|
|
528
841
|
const server = new Server(
|
|
529
842
|
{
|
|
530
843
|
name: "@expcat/tigercat-mcp",
|
|
531
|
-
version:
|
|
844
|
+
version: PACKAGE_VERSION
|
|
532
845
|
},
|
|
533
846
|
{
|
|
534
847
|
capabilities: {
|
|
535
848
|
tools: {},
|
|
536
849
|
resources: {},
|
|
537
850
|
prompts: {}
|
|
538
|
-
}
|
|
851
|
+
},
|
|
852
|
+
instructions: TIGERCAT_SERVER_INSTRUCTIONS
|
|
539
853
|
}
|
|
540
854
|
);
|
|
541
855
|
let indexPromise;
|
|
542
856
|
const getIndex = () => {
|
|
543
|
-
indexPromise ??= loadSkillIndex(options
|
|
857
|
+
indexPromise ??= loadSkillIndex(options);
|
|
544
858
|
return indexPromise;
|
|
545
859
|
};
|
|
546
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
547
|
-
tools: [
|
|
548
|
-
{
|
|
549
|
-
name: "tigercat_search",
|
|
550
|
-
description: "Search Tigercat components, aliases, categories, topics, and command APIs.",
|
|
551
|
-
inputSchema: {
|
|
552
|
-
type: "object",
|
|
553
|
-
properties: {
|
|
554
|
-
query: {
|
|
555
|
-
type: "string",
|
|
556
|
-
description: "Component, alias, category, topic, or use case."
|
|
557
|
-
},
|
|
558
|
-
framework: { type: "string", enum: ["react", "vue"] },
|
|
559
|
-
limit: { type: "number", minimum: 1, maximum: 30 }
|
|
560
|
-
},
|
|
561
|
-
required: ["query"],
|
|
562
|
-
additionalProperties: false
|
|
563
|
-
}
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
name: "tigercat_component",
|
|
567
|
-
description: "Return exact Tigercat component metadata, import paths, docs, examples, and framework notes.",
|
|
568
|
-
inputSchema: {
|
|
569
|
-
type: "object",
|
|
570
|
-
properties: {
|
|
571
|
-
component: {
|
|
572
|
-
type: "string",
|
|
573
|
-
description: "Component name or alias, such as Button or Grid."
|
|
574
|
-
},
|
|
575
|
-
framework: { type: "string", enum: ["react", "vue"] },
|
|
576
|
-
maxBytes: { type: "number", minimum: 200, maximum: 5e4 }
|
|
577
|
-
},
|
|
578
|
-
required: ["component"],
|
|
579
|
-
additionalProperties: false
|
|
580
|
-
}
|
|
581
|
-
},
|
|
582
|
-
{
|
|
583
|
-
name: "tigercat_route",
|
|
584
|
-
description: "Route a natural-language Tigercat task to the smallest useful component/topic references.",
|
|
585
|
-
inputSchema: {
|
|
586
|
-
type: "object",
|
|
587
|
-
properties: {
|
|
588
|
-
task: { type: "string", description: "Natural-language Tigercat task." },
|
|
589
|
-
framework: { type: "string", enum: ["react", "vue"] },
|
|
590
|
-
maxBytes: { type: "number", minimum: 200, maximum: 5e4 },
|
|
591
|
-
limit: { type: "number", minimum: 1, maximum: 30 }
|
|
592
|
-
},
|
|
593
|
-
required: ["task"],
|
|
594
|
-
additionalProperties: false
|
|
595
|
-
}
|
|
596
|
-
},
|
|
597
|
-
{
|
|
598
|
-
name: "tigercat_reference",
|
|
599
|
-
description: "Read an allow-listed Tigercat skill reference with optional byte truncation.",
|
|
600
|
-
inputSchema: {
|
|
601
|
-
type: "object",
|
|
602
|
-
properties: {
|
|
603
|
-
path: {
|
|
604
|
-
type: "string",
|
|
605
|
-
description: "Repo-relative skill reference path under skills/tigercat."
|
|
606
|
-
},
|
|
607
|
-
maxBytes: { type: "number", minimum: 200, maximum: 5e4 }
|
|
608
|
-
},
|
|
609
|
-
required: ["path"],
|
|
610
|
-
additionalProperties: false
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
]
|
|
614
|
-
}));
|
|
860
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TIGERCAT_TOOLS }));
|
|
615
861
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
616
862
|
const index = await getIndex();
|
|
617
863
|
const args = request.params.arguments ?? {};
|
|
618
864
|
try {
|
|
619
865
|
if (request.params.name === "tigercat_search") {
|
|
620
|
-
return
|
|
866
|
+
return renderToolResult(
|
|
621
867
|
await searchTigercat(index, {
|
|
622
868
|
query: stringArg(args.query),
|
|
623
869
|
framework: frameworkArg(args.framework),
|
|
@@ -626,7 +872,7 @@ function createTigercatMcpServer(options = {}) {
|
|
|
626
872
|
);
|
|
627
873
|
}
|
|
628
874
|
if (request.params.name === "tigercat_component") {
|
|
629
|
-
return
|
|
875
|
+
return renderToolResult(
|
|
630
876
|
await getTigercatComponent(index, {
|
|
631
877
|
component: stringArg(args.component),
|
|
632
878
|
framework: frameworkArg(args.framework),
|
|
@@ -635,7 +881,7 @@ function createTigercatMcpServer(options = {}) {
|
|
|
635
881
|
);
|
|
636
882
|
}
|
|
637
883
|
if (request.params.name === "tigercat_route") {
|
|
638
|
-
return
|
|
884
|
+
return renderToolResult(
|
|
639
885
|
await routeTigercatTask(index, {
|
|
640
886
|
task: stringArg(args.task),
|
|
641
887
|
framework: frameworkArg(args.framework),
|
|
@@ -645,7 +891,7 @@ function createTigercatMcpServer(options = {}) {
|
|
|
645
891
|
);
|
|
646
892
|
}
|
|
647
893
|
if (request.params.name === "tigercat_reference") {
|
|
648
|
-
return
|
|
894
|
+
return renderToolResult(
|
|
649
895
|
await readReferenceSource(
|
|
650
896
|
index,
|
|
651
897
|
stringArg(args.path),
|
|
@@ -701,22 +947,22 @@ function createTigercatMcpServer(options = {}) {
|
|
|
701
947
|
const index = await getIndex();
|
|
702
948
|
const uri = request.params.uri;
|
|
703
949
|
if (uri === "tigercat://inventory") {
|
|
704
|
-
return resourceText(uri, JSON.stringify(getInventory(index)
|
|
950
|
+
return resourceText(uri, JSON.stringify(getInventory(index)), JSON_MIME);
|
|
705
951
|
}
|
|
706
952
|
if (uri.startsWith("tigercat://component/")) {
|
|
707
953
|
const component = decodeURIComponent(uri.slice("tigercat://component/".length));
|
|
708
954
|
const lookup = await getTigercatComponent(index, { component });
|
|
709
|
-
return resourceText(uri, JSON.stringify(lookup
|
|
955
|
+
return resourceText(uri, JSON.stringify(lookup), JSON_MIME);
|
|
710
956
|
}
|
|
711
957
|
if (uri.startsWith("tigercat://category/")) {
|
|
712
958
|
const category = decodeURIComponent(uri.slice("tigercat://category/".length));
|
|
713
959
|
const components = getCategoryComponents(index, category);
|
|
714
|
-
return resourceText(uri, JSON.stringify({ category, components }
|
|
960
|
+
return resourceText(uri, JSON.stringify({ category, components }), JSON_MIME);
|
|
715
961
|
}
|
|
716
962
|
if (uri.startsWith("tigercat://topic/")) {
|
|
717
963
|
const topic = decodeURIComponent(uri.slice("tigercat://topic/".length));
|
|
718
964
|
const route = await createTopicRoute(index, topic);
|
|
719
|
-
return resourceText(uri, JSON.stringify(route
|
|
965
|
+
return resourceText(uri, JSON.stringify(route), JSON_MIME);
|
|
720
966
|
}
|
|
721
967
|
if (uri.startsWith("tigercat://reference/")) {
|
|
722
968
|
const path = decodeURIComponent(uri.slice("tigercat://reference/".length));
|
|
@@ -762,7 +1008,7 @@ function createTigercatMcpServer(options = {}) {
|
|
|
762
1008
|
text: [
|
|
763
1009
|
`Use tigercat_route with framework "${framework}" before answering.`,
|
|
764
1010
|
"Use tigercat_component for exact component imports and props when route results mention components.",
|
|
765
|
-
|
|
1011
|
+
'Read only the returned sources; sources marked "inlined": false are optional background \u2014 fetch each at most once via tigercat_reference.',
|
|
766
1012
|
`Task: ${task}`,
|
|
767
1013
|
"Answer with exact import paths, key props/events, and React/Vue binding differences."
|
|
768
1014
|
].join("\n")
|
|
@@ -779,11 +1025,49 @@ function jsonContent(value, isError = false) {
|
|
|
779
1025
|
content: [
|
|
780
1026
|
{
|
|
781
1027
|
type: "text",
|
|
782
|
-
text: JSON.stringify(value
|
|
1028
|
+
text: JSON.stringify(value)
|
|
783
1029
|
}
|
|
784
1030
|
]
|
|
785
1031
|
};
|
|
786
1032
|
}
|
|
1033
|
+
function renderToolResult(value) {
|
|
1034
|
+
const blocks = [];
|
|
1035
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1036
|
+
const visit = (node) => {
|
|
1037
|
+
if (Array.isArray(node)) return node.map(visit);
|
|
1038
|
+
if (!node || typeof node !== "object") return node;
|
|
1039
|
+
const record = node;
|
|
1040
|
+
if (isReferenceSource(record)) {
|
|
1041
|
+
const { text, ...rest } = record;
|
|
1042
|
+
if (typeof text !== "string") return { ...rest, inlined: false };
|
|
1043
|
+
const section = typeof record.section === "string" ? record.section : void 0;
|
|
1044
|
+
const key = `${record.path}#${section ?? ""}`;
|
|
1045
|
+
if (!seen.has(key)) {
|
|
1046
|
+
seen.add(key);
|
|
1047
|
+
blocks.push({
|
|
1048
|
+
type: "text",
|
|
1049
|
+
text: formatSourceBlock(record.path, section, record.truncated, text)
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
return { ...rest, inlined: true };
|
|
1053
|
+
}
|
|
1054
|
+
return Object.fromEntries(Object.entries(record).map(([k, v]) => [k, visit(v)]));
|
|
1055
|
+
};
|
|
1056
|
+
const summary = visit(value);
|
|
1057
|
+
return {
|
|
1058
|
+
isError: false,
|
|
1059
|
+
content: [{ type: "text", text: JSON.stringify(summary) }, ...blocks]
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
function isReferenceSource(record) {
|
|
1063
|
+
return typeof record.path === "string" && typeof record.reason === "string" && typeof record.truncated === "boolean";
|
|
1064
|
+
}
|
|
1065
|
+
function formatSourceBlock(path, section, truncated, text) {
|
|
1066
|
+
const heading = section ? `${path} \xA7 ${section}` : path;
|
|
1067
|
+
const truncatedNote = truncated ? " [truncated: raise maxBytes to read more]" : "";
|
|
1068
|
+
return `===== source: ${heading}${truncatedNote} =====
|
|
1069
|
+
${text}`;
|
|
1070
|
+
}
|
|
787
1071
|
function resourceText(uri, text, mimeType) {
|
|
788
1072
|
return {
|
|
789
1073
|
contents: [
|
|
@@ -821,17 +1105,20 @@ function formatError(error) {
|
|
|
821
1105
|
|
|
822
1106
|
// src/index.ts
|
|
823
1107
|
async function main() {
|
|
824
|
-
const
|
|
825
|
-
if (
|
|
1108
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
1109
|
+
if (parsed.help) {
|
|
826
1110
|
console.log(helpText());
|
|
827
1111
|
return;
|
|
828
1112
|
}
|
|
829
|
-
|
|
830
|
-
|
|
1113
|
+
const options = resolveOptions(parsed);
|
|
1114
|
+
if (parsed.doctor) {
|
|
1115
|
+
const result = await diagnoseTigercatMcp(options);
|
|
831
1116
|
console.log(
|
|
832
1117
|
[
|
|
833
1118
|
`Tigercat MCP doctor: ${result.ok ? "ok" : "failed"}`,
|
|
834
|
-
`
|
|
1119
|
+
`mode: ${result.mode === "http" ? "remote" : "local"}`,
|
|
1120
|
+
`${result.mode === "http" ? "base url" : "root"}: ${result.root}`,
|
|
1121
|
+
...result.remoteVersion ? [`remote version: ${result.remoteVersion}`] : [],
|
|
835
1122
|
`components: ${result.componentCount}`,
|
|
836
1123
|
`aliases: ${result.aliasCount}`,
|
|
837
1124
|
`topics: ${result.topicCount}`,
|
|
@@ -842,12 +1129,13 @@ async function main() {
|
|
|
842
1129
|
if (!result.ok) process.exitCode = 1;
|
|
843
1130
|
return;
|
|
844
1131
|
}
|
|
845
|
-
const server = createTigercatMcpServer(
|
|
1132
|
+
const server = createTigercatMcpServer(options);
|
|
846
1133
|
const transport = new StdioServerTransport();
|
|
847
1134
|
await server.connect(transport);
|
|
848
1135
|
}
|
|
849
1136
|
function parseArgs(args) {
|
|
850
1137
|
let root;
|
|
1138
|
+
let baseUrl;
|
|
851
1139
|
let help = false;
|
|
852
1140
|
let doctor = false;
|
|
853
1141
|
for (let index = 0; index < args.length; index++) {
|
|
@@ -869,20 +1157,47 @@ function parseArgs(args) {
|
|
|
869
1157
|
index++;
|
|
870
1158
|
continue;
|
|
871
1159
|
}
|
|
1160
|
+
if (arg === "--base-url") {
|
|
1161
|
+
const value = args[index + 1];
|
|
1162
|
+
if (!value) {
|
|
1163
|
+
throw new Error("Usage: tigercat-mcp --base-url <skills-base-url>");
|
|
1164
|
+
}
|
|
1165
|
+
baseUrl = value;
|
|
1166
|
+
index++;
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
872
1169
|
throw new Error(`Unknown argument: ${arg}`);
|
|
873
1170
|
}
|
|
874
|
-
return { root, help, doctor };
|
|
1171
|
+
return { root, baseUrl, help, doctor };
|
|
1172
|
+
}
|
|
1173
|
+
function resolveOptions(parsed) {
|
|
1174
|
+
if (parsed.root) {
|
|
1175
|
+
if (parsed.baseUrl) {
|
|
1176
|
+
console.error("tigercat-mcp: --root takes precedence over --base-url; ignoring --base-url.");
|
|
1177
|
+
}
|
|
1178
|
+
return { root: parsed.root };
|
|
1179
|
+
}
|
|
1180
|
+
const envBaseUrl = process.env.TIGERCAT_MCP_BASE_URL?.trim();
|
|
1181
|
+
return { baseUrl: parsed.baseUrl ?? (envBaseUrl || void 0) ?? DEFAULT_REMOTE_BASE_URL };
|
|
875
1182
|
}
|
|
876
1183
|
function helpText() {
|
|
877
1184
|
return [
|
|
878
|
-
"Usage: tigercat-mcp [--root <repo-root>] [--doctor]",
|
|
1185
|
+
"Usage: tigercat-mcp [--root <repo-root>] [--base-url <skills-base-url>] [--doctor]",
|
|
879
1186
|
"",
|
|
880
1187
|
"Runs the Tigercat skill MCP server over stdio.",
|
|
881
|
-
|
|
882
|
-
"
|
|
1188
|
+
`By default skill references are fetched from ${DEFAULT_REMOTE_BASE_URL} (GitHub Pages).`,
|
|
1189
|
+
"",
|
|
1190
|
+
" --root <repo-root> read skills from a local Tigercat checkout (dev/offline mode).",
|
|
1191
|
+
" --base-url <url> fetch skills from a mirror of the /mcp/ Pages route.",
|
|
1192
|
+
" --doctor validate the skill inventory and exit without starting stdio.",
|
|
1193
|
+
" TIGERCAT_MCP_BASE_URL environment fallback for --base-url.",
|
|
1194
|
+
"",
|
|
1195
|
+
"Examples:",
|
|
1196
|
+
" tigercat-mcp # remote skills (default)",
|
|
1197
|
+
" tigercat-mcp --root /path/to/Tigercat # local checkout",
|
|
1198
|
+
" tigercat-mcp --base-url https://mirror.example.com/mcp/",
|
|
883
1199
|
"",
|
|
884
|
-
"
|
|
885
|
-
" tigercat-mcp --root /path/to/Tigercat"
|
|
1200
|
+
"If GitHub Pages is unreachable (offline, proxy, regional block), use --root or --base-url."
|
|
886
1201
|
].join("\n");
|
|
887
1202
|
}
|
|
888
1203
|
var isDirectRun = (() => {
|
|
@@ -900,4 +1215,4 @@ if (isDirectRun) {
|
|
|
900
1215
|
});
|
|
901
1216
|
}
|
|
902
1217
|
|
|
903
|
-
export { createTigercatMcpServer, diagnoseTigercatMcp, getCategoryComponents, getInventory, getTigercatComponent, loadSkillIndex, routeTigercatTask, searchTigercat };
|
|
1218
|
+
export { DEFAULT_REMOTE_BASE_URL, createTigercatMcpServer, diagnoseTigercatMcp, getCategoryComponents, getInventory, getTigercatComponent, loadSkillIndex, routeTigercatTask, searchTigercat };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expcat/tigercat-mcp",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.19",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "MCP server routing LLMs to Tigercat skill references, fetched from GitHub Pages by default with a local checkout fallback",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Yizhe Wang",
|
|
8
8
|
"repository": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"url": "https://github.com/expcat/Tigercat",
|
|
11
11
|
"directory": "packages/mcp"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://github.
|
|
13
|
+
"homepage": "https://expcat.github.io/Tigercat/mcp/",
|
|
14
14
|
"bugs": {
|
|
15
15
|
"url": "https://github.com/expcat/Tigercat/issues"
|
|
16
16
|
},
|