Skip to content

Commit ed4cacf

Browse files
authored
fix: all mcp tools exposed to main agent (#5252)
1 parent 52d1979 commit ed4cacf

File tree

11 files changed

+572
-235
lines changed

11 files changed

+572
-235
lines changed

astrbot/core/astr_main_agent.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
from astrbot.core.platform.astr_message_event import AstrMessageEvent
4343
from astrbot.core.provider import Provider
4444
from astrbot.core.provider.entities import ProviderRequest
45-
from astrbot.core.provider.manager import llm_tools
4645
from astrbot.core.skills.skill_manager import SkillManager, build_skills_prompt
4746
from astrbot.core.star.context import Context
4847
from astrbot.core.star.star_handler import star_map
@@ -770,14 +769,6 @@ def _plugin_tool_fix(event: AstrMessageEvent, req: ProviderRequest) -> None:
770769
if plugin.name in event.plugins_name or plugin.reserved:
771770
new_tool_set.add_tool(tool)
772771
req.func_tool = new_tool_set
773-
else:
774-
# mcp tools
775-
tool_set = req.func_tool
776-
if not tool_set:
777-
tool_set = ToolSet()
778-
for tool in llm_tools.func_list:
779-
if isinstance(tool, MCPTool):
780-
tool_set.add_tool(tool)
781772

782773

783774
async def _handle_webchat(

dashboard/src/components/shared/AstrBotConfigV4.vue

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
44
import { ref, computed } from 'vue'
55
import ConfigItemRenderer from './ConfigItemRenderer.vue'
66
import TemplateListEditor from './TemplateListEditor.vue'
7+
import PersonaQuickPreview from './PersonaQuickPreview.vue'
78
import { useI18n, useModuleI18n } from '@/i18n/composables'
89
910
@@ -274,6 +275,16 @@ function getSpecialSubtype(value) {
274275
</div>
275276
</v-col>
276277
</v-row>
278+
279+
<!-- Default Persona Quick Preview 全宽显示区域 -->
280+
<v-row
281+
v-if="!itemMeta?.invisible && itemMeta?._special === 'select_persona' && itemKey === 'provider_settings.default_personality'"
282+
class="persona-preview-row"
283+
>
284+
<v-col cols="12" class="persona-preview-display">
285+
<PersonaQuickPreview :model-value="createSelectorModel(itemKey).value" />
286+
</v-col>
287+
</v-row>
277288
</template>
278289
<v-divider class="config-divider"
279290
v-if="shouldShowItem(itemMeta, itemKey) && hasVisibleItemsAfter(metadata[metadataKey].items, index)"></v-divider>
@@ -433,6 +444,15 @@ function getSpecialSubtype(value) {
433444
padding: 0 8px;
434445
}
435446
447+
.persona-preview-row {
448+
margin: 16px;
449+
margin-top: 0;
450+
}
451+
452+
.persona-preview-display {
453+
padding: 0 8px;
454+
}
455+
436456
.selected-plugins-full-width {
437457
background-color: rgba(var(--v-theme-primary), 0.05);
438458
border: 1px solid rgba(var(--v-theme-primary), 0.1);
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
<template>
2+
<div class="persona-preview-card">
3+
<div class="preview-header">
4+
<small>{{ tm('personaQuickPreview.title') }}</small>
5+
</div>
6+
7+
<div v-if="loading" class="preview-loading">
8+
<v-progress-circular indeterminate size="18" width="2" color="primary" class="mr-2" />
9+
<small class="text-grey">{{ tm('personaQuickPreview.loading') }}</small>
10+
</div>
11+
12+
<div v-else-if="!modelValue" class="preview-empty">
13+
<small class="text-grey">{{ tm('personaQuickPreview.noPersonaSelected') }}</small>
14+
</div>
15+
16+
<div v-else-if="!personaData" class="preview-empty">
17+
<small class="text-grey">{{ tm('personaQuickPreview.personaNotFound') }}</small>
18+
</div>
19+
20+
<div v-else class="preview-content">
21+
<div class="section-title">{{ tm('personaQuickPreview.systemPromptLabel') }}</div>
22+
<pre class="prompt-content">{{ personaData.system_prompt || '' }}</pre>
23+
24+
<div class="section-title mt-3">{{ tm('personaQuickPreview.toolsLabel') }}</div>
25+
<div class="chip-wrap tools-wrap">
26+
<v-chip
27+
v-if="personaData.tools === null"
28+
size="small"
29+
color="success"
30+
variant="tonal"
31+
label
32+
>
33+
{{ tm('personaQuickPreview.allToolsWithCount', { count: allToolsCount }) }}
34+
</v-chip>
35+
<div v-for="tool in resolvedTools" v-else :key="tool.name" class="tool-item">
36+
<v-chip
37+
size="small"
38+
color="primary"
39+
variant="outlined"
40+
label
41+
>
42+
{{ tool.name }}
43+
</v-chip>
44+
<small v-if="tool.origin || tool.origin_name" class="text-grey tool-meta">
45+
<span v-if="tool.origin">{{ tm('personaQuickPreview.originLabel') }}: {{ tool.origin }}</span>
46+
<span v-if="tool.origin_name"> | {{ tm('personaQuickPreview.originNameLabel') }}: {{ tool.origin_name }}</span>
47+
</small>
48+
</div>
49+
<small v-if="personaData.tools !== null && normalizedTools.length === 0" class="text-grey">
50+
{{ tm('personaQuickPreview.noTools') }}
51+
</small>
52+
</div>
53+
54+
<div class="section-title mt-3">{{ tm('personaQuickPreview.skillsLabel') }}</div>
55+
<div class="chip-wrap">
56+
<v-chip
57+
v-if="personaData.skills === null"
58+
size="small"
59+
color="success"
60+
variant="tonal"
61+
label
62+
>
63+
{{ tm('personaQuickPreview.allSkillsWithCount', { count: allSkillsCount }) }}
64+
</v-chip>
65+
<v-chip
66+
v-for="skillName in normalizedSkills"
67+
v-else
68+
:key="skillName"
69+
size="small"
70+
color="primary"
71+
variant="outlined"
72+
label
73+
>
74+
{{ skillName }}
75+
</v-chip>
76+
<small v-if="personaData.skills !== null && normalizedSkills.length === 0" class="text-grey">
77+
{{ tm('personaQuickPreview.noSkills') }}
78+
</small>
79+
</div>
80+
</div>
81+
</div>
82+
</template>
83+
84+
<script setup>
85+
import { computed, ref, watch, onMounted, onBeforeUnmount } from 'vue'
86+
import axios from 'axios'
87+
import { useModuleI18n } from '@/i18n/composables'
88+
89+
const props = defineProps({
90+
modelValue: {
91+
type: String,
92+
default: ''
93+
}
94+
})
95+
96+
const { tm } = useModuleI18n('core.shared')
97+
98+
const loading = ref(false)
99+
const personaData = ref(null)
100+
const toolMetaMap = ref({})
101+
const availableSkills = ref([])
102+
103+
const defaultPersonaData = {
104+
persona_id: 'default',
105+
system_prompt: 'You are a helpful and friendly assistant.',
106+
tools: null,
107+
skills: null
108+
}
109+
110+
const normalizedTools = computed(() => (Array.isArray(personaData.value?.tools) ? personaData.value.tools : []))
111+
const normalizedSkills = computed(() => (Array.isArray(personaData.value?.skills) ? personaData.value.skills : []))
112+
const allToolsCount = computed(() => Object.keys(toolMetaMap.value).length)
113+
const allSkillsCount = computed(() => availableSkills.value.length)
114+
const resolvedTools = computed(() =>
115+
normalizedTools.value.map((toolName) => {
116+
const meta = toolMetaMap.value[toolName] || {}
117+
return {
118+
name: toolName,
119+
origin: meta.origin || '',
120+
origin_name: meta.origin_name || ''
121+
}
122+
})
123+
)
124+
125+
async function loadToolsMeta() {
126+
try {
127+
const response = await axios.get('/api/tools/list')
128+
if (response.data?.status === 'ok') {
129+
const tools = response.data?.data || []
130+
const nextMap = {}
131+
for (const tool of tools) {
132+
if (!tool?.name) {
133+
continue
134+
}
135+
nextMap[tool.name] = {
136+
origin: tool.origin || '',
137+
origin_name: tool.origin_name || ''
138+
}
139+
}
140+
toolMetaMap.value = nextMap
141+
}
142+
} catch (error) {
143+
console.error('Failed to load tools metadata:', error)
144+
toolMetaMap.value = {}
145+
}
146+
}
147+
148+
async function loadSkillsMeta() {
149+
try {
150+
const response = await axios.get('/api/skills')
151+
if (response.data?.status === 'ok') {
152+
const payload = response.data?.data || []
153+
if (Array.isArray(payload)) {
154+
availableSkills.value = payload.filter((skill) => skill.active !== false)
155+
} else {
156+
const skills = payload.skills || []
157+
availableSkills.value = skills.filter((skill) => skill.active !== false)
158+
}
159+
} else {
160+
availableSkills.value = []
161+
}
162+
} catch (error) {
163+
console.error('Failed to load skills metadata:', error)
164+
availableSkills.value = []
165+
}
166+
}
167+
168+
async function loadPersonaPreview(personaId) {
169+
if (!personaId) {
170+
personaData.value = null
171+
return
172+
}
173+
174+
if (personaId === 'default') {
175+
personaData.value = defaultPersonaData
176+
return
177+
}
178+
179+
loading.value = true
180+
try {
181+
const response = await axios.get('/api/persona/list')
182+
if (response.data?.status === 'ok') {
183+
const personas = response.data?.data || []
184+
personaData.value = personas.find((item) => item.persona_id === personaId) || null
185+
} else {
186+
personaData.value = null
187+
}
188+
} catch (error) {
189+
console.error('Failed to load persona preview:', error)
190+
personaData.value = null
191+
} finally {
192+
loading.value = false
193+
}
194+
}
195+
196+
function handlePersonaSaved() {
197+
if (props.modelValue) {
198+
loadPersonaPreview(props.modelValue)
199+
}
200+
}
201+
202+
watch(
203+
() => props.modelValue,
204+
(newValue) => {
205+
loadPersonaPreview(newValue)
206+
},
207+
{ immediate: true }
208+
)
209+
210+
loadToolsMeta()
211+
loadSkillsMeta()
212+
213+
onMounted(() => {
214+
window.addEventListener('astrbot:persona-saved', handlePersonaSaved)
215+
})
216+
217+
onBeforeUnmount(() => {
218+
window.removeEventListener('astrbot:persona-saved', handlePersonaSaved)
219+
})
220+
</script>
221+
222+
<style scoped>
223+
.persona-preview-card {
224+
background-color: rgba(var(--v-theme-primary), 0.05);
225+
border: 1px solid rgba(var(--v-theme-primary), 0.1);
226+
border-radius: 8px;
227+
padding: 12px;
228+
}
229+
230+
.preview-header {
231+
margin-bottom: 8px;
232+
}
233+
234+
.preview-loading,
235+
.preview-empty {
236+
display: flex;
237+
align-items: center;
238+
min-height: 24px;
239+
}
240+
241+
.section-title {
242+
font-size: 0.75rem;
243+
color: rgb(var(--v-theme-primaryText));
244+
opacity: 0.85;
245+
}
246+
247+
.prompt-content {
248+
margin-top: 6px;
249+
max-height: 180px;
250+
overflow: auto;
251+
font-size: 0.78rem;
252+
line-height: 1.45;
253+
white-space: pre-wrap;
254+
word-break: break-word;
255+
background: rgba(0, 0, 0, 0.03);
256+
border-radius: 6px;
257+
padding: 8px;
258+
}
259+
260+
.chip-wrap {
261+
display: grid;
262+
gap: 6px;
263+
margin-top: 6px;
264+
}
265+
266+
.tools-wrap {
267+
max-height: 160px;
268+
overflow: auto;
269+
}
270+
271+
.tool-item {
272+
display: flex;
273+
align-items: center;
274+
flex-wrap: wrap;
275+
gap: 6px;
276+
}
277+
278+
.tool-meta {
279+
font-size: 0.74rem;
280+
}
281+
282+
@media (max-width: 600px) {
283+
.tools-wrap {
284+
max-height: 120px;
285+
}
286+
}
287+
</style>

dashboard/src/components/shared/PersonaSelector.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,16 @@ function openEditPersona(persona: Persona) {
188188
// 人格保存成功(创建或编辑)
189189
async function handlePersonaSaved(message: string) {
190190
console.log('人格保存成功:', message)
191+
const savedPersonaId = editingPersona.value?.persona_id || ''
191192
showPersonaDialog.value = false
192193
editingPersona.value = null
193194
// 刷新当前文件夹的人格列表
194195
await loadPersonasInFolder(currentFolderId.value)
196+
window.dispatchEvent(
197+
new CustomEvent('astrbot:persona-saved', {
198+
detail: { persona_id: savedPersonaId }
199+
})
200+
)
195201
}
196202
197203
// 错误处理

dashboard/src/i18n/locales/en-US/core/shared.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,23 @@
6262
"rootFolder": "All Personas",
6363
"emptyFolder": "This folder is empty"
6464
},
65+
"personaQuickPreview": {
66+
"title": "Quick Persona Preview",
67+
"loading": "Loading...",
68+
"noPersonaSelected": "No persona selected",
69+
"personaNotFound": "Persona details not found",
70+
"systemPromptLabel": "System Prompt",
71+
"toolsLabel": "Tools",
72+
"skillsLabel": "Skills",
73+
"originLabel": "Origin",
74+
"originNameLabel": "Origin Name",
75+
"allTools": "All tools available",
76+
"allToolsWithCount": "All tools available ({count})",
77+
"noTools": "No tools configured",
78+
"allSkills": "All Skills available",
79+
"allSkillsWithCount": "All Skills available ({count})",
80+
"noSkills": "No Skills configured"
81+
},
6582
"t2iTemplateEditor": {
6683
"buttonText": "Customize T2I Template",
6784
"dialogTitle": "Customize Text-to-Image HTML Template",

0 commit comments

Comments
 (0)