Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/content/docs/1.getting-started/7.ai/1.mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ The Nuxt UI MCP server provides the following tools organized by category:

- **`get_migration_guide`**: Retrieves version-specific migration guides and upgrade instructions

### Assistant Tools

- **`ask_nuxt_ui_agent`**: Ask the Nuxt UI assistant for a concise direct answer (returns only the final content, no intermediate steps).

::note{icon="i-lucide-info"}
Disabled when using the Agent from the Nuxt UI website.
::

## Available Prompts

The Nuxt UI MCP server provides guided prompts for common workflows:
Expand Down
29 changes: 24 additions & 5 deletions docs/server/api/search.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
import { streamText, convertToModelMessages, stepCountIs } from 'ai'
import { streamText, convertToModelMessages, stepCountIs, generateText } from 'ai'
import { experimental_createMCPClient } from '@ai-sdk/mcp'
import { gateway } from '@ai-sdk/gateway'

export default defineEventHandler(async (event) => {
const { messages } = await readBody(event)
const { messages, stream = true } = await readBody(event)

const httpTransport = new StreamableHTTPClientTransport(
new URL(import.meta.dev ? 'http://localhost:3000/mcp' : 'https://ui.nuxt.com/mcp')
Expand All @@ -14,9 +14,12 @@ export default defineEventHandler(async (event) => {
})
const tools = await httpClient.tools()

return streamText({
// Remove the ask_nuxt_ui_agent tool to avoid infinite loops
delete tools['ask_nuxt_ui_agent']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ask_nuxt_ui_agent tool is being removed unconditionally, which prevents the main agent from ever accessing it. The deletion should only occur when handling non-streaming requests from the MCP agent itself.

View Details
πŸ“ Patch Details
diff --git a/docs/server/api/search.ts b/docs/server/api/search.ts
index ab4607c7..4899a046 100644
--- a/docs/server/api/search.ts
+++ b/docs/server/api/search.ts
@@ -14,9 +14,6 @@ export default defineEventHandler(async (event) => {
   })
   const tools = await httpClient.tools()
 
-  // Remove the ask_nuxt_ui_agent tool to avoid infinite loops
-  delete tools['ask_nuxt_ui_agent']
-
   const options = {
     model: gateway('anthropic/claude-sonnet-4.5'),
     maxOutputTokens: 10_000,
@@ -48,6 +45,9 @@ Guidelines:
 
   // If not streaming, it's called from the MCP route as a tool.
   if (stream === false) {
+    // Remove the ask_nuxt_ui_agent tool to avoid infinite loops
+    delete tools['ask_nuxt_ui_agent']
+
     const { text } = await generateText({
       ...options,
       tools

Analysis

ask_nuxt_ui_agent tool unconditionally removed, blocking main agent delegation

What fails: The main agent cannot delegate Nuxt UI queries to the specialized Nuxt UI agent because the ask_nuxt_ui_agent tool is unconditionally deleted before the streaming/non-streaming path check.

How to reproduce:

  1. Call /api/search endpoint with user query (default stream=true)
  2. LLM attempts to use ask_nuxt_ui_agent tool for Nuxt UI questions
  3. Tool is unavailable in the tools object despite being registered in the MCP server

Result: Main agent cannot delegate - tool deletion on line 18 removes it before the streaming check, preventing streaming requests from accessing it. Tool is missing from both streaming (stream=true) and non-streaming (stream=false) paths when it should only be missing from non-streaming requests.

Expected: The tool deletion should only occur for non-streaming requests (stream=false) from the MCP agent itself. Streaming requests should have full access to all MCP tools including ask_nuxt_ui_agent to enable proper delegation. The tool prevents infinite loops only when the MCP agent calls back with stream=false.

Fix applied: Moved delete tools['ask_nuxt_ui_agent'] from line 18 (unconditional) into the if (stream === false) block (conditional), so the main agent retains tool access while MCP callbacks are protected from recursion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, always remove this tool.

When using the MCP, the tool ask_nuxt_ui_agent is available. This tool will fetch /api/search that will connect to the MCP to get tools. In that case, the tool ask_nuxt_ui_agent must be removed.


const options = {
model: gateway('anthropic/claude-sonnet-4.5'),
maxOutputTokens: 10000,
maxOutputTokens: 10_000,
system: `You are a helpful assistant for Nuxt UI, a UI library for Nuxt and Vue. Use your knowledge base tools to search for relevant information before answering questions.

Guidelines:
Expand All @@ -40,7 +43,23 @@ Guidelines:
- Format responses in a conversational way, not as documentation sections.
`,
messages: convertToModelMessages(messages),
stopWhen: stepCountIs(6),
stopWhen: stepCountIs(6)
}

// If not streaming, it's called from the MCP route as a tool.
if (stream === false) {
const { text } = await generateText({
...options,
tools
})

await httpClient.close()

return text
}

return streamText({
...options,
tools,
onFinish: async () => {
await httpClient.close()
Expand Down
31 changes: 31 additions & 0 deletions docs/server/routes/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { z } from 'zod/v3'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
import type { UIMessage } from 'ai'

function createServer() {
const server = new McpServer({
Expand Down Expand Up @@ -413,6 +414,36 @@ function createServer() {
}
)

server.registerTool(
'ask_nuxt_ui_agent',
{
title: 'Ask Nuxt UI Agent',
description: 'Asks the Nuxt UI agent a question',
inputSchema: {
// @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869)
query: z.string().describe('The question to ask the agent')
}
},
async (params: { query: string }) => {
const result = await $fetch<string>('/api/search', {
method: 'POST',
timeout: 60_000,
body: {
stream: false,
messages: [{
role: 'user',
parts: [{ type: 'text', text: params.query }]
}] satisfies Array<Omit<UIMessage, 'id'>> }
})

console.log(result)

return {
content: [{ type: 'text' as const, text: result }]
}
}
)

return server
}

Expand Down
Loading