---
title: "Groups, files & dynamic registration"
description: "group and tags, directory layout, type safety, and enabled guards for tools."
canonical_url: "https://mcp-toolkit.nuxt.dev/tools/groups-organization"
---
# Groups, files & dynamic registration

> group and tags, directory layout, type safety, and enabled guards for tools.

## Advanced Examples

### Tool with API Integration

Here's an example showing a typical API-backed tool:

```typescript [server/mcp/tools/get-weather.ts]
import { z } from 'zod'
import { createError } from 'h3'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'get-weather',
  description: 'Get current weather for a city',
  inputSchema: {
    city: z.string().describe('City name'),
  },
  handler: async ({ city }) => {
    const data = await $fetch(`/api/weather/${city}`)
    if (!data) throw createError({ statusCode: 404, message: `City "${city}" not found` })
    return data
  },
})
```

## Groups and Tags

Organize your tools with `group` and `tags` for filtering and progressive discovery. Groups and tags are exposed in `_meta` and will map to [SEP-1300](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1300) when adopted.

### Explicit Group and Tags

Set `group` and `tags` directly on the definition:

```typescript [server/mcp/tools/delete-user.ts]
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  group: 'admin',
  tags: ['destructive', 'user-management'],
  description: 'Delete a user account',
  inputSchema: {
    userId: z.string(),
  },
  handler: async ({ userId }) => {
    // ...
  },
})
```

### Auto-Inferred Group from Directory

Place tools in subdirectories and the group is inferred automatically:

```text
server/mcp/tools/
├── admin/
│   ├── delete-user.ts    → group: 'admin'
│   └── stats.ts          → group: 'admin'
├── content/
│   └── list-pages.ts     → group: 'content'
└── search.ts             → no group
```

An explicit `group` on the definition always takes precedence over the directory-inferred value.

### How Clients See Groups and Tags

Groups and tags are included in the `_meta` field of `tools/list` responses:

```json
{
  "name": "delete-user",
  "_meta": {
    "group": "admin",
    "tags": ["destructive", "user-management"]
  }
}
```

MCP clients can use these values to filter, sort, or group tools in their UI.

<callout color="info" icon="i-lucide-info">

For **resources** and **prompts**, `group` and `tags` are stored on the definition objects but are not yet exposed in protocol responses (`resources/list`, `prompts/list`). This will be supported when [SEP-1300](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1300) is adopted by the MCP SDK.

</callout>

## File Organization

Organize your tools in the `server/mcp/tools/` directory. Both flat and nested layouts are supported:

```text
server/
└── mcp/
    └── tools/
        ├── echo.ts
        ├── calculator.ts
        ├── admin/
        │   ├── delete-user.ts
        │   └── stats.ts
        └── content/
            └── list-pages.ts
```

Each file should export a default tool definition. Subdirectories automatically set the `group` for all tools within them.

## Type Safety

The module provides full TypeScript type inference:

```typescript
// Input types are inferred from inputSchema
handler: async ({ message }) => {
  // message is typed as string
}

// Output types are inferred from outputSchema
const result = {
  structuredContent: {
    bmi: 25.5,      // number
    category: '...', // string
  },
}
```

## Conditional Registration

You can control whether a tool is visible to clients using the `enabled` guard:

```typescript [server/mcp/tools/admin-tool.ts]
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'admin-tool',
  description: 'Admin-only tool',
  enabled: event => event.context.user?.role === 'admin',
  handler: async () => {
    // ...
  },
})
```

When `enabled` returns `false`, the tool is hidden from `tools/list` and cannot be called.

<callout color="primary" icon="i-lucide-book-open">

See the [Dynamic Definitions](https://mcp-toolkit.nuxt.dev/advanced/dynamic-definitions) guide for detailed documentation on auth-based filtering.

</callout>

## Client allowlist (`X-MCP-Tools`)

Clients can subset the catalog themselves by sending `X-MCP-Tools` with a comma-separated list of names. The toolkit applies this after `enabled()` guards and `mcp:config:resolved`, so the header is the last word on whatever this endpoint would have served. Names must match `tools/list`; unknown names return HTTP 400.

See [Connect MCP clients](https://mcp-toolkit.nuxt.dev/getting-started/connection#limit-available-tools) for Cursor and VS Code config.

## Next Steps

- [Resources](https://mcp-toolkit.nuxt.dev/resources/overview) - Create resources to expose data
- [Prompts](https://mcp-toolkit.nuxt.dev/prompts/overview) - Create reusable prompts
- [Handlers](https://mcp-toolkit.nuxt.dev/handlers/overview) - Create custom MCP endpoints
- [Code Mode](https://mcp-toolkit.nuxt.dev/advanced/code-mode) - Let LLMs orchestrate tools with JavaScript
- [Dynamic Definitions](https://mcp-toolkit.nuxt.dev/advanced/dynamic-definitions) - Conditionally register definitions
- [Examples](https://mcp-toolkit.nuxt.dev/examples/common-patterns) - See more tool examples


## Sitemap

See the full [sitemap](https://mcp-toolkit.nuxt.dev/sitemap.md) for all pages.
