@jrmc/adonis-mcp
by batosai
README.md
# @jrmc/adonis-mcp
[](https://badge.fury.io/js/%40jrmc%2Fadonis-mcp)
[](https://opensource.org/licenses/MIT)
Adonis MCP provides a simple and elegant way for AI clients to interact with your AdonisJS application through the [Model Context Protocol](https://modelcontextprotocol.io/). It offers an expressive, fluent interface for defining **tools**, **resources**, and **prompts** that enable AI-powered interactions with your application.
> Compatible with **AdonisJS 6** and **AdonisJS 7**.
## Features
- **Tools** — Type-safe handlers that AI models can call to perform actions (query a database, send emails, call external APIs…)
- **Resources** — Expose application data through URI templates with support for text and binary content
- **Prompts** — Reusable, argument-validated prompt templates to guide AI interactions
- **Multiple transports** — HTTP, stdio, and fake (testing) transports out of the box
- **Stateless MCP** — MCP `2026-07-28` discovery, routing headers, and cache hints with legacy protocol compatibility
- **Annotations** — Decorate tools and resources with metadata (`@isReadOnly`, `@isDestructive`, `@priority`…) so AI clients understand their behaviour
- **Authentication & Authorization** — First-class integration with AdonisJS Auth and Bouncer
- **Inspector** — Built-in debugging tool to test your MCP implementation during development
- **Testing support** — Fake transport for easy unit testing of tools, resources, and prompts
Coming soon: SSE transport, Output tool.
## Links
[View documentation](https://adonis-mcp.jrmc.dev/)
## Agent Skills
If you use an AI coding agent (Claude Code, Cursor, Codex, etc.), install the [adonis-mcp skill](skills/README.md) to teach it the package conventions:
```bash
npx skills add batosai/adonis-mcp
```
## Installation & Configuration
```bash
node ace add @jrmc/adonis-mcp
```
This will create a configuration file `config/mcp.ts`:
```typescript
import { defineConfig } from '@jrmc/adonis-mcp'
export default defineConfig({
name: 'adonis-mcp-server',
version: '1.0.0',
cache: {
tools: { ttlMs: 60_000, scope: 'private' },
},
})
```
Cache hints default to `ttlMs: 0` and `private`. Version 2 serves stateless `2026-07-28`
requests and retains the `initialize` lifecycle for `2025-11-25` and `2025-06-18` clients.
See the [protocol guide](docs/protocol.md) and [middleware lifecycle guide](docs/sessions.md).
By default, your MCP tools, resources, and prompts will be stored in `app/mcp`. If you want to use a different path, you need to configure it in your `adonisrc.ts` file:
```typescript
directories: {
mcp: 'app/custom/mcp', // Optional: custom path for MCP files (defaults to 'app/mcp')
}
```
## Usage
### Creating a Tool
To create a new tool, use the Ace command:
```bash
node ace make:mcp-tool my_tool
```
This command will create a file in `app/mcp/tools/my_tool.ts` with a base template:
```typescript
import type { ToolContext } from '@jrmc/adonis-mcp/types/context'
import type { BaseSchema } from '@jrmc/adonis-mcp/types/method'
import { Tool } from '@jrmc/adonis-mcp'
type Schema = BaseSchema<{
text: { type: "string" }
}>
export default class MyToolTool extends Tool<Schema> {
name = 'tool_name'
title = 'Tool title'
description = 'Tool description'
async handle({ args, response }: ToolContext<Schema>) {
return response.text('Hello, world!')
}
schema() {
return {
type: "object",
properties: {
text: {
type: "string",
description: "Description text argument"
},
},
required: ["text"]
} as Schema
}
}
```
### Schema Definition
The schema defines the input parameters of your tool. It follows the [JSON Schema](https://json-schema.org/) specification:
```typescript
schema() {
return {
type: "object",
properties: {
title: {
type: "string",
description: "Bookmark title"
},
url: {
type: "string",
description: "Bookmark URL"
}
},
required: ["title", "url"]
} as Schema
}
```
You can also use Zod to define your schema:
```typescript
import * as z from 'zod'
const zodSchema = z.object({
page: z.number().optional(),
perPage: z.number().optional()
})
schema() {
return z.toJSONSchema(
zodSchema,
{ io: "input" }
) as Schema
}
```
You can also use VineJS(>= [v4](https://vinejs.dev/docs/json-schema-generation)) to define your schema:
```typescript
import vine from '@vinejs/vine'
const vineSchema = vine.object({
page: vine.number().meta({
description: 'page number for pagination',
}),
perPage: vine.number().optional().meta({
description: 'per page limit for pagination',
})
})
schema() {
return vine.create(
vineSchema
).toJSONSchema() as Schema
}
```
### Handler Implementation
The `handle` method contains your tool's logic. It receives a typed context with validated arguments:
```typescript
async handle({ args, response, auth, bouncer }: ToolContext<Schema>) {
// Your logic here
const result = await SomeModel.query().where('id', args.id)
return response.text(JSON.stringify({ result }))
}
```
### Setting up Authentication and Bouncer
To use `auth` and `bouncer` in your MCP tools, prompts, and resources, add the following TypeScript declaration in your middleware (e.g., in your MCP middleware):
```typescript
declare module '@jrmc/adonis-mcp/types/context' {
export interface McpContext {
auth: {
user?: HttpContext['auth']['user']
}
bouncer: McpBouncer<
Exclude<HttpContext['auth']['user'], undefined>,
typeof abilities,
typeof policies
>
}
}
```
The MCP context automatically binds `auth` and `bouncer` from the `HttpContext` if they are available, so make sure your middleware initializes them on the `HttpContext` first.
#### Registering the MCP Route
In your `start/routes.ts` file, register the MCP route and apply middleware:
```typescript
import { middleware } from '#start/kernel'
import router from '@adonisjs/core/services/router'
// Register MCP route (defaults to /mcp) with protocol and auth middleware
router.mcp().use([middleware.mcp(), middleware.auth()])
```
You can also specify a custom path:
```typescript
router.mcp('/custom-mcp-path').use([middleware.mcp(), middleware.auth()])
```
The generated `mcp` middleware handles the stateless v2 protocol and legacy MCP sessions. Keep it
on the route even when authentication is not required.
> **⚠️ Important: CSRF Protection**
>
> If you have CSRF protection enabled in your application, you **must** exclude the MCP route from CSRF validation. MCP clients typically don't include CSRF tokens in their requests.
>
> In your `config/shield.ts` file, add the MCP route to the CSRF exceptions:
>
> ```typescript
> export const shieldConfig = defineConfig({
> csrf: {
> enabled: true,
> exceptRoutes: [
> '/mcp', // Or your custom MCP path
> ],
> },
> })
> ```
### Using Authentication
The MCP context automatically includes the `auth` instance from the `HttpContext` if available. You can use it to access the authenticated user:
```typescript
async handle({ args, auth, response }: ToolContext<Schema>) {
const user = auth?.user
if (!user) {
throw new Error('User not authenticated')
}
// Use the authenticated user
const bookmark = await Bookmark.create({
title: args.title,
userId: user.id,
})
return response.text(JSON.stringify({ bookmark }))
}
```
### Using Bouncer
The MCP context automatically includes the `bouncer` instance from the `HttpContext` if available. You can use it to check permissions:
```typescript
async handle({ args, bouncer, response }: ToolContext<Schema>) {
// Check a permission
await bouncer.authorize('viewUsers')
// Or use a policy
const user = await User.findOrFail(args.userId)
await bouncer.with(UserPolicy).authorize('view', user)
return response.text(JSON.stringify({ user }))
}
```
### Response Return
The context includes a `response` instance to format your responses. The available methods depend on the context type:
#### Tool Responses
For tools, you can use:
- `response.text(text: string)`: Return plain text content
- `response.image(data: string, mimeType: string)`: Return image content (base64 encoded)
- `response.audio(data: string, mimeType: string)`: Return audio content (base64 encoded)
- `response.structured(object: Record<string, unknown>)`: Return structured JSON data
- `response.resourceLink(uri: string)`: Return a link to a resource
- `response.error(message: string)`: Return an error message
Return an array directly when a tool needs to send multiple content objects:
```typescript
return [response.text('Report generated'), response.resourceLink('file:///reports/latest.pdf')]
```
```typescript
async handle({ args, response }: ToolContext<Schema>) {
// Return text
return response.text(JSON.stringify({ success: true }))
// Return structured data
return response.structured({
temperature: 22.5,
conditions: 'Partly cloudy',
humidity: 65
})
// Return image
const imageData = await fs.readFile('path/to/image.png', 'base64')
return response.image(imageData, 'image/png')
// Return a resource link
return response.resourceLink('file:///path/to/resource.txt')
// Return error
return response.error('Something went wrong')
}
```
#### Resource Responses
For resources, you can use:
- `response.text(text: string)`: Return text content
- `response.blob(data: string | Buffer)`: Return binary content (encoded to base64)
```typescript
async handle({ response }: ResourceContext) {
const content = await fs.readFile('path/to/file.pdf')
return response.blob(content)
}
```
### Complete Example
Here is a complete example of a tool that creates a bookmark:
```typescript
import type { ToolContext } from '@jrmc/adonis-mcp/types/context'
import type { BaseSchema } from '@jrmc/adonis-mcp/types/method'
import { Tool } from '@jrmc/adonis-mcp'
import Bookmark from '#models/bookmark'
type Schema = BaseSchema<{
title: { type: "string" }
url: { type: "string" }
}>
export default class AddBookmarkTool extends Tool<Schema> {
name = 'create_bookmark'
title = 'Create Bookmark'
description = 'Create a new bookmark'
async handle({ args, response, auth }: ToolContext<Schema>) {
const bookmark = await Bookmark.create({
title: args.title,
text: args.url,
userId: auth?.user?.id,
})
return response.text(JSON.stringify({ bookmark }))
}
schema() {
return {
type: "object",
properties: {
title: {
type: "string",
description: "Bookmark title"
},
url: {
type: "string",
description: "Bookmark URL"
}
},
required: ["title", "url"]
} as Schema
}
}
```
## Advanced Features
### Structured Output
The `response.structured()` method allows you to return JSON data in a structured format. This is particularly useful when you want to return data that can be easily parsed and used by the MCP client without additional processing:
```typescript
import type { ToolContext } from '@jrmc/adonis-mcp/types/context'
import { Tool } from '@jrmc/adonis-mcp'
export default class GetWeatherTool extends Tool {
name = 'get_weather'
title = 'Get Weather'
description = 'Get current weather data'
async handle({ args, response }: ToolContext) {
const weatherData = {
temperature: 22.5,
conditions: 'Partly cloudy',
humidity: 65,
windSpeed: 12,
location: args.location
}
return response.structured(weatherData)
}
}
```
**Note:** Structured content can only be used in tools, not in prompts or resources.
### Resource Links
Resource links allow you to reference other resources in your tool responses. This is useful when you want to point to additional information without embedding the entire resource content:
```typescript
import type { ToolContext } from '@jrmc/adonis-mcp/types/context'
import { Tool } from '@jrmc/adonis-mcp'
export default class GetDocumentationTool extends Tool {
name = 'get_documentation'
title = 'Get Documentation'
description = 'Get documentation for a specific topic'
async handle({ args, response }: ToolContext) {
return [
response.text('Here is the documentation for your topic:'),
response.resourceLink(`file:///docs/${args.topic}.md`)
]
}
}
```
The resource link will include metadata about the resource (name, mimeType, title, description, size) without fetching the actual content. The MCP client can then decide whether to fetch the resource content separately.
**Note:** Resource links can only be used in tools, not in prompts or resources.
### Metadata with `withMeta()`
The `withMeta()` method is available on text, image, audio, blob, resource link, and embedded
resource content. It allows you to attach custom metadata used by MCP clients for logging,
analytics, or custom processing. Structured responses do not support `withMeta()`:
```typescript
async handle({ args, response }: ToolContext<Schema>) {
const users = await User.all()
return response.text(JSON.stringify(users)).withMeta({
source: 'database',
queryTime: Date.now(),
count: users.length,
cacheHit: false
})
}
```
Metadata is particularly useful when:
- You want to provide debugging information
- You need to track the source of data
- You want to include performance metrics
- You need to pass additional context to the client
### Annotations
Annotations allow you to provide additional metadata about your tools and resources to help MCP clients better understand their behavior and characteristics.
#### Tool Annotations
Tools support the following annotations that describe their operational characteristics:
##### `@isReadOnly()`
Indicates that a tool only reads data and does not modify any state:
```typescript
import { Tool } from '@jrmc/adonis-mcp'
import { isReadOnly } from '@jrmc/adonis-mcp/tool_annotations'
@isReadOnly()
export default class GetUserTool extends Tool {
name = 'get_user'
async handle({ args, response }: ToolContext) {
const user = await User.find(args.id)
return response.text(JSON.stringify(user))
}
}
```
You can also explicitly set it to false:
```typescript
@isReadOnly(false)
export default class UpdateUserTool extends Tool {
// ...
}
```
##### `@isOpenWorld()`
Indicates that a tool can access information from the internet or external sources:
```typescript
import { isOpenWorld } from '@jrmc/adonis-mcp/tool_annotations'
@isOpenWorld()
export default class FetchWeatherTool extends Tool {
name = 'fetch_weather'
async handle({ args, response }: ToolContext) {
const weather = await externalApi.getWeather(args.city)
return response.text(JSON.stringify(weather))
}
}
```
##### `@isDestructive()`
Indicates that a tool performs destructive operations like deleting data:
```typescript
import { isDestructive } from '@jrmc/adonis-mcp/tool_annotations'
@isDestructive()
export default class DeleteUserTool extends Tool {
name = 'delete_user'
async handle({ args, response }: ToolContext) {
await User.query().where('id', args.id).delete()
return response.text('User deleted successfully')
}
}
```
##### `@isIdempotent()`
Indicates that a tool can be safely called multiple times with the same arguments without causing different effects:
```typescript
import { isIdempotent } from '@jrmc/adonis-mcp/tool_annotations'
@isIdempotent()
export default class SetUserStatusTool extends Tool {
name = 'set_user_status'
async handle({ args, response }: ToolContext) {
await User.query().where('id', args.id).update({ status: args.status })
return response.text('Status updated')
}
}
```
##### Combining Multiple Annotations
You can use multiple annotations on the same tool:
```typescript
import { isReadOnly, isOpenWorld, isIdempotent } from '@jrmc/adonis-mcp/tool_annotations'
@isReadOnly()
@isOpenWorld()
@isIdempotent()
export default class SearchOnlineTool extends Tool {
name = 'search_online'
async handle({ args, response }: ToolContext) {
const results = await searchEngine.search(args.query)
return response.text(JSON.stringify(results))
}
}
```
#### Resource Annotations
Resources support the following annotations to provide additional context:
##### `@priority()`
Specifies the importance of a resource as a number between 0.0 and 1.0:
```typescript
import { Resource } from '@jrmc/adonis-mcp'
import { priority } from '@jrmc/adonis-mcp/annotations'
@priority(0.9)
export default class ImportantDocResource extends Resource {
name = 'important_doc.txt'
uri = 'file:///important_doc.txt'
async handle({ response }: ResourceContext) {
return response.text('Critical documentation content')
}
}
```
##### `@audience()`
Specifies the intended audience for a resource (user, assistant, or both):
```typescript
import { Resource } from '@jrmc/adonis-mcp'
import { audience } from '@jrmc/adonis-mcp/annotations'
import Role from '@jrmc/adonis-mcp/enums/role'
@audience(Role.USER)
export default class UserManualResource extends Resource {
name = 'user_manual.txt'
uri = 'file:///user_manual.txt'
async handle({ response }: ResourceContext) {
return response.text('User manual content')
}
}
```
You can also specify multiple audiences:
```typescript
@audience([Role.USER, Role.ASSISTANT])
export default class SharedDocResource extends Resource {
// ...
}
```
##### `@lastModified()`
Indicates when a resource was last updated (ISO 8601 timestamp):
```typescript
import { Resource } from '@jrmc/adonis-mcp'
import { lastModified } from '@jrmc/adonis-mcp/annotations'
@lastModified('2024-12-12T10:00:00Z')
export default class DocumentResource extends Resource {
name = 'document.txt'
uri = 'file:///document.txt'
async handle({ response }: ResourceContext) {
return response.text('Document content')
}
}
```
##### Combining Resource Annotations
You can use multiple annotations on the same resource:
```typescript
import { Resource } from '@jrmc/adonis-mcp'
import { priority, audience, lastModified } from '@jrmc/adonis-mcp/annotations'
import Role from '@jrmc/adonis-mcp/enums/role'
@priority(0.8)
@audience([Role.USER, Role.ASSISTANT])
@lastModified('2024-12-12T10:00:00Z')
export default class ApiDocResource extends Resource {
name = 'api_docs.txt'
uri = 'file:///api_docs.txt'
async handle({ response }: ResourceContext) {
return response.text('API documentation content')
}
}
```
### Creating a Resource
To create a new resource, use the Ace command:
```bash
node ace make:mcp-resource my_resource
```
This command will create a file in `app/mcp/resources/my_resource.ts` with a base template:
```typescript
import type { ResourceContext } from '@jrmc/adonis-mcp/types/context'
import { Resource } from '@jrmc/adonis-mcp'
export default class MyResourceResource extends Resource {
name = 'example.txt'
uri = 'file:///example.txt'
mimeType = 'text/plain'
title = 'Resource title'
description = 'Resource description'
size = 0
async handle({ response }: ResourceContext) {
this.size = 1000
return response.text('Hello World')
}
}
```
### Resource Properties
Resources have the following properties:
- `name` (optional): The name of the resource
- `uri` (required): The unique identifier for the resource (must be unique)
- `mimeType` (optional): The MIME type of the resource
- `title` (optional): A human-readable title
- `description` (optional): A description of the resource
- `size` (optional): The size of the resource in bytes
### Resource Handler
The `handle` method returns the content of the resource. You can use `response.text()` for text content or `response.blob()` for binary content:
```typescript
async handle({ response }: ResourceContext) {
const content = await fs.readFile('path/to/file.txt', 'utf-8')
this.size = content.length
return response.text(content)
}
```
### URI Templates
Resources support URI templates (RFC 6570) to create dynamic resources. This allows you to define resources with variable parts in their URIs:
```typescript
import type { ResourceContext } from '@jrmc/adonis-mcp/types/context'
import { Resource } from '@jrmc/adonis-mcp'
type Args = {
name: string
}
export default class RobotsResource extends Resource<Args> {
name = 'robots.txt'
uri = 'file:///{name}.txt'
mimeType = 'text/plain'
title = 'Robots file'
description = 'Dynamic robots.txt file'
async handle({ args, response }: ResourceContext<Args>) {
this.size = 1000
return response.text(`Hello World ${args?.name}`)
}
}
```
When a client requests `file:///robots.txt`, the template `file:///{name}.txt` will match and extract `name: "robots"` as an argument, which will be available in the `handle` method via `args.name`.
URI templates support various operators:
- `{name}` - Simple variable substitution
- `{/name}` - Path segment
- `{?name}` - Query parameter
- `{&name}` - Additional query parameter
- `{#name}` - Fragment identifier
- `{+name}` - Reserved characters allowed
- `{.name}` - Dot-prefixed segment
For more information, see [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570).
### Creating a Prompt
To create a new prompt, use the Ace command:
```bash
node ace make:mcp-prompt my_prompt
```
This command will create a file in `app/mcp/prompts/my_prompt.ts` with a base template:
```typescript
import type { PromptContext } from '@jrmc/adonis-mcp/types/context'
import type { BaseSchema } from '@jrmc/adonis-mcp/types/method'
import { Prompt } from '@jrmc/adonis-mcp'
type Schema = BaseSchema<{
text: { type: "string" }
}>
export default class MyPromptPrompt extends Prompt<Schema> {
name = 'my_prompt'
title = 'Prompt title'
description = 'Prompt description'
async handle({ args, response }: PromptContext<Schema>) {
return [
response.text('Hello, world!')
]
}
schema() {
return {
type: "object",
properties: {
text: {
type: "string",
description: "Description text argument"
},
},
required: ["text"]
} as Schema
}
}
```
### Prompt Schema
Prompts use the same schema definition system as tools, following the [JSON Schema](https://json-schema.org/) specification. You can also use Zod to define your schema:
```typescript
import * as z from 'zod'
const zodSchema = z.object({
code: z.string(),
language: z.string().optional()
})
schema() {
return z.toJSONSchema(
zodSchema,
{ io: "input" }
) as Schema
}
```
### Prompt Handler
The `handle` method for prompts returns an array of content objects. This allows you to return multiple pieces of content, including embedded resources:
```typescript
async handle({ args, response }: PromptContext<Schema>) {
return [
response.text(`Please review this code:\n\n${args.code}`),
response.embeddedResource('file:///example.txt')
]
}
```
### Prompt Response Methods
For prompts, you can use the same response methods as tools, but you must return an array:
- `response.text(text: string)`: Return plain text content
- `response.image(data: string, mimeType: string)`: Return image content (base64 encoded)
- `response.audio(data: string, mimeType: string)`: Return audio content (base64 encoded)
- `response.embeddedResource(uri: string)`: Embed another resource in the prompt response
All response methods also support the `withMeta()` method to add metadata:
```typescript
async handle({ args, response }: PromptContext<Schema>) {
return [
response.text('Here is the code to review:').withMeta({
language: args.language
}),
response.embeddedResource('file:///code.py'),
response.text('Please provide feedback.')
]
}
```
### Complete Prompt Example
Here is a complete example of a prompt for code review:
```typescript
import type { PromptContext } from '@jrmc/adonis-mcp/types/context'
import type { BaseSchema } from '@jrmc/adonis-mcp/types/method'
import { Prompt } from '@jrmc/adonis-mcp'
type Schema = BaseSchema<{
code: { type: "string" }
language: { type: "string" }
}>
export default class CodeReviewPrompt extends Prompt<Schema> {
name = 'code_review'
title = 'Code Review'
description = 'Review code and provide feedback'
async handle({ args, response }: PromptContext<Schema>) {
return [
response.text(`Please review this ${args.language} code:\n\n${args.code}`),
response.text('Provide feedback on code quality, potential bugs, and improvements.')
]
}
schema() {
return {
type: "object",
properties: {
code: {
type: "string",
description: "The code to review"
},
language: {
type: "string",
description: "Programming language"
}
},
required: ["code", "language"]
} as Schema
}
}
```
## Completions
Completions provide argument suggestions for prompts and resources, helping users fill in parameters interactively. This feature must be enabled in your configuration and can be implemented for both prompts and resources.
### Enabling Completions
First, enable completions in your `config/mcp.ts`:
```typescript
import { defineConfig } from '@jrmc/adonis-mcp'
export default defineConfig({
name: 'adonis-mcp-server',
version: '1.0.0',
completions: true, // Enable completions
})
```
### Implementing Completions in Prompts
Add a `complete()` method to your prompt to provide argument suggestions:
```typescript
import type { PromptContext, CompleteContext } from '@jrmc/adonis-mcp/types/context'
import type { BaseSchema } from '@jrmc/adonis-mcp/types/method'
import { Prompt } from '@jrmc/adonis-mcp'
type Schema = BaseSchema<{
language: { type: "string" }
code: { type: "string" }
}>
export default class CodeReviewPrompt extends Prompt<Schema> {
name = 'code_review'
title = 'Code Review'
description = 'Review code and provide feedback'
async handle({ args, response }: PromptContext<Schema>) {
return [
response.text(`Please review this ${args.language} code:\n\n${args.code}`)
]
}
async complete({ args, response }: CompleteContext<Schema>) {
// Provide language suggestions when the user types
if (args?.language !== undefined) {
return response.complete({
values: ['python', 'javascript', 'typescript', 'java', 'go', 'rust']
})
}
return response.complete({ values: [] })
}
schema() {
return {
type: "object",
properties: {
language: {
type: "string",
description: "Programming language"
},
code: {
type: "string",
description: "Code to review"
}
},
required: ["language", "code"]
} as Schema
}
}
```
### Implementing Completions in Resources
Resources with URI templates can also provide completions for their path parameters:
```typescript
import type { ResourceContext, CompleteContext } from '@jrmc/adonis-mcp/types/context'
import { Resource } from '@jrmc/adonis-mcp'
type Args = {
directory: string
name: string
}
export default class ConfigFileResource extends Resource<Args> {
name = 'config_file'
uri = 'file://{directory}/{name}.txt'
mimeType = 'text/plain'
title = 'Configuration File'
description = 'Access configuration files'
async handle({ args, response }: ResourceContext<Args>) {
const content = await readConfigFile(args.directory, args.name)
return response.text(content)
}
async complete({ args, response }: CompleteContext<Args>) {
// Provide suggestions based on available directories and files
if (args?.name !== undefined) {
return response.complete({
values: ['config', 'settings', 'environment', 'database']
})
}
if (args?.directory !== undefined) {
return response.complete({
values: ['production', 'staging', 'development']
})
}
return response.complete({ values: [] })
}
}
```
### Completion Context
The `complete()` method receives a `CompleteContext` that includes:
- `args`: The current argument values (partial or complete)
- `response`: The response object with a `complete()` method
The response format includes:
```typescript
response.complete({
values: string[], // Array of suggested values
hasMore?: boolean, // Optional: indicates if more values are available
total?: number // Optional: total number of available values
})
```
### Transports
The package supports multiple transport mechanisms:
- **HTTP Transport**: Default transport for HTTP-based MCP servers (used when accessing via HTTP routes)
- **Stdio Transport**: For command-line MCP servers that communicate via standard input/output
- **Fake Transport**: For testing purposes, allows you to capture and inspect MCP messages
### Version 2 protocol
HTTP requests using MCP `2026-07-28` are stateless and carry their protocol metadata with every
request. Legacy `2025-11-25` and `2025-06-18` clients retain the `initialize` lifecycle and
`MCP-Session-Id` handling.
Modern HTTP requests use `MCP-Protocol-Version` and `Mcp-Method` routing headers. `Mcp-Name` is
also required for `tools/call`, `prompts/get`, and `resources/read`. Tool schema properties marked
with `x-mcp-header` must match their corresponding `Mcp-Param-*` header.
Clients may call `server/discover` to retrieve the supported protocol versions, capabilities,
instructions, and server identity. Handlers can adapt their behavior using the request context:
```typescript
ctx.protocolEra
ctx.protocolVersion
ctx.clientCapabilities
ctx.clientInfo
ctx.logLevel
```
See the [protocol guide](docs/protocol.md) for request envelopes, response metadata, routing
headers, and cache hints. See [MCP middleware and sessions](docs/sessions.md) for the dual-generation
lifecycle.
### Pagination
The `tools/list` and `resources/list` methods support cursor-based pagination to handle large numbers of tools and resources efficiently. This is particularly useful when you have many tools or resources registered in your application. [More information](https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/pagination)
## Testing & Debugging
### MCP Inspector
The MCP Inspector is a powerful tool for debugging and testing your MCP server. It provides a graphical interface to interact with your tools, resources, and prompts.
To open the MCP Inspector, use the following command:
```bash
node ace mcp:inspector
```
By default, this command uses HTTP transport. You can specify a different transport type:
```bash
# Use HTTP transport (default)
node ace mcp:inspector http
# Use stdio transport
node ace mcp:inspector stdio
```
The command creates a writable `.mcp-inspector.json` catalog at the application root when needed.
HTTP servers default to the modern protocol era, while stdio servers use the legacy era. Existing
catalog entries and settings are preserved between launches.
The Inspector may persist headers or other local connection settings in the catalog. Add
`.mcp-inspector.json` to `.gitignore` when it contains credentials.
**Important notes:**
- The inspector can only be used in development environment (not in production)
- For HTTP transport, make sure your server is running and the MCP route is configured
- The inspector catalog includes your MCP server and allows you to:
- List and test all available tools
- Browse and read resources
- Execute prompts with different arguments
- Inspect request/response payloads
- Debug any issues with your MCP implementation
## Support
For any questions or issues, please open an issue on the [GitHub repository](https://github.com/batosai/adonis-mcp).
## Inspiration
This package is inspired by [laravel/mcp](https://github.com/laravel/mcp).
---
> **Note:** This documentation has been generated with the help of AI and has not been fully verified yet. Please report any inaccuracies or issues you encounter.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessResponsive