### Install AdonisJS MCP Starter Kit Source: https://adonis-mcp.jrmc.dev/starter.html Use this command to create a new project with the AdonisJS MCP Starter Kit. Navigate into the project directory and start the development server to have your MCP server available. ```bash npm init adonisjs@latest -- -K="batosai/adonisjs-mcp-starter-kit" my-mcp-server cd my-mcp-server npm run dev ``` -------------------------------- ### Complete Example: Add Bookmark Tool Source: https://adonis-mcp.jrmc.dev/tools.html A full example of an Adonis MCP tool that creates a bookmark. It includes type definitions for context and schema, imports necessary modules, defines tool properties, and implements the handle and schema methods. ```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 { name = 'create_bookmark' title = 'Create Bookmark' description = 'Create a new bookmark' async handle({ args, response, auth }: ToolContext) { 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 } } ``` -------------------------------- ### Start MCP Inspector Source: https://adonis-mcp.jrmc.dev/inspector.html Use this command to launch the MCP Inspector. It defaults to HTTP transport. ```bash node ace mcp:inspector ``` -------------------------------- ### Implement Resource Completions Source: https://adonis-mcp.jrmc.dev/resources.html Implement the `complete()` method in a resource to provide suggestions for path parameters. This example provides suggestions for 'name' and 'directory' 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 { name = 'config_file' uri = 'file://{directory}/{name}.txt' mimeType = 'text/plain' async handle({ args, response }: ResourceContext) { const content = await readConfigFile(args?.directory, args?.name) return response.text(content) } async complete({ args, response }: CompleteContext) { // Provide suggestions based on what's being completed 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: [] }) } } ``` -------------------------------- ### Install Bouncer Package Source: https://adonis-mcp.jrmc.dev/authentication.html Install the @adonisjs/bouncer package using the ace add command. This command sets up the necessary files for Bouncer. ```bash node ace add @adonisjs/bouncer ``` -------------------------------- ### Initialize AdonisJS MCP Starter Kit Source: https://adonis-mcp.jrmc.dev/installation.html Use this command to create a new, dedicated MCP server with the minimum required setup. Ideal for applications solely exposing an MCP server. ```bash npm init adonisjs@latest -- -K="batosai/adonisjs-mcp-starter-kit" my-mcp-server ``` -------------------------------- ### Define Custom Scheme Resource Source: https://adonis-mcp.jrmc.dev/resources.html Example of a resource with a custom URI scheme 'myapp://' for application-specific resources. ```typescript uri = 'myapp://users/profile' ``` -------------------------------- ### MCP Server Configuration with Completions Enabled Source: https://adonis-mcp.jrmc.dev/installation.html Example of MCP server configuration enabling argument completions for prompts and resources. Set the `completions` option to `true`. ```typescript import { defineConfig } from '@jrmc/adonis-mcp' export default defineConfig({ name: 'adonis-mcp-server', version: '1.0.0', completions: true, // Enable completions }) ``` -------------------------------- ### Handle Simple Resource Request Source: https://adonis-mcp.jrmc.dev/resources.html The `handle` method processes resource requests. This example shows a basic implementation returning plain text content and setting the resource size. ```typescript async handle({ response }: ResourceContext) { const content = 'Hello, World!' this.size = content.length return response.text(content) } ``` -------------------------------- ### Install Adonis MCP Package Source: https://adonis-mcp.jrmc.dev/validation.html Install the Adonis MCP package using the ace command. The installer will prompt to enable VineJS validation. ```bash node ace add @jrmc/adonis-mcp ``` -------------------------------- ### Start MCP Inspector with Specific Transport Source: https://adonis-mcp.jrmc.dev/inspector.html Specify the transport type when starting the MCP Inspector. HTTP is the default, but stdio is also supported. ```bash # Use HTTP transport (default) node ace mcp:inspector http ``` ```bash # Use stdio transport node ace mcp:inspector stdio ``` -------------------------------- ### Define File System Resource Source: https://adonis-mcp.jrmc.dev/resources.html Example of a resource with a 'file:///' URI scheme for accessing local file system resources. ```typescript uri = 'file:///documents/readme.txt' ``` -------------------------------- ### Create a Dynamic Resource with URI Templates Source: https://adonis-mcp.jrmc.dev/resources.html Implement dynamic resources using URI templates (RFC 6570) to create resources with variable parts in their URIs. This example dynamically accesses files by name. ```typescript import type { ResourceContext } from '@jrmc/adonis-mcp/types/context' import { Resource } from '@jrmc/adonis-mcp' type Args = { name: string } export default class DynamicFileResource extends Resource { name = 'dynamic_file' uri = 'file:///{name}.txt' mimeType = 'text/plain' title = 'Dynamic file' description = 'Access files dynamically by name' async handle({ args, response }: ResourceContext) { const content = await readFile(args?.name) this.size = content.length return response.text(content) } } ``` -------------------------------- ### Basic JSON Schema for Tool Input Source: https://adonis-mcp.jrmc.dev/tools.html Defines the input parameters for a tool using JSON Schema. This example includes pagination parameters. ```typescript schema() { return { type: "object", properties: { page: { type: "number", description: "page number for pagination" }, url: { type: "number", description: "per page limit for pagination" } }, required: ["page"] } } ``` -------------------------------- ### Complete User Document Resource Example Source: https://adonis-mcp.jrmc.dev/resources.html Provides access to user documents by ID, requiring authentication and filtering by user ID. Sets metadata like document ID and timestamps. ```typescript import type { ResourceContext } from '@jrmc/adonis-mcp/types/context' import { Resource } from '@jrmc/adonis-mcp' import { priority, audience } from '@jrmc/adonis-mcp/annotations' import Role from '@jrmc/adonis-mcp/enums/role' import Document from '#models/document' type Args = { id: string } @priority(0.7) @audience([Role.USER, Role.ASSISTANT]) export default class UserDocumentResource extends Resource { name = 'user_document' uri = 'document:///{id}' mimeType = 'text/plain' title = 'User Document' description = 'Access user documents by ID' async handle({ args, auth, response }: ResourceContext) { const user = auth?.user if (!user) { throw new Error('Authentication required') } const document = await Document.query() .where('id', args?.id) .where('userId', user.id) .firstOrFail() this.size = document.content.length return response.text(document.content).withMeta({ documentId: document.id, createdAt: document.createdAt.toISOString(), updatedAt: document.updatedAt.toISOString() }) } } ``` -------------------------------- ### Combine Multiple Tool Annotations Source: https://adonis-mcp.jrmc.dev/tools.html Multiple annotations can be applied to a single tool to define its behavior comprehensively. This example combines @isReadOnly(), @isOpenWorld(), and @isIdempotent() for a search 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)) } } ``` -------------------------------- ### Define Web Resource Source: https://adonis-mcp.jrmc.dev/resources.html Example of a resource with an 'https://' URI scheme for accessing web resources. ```typescript uri = 'https://api.example.com/data' ``` -------------------------------- ### Documentation Generation Prompt Source: https://adonis-mcp.jrmc.dev/prompts.html A prompt designed to generate documentation for provided code. It specifies the required components of the documentation, such as function descriptions, parameter explanations, and usage examples. ```typescript export default class GenerateDocsPrompt extends Prompt { name = 'generate_docs' title = 'Generate Documentation' description = 'Generate documentation for code' async handle({ args, response }: PromptContext) { return [ response.text('Generate comprehensive documentation for the following code:'), response.text(args?.code), response.text('Include:'), response.text('- Function/class description'), response.text('- Parameter explanations'), response.text('- Return value description'), response.text('- Usage examples'), response.text('- Edge cases and error handling') ] } } ``` -------------------------------- ### Tool Schema using VineJS Source: https://adonis-mcp.jrmc.dev/tools.html Defines the tool's input schema using VineJS for validation. This example includes optional 'perPage' parameter. ```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 } ``` -------------------------------- ### Tool Schema using Zod Source: https://adonis-mcp.jrmc.dev/tools.html Defines the tool's input schema using Zod for validation. This example includes optional 'perPage' parameter. ```typescript import * as z from 'zod' const zodSchema = z.object({ page: z.number().optional().meta({ description: 'page number for pagination', }), perPage: z.number().optional().meta({ description: 'per page limit for pagination', }) }) schema() { return z.toJSONSchema( zodSchema, { io: "input" } ) as Schema } ``` -------------------------------- ### Scaffold MCP Tools, Resources, and Prompts with Ace Generators Source: https://adonis-mcp.jrmc.dev/starter.html Use the `ace` command-line interface to generate new MCP tools, resources, and prompts. Ensure you have the necessary AdonisJS project setup. ```bash node ace make:mcp-tool my_tool ``` ```bash node ace make:mcp-resource my_resource ``` ```bash node ace make:mcp-prompt my_prompt ``` -------------------------------- ### Type-Safe Bouncer Usage Source: https://adonis-mcp.jrmc.dev/authentication.html Demonstrates type-safe usage of the bouncer with autocompletion for abilities and policy methods. Shows examples of correct and incorrect calls. ```typescript // ✅ Autocompletion on ability names await bouncer.authorize(editUser) // ✅ Autocompletion on policy methods and their arguments await bouncer.with(UserPolicy).authorize('view', user) // ❌ TypeScript error — unknown ability await bouncer.authorize(unknownAbility) // ❌ TypeScript error — wrong arguments for policy method await bouncer.with(UserPolicy).authorize('view') ``` -------------------------------- ### Define Nested File Resource with Multiple Template Variables Source: https://adonis-mcp.jrmc.dev/resources.html This example demonstrates using multiple variables within a URI template to construct a file path dynamically, allowing access to nested files. ```typescript type Args = { directory: string filename: string } export default class NestedFileResource extends Resource { uri = 'file:///{directory}/{filename}.txt' async handle({ args, response }: ResourceContext) { const path = `${args?.directory}/${args?.filename}.txt` const content = await readFile(path) return response.text(content) } } ``` -------------------------------- ### Resource Authorization Check Source: https://adonis-mcp.jrmc.dev/authentication.html Example of how to implement authorization within an Adonis MCP Resource using policies. Ensures the user is authorized before returning resource content. ```typescript import type { ResourceContext } from '@jrmc/adonis-mcp/types/context' import { Resource } from '@jrmc/adonis-mcp' import UserPolicy from '#policies/user_policy' export default class SecretDocResource extends Resource { uri = 'file:///docs/secret' name = 'Secret Document' description = 'A confidential document' async handle({ bouncer, response }: ResourceContext) { await bouncer.with(UserPolicy).authorize('viewSecret') return response.text('Top secret content here...') } } ``` -------------------------------- ### Register Custom MCP Route with Middleware Source: https://adonis-mcp.jrmc.dev/installation.html Register the MCP route with a custom path and apply middleware, such as authentication. This example uses `/custom-mcp-path` and `middleware.auth()`. ```typescript // With authentication middleware router.mcp('/custom-mcp-path').use(middleware.auth()) ``` -------------------------------- ### Code Explanation Prompt Source: https://adonis-mcp.jrmc.dev/prompts.html This prompt facilitates getting a detailed explanation of a given code snippet. It allows specifying the desired explanation level (e.g., intermediate) and covers aspects like functionality, step-by-step workings, key concepts, and potential improvements. ```typescript export default class ExplainCodePrompt extends Prompt { name = 'explain_code' title = 'Explain Code' description = 'Get detailed explanation of code' async handle({ args, response }: PromptContext) { return [ response.text(`Explain this ${args?.language} code in detail:`), response.text(args?.code), response.text(`Explain at ${args?.level || 'intermediate'} level.`), response.text('Cover:'), response.text('- What the code does'), response.text('- How it works step by step'), response.text('- Key concepts used'), response.text('- Potential improvements') ] } } ``` -------------------------------- ### Generate Unit Tests with AdonisJS MCP Source: https://adonis-mcp.jrmc.dev/prompts.html Use this prompt to generate unit tests for code in a specified language. It follows a template and includes happy path, edge case, error handling, and mock setup tests. ```typescript export default class GenerateTestsPrompt extends Prompt { name = 'generate_tests' title = 'Generate Tests' description = 'Generate unit tests for code' async handle({ args, response }: PromptContext) { return [ response.text(`Generate unit tests for this ${args?.language} code:`), response.text(args?.code), response.embeddedResource(`file:///templates/test-template-${args?.language}.txt`), response.text('Follow the template above and include:'), response.text('- Happy path tests'), response.text('- Edge case tests'), response.text('- Error handling tests'), response.text('- Mock setup if needed') ] } } ``` -------------------------------- ### MCP Middleware with Session Cleanup and Redis Source: https://adonis-mcp.jrmc.dev/sessions.html Customize the MCP middleware to add session cleanup logic using Redis. This version initializes session data in Redis for 'initialize' requests, validates session activity, and extends session expiration for subsequent requests. Requires `@adonisjs/redis` to be installed and configured. ```typescript import type { HttpContext } from '@adonisjs/core/http' import type { NextFn } from '@adonisjs/core/types/http' import redis from '@adonisjs/redis/services/main' export default class McpMiddleware { async handle(ctx: HttpContext, next: NextFn) { const body = ctx.request.body() const method = body.method const contentType = ctx.request.header('Content-Type') if (!contentType || !['application/json', 'text/event-stream'].includes(contentType)) { return ctx.response.badRequest('Content-Type header must be application/json') } if (method === 'initialize') { const sessionId = crypto.randomUUID() ctx.response.safeHeader('MCP-Session-Id', sessionId) // Initialize session data in Redis await redis.setex(`mcp:session:${sessionId}:active`, 3600, '1') } else { const sessionId = ctx.request.header('MCP-Session-Id') if (!sessionId) { return ctx.response.badRequest('MCP-Session-Id header is required') } // Validate session is still active const isActive = await redis.get(`mcp:session:${sessionId}:active`) if (!isActive) { return ctx.response.badRequest('Session expired') } // Extend session expiration await redis.expire(`mcp:session:${sessionId}:active`, 3600) ctx.response.safeHeader('MCP-Session-Id', sessionId) } return next() } } ``` -------------------------------- ### Stdio MCP Server Entry Point in bin/mcp.ts Source: https://adonis-mcp.jrmc.dev/starter.html This file serves as the entry point for running the MCP server over stdio, useful for integrating with AI clients that use standard input/output. It boots the application and handles SIGTERM and SIGINT signals for graceful termination. ```typescript new Ignitor(APP_ROOT, { importer: IMPORTER }) .tap((app) => { app.booting(async () => { await import('#start/env') }) app.listen('SIGTERM', () => app.terminate()) app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()) }) .ace() .handle(['mcp:start']) ``` -------------------------------- ### Enable VineJS Provider Manually Source: https://adonis-mcp.jrmc.dev/validation.html If VineJS validation was not enabled during installation, manually add the provider to `adonisrc.ts` to enable `request.validateUsing()`. ```typescript providers: [ // ... '@jrmc/adonis-mcp/vinejs_provider', ] ``` -------------------------------- ### Configure Custom MCP Directory in adonisrc.ts Source: https://adonis-mcp.jrmc.dev/starter.html The starter kit uses 'mcp/' at the project root for MCP-related files. This configuration in 'adonisrc.ts' can be changed to any preferred path. ```typescript directories: { mcp: 'mcp', } ``` -------------------------------- ### Create a new MCP Tool Source: https://adonis-mcp.jrmc.dev/tools.html Use the Ace command to generate a new tool file. This command creates a base template for your tool. ```bash node ace make:mcp-tool my_tool ``` -------------------------------- ### Test Server Initialization and Capabilities in Adonis MCP Source: https://adonis-mcp.jrmc.dev/unit-tests.html Verifies server initialization and its reported capabilities, including tools, resources, and prompts. Ensure all necessary components are added to the server before connecting. ```typescript test.group('MCP Server', () => { test('should initialize with correct capabilities', async ({ assert }) => { const server = new Server({ name: 'Test Server', version: '1.0.0' }) server.addTool({ 'my_tool': app.makePath('app/mcp/tools/my_tool_tool.ts') }) server.addResource({ 'file:///doc.txt': app.makePath('app/mcp/resources/document_resource.ts') }) server.addPrompt({ 'my_prompt': app.makePath('app/mcp/prompts/my_prompt_prompt.ts') }) const transport = new FakeTransport() await server.connect(transport) await server.handle({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'Test Client', version: '1.0.0' } } }) const lastMessage = transport.getLastMessage() const result = lastMessage?.result as InitializeResult assert.exists(lastMessage) assert.exists(result) assert.equal(result.serverInfo.name, 'Test Server') assert.equal(result.serverInfo.version, '1.0.0') assert.exists(result.capabilities) assert.exists(result.capabilities.tools) assert.exists(result.capabilities.resources) assert.exists(result.capabilities.prompts) }) }) ``` -------------------------------- ### Create Events Preload File Source: https://adonis-mcp.jrmc.dev/events.html Use the Ace CLI to generate a preload file for handling events. This sets up the necessary structure for event listeners. ```bash node ace make:preload events ``` -------------------------------- ### Implement a Simple MCP Tool Source: https://adonis-mcp.jrmc.dev/starter.html This TypeScript code defines a basic MCP tool named 'hello' that returns a text response. It extends the 'Tool' class and implements the 'handle' method. ```typescript import type { ToolContext } from '@jrmc/adonis-mcp/types/context' import { Tool } from '@jrmc/adonis-mcp' export default class HelloTool extends Tool { name = 'hello' description = 'Say hello' async handle({ response }: ToolContext) { return response.text('Hello from AdonisJS MCP!') } } ``` -------------------------------- ### Send Audio Response from Tool Source: https://adonis-mcp.jrmc.dev/tools.html Return base64-encoded audio content with its MIME type. Verify that the audio file is read and encoded properly. ```typescript const audioData = await fs.readFile('path/to/audio.mp3', 'base64') return response.audio(audioData, 'audio/mpeg') ``` -------------------------------- ### Create a New MCP Resource Source: https://adonis-mcp.jrmc.dev/resources.html Use this Ace command to generate a new resource file. It creates a base template for defining accessible data. ```bash node ace make:mcp-resource my_resource ``` -------------------------------- ### Basic Tool with Schema Source: https://adonis-mcp.jrmc.dev/tools.html Defines a tool with a name, title, description, and a schema for input arguments. The handle method processes the arguments and returns a response. ```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<{ name: { type: "string" } }> export default class MyToolTool extends Tool { name = 'MyTool' title = 'Tool title' description = 'Tool description' async handle({ args, response }: ToolContext) { return response.text(`Hello, ${args?.name}`) } schema() { return { type: "object", properties: { name: { type: "string", description: "Description text argument" }, }, required: ["name"] } as Schema } } ``` -------------------------------- ### Create New MCP Prompt Source: https://adonis-mcp.jrmc.dev/prompts.html Use this Ace command to generate a new prompt file. It creates a base template for defining AI interactions. ```bash node ace make:mcp-prompt my_prompt ``` -------------------------------- ### Tool Schema with Complex Types Source: https://adonis-mcp.jrmc.dev/tools.html Example of a tool schema using complex types like integers, arrays, and nested objects. It includes validation for age range and array item types. ```typescript schema() { return { type: "object", properties: { name: { type: "string", description: "User name" }, age: { type: "integer", minimum: 0, maximum: 150 }, tags: { type: "array", items: { type: "string" } }, settings: { type: "object", properties: { theme: { type: "string" }, notifications: { type: "boolean" } } } }, required: ["name"] } as Schema } ``` -------------------------------- ### Test Basic MCP Resource Read Source: https://adonis-mcp.jrmc.dev/unit-tests.html Tests reading a resource with a simple URI by setting up a server, registering the resource, and handling a 'resources/read' request. Asserts the response content and MIME type. ```typescript test.group('MCP Resource Integration', () => { test('should handle resource read end-to-end', async ({ assert }) => { const server = new Server({ name: 'Test Server', version: '1.0.0' }) // Register your resource server.addResource({ 'file:///document.txt': app.makePath('app/mcp/resources/document_resource.ts') }) const transport = new FakeTransport() await server.connect(transport) await server.handle({ jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }) // Read the resource await server.handle({ jsonrpc: '2.0', id: 2, method: 'resources/read', params: { uri: 'file:///document.txt' } }) const lastMessage = transport.getLastMessage() const result = lastMessage?.result as ReadResourceResult const content = result?.contents[0] as TextResourceContents assert.exists(lastMessage) assert.equal(lastMessage?.id, 2) assert.exists(result) assert.isArray(result.contents) assert.equal(content.uri, 'file:///document.txt') assert.equal(content.mimeType, 'text/plain') }) }) ``` -------------------------------- ### Custom MCP Session ID Generation Source: https://adonis-mcp.jrmc.dev/sessions.html Example of customizing session ID generation using nanoid for shorter, alphanumeric IDs. Replace the default UUID generation when a different format is required. ```typescript // Example: Using shorter, alphanumeric IDs import { nanoid } from 'nanoid' if (method === 'initialize') { const sessionId = nanoid(21) // Generates URL-safe ID ctx.response.safeHeader('MCP-Session-Id', sessionId) } ``` -------------------------------- ### Base Prompt Template Source: https://adonis-mcp.jrmc.dev/prompts.html A foundational prompt structure including schema definition and a basic handler. Imports necessary types and the Prompt class. ```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 { name = 'my_prompt' title = 'Prompt title' description = 'Prompt description' async handle({ args, response }: PromptContext) { return [ response.text('Hello, world!') ] } schema() { return { type: "object", properties: { text: { type: "string", description: "Description text argument" }, }, required: ["text"] } as Schema } } ``` -------------------------------- ### Handle Dynamic Resource with Arguments Source: https://adonis-mcp.jrmc.dev/resources.html Retrieve a file based on a name argument. Throws an error if the file is not found. ```typescript async handle({ args, response }: ResourceContext) { const file = await File.findBy('name', args?.name) if (!file) { throw new Error('File not found') } this.size = file.size return response.text(file.content) } ``` -------------------------------- ### Send Resource Link Response from Tool Source: https://adonis-mcp.jrmc.dev/tools.html Return a URI that points to another resource, such as a local file. ```typescript return response.resourceLink('file:///path/to/resource.txt') ``` -------------------------------- ### Basic Prompt Handler Implementation Source: https://adonis-mcp.jrmc.dev/prompts.html A simple prompt handler that returns an array of text responses based on provided arguments. It signals the type of content being returned. ```typescript async handle({ args, response }: PromptContext) { return [ response.text(`Please review this ${args?.language} code:`), response.text(args?.code), response.text('Provide feedback on code quality, potential bugs, and improvements.') ] } ``` -------------------------------- ### Implement Completion Method for Prompt Source: https://adonis-mcp.jrmc.dev/prompts.html Implements the `complete()` method in a custom prompt class to provide suggestions for prompt arguments. ```typescript import type { PromptContext, CompleteContext } from '@jrmc/adonis-mcp/types/context' import type { BaseSchema, InferJSONSchema } 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 { name = 'code_review' title = 'Code Review' description = 'Review code and provide feedback' async handle({ args, response }: PromptContext) { return [ response.text(`Please review this ${args?.language} code:`), response.text(args?.code) ] } async complete({ args, response }: CompleteContext>) { // 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 } } ``` -------------------------------- ### Define Prompt Arguments Schema with VineJS Source: https://adonis-mcp.jrmc.dev/prompts.html Uses VineJS to define a schema for prompt arguments, ensuring 'code' and 'language' are strings with descriptions. ```typescript import vine from '@vinejs/vine' const vineSchema = vine.object({ code: vine.string().meta({ description: 'The code to review', }), language: vine.string().meta({ description: 'Programming language', }) }) schema() { return vine.create( vineSchema ).toJSONSchema() as Schema } ``` -------------------------------- ### Simple Tool Without Schema Source: https://adonis-mcp.jrmc.dev/tools.html Creates a tool that does not require any input parameters. Useful for tools that perform actions directly. ```typescript import type { ToolContext } from '@jrmc/adonis-mcp/types/context' import { Tool } from '@jrmc/adonis-mcp' export default class GetServerTimeTool extends Tool { name = 'get_server_time' title = 'Get Server Time' description = 'Returns the current server time' async handle({ response }: ToolContext) { const currentTime = new Date().toISOString() return response.text(`Current server time: ${currentTime}`) } } ``` -------------------------------- ### Dynamic Project Name Completions Source: https://adonis-mcp.jrmc.dev/prompts.html Fetches project names from the database to provide dynamic completions. Ensure the Project model is accessible and has a 'name' property. ```typescript async complete({ args, response }: CompleteContext) { if (args?.projectName !== undefined) { // Fetch project names from database const projects = await Project.query().select('name') const names = projects.map(p => p.name) return response.complete({ values: names, total: names.length }) } return response.complete({ values: [] }) } ``` -------------------------------- ### Handle Resource with Authorization Source: https://adonis-mcp.jrmc.dev/resources.html Access a resource only after authorizing the user with the 'viewSecretDocuments' permission. Fetches secret content. ```typescript async handle({ bouncer, response }: ResourceContext) { await bouncer.authorize('viewSecretDocuments') const content = await getSecretContent() this.size = content.length return response.text(content) } ``` -------------------------------- ### Return Audio Response Source: https://adonis-mcp.jrmc.dev/prompts.html Includes audio in the prompt response by providing base64-encoded audio data and its MIME type. ```typescript const audioData = await fs.readFile('instruction.mp3', 'base64') return [ response.text('Listen to this instruction:'), response.audio(audioData, 'audio/mpeg') ] ``` -------------------------------- ### Define Prompt with Optional Arguments Source: https://adonis-mcp.jrmc.dev/prompts.html Illustrates how to define optional arguments in a prompt schema by omitting them from the 'required' array. 'level' is optional here. ```typescript schema() { return { type: "object", properties: { topic: { type: "string", description: "The topic to explain" }, level: { type: "string", description: "Difficulty level (beginner, intermediate, advanced)", default: "beginner" } }, required: ["topic"] // level is optional } as Schema } ``` -------------------------------- ### Return Text Content from Resource Source: https://adonis-mcp.jrmc.dev/resources.html Read a text file and return its content as a text response. Ensure the file is read with UTF-8 encoding. ```typescript async handle({ response }: ResourceContext) { const content = await fs.readFile('path/to/file.txt', 'utf-8') return response.text(content) } ``` -------------------------------- ### Define Admin Report Prompt Source: https://adonis-mcp.jrmc.dev/authentication.html Create a custom prompt for generating admin reports. Ensure the user has the necessary 'viewAdmin' ability before proceeding. ```typescript import type { PromptContext } from '@jrmc/adonis-mcp/types/context' import { Prompt } from '@jrmc/adonis-mcp' import { viewAdmin } from '#abilities/main' export default class AdminReportPrompt extends Prompt { name = 'admin_report' description = 'Generate an admin report' async handle({ bouncer, response }: PromptContext) { await bouncer.authorize(viewAdmin) return response.message('user', response.text('Please generate the monthly admin report.')) } } ``` -------------------------------- ### Generate a New MCP Tool Source: https://adonis-mcp.jrmc.dev/starter.html Use the 'make:mcp-tool' ace command to generate a new tool file. This command creates the tool file in the 'mcp/tools/' directory. ```bash node ace make:mcp-tool hello ``` -------------------------------- ### Send Plain Text Response from Tool Source: https://adonis-mcp.jrmc.dev/tools.html Format a tool's response as plain text. Useful for simple string outputs. ```typescript return response.text(JSON.stringify({ success: true })) ``` -------------------------------- ### Code Review Prompt with Completions Source: https://adonis-mcp.jrmc.dev/prompts.html A comprehensive prompt for code review that includes dynamic completions for programming language and review focus. It handles user authentication and formats the output with embedded resources. ```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<{ code: { type: "string" } language: { type: "string" } focus: { type: "string" } }> export default class CodeReviewPrompt extends Prompt { name = 'code_review' title = 'Code Review' description = 'Review code and provide detailed feedback' async handle({ args, response, auth }: PromptContext) { const user = auth?.user const greeting = user ? `Hello ${user.name},` : 'Hello,' return [ response.text(greeting), response.text(`Please perform a ${args?.focus} review of this ${args?.language} code:`), response.text('```' + args?.language), response.text(args?.code), response.text('```'), response.embeddedResource('file:///guidelines/code-review-checklist.md'), response.text('Provide feedback following the checklist above.') ] } async complete({ args, response }: CompleteContext) { // Language suggestions if (args?.language !== undefined) { return response.complete({ values: [ 'javascript', 'typescript', 'python', 'java', 'go', 'rust', 'php', 'ruby' ] }) } // Focus area suggestions if (args?.focus !== undefined) { return response.complete({ values: [ 'security', 'performance', 'maintainability', 'best practices', 'bug detection', 'code style' ] }) } return response.complete({ values: [] }) } schema() { return { type: "object", properties: { code: { type: "string", description: "The code to review" }, language: { type: "string", description: "Programming language" }, focus: { type: "string", description: "Area to focus on (security, performance, etc.)", default: "general" } }, required: ["code", "language"] } as Schema } } ``` -------------------------------- ### Define a Basic MCP Resource Source: https://adonis-mcp.jrmc.dev/resources.html This TypeScript code defines a simple resource with a static name, URI, MIME type, and a handler for content. ```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') } } ``` -------------------------------- ### Define Basic Prompt Arguments Schema Source: https://adonis-mcp.jrmc.dev/prompts.html Defines a simple JSON schema for prompt arguments, specifying 'code' and 'language' as required string properties. ```typescript schema() { return { type: "object", properties: { code: { type: "string", description: "The code to review" }, language: { type: "string", description: "Programming language" } }, required: ["code", "language"] } as Schema } ``` -------------------------------- ### Test Basic Prompt Handling in Adonis MCP Source: https://adonis-mcp.jrmc.dev/unit-tests.html Tests the end-to-end handling of a simple prompt. Ensure the prompt is registered with the server before use. ```typescript test.group('MCP Prompt Integration', () => { test('should handle prompt get end-to-end', async ({ assert }) => { const server = new Server({ name: 'Test Server', version: '1.0.0' }) // Register your prompt server.addPrompt({ 'my_prompt': app.makePath('app/mcp/prompts/my_prompt_prompt.ts') }) const transport = new FakeTransport() await server.connect(transport) await server.handle({ jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }) // Get the prompt await server.handle({ jsonrpc: '2.0', id: 2, method: 'prompts/get', params: { name: 'my_prompt', arguments: { text: 'Hello' } } }) const lastMessage = transport.getLastMessage() const result = lastMessage?.result as GetPromptResult const content = result?.messages[0].content as TextContent assert.exists(lastMessage) assert.equal(lastMessage?.id, 2) assert.exists(result) assert.isArray(result.messages) assert.equal(result.messages[0].role, 'user') assert.equal(content.text, 'Hello, world!') }) }) ``` -------------------------------- ### Define Complex Prompt Schema Source: https://adonis-mcp.jrmc.dev/prompts.html Shows how to define a complex prompt schema with nested objects for 'files' and 'options', including arrays and booleans. ```typescript type Schema = BaseSchema<{ files: { type: "array" items: { type: "object" properties: { path: { type: "string" } content: { type: "string" } } } } options: { type: "object" properties: { strict: { type: "boolean" } style: { type: "string" } } } }> schema() { return { type: "object", properties: { files: { type: "array", items: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"] } }, options: { type: "object", properties: { strict: { type: "boolean" }, style: { type: "string" } } } }, required: ["files"] } as Schema } ``` -------------------------------- ### Tool Handler Implementation Source: https://adonis-mcp.jrmc.dev/tools.html The handle method contains the tool's logic, accessing validated arguments, authentication, and permissions. It returns a response. ```typescript async handle({ args, response, auth, bouncer }: ToolContext) { // Access validated arguments const result = await SomeModel.query().where('id', args?.id) // Use authentication if available const user = auth.user // Check permissions with Bouncer await bouncer.authorize('viewData') // Return a response return response.text(JSON.stringify({ result })) } ``` -------------------------------- ### Mark Tool as Open World Source: https://adonis-mcp.jrmc.dev/tools.html Use the `@isOpenWorld()` annotation to indicate that a tool can access external information, such as the internet. This informs AI clients about potential external data 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)) } } ``` -------------------------------- ### Handle Prompt with Embedded Resource Source: https://adonis-mcp.jrmc.dev/prompts.html Embeds a local resource file into the prompt response. Ensure the file path is correctly formatted. ```typescript async handle({ args, response }: PromptContext) { return [ response.text('Please review this code:'), response.embeddedResource(`file:///${args?.filename}`), response.text('Provide detailed feedback.') ] } ``` -------------------------------- ### Send Multiple Content Items from Tool Source: https://adonis-mcp.jrmc.dev/tools.html Return an array of different response types (text, structured, resource link) in a single response. ```typescript return response.send([ response.text('Here is some information:'), response.structured({ data: 'value' }), response.resourceLink('file:///docs/help.txt') ]) ``` -------------------------------- ### Authorize User with Policy Source: https://adonis-mcp.jrmc.dev/authentication.html Authorize actions using policies by specifying the policy and the action. Requires defining a schema for arguments. ```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 User from '#models/user' import UserPolicy from '#policies/user_policy' type Schema = BaseSchema<{ userId: { type: "number" } }> export default class ViewUserTool extends Tool { name = 'view_user' description = 'View a user profile' async handle({ args, bouncer, response }: ToolContext) { const user = await User.findOrFail(args?.userId) // Authorize using a policy — throws JSON-RPC error if denied await bouncer.with(UserPolicy).authorize('view', user) return response.text(JSON.stringify(user)) } schema() { return { type: "object", properties: { userId: { type: "number", description: "The ID of the user to view" } }, required: ["userId"] } as Schema } } ``` -------------------------------- ### Define Prompt Arguments Schema with Zod Source: https://adonis-mcp.jrmc.dev/prompts.html Leverages Zod to define a schema for prompt arguments, specifying 'code' and 'language' as required strings with metadata. ```typescript import * as z from 'zod' const zodSchema = z.object({ code: z.string().meta({ description: 'The code to review', }), language: z.string().meta({ description: 'Programming language', }) }) schema() { return z.toJSONSchema( zodSchema, { io: "input" } ) as Schema } ``` -------------------------------- ### Return Binary Content (Blob) from Resource Source: https://adonis-mcp.jrmc.dev/resources.html Read a binary file (like an image or PDF) and return its content as a base64 encoded blob response. ```typescript async handle({ response }: ResourceContext) { const content = await fs.readFile('path/to/image.png', 'base64') return response.blob(content) } ``` -------------------------------- ### Send Image Response from Tool Source: https://adonis-mcp.jrmc.dev/tools.html Return base64-encoded image content along with its MIME type. Ensure the image data is correctly read and encoded. ```typescript const imageData = await fs.readFile('path/to/image.png', 'base64') return response.image(imageData, 'image/png') ``` -------------------------------- ### Configure Custom MCP Directory Source: https://adonis-mcp.jrmc.dev/installation.html Customize the default directory for MCP tools, resources, and prompts by updating the `directories.mcp` path in your `adonisrc.ts` file. ```typescript directories: { mcp: 'app/custom/mcp', // Optional: custom path for MCP files (defaults to 'app/mcp') } ``` -------------------------------- ### Handle Authentication in Tool Source: https://adonis-mcp.jrmc.dev/tools.html Access the authenticated user within a tool's handle method. Ensure authentication middleware is applied. ```typescript async handle({ args, auth, response }: ToolContext) { const user = auth.user return response.text(`Hello, ${user?.name}`) } ``` -------------------------------- ### Return Multiple Embedded Resources Source: https://adonis-mcp.jrmc.dev/prompts.html Embeds multiple local resource files into the prompt response. Each resource is added as a separate item. ```typescript return [ response.text('Review these files:'), response.embeddedResource('file:///src/main.ts'), response.embeddedResource('file:///src/utils.ts'), response.text('Look for any potential issues.') ] ``` -------------------------------- ### Listen to MCP Events Source: https://adonis-mcp.jrmc.dev/events.html Register listeners for 'mcp:request' and 'mcp:response' events in your preload file. Ensure you have the necessary types imported for JsonRpcRequest and JsonRpcResponse. ```typescript // start/attachment.ts import emitter from '@adonisjs/core/services/emitter' import type { JsonRpcRequest, JsonRpcResponse } from '@jrmc/adonis-mcp/types/jsonrpc' emitter.on('mcp:request', (request: JsonRpcRequest) => { logger.info(request) }) emitter.on('mcp:response', (response: JsonRpcResponse) => { logger.info(response) }) ``` -------------------------------- ### Combine Multiple Content Types in Response Source: https://adonis-mcp.jrmc.dev/prompts.html Constructs a prompt response that includes a mix of text and embedded resources. ```typescript return [ response.text('Code Review Session'), response.text(`Language: ${args?.language}`), response.text('Code to review:'), response.text(args?.code), response.embeddedResource('file:///guidelines/coding-standards.md'), response.text('Please provide feedback following the guidelines above.') ] ```