kiku-agent 1.0.11 → 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.
- package/README.md +300 -495
- package/package.json +12 -11
- package/public/fonts/GoogleSans-Bold.ttf +0 -0
- package/public/fonts/GoogleSans-Bold.woff2 +0 -0
- package/public/fonts/GoogleSans-BoldItalic.ttf +0 -0
- package/public/fonts/GoogleSans-Italic.ttf +0 -0
- package/public/fonts/GoogleSans-Latin-400.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-500.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-600.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-700.woff2 +0 -0
- package/public/fonts/GoogleSans-Medium.ttf +0 -0
- package/public/fonts/GoogleSans-Medium.woff2 +0 -0
- package/public/fonts/GoogleSans-Regular.ttf +0 -0
- package/public/fonts/GoogleSans-Regular.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-400.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-500.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-600.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-700.woff2 +0 -0
- package/public/fonts/NotoSansThai-Bold.woff2 +0 -0
- package/public/fonts/NotoSansThai-Regular.woff2 +0 -0
- package/public/fonts/google-sans-official.css +900 -0
- package/public/images/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
- package/public/images/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
- package/public/images/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
- package/public/images/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
- package/public/images/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
- package/public/images/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
- package/public/images/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
- package/public/images/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-399915.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-424041.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-588166.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-672198.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-700522.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-839722.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026825-460586.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/263202694-689407.jpg +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-399915.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-424041.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-588166.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-672198.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-700522.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-839722.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026825-460586.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/263202694-689407.mp4 +0 -0
- package/public/images/general/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
- package/public/images/general/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
- package/public/images/general/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
- package/public/images/general/jujingyi_portrait.jpg +0 -0
- package/public/images/jujingyi_portrait.jpg +0 -0
- package/public/images/series/a-millenniums-wait/poster.jpg +0 -0
- package/public/images/series/beauty-of-resilience/poster.jpg +0 -0
- package/public/images/series/detective-samoyeds/poster.jpg +0 -0
- package/public/images/series/everyday/poster.jpg +0 -0
- package/public/images/series/fu-rong-ancient-painting/poster.jpg +0 -0
- package/public/images/series/in-a-class-of-her-own/poster.jpg +0 -0
- package/public/images/series/in-blossom/poster.jpg +0 -0
- package/public/images/series/legend-of-yunxi/poster.jpg +0 -0
- package/public/images/series/lets-fight/poster-2.jpg +0 -0
- package/public/images/series/lets-fight/poster.jpg +0 -0
- package/public/images/series/love-emergency/poster.jpg +0 -0
- package/public/images/series/love-under-the-full-moon/poster.jpg +0 -0
- package/public/images/series/money-bastion/poster.jpg +0 -0
- package/public/images/series/mr-swimmer/poster.jpg +0 -0
- package/public/images/series/novoland-castle-in-the-sky/poster.jpg +0 -0
- package/public/images/series/please-give-me-a-pair-of-wings/poster.jpg +0 -0
- package/public/images/series/rebirth-for-you/poster.jpg +0 -0
- package/public/images/series/sword-and-fairy-4/poster.jpg +0 -0
- package/public/images/series/the-blooms-at-ruyi-pavilion/poster.jpg +0 -0
- package/public/images/series/the-eternal-fragrance/poster-2.jpg +0 -0
- package/public/images/series/the-eternal-fragrance/poster.jpg +0 -0
- package/public/images/series/the-fallen-flowers/poster.jpg +0 -0
- package/public/images/series/the-legend-of-white-snake/poster.jpg +0 -0
- package/public/images/series/veil-of-shadows/poster.jpg +0 -0
- package/public/images/series/wanhua-sin/poster-2.jpg +0 -0
- package/public/images/series/wanhua-sin/poster.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj10yogj62u03s0npe02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj2pdvyj61li25jkjl02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj71177j62672w9e8302.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj7twpfj62192197rw02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj8jfu8j62c03401kx02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj9dhdsj622k2l77wh02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkja31s4j62bw3407r802.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjcf331j61402ey1kx02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjdx8gqj62602pihdt02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjfvxnzj62432n57wh02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixniv2yj627y4fxb2b02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixsvbdaj625k2vfu1102.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixxxdaoj62c03501l202.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixzeef9j62c0340npd02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy0vs6tj626t4rbhdu02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy21wwuj62bt34hx6q02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly4wr85j62z629y7wh02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly67oilj623u2t5npd02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly9qtw8j62c0340u0x02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyem4m4j63402c01kz02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlygkoq4j627s2yeavd02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyjh0daj62bw340nm102.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlylejlhj62bw3401kx02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiaa8b7fj627j2y67wh02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiagw3ezj61s02ebu0x02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiaivng3j61rv2ebx6p02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiajuixvj62bw340qv502.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiapyartj62bw340hdt02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaibkwu4ij61vy2iq1kx02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaic1fvkpj61u92eo1ky02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaic3cruhj62822ywb2902.jpg +0 -0
- package/public/manifest.json +25 -0
- package/public/mcp-logo-128.png +0 -0
- package/public/mcp-logo-64.png +0 -0
- package/public/mcp-logo.svg +16 -0
- package/server/agent/agentCore.js +542 -74
- package/server/agent/appPaths.js +1 -0
- package/server/agent/audioPlayer.js +4 -2
- package/server/agent/citations.js +294 -0
- package/server/agent/clarificationAnalytics.js +477 -0
- package/server/agent/clarifyingQuestions.js +1089 -0
- package/server/agent/cli.js +549 -125
- package/server/agent/commandFeatures.js +1 -0
- package/server/agent/commandRegistry.js +946 -879
- package/server/agent/commands.js +546 -272
- package/server/agent/config.js +581 -412
- package/server/agent/contextCompactor.js +2 -2
- package/server/agent/debate.js +235 -0
- package/server/agent/eventBus.js +3 -0
- package/server/agent/feedbackLearning.js +217 -0
- package/server/agent/llmClient.js +512 -394
- package/server/agent/mcpClient.js +182 -32
- package/server/agent/memory.js +283 -224
- package/server/agent/modelManager.js +79 -39
- package/server/agent/profiles.js +178 -0
- package/server/agent/publicTools.js +22 -0
- package/server/agent/queryDetails.js +68 -8
- package/server/agent/quizData.js +398 -384
- package/server/agent/research.js +121 -79
- package/server/agent/sanitizer.js +104 -13
- package/server/agent/session.js +319 -11
- package/server/agent/subagent.js +8 -3
- package/server/agent/suggestionTui.js +294 -0
- package/server/agent/suggestions.js +69 -3
- package/server/agent/telemetry.js +119 -0
- package/server/agent/terminalImage.js +146 -27
- package/server/agent/theme.js +17 -0
- package/server/agent/toolRouter.js +16 -1
- package/server/agent/tools.js +424 -225
- package/server/agent/ui.js +486 -64
- package/server/agent/version.js +2 -4
- package/server/agent/webSearch.js +111 -11
- package/server/agent/youtubeAuth.js +24 -6
- package/server/apiRouter.js +4222 -0
- package/server/mcp/data/discography.js +25 -16
- package/server/mcp/data/gallery.js +308 -20
- package/server/mcp/data/profile.js +74 -44
- package/server/mcp/data/timeline.js +1 -1
- package/server/mcp/data/works.js +51 -17
- package/server/mcp/dataAdapter.js +5 -3
- package/server/mcp/handler.js +5 -11
- package/server/mcp/prompts.js +86 -0
- package/server/mcp/security.js +18 -15
- package/server/mcp/server.js +7 -2
- package/server/mcp/tools.js +66 -2
- package/server/web-chat/core/browserLauncher.js +18 -0
- package/server/web-chat/core/connection.js +194 -0
- package/server/web-chat/core/conversationContext.js +82 -0
- package/server/web-chat/core/directCatalogEngine.js +196 -0
- package/server/web-chat/core/dramaKnowledge.js +142 -0
- package/server/web-chat/core/errorHandler.js +51 -0
- package/server/web-chat/core/followUpEngine.js +182 -0
- package/server/web-chat/core/futureEventGuard.js +80 -0
- package/server/web-chat/core/memorySnapshot.js +12 -0
- package/server/web-chat/core/security.js +146 -0
- package/server/web-chat/core/toolInspector.js +52 -0
- package/server/web-chat/router.js +120 -0
- package/server/web-chat/routes/chatRoute.js +397 -0
- package/server/web-chat/routes/clarifyConfigRoute.js +98 -0
- package/server/web-chat/routes/clarifyRoute.js +83 -0
- package/server/web-chat/routes/compareRoute.js +79 -0
- package/server/web-chat/routes/exportRoute.js +59 -0
- package/server/web-chat/routes/feedbackRoute.js +57 -0
- package/server/web-chat/routes/newsRoute.js +33 -0
- package/server/web-chat/routes/quizRoute.js +11 -0
- package/server/web-chat/routes/staticRoute.js +103 -0
- package/server/web-chat/routes/statusRoute.js +52 -0
- package/server/web-chat/routes/suggestRoute.js +77 -0
- package/server/web-chat/routes/timelineRoute.js +34 -0
- package/server/web-chat/routes/verifyRoute.js +57 -0
- package/server/web-chat/server.js +116 -0
- package/server/web-chat/ui/htmlBuilder.js +91 -0
- package/server/web-chat/ui/previews/agentBubble.preview.html +134 -0
- package/server/web-chat/ui/scripts/agentBubbleSystem.js +320 -0
- package/server/web-chat/ui/scripts/client.js +8397 -0
- package/server/web-chat/ui/scripts/kikuDb.js +177 -0
- package/server/web-chat/ui/scripts/suggestionsData.js +118 -0
- package/server/web-chat/ui/scripts/webchatI18n.js +291 -0
- package/server/web-chat/ui/styles/main.css +8568 -0
- package/server/web-chat/ui/templates/body.html +796 -0
- package/server/webChatHtml.js +1 -0
- package/server/webChatServer.js +6 -0
- package/src/data/discographyData.js +892 -866
- package/src/data/galleryData.js +1805 -561
- package/src/data/profileData.js +79 -2
- package/src/data/socialUpdatesData.js +106 -47
- package/src/data/timelineData.js +1 -1
- package/src/data/worksData.js +25 -4
- package/tokens.css +97 -0
- package/server/agent/updateManager.js +0 -456
|
@@ -7,6 +7,26 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
|
7
7
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
8
8
|
import { KIKU_DATA_DIR } from './appPaths.js';
|
|
9
9
|
|
|
10
|
+
function getPredefinedKikuMcpTools() {
|
|
11
|
+
return [
|
|
12
|
+
{ name: 'search_works', description: 'Search drama and film works by year, title, or role', inputSchema: { type: 'object', properties: { query: { type: 'string' }, year: { type: 'number' } } } },
|
|
13
|
+
{ name: 'get_work_detail', description: 'Get detailed information about a specific drama or film', inputSchema: { type: 'object', properties: { title: { type: 'string' } }, required: ['title'] } },
|
|
14
|
+
{ name: 'get_character_roles', description: 'Explore character roles played by Ju Jingyi', inputSchema: { type: 'object', properties: { role: { type: 'string' } } } },
|
|
15
|
+
{ name: 'search_discography', description: 'Search singles, albums, OSTs, and songs', inputSchema: { type: 'object', properties: { query: { type: 'string' }, type: { type: 'string' } } } },
|
|
16
|
+
{ name: 'get_music_videos', description: 'Find official music videos, dance practices, and live stages', inputSchema: { type: 'object', properties: { query: { type: 'string' } } } },
|
|
17
|
+
{ name: 'get_media_links', description: 'Get streaming and watch links across YouTube, Bilibili, and QQ Music', inputSchema: { type: 'object', properties: { title: { type: 'string' } } } },
|
|
18
|
+
{ name: 'get_brand_endorsements', description: 'Look up global brand ambassadorships and fashion partnerships', inputSchema: { type: 'object', properties: { brand: { type: 'string' }, tier: { type: 'string' } } } },
|
|
19
|
+
{ name: 'get_career_timeline', description: 'Explore milestones from SNH48 debut to solo career', inputSchema: { type: 'object', properties: { era: { type: 'string' }, year: { type: 'number' } } } },
|
|
20
|
+
{ name: 'get_awards', description: 'Look up music awards, acting honors, and Weibo Night recognitions', inputSchema: { type: 'object', properties: { query: { type: 'string' }, year: { type: 'number' } } } },
|
|
21
|
+
{ name: 'get_profile_and_fandom', description: 'Get biographical details, fandom facts, and official links', inputSchema: { type: 'object', properties: { topic: { type: 'string' } } } },
|
|
22
|
+
{ name: 'get_quotes', description: 'Get famous quotes and inspirational sayings', inputSchema: { type: 'object', properties: { topic: { type: 'string' } } } },
|
|
23
|
+
{ name: 'get_social_feed', description: 'Fetch official social media posts and updates', inputSchema: { type: 'object', properties: { platform: { type: 'string' }, limit: { type: 'number' } } } },
|
|
24
|
+
{ name: 'get_gallery_photos', description: 'Search gallery photoshoot pictures and posters', inputSchema: { type: 'object', properties: { query: { type: 'string' }, tag: { type: 'string' } } } },
|
|
25
|
+
{ name: 'global_search', description: 'Universal search across dramas, songs, brands, and awards', inputSchema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] } },
|
|
26
|
+
{ name: 'get_system_stats', description: 'View knowledge base statistics and metrics', inputSchema: { type: 'object', properties: {} } },
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
|
|
10
30
|
export class McpClientManager {
|
|
11
31
|
constructor(options = {}) {
|
|
12
32
|
this.clientInfo = options.clientInfo || { name: 'kiku-agent', version: PACKAGE_VERSION };
|
|
@@ -22,6 +42,40 @@ export class McpClientManager {
|
|
|
22
42
|
this.persistedConfigs = new Map();
|
|
23
43
|
this.configSources = new Map();
|
|
24
44
|
this.toolRegistry = new Map();
|
|
45
|
+
this.cooldowns = new Map();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_isRateLimited(serverName) {
|
|
49
|
+
const cd = this.cooldowns.get(serverName);
|
|
50
|
+
if (!cd) return false;
|
|
51
|
+
if (Date.now() < cd.until) return true;
|
|
52
|
+
this.cooldowns.delete(serverName);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_registerRateLimit(serverName, errorMessage, { logWarning = false } = {}) {
|
|
57
|
+
const is429 = String(errorMessage).includes('429') ||
|
|
58
|
+
/rate limit|too many requests/i.test(String(errorMessage));
|
|
59
|
+
if (!is429) return false;
|
|
60
|
+
|
|
61
|
+
let retrySec = 45;
|
|
62
|
+
try {
|
|
63
|
+
const match = String(errorMessage).match(/"retryAfterSeconds"\s*:\s*(\d+)/i) ||
|
|
64
|
+
String(errorMessage).match(/(\d+)\s*seconds?/i);
|
|
65
|
+
if (match && match[1]) retrySec = Math.min(Math.max(parseInt(match[1], 10), 10), 300);
|
|
66
|
+
} catch {}
|
|
67
|
+
|
|
68
|
+
const existing = this.cooldowns.get(serverName);
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const until = now + retrySec * 1000;
|
|
71
|
+
|
|
72
|
+
if (!existing || now >= existing.until) {
|
|
73
|
+
this.cooldowns.set(serverName, { until, warned: true, error: errorMessage });
|
|
74
|
+
if (logWarning) {
|
|
75
|
+
console.warn(`[MCP] Notice: Server "${serverName}" hit rate limit (429). Cooldown for ${retrySec}s — requests paused to avoid spam.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
25
79
|
}
|
|
26
80
|
|
|
27
81
|
_trackConfigSource(serverName, filePath) {
|
|
@@ -58,12 +112,30 @@ export class McpClientManager {
|
|
|
58
112
|
return changed;
|
|
59
113
|
}
|
|
60
114
|
|
|
61
|
-
async
|
|
115
|
+
async _ensureConnected(serverName) {
|
|
116
|
+
const entry = this.servers.get(serverName);
|
|
117
|
+
if (entry && entry.client && !entry.isLazy) {
|
|
118
|
+
return entry;
|
|
119
|
+
}
|
|
120
|
+
const cfg = this.persistedConfigs.get(serverName);
|
|
121
|
+
if (!cfg) {
|
|
122
|
+
throw new Error(`MCP server "${serverName}" configuration not found.`);
|
|
123
|
+
}
|
|
124
|
+
await this.connect(serverName, cfg, { persist: false, fetchMetadata: false });
|
|
125
|
+
return this.servers.get(serverName);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async connect(serverName, serverConfig = {}, { persist = true, fetchMetadata = false } = {}) {
|
|
62
129
|
const name = String(serverName || '').trim();
|
|
63
130
|
if (!name) throw new Error('Server name is required to connect to MCP.');
|
|
64
131
|
|
|
65
132
|
if (this.servers.has(name)) {
|
|
66
|
-
|
|
133
|
+
const existing = this.servers.get(name);
|
|
134
|
+
if (existing.client) {
|
|
135
|
+
await this.disconnect(name, { persist: false });
|
|
136
|
+
} else {
|
|
137
|
+
this.servers.delete(name);
|
|
138
|
+
}
|
|
67
139
|
}
|
|
68
140
|
|
|
69
141
|
let transport;
|
|
@@ -109,28 +181,39 @@ export class McpClientManager {
|
|
|
109
181
|
try {
|
|
110
182
|
await client.connect(transport);
|
|
111
183
|
|
|
112
|
-
let tools = [];
|
|
113
|
-
let resources = [];
|
|
114
|
-
let prompts = [];
|
|
184
|
+
let tools = serverConfig.tools || [];
|
|
185
|
+
let resources = serverConfig.resources || [];
|
|
186
|
+
let prompts = serverConfig.prompts || [];
|
|
115
187
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
188
|
+
// If tools not pre-provided, fetch once upon connection
|
|
189
|
+
if (!tools || tools.length === 0) {
|
|
190
|
+
try {
|
|
191
|
+
if (!this._isRateLimited(name)) {
|
|
192
|
+
const toolsResult = await client.listTools();
|
|
193
|
+
tools = toolsResult.tools || [];
|
|
194
|
+
}
|
|
195
|
+
} catch (err) {
|
|
196
|
+
this._registerRateLimit(name, err.message, { logWarning: false });
|
|
197
|
+
}
|
|
121
198
|
}
|
|
122
199
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
200
|
+
if (fetchMetadata) {
|
|
201
|
+
try {
|
|
202
|
+
if (!this._isRateLimited(name)) {
|
|
203
|
+
const resResult = await client.listResources();
|
|
204
|
+
resources = resResult.resources || [];
|
|
205
|
+
}
|
|
206
|
+
} catch {}
|
|
127
207
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
208
|
+
try {
|
|
209
|
+
if (!this._isRateLimited(name)) {
|
|
210
|
+
const promptResult = await client.listPrompts();
|
|
211
|
+
prompts = promptResult.prompts || [];
|
|
212
|
+
}
|
|
213
|
+
} catch {}
|
|
214
|
+
}
|
|
132
215
|
|
|
133
|
-
const cleanConfig = { ...serverConfig, enabled: true };
|
|
216
|
+
const cleanConfig = { ...serverConfig, enabled: true, tools };
|
|
134
217
|
delete cleanConfig.lastError;
|
|
135
218
|
|
|
136
219
|
const serverEntry = {
|
|
@@ -142,6 +225,7 @@ export class McpClientManager {
|
|
|
142
225
|
resources,
|
|
143
226
|
prompts,
|
|
144
227
|
connectedAt: new Date().toISOString(),
|
|
228
|
+
isLazy: false,
|
|
145
229
|
};
|
|
146
230
|
|
|
147
231
|
this.servers.set(name, serverEntry);
|
|
@@ -173,8 +257,8 @@ export class McpClientManager {
|
|
|
173
257
|
if (!entry) return false;
|
|
174
258
|
|
|
175
259
|
try {
|
|
176
|
-
await entry.client.close().catch(() => {});
|
|
177
|
-
await entry.transport.close().catch(() => {});
|
|
260
|
+
if (entry.client) await entry.client.close().catch(() => {});
|
|
261
|
+
if (entry.transport) await entry.transport.close().catch(() => {});
|
|
178
262
|
if (entry.transport?._process) {
|
|
179
263
|
try {
|
|
180
264
|
entry.transport._process.kill('SIGKILL');
|
|
@@ -310,8 +394,33 @@ export class McpClientManager {
|
|
|
310
394
|
};
|
|
311
395
|
}
|
|
312
396
|
|
|
313
|
-
|
|
314
|
-
|
|
397
|
+
if (this._isRateLimited(registration.serverName)) {
|
|
398
|
+
const cd = this.cooldowns.get(registration.serverName);
|
|
399
|
+
const remainingSec = Math.max(1, Math.ceil((cd.until - Date.now()) / 1000));
|
|
400
|
+
return {
|
|
401
|
+
status: 'rate_limited',
|
|
402
|
+
server: registration.serverName,
|
|
403
|
+
tool: registration.originalName,
|
|
404
|
+
message: `MCP server "${registration.serverName}" is currently in rate-limit cooldown (${remainingSec}s remaining). Requests are paused to prevent rate limiting.`,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
let serverEntry = this.servers.get(registration.serverName);
|
|
409
|
+
if (!serverEntry || !serverEntry.client || serverEntry.isLazy) {
|
|
410
|
+
try {
|
|
411
|
+
serverEntry = await this._ensureConnected(registration.serverName);
|
|
412
|
+
} catch (connErr) {
|
|
413
|
+
this._registerRateLimit(registration.serverName, connErr.message, { logWarning: false });
|
|
414
|
+
return {
|
|
415
|
+
status: 'error',
|
|
416
|
+
server: registration.serverName,
|
|
417
|
+
tool: registration.originalName,
|
|
418
|
+
message: `Could not connect to MCP server "${registration.serverName}": ${connErr.message}`,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (!serverEntry || !serverEntry.client) {
|
|
315
424
|
return {
|
|
316
425
|
status: 'error',
|
|
317
426
|
message: `MCP server "${registration.serverName}" is disconnected.`,
|
|
@@ -358,6 +467,7 @@ export class McpClientManager {
|
|
|
358
467
|
_rawMcp: response.content,
|
|
359
468
|
};
|
|
360
469
|
} catch (err) {
|
|
470
|
+
this._registerRateLimit(registration.serverName, err.message, { logWarning: false });
|
|
361
471
|
return {
|
|
362
472
|
status: 'error',
|
|
363
473
|
server: registration.serverName,
|
|
@@ -373,14 +483,18 @@ export class McpClientManager {
|
|
|
373
483
|
|
|
374
484
|
for (const [name, server] of this.servers.entries()) {
|
|
375
485
|
seen.add(name);
|
|
486
|
+
const isRateLimited = this._isRateLimited(name);
|
|
487
|
+
const cd = this.cooldowns.get(name);
|
|
376
488
|
list.push({
|
|
377
489
|
name,
|
|
378
|
-
status: 'connected',
|
|
379
|
-
type: server.config
|
|
380
|
-
toolsCount: server.tools
|
|
381
|
-
resourcesCount: server.resources
|
|
382
|
-
promptsCount: server.prompts
|
|
490
|
+
status: isRateLimited ? 'rate_limited' : 'connected',
|
|
491
|
+
type: server.config?.type || (server.config?.command ? 'stdio' : 'streamable-http'),
|
|
492
|
+
toolsCount: server.tools?.length || 0,
|
|
493
|
+
resourcesCount: server.resources?.length || 0,
|
|
494
|
+
promptsCount: server.prompts?.length || 0,
|
|
383
495
|
connectedAt: server.connectedAt,
|
|
496
|
+
isLazy: Boolean(server.isLazy),
|
|
497
|
+
cooldownRemainingSec: isRateLimited ? Math.max(1, Math.ceil((cd.until - Date.now()) / 1000)) : null,
|
|
384
498
|
sources: [...(this.configSources.get(name) || [])],
|
|
385
499
|
config: server.config,
|
|
386
500
|
});
|
|
@@ -392,9 +506,9 @@ export class McpClientManager {
|
|
|
392
506
|
name,
|
|
393
507
|
status: cfg.lastError ? 'error' : (cfg.enabled === false ? 'disabled' : 'saved_offline'),
|
|
394
508
|
type: cfg.type || (cfg.command ? 'stdio' : 'streamable-http'),
|
|
395
|
-
toolsCount:
|
|
396
|
-
resourcesCount:
|
|
397
|
-
promptsCount:
|
|
509
|
+
toolsCount: (cfg.tools || []).length,
|
|
510
|
+
resourcesCount: (cfg.resources || []).length,
|
|
511
|
+
promptsCount: (cfg.prompts || []).length,
|
|
398
512
|
error: cfg.lastError || null,
|
|
399
513
|
sources: [...(this.configSources.get(name) || [])],
|
|
400
514
|
config: cfg,
|
|
@@ -455,8 +569,44 @@ export class McpClientManager {
|
|
|
455
569
|
this._trackConfigSource(name, filePath);
|
|
456
570
|
if (cfg.enabled === false || cfg.disabled === true) continue;
|
|
457
571
|
if (this.servers.has(name)) continue;
|
|
572
|
+
|
|
573
|
+
// If tools are cached or it's a known Kiku remote MCP server, register lazily without network call on startup
|
|
574
|
+
let tools = cfg.tools;
|
|
575
|
+
if (!tools || tools.length === 0) {
|
|
576
|
+
if (name === 'kiku-mcp' || /jujingyi-kikuuu|vercel\.app/i.test(cfg.url || '')) {
|
|
577
|
+
tools = getPredefinedKikuMcpTools();
|
|
578
|
+
cfg.tools = tools;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (tools && Array.isArray(tools) && tools.length > 0) {
|
|
583
|
+
const serverEntry = {
|
|
584
|
+
name,
|
|
585
|
+
config: cfg,
|
|
586
|
+
client: null,
|
|
587
|
+
transport: null,
|
|
588
|
+
tools,
|
|
589
|
+
resources: cfg.resources || [],
|
|
590
|
+
prompts: cfg.prompts || [],
|
|
591
|
+
connectedAt: new Date().toISOString(),
|
|
592
|
+
isLazy: true,
|
|
593
|
+
};
|
|
594
|
+
this.servers.set(name, serverEntry);
|
|
595
|
+
this._rebuildToolRegistry();
|
|
596
|
+
results.push({
|
|
597
|
+
success: true,
|
|
598
|
+
serverName: name,
|
|
599
|
+
toolsCount: tools.length,
|
|
600
|
+
resourcesCount: (cfg.resources || []).length,
|
|
601
|
+
promptsCount: (cfg.prompts || []).length,
|
|
602
|
+
tools,
|
|
603
|
+
lazy: true,
|
|
604
|
+
});
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
|
|
458
608
|
try {
|
|
459
|
-
const res = await this.connect(name, cfg, { persist: false });
|
|
609
|
+
const res = await this.connect(name, cfg, { persist: false, fetchMetadata: false });
|
|
460
610
|
results.push(res);
|
|
461
611
|
} catch (err) {
|
|
462
612
|
results.push({ success: false, serverName: name, error: err.message });
|