Skip to content

Commit ad53978

Browse files
committed
feat: add new fetch tool and related components
- Introduced a new fetch tool in `src/mastra/tools/fetch.tool.ts` for web content fetching and markdown conversion. - Implemented various search functionalities including DuckDuckGo, Google, and Bing search. - Added support for Google News RSS fetching. - Included URL validation and sanitization to ensure safe fetching. - Created a new React component for MCP A2A page in `app/chat/mcp-a2a/page.tsx` to manage MCP servers and tools. - Developed a workspace management page in `app/chat/workspaces/page.tsx` to handle file browsing and skill display. - Updated exports in `src/mastra/tools/index.ts` to include the new fetch tool.
1 parent ccbf099 commit ad53978

12 files changed

Lines changed: 2030 additions & 5 deletions

File tree

.github/copilot-instructions.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ applyTo: '**'
2222
- This tool will help you identify issues and suggest fixes.
2323
- This is especially useful for debugging and improving code quality.
2424
- Try run it before writing new code & after completing so you can ensure everything works correctly.
25+
- 🧪 When editing a page/component (especially `app/**/page.tsx`), use VS Code interaction error checks (`get_errors` / `#problems`) on the edited files before and after changes.
26+
- ⚙️ Internal error-tool enable flow (required for page edits):
27+
- 1) Activate VS Code interaction tools.
28+
- 2) Run `get_errors` on the exact files being edited (not project-wide).
29+
- 3) Fix reported issues.
30+
- 4) Run `get_errors` again on those same files to verify clean state.
31+
- 🌐 When unsure about framework/API behavior while editing UI pages, use internet research tools first (`#web`, `#websearch`, or `fetch_webpage`) and then apply fixes.
32+
- 🚫 Do not run project-wide type checks/lint commands by default for page edits. Use targeted `get_errors` checks unless the user explicitly asks for `typecheck`/`lint` runs.
2533
- 📌 To update your memory bank, use [#update-memory-bank] tool to add new information.
2634
- 🛠 Mastra mcp tools use [#mastradocs], [#mastraChanges], [#mastraexamples] tool.
2735
- These tools provide access to Mastra documentation, recent changes, and code examples.

app/chat/components/main-sidebar.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import {
3333
WorkflowIcon,
3434
CpuIcon,
3535
ActivityIcon,
36+
FolderTreeIcon,
37+
NetworkIcon,
3638
Loader2Icon,
3739
PlusIcon,
3840
} from 'lucide-react'
@@ -216,6 +218,26 @@ export function MainSidebar() {
216218
</SidebarGroupLabel>
217219
<SidebarGroupContent>
218220
<SidebarMenu>
221+
<SidebarMenuItem>
222+
<SidebarMenuButton
223+
className="hover:bg-muted/50 transition-all duration-200"
224+
onClick={() => handleNavClick('/chat/workspaces')}
225+
>
226+
<FolderTreeIcon className="mr-2 size-4 text-primary/70" />
227+
<span className="font-medium">Workspaces</span>
228+
</SidebarMenuButton>
229+
</SidebarMenuItem>
230+
231+
<SidebarMenuItem>
232+
<SidebarMenuButton
233+
className="hover:bg-muted/50 transition-all duration-200"
234+
onClick={() => handleNavClick('/chat/mcp-a2a')}
235+
>
236+
<NetworkIcon className="mr-2 size-4 text-primary/70" />
237+
<span className="font-medium">MCP / A2A</span>
238+
</SidebarMenuButton>
239+
</SidebarMenuItem>
240+
219241
<Collapsible
220242
open={openSection === 'agents'}
221243
onOpenChange={() => toggleSection('agents')}
@@ -284,12 +306,12 @@ export function MainSidebar() {
284306
<h3 className="font-bold text-sm tracking-tight text-foreground">
285307
{agent.name}
286308
</h3>
287-
{!!(agent.provider || agent.modelId) && (
309+
{!(!(agent.provider ?? agent.modelId)) && (
288310
<div className="mt-1.5 flex items-center gap-2">
289311
<div className="flex h-5 items-center rounded-md border border-primary/20 bg-primary/10 px-1.5 text-[9px] font-bold uppercase tracking-wider text-primary">
290312
<CpuIcon className="mr-1 size-3" />
291313
<span>
292-
{agent.provider && `${agent.provider} • `}
314+
{(Boolean(agent.provider)) && `${agent.provider} • `}
293315
{agent.modelId}
294316
</span>
295317
</div>
@@ -299,7 +321,7 @@ export function MainSidebar() {
299321

300322
{/* Description section */}
301323
<div className="p-4 pt-3">
302-
{agent.description ? (
324+
{(agent.description) ? (
303325
<div className="space-y-2">
304326
<span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground/70">
305327
Capabilities
@@ -309,7 +331,7 @@ export function MainSidebar() {
309331
</p>
310332
</div>
311333
) : (
312-
<p className="text-xs italic text-muted-foreground/60 italic leading-relaxed">
334+
<p className="text-xs italic text-muted-foreground/60 leading-relaxed">
313335
Specialized AI assistant ready to help with your task.
314336
</p>
315337
)}

app/chat/mcp-a2a/page.tsx

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { useMastraQuery } from '@/lib/hooks/use-mastra-query'
5+
6+
export default function McpA2APage() {
7+
const {
8+
useMcpServers,
9+
useMcpServerTools,
10+
useAgents,
11+
useA2ACard,
12+
} = useMastraQuery()
13+
14+
const serversResult = useMcpServers({ page: 0, perPage: 50 })
15+
const servers = serversResult.data?.servers ?? []
16+
17+
const [selectedServerId, setSelectedServerId] = useState<string>('')
18+
const activeServerId = selectedServerId || servers[0]?.id || ''
19+
20+
const toolsResult = useMcpServerTools(activeServerId)
21+
const serverTools = toolsResult.data?.tools ?? []
22+
23+
const agentsResult = useAgents()
24+
const agents = agentsResult.data ?? []
25+
26+
const [selectedAgentId, setSelectedAgentId] = useState<string>('')
27+
const activeAgentId = selectedAgentId || agents[0]?.id || ''
28+
29+
const a2aCardResult = useA2ACard(activeAgentId)
30+
const a2aCard = a2aCardResult.data
31+
32+
return (
33+
<div className="flex h-full min-h-0 flex-col gap-4 p-4 md:p-6">
34+
<h1 className="text-xl font-semibold">MCP / A2A</h1>
35+
36+
<div className="grid min-h-0 flex-1 grid-cols-1 gap-4 lg:grid-cols-2">
37+
<section className="min-h-0 overflow-auto rounded-lg border p-4">
38+
<div className="mb-3 flex items-center justify-between">
39+
<h2 className="text-sm font-medium">MCP Servers & Tools</h2>
40+
<select
41+
aria-label="Select MCP server"
42+
title="Select MCP server"
43+
className="rounded-md border bg-background px-2 py-1 text-sm"
44+
value={activeServerId}
45+
onChange={(e) => setSelectedServerId(e.target.value)}
46+
>
47+
{servers.map((server: { id: string; name?: string }) => (
48+
<option key={server.id} value={server.id}>
49+
{server.name ?? server.id}
50+
</option>
51+
))}
52+
</select>
53+
</div>
54+
55+
<ul className="space-y-2">
56+
{serverTools.length === 0 ? (
57+
<li className="text-sm text-muted-foreground">No MCP tools found.</li>
58+
) : (
59+
serverTools.map((tool: { id: string; description?: string }) => (
60+
<li key={tool.id} className="rounded-md border p-2">
61+
<div className="text-sm font-medium">{tool.id}</div>
62+
{typeof tool.description === 'string' && tool.description.trim().length > 0 ? (
63+
<p className="mt-1 text-xs text-muted-foreground">{tool.description}</p>
64+
) : null}
65+
</li>
66+
))
67+
)}
68+
</ul>
69+
</section>
70+
71+
<section className="min-h-0 overflow-auto rounded-lg border p-4">
72+
<div className="mb-3 flex items-center justify-between">
73+
<h2 className="text-sm font-medium">A2A Agent Card</h2>
74+
<select
75+
aria-label="Select A2A agent"
76+
title="Select A2A agent"
77+
className="rounded-md border bg-background px-2 py-1 text-sm"
78+
value={activeAgentId}
79+
onChange={(e) => setSelectedAgentId(e.target.value)}
80+
>
81+
{agents.map((agent) => (
82+
<option key={agent.id} value={agent.id}>
83+
{agent.name}
84+
</option>
85+
))}
86+
</select>
87+
</div>
88+
89+
{!a2aCard ? (
90+
<p className="text-sm text-muted-foreground">No A2A card available.</p>
91+
) : (
92+
<div className="space-y-3">
93+
<div>
94+
<div className="text-xs text-muted-foreground">Agent</div>
95+
<div className="text-sm font-medium">{a2aCard.name ?? activeAgentId}</div>
96+
</div>
97+
<div>
98+
<div className="text-xs text-muted-foreground">Description</div>
99+
<div className="text-sm">{a2aCard.description ?? 'N/A'}</div>
100+
</div>
101+
<div>
102+
<div className="text-xs text-muted-foreground">Skills</div>
103+
<ul className="mt-1 list-disc pl-5 text-sm">
104+
{(a2aCard.skills ?? []).map((skill: { id?: string; name?: string }, idx: number) => (
105+
<li key={skill.id ?? skill.name ?? `skill-${idx}`}>
106+
{skill.name ?? skill.id ?? 'Unnamed skill'}
107+
</li>
108+
))}
109+
</ul>
110+
</div>
111+
</div>
112+
)}
113+
</section>
114+
</div>
115+
</div>
116+
)
117+
}

0 commit comments

Comments
 (0)