Skip to content

Commit 79b998d

Browse files
authored
feat: add MCP & call tools (#430)
* feat: add call tools * feat: add chat call tools * feat: add MCP & call LLM tools * docs: update notes * build: build error * chore: replace iconfont * chore: web icon * chore: add
1 parent 839a51b commit 79b998d

28 files changed

Lines changed: 717 additions & 71 deletions

File tree

docs/content.en/docs/release-notes/_index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ Information about release notes of Coco Server is provided here.
3030
- feat: web components assistant #422
3131
- feat: right-click menu support for search #423
3232
- feat: add chat mode launch page #424
33-
feat: ai assistant supports search and paging #431
33+
- feat: add MCP & call LLM tools #430
34+
- feat: ai assistant supports search and paging #431
3435
- feat: data sources support displaying customized icons #432
3536

3637
### Bug fix

public/assets/fonts/icons/iconfont.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ pub fn run() {
111111
server::servers::disable_server,
112112
server::auth::handle_sso_callback,
113113
server::profile::get_user_profiles,
114-
server::datasource::get_datasources_by_server,
114+
server::datasource::datasource_search,
115+
server::datasource::mcp_server_search,
115116
server::connector::get_connectors_by_server,
116117
search::query_coco_fusion,
117118
assistant::chat_history,

src-tauri/src/server/datasource.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub async fn refresh_all_datasources<R: Runtime>(_app_handle: &AppHandle<R>) ->
4848
// dbg!("fetch datasources for server: {}", &server.id);
4949

5050
// Attempt to get datasources by server, and continue even if it fails
51-
let connectors = match get_datasources_by_server(server.id.as_str(), None).await {
51+
let connectors = match datasource_search(server.id.as_str(), None).await {
5252
Ok(connectors) => {
5353
// Process connectors only after fetching them
5454
let connectors_map: HashMap<String, DataSource> = connectors
@@ -90,7 +90,7 @@ pub async fn refresh_all_datasources<R: Runtime>(_app_handle: &AppHandle<R>) ->
9090
}
9191

9292
#[tauri::command]
93-
pub async fn get_datasources_by_server(
93+
pub async fn datasource_search(
9494
id: &str,
9595
options: Option<GetDatasourcesByServerOptions>,
9696
) -> Result<Vec<DataSource>, String> {
@@ -144,3 +144,59 @@ pub async fn get_datasources_by_server(
144144

145145
Ok(datasources)
146146
}
147+
148+
#[tauri::command]
149+
pub async fn mcp_server_search(
150+
id: &str,
151+
options: Option<GetDatasourcesByServerOptions>,
152+
) -> Result<Vec<DataSource>, String> {
153+
let from = options.as_ref().and_then(|opt| opt.from).unwrap_or(0);
154+
let size = options.as_ref().and_then(|opt| opt.size).unwrap_or(10000);
155+
let query = options
156+
.and_then(|opt| opt.query)
157+
.unwrap_or(String::default());
158+
159+
let mut body = serde_json::json!({
160+
"from": from,
161+
"size": size,
162+
});
163+
164+
if !query.is_empty() {
165+
body["query"] = serde_json::json!({
166+
"bool": {
167+
"must": [{
168+
"query_string": {
169+
"fields": ["combined_fulltext"],
170+
"query": query,
171+
"fuzziness": "AUTO",
172+
"fuzzy_prefix_length": 2,
173+
"fuzzy_max_expansions": 10,
174+
"fuzzy_transpositions": true,
175+
"allow_leading_wildcard": false
176+
}
177+
}]
178+
}
179+
});
180+
}
181+
182+
// Perform the async HTTP request outside the cache lock
183+
let resp = HttpClient::post(
184+
id,
185+
"/mcp_server/_search",
186+
None,
187+
Some(reqwest::Body::from(body.to_string())),
188+
)
189+
.await
190+
.map_err(|e| format!("Error fetching datasource: {}", e))?;
191+
192+
// Parse the search results from the response
193+
let mcp_server: Vec<DataSource> = parse_search_results(resp).await.map_err(|e| {
194+
dbg!("Error parsing search results: {}", &e);
195+
e.to_string()
196+
})?;
197+
198+
// Save the updated mcp_server to cache
199+
// save_datasource_to_cache(&id, mcp_server.clone());
200+
201+
Ok(mcp_server)
202+
}

src-tauri/src/server/servers.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::common::http::get_response_body_text;
22
use crate::common::register::SearchSourceRegistry;
33
use crate::common::server::{AuthProvider, Provider, Server, ServerAccessToken, Sso, Version};
44
use crate::server::connector::fetch_connectors_by_server;
5-
use crate::server::datasource::get_datasources_by_server;
5+
use crate::server::datasource::datasource_search;
66
use crate::server::http_client::HttpClient;
77
use crate::server::search::CocoSearchSource;
88
use crate::COCO_TAURI_STORE;
@@ -347,7 +347,7 @@ pub async fn refresh_coco_server_info<R: Runtime>(
347347

348348
// Refresh connectors and datasources (best effort)
349349
let _ = fetch_connectors_by_server(&id).await;
350-
let _ = get_datasources_by_server(&id, None).await;
350+
let _ = datasource_search(&id, None).await;
351351

352352
Ok(updated_server)
353353
}

src/api/axiosRequest.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,10 @@ export const Get = <T>(
7070
): Promise<[any, FcResponse<T> | undefined]> =>
7171
new Promise((resolve) => {
7272
const appStore = JSON.parse(localStorage.getItem("app-store") || "{}");
73-
// console.log("baseURL", appStore.state?.endpoint_http)
7473

75-
let baseURL = "";
76-
77-
if (import.meta.env.PROD) {
78-
baseURL = appStore.state?.endpoint_https;
74+
let baseURL = appStore.state?.endpoint_http;
75+
if (!baseURL || baseURL === "undefined") {
76+
baseURL = "";
7977
}
8078

8179
axios
@@ -103,12 +101,11 @@ export const Post = <T>(
103101
): Promise<[any, FcResponse<T> | undefined]> => {
104102
return new Promise((resolve) => {
105103
const appStore = JSON.parse(localStorage.getItem("app-store") || "{}");
106-
// console.log("baseURL", appStore.state?.endpoint_http)
107104

108-
let baseURL = "";
109105

110-
if (import.meta.env.PROD) {
111-
baseURL = appStore.state?.endpoint_https;
106+
let baseURL = appStore.state?.endpoint_http
107+
if (!baseURL || baseURL === "undefined") {
108+
baseURL = "";
112109
}
113110

114111
axios

src/commands/servers.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,12 @@ export function get_connectors_by_server(id: string): Promise<Connector[]> {
103103
return invokeWithErrorHandler(`get_connectors_by_server`, { id });
104104
}
105105

106-
export function get_datasources_by_server(id: string): Promise<DataSource[]> {
107-
return invokeWithErrorHandler(`get_datasources_by_server`, { id });
106+
export function datasource_search(id: string): Promise<DataSource[]> {
107+
return invokeWithErrorHandler(`datasource_search`, { id });
108+
}
109+
110+
export function mcp_server_search(id: string): Promise<DataSource[]> {
111+
return invokeWithErrorHandler(`mcp_server_search`, { id });
108112
}
109113

110114
export function connect_to_server(id: string, clientId: string): Promise<void> {

src/components/Assistant/AssistantList.tsx

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,24 +67,32 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) {
6767
size,
6868
};
6969

70-
if (debounceKeyword) {
70+
if (debounceKeyword || assistantIDs.length > 0) {
7171
body.query = {
7272
bool: {
73-
must: [
74-
{
75-
query_string: {
76-
fields: ["combined_fulltext"],
77-
query: debounceKeyword,
78-
fuzziness: "AUTO",
79-
fuzzy_prefix_length: 2,
80-
fuzzy_max_expansions: 10,
81-
fuzzy_transpositions: true,
82-
allow_leading_wildcard: false,
83-
},
84-
},
85-
],
73+
must: [],
8674
},
8775
};
76+
if (debounceKeyword) {
77+
body.query.bool.must.push({
78+
query_string: {
79+
fields: ["combined_fulltext"],
80+
query: debounceKeyword,
81+
fuzziness: "AUTO",
82+
fuzzy_prefix_length: 2,
83+
fuzzy_max_expansions: 10,
84+
fuzzy_transpositions: true,
85+
allow_leading_wildcard: false,
86+
},
87+
});
88+
}
89+
if (assistantIDs.length > 0) {
90+
body.query.bool.must.push({
91+
terms: {
92+
id: assistantIDs.map((id) => id),
93+
},
94+
});
95+
}
8896
}
8997

9098
if (isTauri) {
@@ -107,12 +115,6 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) {
107115

108116
let assistantList = response?.hits?.hits ?? [];
109117

110-
if (assistantIDs.length > 0) {
111-
assistantList = assistantList.filter((item: any) => {
112-
return assistantIDs.includes(item._id);
113-
});
114-
}
115-
116118
if (assistantList.length > 0) {
117119
const matched = assistantList.find((item: any) => {
118120
return item._id === currentAssistant?._id;

src/components/Assistant/Chat.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010

1111
import { useChatStore } from "@/stores/chatStore";
1212
import { useConnectStore } from "@/stores/connectStore";
13-
import { useSearchStore } from "@/stores/searchStore";
1413
import { useWindows } from "@/hooks/useWindows";
1514
import useMessageChunkData from "@/hooks/useMessageChunkData";
1615
import useWebSocket from "@/hooks/useWebSocket";
@@ -27,6 +26,7 @@ import { useAppStore } from "@/stores/appStore";
2726
interface ChatAIProps {
2827
isSearchActive?: boolean;
2928
isDeepThinkActive?: boolean;
29+
isMCPActive?: boolean;
3030
activeChatProp?: Chat;
3131
changeInput?: (val: string) => void;
3232
setIsSidebarOpen?: (value: boolean) => void;
@@ -52,6 +52,7 @@ const ChatAI = memo(
5252
changeInput,
5353
isSearchActive,
5454
isDeepThinkActive,
55+
isMCPActive,
5556
activeChatProp,
5657
setIsSidebarOpen,
5758
isSidebarOpen = false,
@@ -88,7 +89,6 @@ const ChatAI = memo(
8889

8990
const [isSidebarOpenChat, setIsSidebarOpenChat] = useState(isSidebarOpen);
9091
const [chats, setChats] = useState<Chat[]>([]);
91-
const sourceDataIds = useSearchStore((state) => state.sourceDataIds);
9292

9393
useEffect(() => {
9494
activeChatProp && setActiveChat(activeChatProp);
@@ -107,6 +107,7 @@ const ChatAI = memo(
107107
const {
108108
data: {
109109
query_intent,
110+
tools,
110111
fetch_source,
111112
pick_source,
112113
deep_read,
@@ -119,6 +120,7 @@ const ChatAI = memo(
119120

120121
const [loadingStep, setLoadingStep] = useState<Record<string, boolean>>({
121122
query_intent: false,
123+
tools: false,
122124
fetch_source: false,
123125
pick_source: false,
124126
deep_read: false,
@@ -161,10 +163,10 @@ const ChatAI = memo(
161163
setChats,
162164
isSearchActive,
163165
isDeepThinkActive,
164-
sourceDataIds,
166+
isMCPActive,
165167
changeInput,
166168
websocketSessionId,
167-
showChatHistory
169+
showChatHistory,
168170
);
169171

170172
const { dealMsg, messageTimeoutRef } = useMessageHandler(
@@ -388,6 +390,7 @@ const ChatAI = memo(
388390
activeChat={activeChat}
389391
curChatEnd={curChatEnd}
390392
query_intent={query_intent}
393+
tools={tools}
391394
fetch_source={fetch_source}
392395
pick_source={pick_source}
393396
deep_read={deep_read}

src/components/Assistant/ChatContent.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface ChatContentProps {
1616
activeChat?: Chat;
1717
curChatEnd: boolean;
1818
query_intent?: IChunkData;
19+
tools?: IChunkData;
1920
fetch_source?: IChunkData;
2021
pick_source?: IChunkData;
2122
deep_read?: IChunkData;
@@ -32,6 +33,7 @@ export const ChatContent = ({
3233
activeChat,
3334
curChatEnd,
3435
query_intent,
36+
tools,
3537
fetch_source,
3638
pick_source,
3739
deep_read,
@@ -94,6 +96,7 @@ export const ChatContent = ({
9496
))}
9597
{(!curChatEnd ||
9698
query_intent ||
99+
tools ||
97100
fetch_source ||
98101
pick_source ||
99102
deep_read ||
@@ -113,6 +116,7 @@ export const ChatContent = ({
113116
onResend={handleSendMessage}
114117
isTyping={!curChatEnd}
115118
query_intent={query_intent}
119+
tools={tools}
116120
fetch_source={fetch_source}
117121
pick_source={pick_source}
118122
deep_read={deep_read}

0 commit comments

Comments
 (0)