@jrmc/adonis-mcp 1.0.0-alpha.2 → 1.0.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.md +1064 -0
  2. package/build/commands/commands.json +1 -1
  3. package/build/commands/inspector.d.ts +16 -0
  4. package/build/commands/inspector.d.ts.map +1 -0
  5. package/build/commands/inspector.js +54 -0
  6. package/build/commands/make/prompt.d.ts +2 -0
  7. package/build/commands/make/prompt.d.ts.map +1 -1
  8. package/build/commands/make/prompt.js +6 -1
  9. package/build/commands/make/resource.d.ts +2 -0
  10. package/build/commands/make/resource.d.ts.map +1 -1
  11. package/build/commands/make/resource.js +6 -1
  12. package/build/commands/make/tool.d.ts +2 -0
  13. package/build/commands/make/tool.d.ts.map +1 -1
  14. package/build/commands/make/tool.js +6 -1
  15. package/build/commands/start.d.ts +16 -0
  16. package/build/commands/start.d.ts.map +1 -0
  17. package/build/commands/start.js +30 -0
  18. package/build/configure.d.ts.map +1 -1
  19. package/build/configure.js +7 -0
  20. package/build/index.d.ts +5 -0
  21. package/build/index.d.ts.map +1 -1
  22. package/build/index.js +5 -0
  23. package/build/providers/mcp_provider.d.ts +3 -2
  24. package/build/providers/mcp_provider.d.ts.map +1 -1
  25. package/build/providers/mcp_provider.js +35 -29
  26. package/build/src/define_config.d.ts +1 -1
  27. package/build/src/define_config.d.ts.map +1 -1
  28. package/build/src/define_config.js +0 -3
  29. package/build/src/enums/error.d.ts +16 -0
  30. package/build/src/enums/error.d.ts.map +1 -0
  31. package/build/src/enums/error.js +17 -0
  32. package/build/src/enums/role.d.ts +12 -0
  33. package/build/src/enums/role.d.ts.map +1 -0
  34. package/build/src/enums/role.js +12 -0
  35. package/build/src/request.d.ts +18 -0
  36. package/build/src/request.d.ts.map +1 -0
  37. package/build/src/request.js +12 -0
  38. package/build/src/response.d.ts +33 -0
  39. package/build/src/response.d.ts.map +1 -0
  40. package/build/src/response.js +69 -0
  41. package/build/src/server/annotations/annotations.d.ts +10 -0
  42. package/build/src/server/annotations/annotations.d.ts.map +1 -0
  43. package/build/src/server/annotations/annotations.js +9 -0
  44. package/build/src/server/annotations/audience.d.ts +22 -0
  45. package/build/src/server/annotations/audience.d.ts.map +1 -0
  46. package/build/src/server/annotations/audience.js +28 -0
  47. package/build/src/server/annotations/is_destructive.d.ts +17 -0
  48. package/build/src/server/annotations/is_destructive.d.ts.map +1 -0
  49. package/build/src/server/annotations/is_destructive.js +24 -0
  50. package/build/src/server/annotations/is_idempotent.d.ts +17 -0
  51. package/build/src/server/annotations/is_idempotent.d.ts.map +1 -0
  52. package/build/src/server/annotations/is_idempotent.js +24 -0
  53. package/build/src/server/annotations/is_open_world.d.ts +17 -0
  54. package/build/src/server/annotations/is_open_world.d.ts.map +1 -0
  55. package/build/src/server/annotations/is_open_world.js +24 -0
  56. package/build/src/server/annotations/is_read_only.d.ts +17 -0
  57. package/build/src/server/annotations/is_read_only.d.ts.map +1 -0
  58. package/build/src/server/annotations/is_read_only.js +24 -0
  59. package/build/src/server/annotations/last_modified.d.ts +21 -0
  60. package/build/src/server/annotations/last_modified.d.ts.map +1 -0
  61. package/build/src/server/annotations/last_modified.js +28 -0
  62. package/build/src/server/annotations/priority.d.ts +21 -0
  63. package/build/src/server/annotations/priority.d.ts.map +1 -0
  64. package/build/src/server/annotations/priority.js +31 -0
  65. package/build/src/server/annotations/tool_annotations.d.ts +11 -0
  66. package/build/src/server/annotations/tool_annotations.d.ts.map +1 -0
  67. package/build/src/server/annotations/tool_annotations.js +10 -0
  68. package/build/src/server/contents/audio.d.ts +24 -0
  69. package/build/src/server/contents/audio.d.ts.map +1 -0
  70. package/build/src/server/contents/audio.js +57 -0
  71. package/build/src/server/contents/blob.d.ts +20 -0
  72. package/build/src/server/contents/blob.d.ts.map +1 -0
  73. package/build/src/server/contents/blob.js +42 -0
  74. package/build/src/server/contents/embedded_resource.d.ts +25 -0
  75. package/build/src/server/contents/embedded_resource.d.ts.map +1 -0
  76. package/build/src/server/contents/embedded_resource.js +72 -0
  77. package/build/src/server/contents/error.d.ts +15 -0
  78. package/build/src/server/contents/error.d.ts.map +1 -0
  79. package/build/src/server/contents/error.js +16 -0
  80. package/build/src/server/contents/image.d.ts +24 -0
  81. package/build/src/server/contents/image.d.ts.map +1 -0
  82. package/build/src/server/contents/image.js +57 -0
  83. package/build/src/server/contents/resource_link.d.ts +22 -0
  84. package/build/src/server/contents/resource_link.d.ts.map +1 -0
  85. package/build/src/server/contents/resource_link.js +55 -0
  86. package/build/src/server/contents/structured.d.ts +20 -0
  87. package/build/src/server/contents/structured.d.ts.map +1 -0
  88. package/build/src/server/contents/structured.js +26 -0
  89. package/build/src/server/contents/text.d.ts +24 -0
  90. package/build/src/server/contents/text.d.ts.map +1 -0
  91. package/build/src/server/contents/text.js +61 -0
  92. package/build/src/server/context.d.ts +32 -0
  93. package/build/src/server/context.d.ts.map +1 -0
  94. package/build/src/server/context.js +61 -0
  95. package/build/src/server/contracts/content.d.ts +16 -0
  96. package/build/src/server/contracts/content.d.ts.map +1 -0
  97. package/build/src/server/contracts/content.js +8 -0
  98. package/build/src/server/contracts/context.d.ts +29 -0
  99. package/build/src/server/contracts/context.d.ts.map +1 -0
  100. package/build/src/server/contracts/context.js +7 -0
  101. package/build/src/server/contracts/response.d.ts +26 -0
  102. package/build/src/server/contracts/response.d.ts.map +1 -0
  103. package/build/src/server/contracts/response.js +7 -0
  104. package/build/src/server/contracts/transport.d.ts +14 -0
  105. package/build/src/server/contracts/transport.d.ts.map +1 -0
  106. package/build/src/server/contracts/transport.js +7 -0
  107. package/build/src/{controllers → server/controllers}/mcp_controller.d.ts +0 -2
  108. package/build/src/server/controllers/mcp_controller.d.ts.map +1 -0
  109. package/build/src/server/controllers/mcp_controller.js +15 -0
  110. package/build/src/server/exceptions/jsonrpc_exception.d.ts +10 -0
  111. package/build/src/server/exceptions/jsonrpc_exception.d.ts.map +1 -0
  112. package/build/src/server/exceptions/jsonrpc_exception.js +31 -0
  113. package/build/src/server/methods/call_tool.d.ts +12 -0
  114. package/build/src/server/methods/call_tool.d.ts.map +1 -0
  115. package/build/src/server/methods/call_tool.js +65 -0
  116. package/build/src/server/methods/completion.d.ts +12 -0
  117. package/build/src/server/methods/completion.d.ts.map +1 -0
  118. package/build/src/server/methods/completion.js +45 -0
  119. package/build/src/server/methods/get_prompt.d.ts +12 -0
  120. package/build/src/server/methods/get_prompt.d.ts.map +1 -0
  121. package/build/src/server/methods/get_prompt.js +65 -0
  122. package/build/src/server/methods/initialize.d.ts +12 -0
  123. package/build/src/server/methods/initialize.d.ts.map +1 -0
  124. package/build/src/server/methods/initialize.js +34 -0
  125. package/build/src/server/methods/list_prompts.d.ts +12 -0
  126. package/build/src/server/methods/list_prompts.d.ts.map +1 -0
  127. package/build/src/server/methods/list_prompts.js +34 -0
  128. package/build/src/server/methods/list_resource_templates.d.ts +12 -0
  129. package/build/src/server/methods/list_resource_templates.d.ts.map +1 -0
  130. package/build/src/server/methods/list_resource_templates.js +34 -0
  131. package/build/src/server/methods/list_resources.d.ts +12 -0
  132. package/build/src/server/methods/list_resources.d.ts.map +1 -0
  133. package/build/src/server/methods/list_resources.js +34 -0
  134. package/build/src/server/methods/list_tools.d.ts +12 -0
  135. package/build/src/server/methods/list_tools.d.ts.map +1 -0
  136. package/build/src/server/methods/list_tools.js +34 -0
  137. package/build/src/server/methods/ping.d.ts +12 -0
  138. package/build/src/server/methods/ping.d.ts.map +1 -0
  139. package/build/src/server/methods/ping.js +12 -0
  140. package/build/src/server/methods/read_resource.d.ts +12 -0
  141. package/build/src/server/methods/read_resource.d.ts.map +1 -0
  142. package/build/src/server/methods/read_resource.js +30 -0
  143. package/build/src/server/pagination/cursor_paginator.d.ts +10 -0
  144. package/build/src/server/pagination/cursor_paginator.d.ts.map +1 -0
  145. package/build/src/server/pagination/cursor_paginator.js +45 -0
  146. package/build/src/server/prompt.d.ts +27 -0
  147. package/build/src/server/prompt.d.ts.map +1 -0
  148. package/build/src/server/prompt.js +32 -0
  149. package/build/src/server/resource.d.ts +23 -0
  150. package/build/src/server/resource.d.ts.map +1 -0
  151. package/build/src/server/resource.js +40 -0
  152. package/build/src/server/tool.d.ts +28 -0
  153. package/build/src/server/tool.d.ts.map +1 -0
  154. package/build/src/server/tool.js +29 -0
  155. package/build/src/server/transports/fake_transport.d.ts +20 -0
  156. package/build/src/server/transports/fake_transport.d.ts.map +1 -0
  157. package/build/src/server/transports/fake_transport.js +28 -0
  158. package/build/src/server/transports/http_transport.d.ts +19 -0
  159. package/build/src/server/transports/http_transport.d.ts.map +1 -0
  160. package/build/src/server/transports/http_transport.js +32 -0
  161. package/build/src/server/transports/stdio_transport.d.ts +17 -0
  162. package/build/src/server/transports/stdio_transport.d.ts.map +1 -0
  163. package/build/src/server/transports/stdio_transport.js +36 -0
  164. package/build/src/server.d.ts +46 -0
  165. package/build/src/server.d.ts.map +1 -0
  166. package/build/src/server.js +139 -0
  167. package/build/src/types/config.d.ts +16 -0
  168. package/build/src/types/config.d.ts.map +1 -0
  169. package/build/src/types/config.js +7 -0
  170. package/build/src/types/content.d.ts +35 -0
  171. package/build/src/types/content.d.ts.map +1 -0
  172. package/build/src/types/content.js +7 -0
  173. package/build/src/types/context.d.ts +39 -0
  174. package/build/src/types/context.d.ts.map +1 -0
  175. package/build/src/types/context.js +7 -0
  176. package/build/src/types/jsonrpc.d.ts +406 -0
  177. package/build/src/types/jsonrpc.d.ts.map +1 -0
  178. package/build/src/types/jsonrpc.js +7 -0
  179. package/build/src/types/method.d.ts +37 -0
  180. package/build/src/types/method.d.ts.map +1 -0
  181. package/build/src/types/method.js +7 -0
  182. package/build/src/types/request.d.ts +10 -0
  183. package/build/src/types/request.d.ts.map +1 -0
  184. package/build/src/types/request.js +7 -0
  185. package/build/src/types/response.d.ts +17 -0
  186. package/build/src/types/response.d.ts.map +1 -0
  187. package/build/src/types/response.js +7 -0
  188. package/build/src/utils/find_resource_pattern.d.ts +19 -0
  189. package/build/src/utils/find_resource_pattern.d.ts.map +1 -0
  190. package/build/src/utils/find_resource_pattern.js +35 -0
  191. package/build/src/utils/stdio.d.ts +19 -0
  192. package/build/src/utils/stdio.d.ts.map +1 -0
  193. package/build/src/utils/stdio.js +36 -0
  194. package/build/src/utils/uri_template.d.ts +25 -0
  195. package/build/src/utils/uri_template.d.ts.map +1 -0
  196. package/build/src/utils/uri_template.js +241 -0
  197. package/build/stubs/config.ts.stub +2 -1
  198. package/build/stubs/make/mcp/prompts/main.ts.stub +31 -16
  199. package/build/stubs/make/mcp/resources/main.ts.stub +13 -19
  200. package/build/stubs/make/mcp/tools/main.ts.stub +29 -9
  201. package/build/stubs/make/middleware/mcp_middleware.ts.stub +31 -0
  202. package/build/tsconfig.tsbuildinfo +1 -1
  203. package/package.json +15 -7
  204. package/build/src/controllers/mcp_controller.d.ts.map +0 -1
  205. package/build/src/controllers/mcp_controller.js +0 -70
  206. package/build/src/mcp.d.ts +0 -22
  207. package/build/src/mcp.d.ts.map +0 -1
  208. package/build/src/mcp.js +0 -32
  209. package/build/src/types.d.ts +0 -49
  210. package/build/src/types.d.ts.map +0 -1
  211. package/build/src/types.js +0 -8
package/README.md CHANGED
@@ -5,3 +5,1067 @@
5
5
 
6
6
  AdonisJS MCP - Server MCP for your AdonisJS applications.
7
7
 
8
+ > **Note:** This documentation has been generated by AI and has not been fully verified yet. Please report any inaccuracies or issues you encounter.
9
+
10
+ ## Roadmap
11
+
12
+ - [x] MCP tools support
13
+ - [x] MCP resources support
14
+ - [x] MCP prompts support
15
+ - [x] HTTP transport
16
+ - [x] Stdio transport
17
+ - [x] Fake transport (for testing)
18
+ - [x] Advanced pagination support
19
+ - [x] Meta support
20
+ - [x] Annotations
21
+ - [x] Completion
22
+ - [x] Inspector
23
+ - [x] Session
24
+ - [ ] Output tool
25
+ - [ ] Error(review)
26
+ - [ ] Vine
27
+ - [ ] Bounce
28
+ - [ ] Auth
29
+ - [ ] Inject
30
+ - [ ] Alternative transports support SSE
31
+ - [ ] JSON Schema with Vine ??
32
+ - [ ] Login
33
+ - [ ] Documentation
34
+ - [ ] Kit Stater
35
+ - [ ] Demos
36
+
37
+ ## Installation & Configuration
38
+
39
+ ```bash
40
+ node ace add @jrmc/adonis-mcp
41
+ ```
42
+
43
+ This will create a configuration file `config/mcp.ts`:
44
+
45
+ ```typescript
46
+ import { defineConfig } from '@jrmc/adonis-mcp'
47
+
48
+ export default defineConfig({
49
+ name: 'adonis-mcp-server',
50
+ version: '1.0.0',
51
+ })
52
+ ```
53
+
54
+ 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:
55
+
56
+ ```typescript
57
+ directories: {
58
+ mcp: 'app/custom/mcp', // Optional: custom path for MCP files (defaults to 'app/mcp')
59
+ }
60
+ ```
61
+
62
+ ## Usage
63
+
64
+ ### Creating a Tool
65
+
66
+ To create a new tool, use the Ace command:
67
+
68
+ ```bash
69
+ node ace make:mcp-tool my_tool
70
+ ```
71
+
72
+ This command will create a file in `app/mcp/tools/my_tool.ts` with a base template:
73
+
74
+ ```typescript
75
+ import type { ToolContext } from '@jrmc/adonis-mcp/types/context'
76
+ import type { BaseSchema } from '@jrmc/adonis-mcp/types/method'
77
+
78
+ import { Tool } from '@jrmc/adonis-mcp'
79
+
80
+ type Schema = BaseSchema<{
81
+ text: { type: "string" }
82
+ }>
83
+
84
+ export default class MyToolTool extends Tool<Schema> {
85
+ name = 'tool_name'
86
+ title = 'Tool title'
87
+ description = 'Tool description'
88
+
89
+ async handle({ args, response }: ToolContext<Schema>) {
90
+ console.log(args.text)
91
+ return response.text('Hello, world!')
92
+ }
93
+
94
+ schema() {
95
+ return {
96
+ type: "object",
97
+ properties: {
98
+ text: {
99
+ type: "string",
100
+ description: "Description text argument"
101
+ },
102
+ },
103
+ required: ["text"]
104
+ } as Schema
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### Schema Definition
110
+
111
+ The schema defines the input parameters of your tool. It follows the [JSON Schema](https://json-schema.org/) specification:
112
+
113
+ ```typescript
114
+ schema() {
115
+ return {
116
+ type: "object",
117
+ properties: {
118
+ title: {
119
+ type: "string",
120
+ description: "Bookmark title"
121
+ },
122
+ url: {
123
+ type: "string",
124
+ description: "Bookmark URL"
125
+ }
126
+ },
127
+ required: ["title", "url"]
128
+ } as Schema
129
+ }
130
+ ```
131
+
132
+ You can also use Zod to define your schema:
133
+
134
+ ```typescript
135
+ import * as z from 'zod'
136
+
137
+ const zodSchema = z.object({
138
+ page: z.number().optional(),
139
+ perPage: z.number().optional()
140
+ })
141
+
142
+ schema() {
143
+ return z.toJSONSchema(
144
+ zodSchema,
145
+ { io: "input" }
146
+ ) as Schema
147
+ }
148
+ ```
149
+
150
+ ### Handler Implementation
151
+
152
+ The `handle` method contains your tool's logic. It receives a typed context with validated arguments:
153
+
154
+ ```typescript
155
+ async handle({ args, response, auth, bouncer }: ToolContext<Schema>) {
156
+ // Your logic here
157
+ const result = await SomeModel.query().where('id', args.id)
158
+
159
+ return response.text(JSON.stringify({ result }))
160
+ }
161
+ ```
162
+
163
+ ### Setting up Authentication and Bouncer
164
+
165
+ To use `auth` and `bouncer` in your MCP tools, prompts, and resources, add the following TypeScript declaration in your middleware (e.g., in your Bouncer initialization middleware):
166
+
167
+ ```typescript
168
+ declare module '@jrmc/adonis-mcp/types/context' {
169
+ export interface McpContext {
170
+ auth?: {
171
+ user?: HttpContext['auth']['user']
172
+ }
173
+ bouncer?: Bouncer<
174
+ Exclude<HttpContext['auth']['user'], undefined>,
175
+ typeof abilities,
176
+ typeof policies
177
+ >
178
+ }
179
+ }
180
+ ```
181
+
182
+ 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.
183
+
184
+ #### Registering the MCP Route
185
+
186
+ In your `start/routes.ts` file, register the MCP route and apply middleware:
187
+
188
+ ```typescript
189
+ import { middleware } from '#start/kernel'
190
+ import router from '@adonisjs/core/services/router'
191
+
192
+ // Register MCP route (defaults to /mcp, or specify a custom path)
193
+ router.mcp().use(middleware.auth())
194
+ ```
195
+
196
+ You can also specify a custom path:
197
+
198
+ ```typescript
199
+ router.mcp('/custom-mcp-path').use(middleware.auth())
200
+ ```
201
+
202
+ > **⚠️ Important: CSRF Protection**
203
+ >
204
+ > 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.
205
+ >
206
+ > In your `config/shield.ts` file, add the MCP route to the CSRF exceptions:
207
+ >
208
+ > ```typescript
209
+ > export const shieldConfig = defineConfig({
210
+ > csrf: {
211
+ > enabled: true,
212
+ > exceptRoutes: [
213
+ > '/mcp', // Or your custom MCP path
214
+ > ],
215
+ > },
216
+ > })
217
+ > ```
218
+
219
+ ### Using Authentication
220
+
221
+ The MCP context automatically includes the `auth` instance from the `HttpContext` if available. You can use it to access the authenticated user:
222
+
223
+ ```typescript
224
+ async handle({ args, auth, response }: ToolContext<Schema>) {
225
+ const user = auth?.user
226
+
227
+ if (!user) {
228
+ throw new Error('User not authenticated')
229
+ }
230
+
231
+ // Use the authenticated user
232
+ const bookmark = await Bookmark.create({
233
+ title: args.title,
234
+ userId: user.id,
235
+ })
236
+
237
+ return response.text(JSON.stringify({ bookmark }))
238
+ }
239
+ ```
240
+
241
+ ### Using Bouncer
242
+
243
+ The MCP context automatically includes the `bouncer` instance from the `HttpContext` if available. You can use it to check permissions:
244
+
245
+ ```typescript
246
+ async handle({ args, bouncer, response }: ToolContext<Schema>) {
247
+ // Check a permission
248
+ await bouncer.authorize('viewUsers')
249
+
250
+ // Or use a policy
251
+ const user = await User.findOrFail(args.userId)
252
+ await bouncer.with(UserPolicy).authorize('view', user)
253
+
254
+ return response.text(JSON.stringify({ user }))
255
+ }
256
+ ```
257
+
258
+ ### Response Return
259
+
260
+ The context includes a `response` instance to format your responses. The available methods depend on the context type:
261
+
262
+ #### Tool Responses
263
+
264
+ For tools, you can use:
265
+
266
+ - `response.text(text: string)`: Return plain text content
267
+ - `response.image(data: string, mimeType: string)`: Return image content (base64 encoded)
268
+ - `response.audio(data: string, mimeType: string)`: Return audio content (base64 encoded)
269
+ - `response.structured(object: Record<string, unknown>)`: Return structured JSON data
270
+ - `response.resourceLink(uri: string)`: Return a link to a resource
271
+ - `response.error(message: string)`: Return an error message
272
+ - `response.send(content: Content | Content[])`: Send custom content objects
273
+
274
+ ```typescript
275
+ async handle({ args, response }: ToolContext<Schema>) {
276
+ // Return text
277
+ return response.text(JSON.stringify({ success: true }))
278
+
279
+ // Return structured data
280
+ return response.structured({
281
+ temperature: 22.5,
282
+ conditions: 'Partly cloudy',
283
+ humidity: 65
284
+ })
285
+
286
+ // Return image
287
+ const imageData = await fs.readFile('path/to/image.png', 'base64')
288
+ return response.image(imageData, 'image/png')
289
+
290
+ // Return a resource link
291
+ return response.resourceLink('file:///path/to/resource.txt')
292
+
293
+ // Return error
294
+ return response.error('Something went wrong')
295
+ }
296
+ ```
297
+
298
+ #### Resource Responses
299
+
300
+ For resources, you can use:
301
+
302
+ - `response.text(text: string)`: Return text content
303
+ - `response.blob(text: string)`: Return binary content (base64 encoded)
304
+
305
+ ```typescript
306
+ async handle({ response }: ResourceContext) {
307
+ const content = await fs.readFile('path/to/file.txt', 'utf-8')
308
+ return response.text(content)
309
+ }
310
+ ```
311
+
312
+ ### Complete Example
313
+
314
+ Here is a complete example of a tool that creates a bookmark:
315
+
316
+ ```typescript
317
+ import type { ToolContext } from '@jrmc/adonis-mcp/types/context'
318
+ import type { BaseSchema } from '@jrmc/adonis-mcp/types/method'
319
+
320
+ import { Tool } from '@jrmc/adonis-mcp'
321
+ import Bookmark from '#models/bookmark'
322
+
323
+ type Schema = BaseSchema<{
324
+ title: { type: "string" }
325
+ url: { type: "string" }
326
+ }>
327
+
328
+ export default class AddBookmarkTool extends Tool<Schema> {
329
+ name = 'create_bookmark'
330
+ title = 'Create Bookmark'
331
+ description = 'Create a new bookmark'
332
+
333
+ async handle({ args, response, auth }: ToolContext<Schema>) {
334
+ const bookmark = await Bookmark.create({
335
+ title: args.title,
336
+ text: args.url,
337
+ userId: auth?.user?.id,
338
+ })
339
+
340
+ return response.text(JSON.stringify({ bookmark }))
341
+ }
342
+
343
+ schema() {
344
+ return {
345
+ type: "object",
346
+ properties: {
347
+ title: {
348
+ type: "string",
349
+ description: "Bookmark title"
350
+ },
351
+ url: {
352
+ type: "string",
353
+ description: "Bookmark URL"
354
+ }
355
+ },
356
+ required: ["title", "url"]
357
+ } as Schema
358
+ }
359
+ }
360
+ ```
361
+
362
+ ## Advanced Features
363
+
364
+ ### Structured Output
365
+
366
+ 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:
367
+
368
+ ```typescript
369
+ import type { ToolContext } from '@jrmc/adonis-mcp/types/context'
370
+ import { Tool } from '@jrmc/adonis-mcp'
371
+
372
+ export default class GetWeatherTool extends Tool {
373
+ name = 'get_weather'
374
+ title = 'Get Weather'
375
+ description = 'Get current weather data'
376
+
377
+ async handle({ args, response }: ToolContext) {
378
+ const weatherData = {
379
+ temperature: 22.5,
380
+ conditions: 'Partly cloudy',
381
+ humidity: 65,
382
+ windSpeed: 12,
383
+ location: args.location
384
+ }
385
+
386
+ return response.structured(weatherData)
387
+ }
388
+ }
389
+ ```
390
+
391
+ **Note:** Structured content can only be used in tools, not in prompts or resources.
392
+
393
+ ### Resource Links
394
+
395
+ 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:
396
+
397
+ ```typescript
398
+ import type { ToolContext } from '@jrmc/adonis-mcp/types/context'
399
+ import { Tool } from '@jrmc/adonis-mcp'
400
+
401
+ export default class GetDocumentationTool extends Tool {
402
+ name = 'get_documentation'
403
+ title = 'Get Documentation'
404
+ description = 'Get documentation for a specific topic'
405
+
406
+ async handle({ args, response }: ToolContext) {
407
+ return [
408
+ response.text('Here is the documentation for your topic:'),
409
+ response.resourceLink(`file:///docs/${args.topic}.md`)
410
+ ]
411
+ }
412
+ }
413
+ ```
414
+
415
+ 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.
416
+
417
+ **Note:** Resource links can only be used in tools, not in prompts or resources.
418
+
419
+ ### Metadata with `withMeta()`
420
+
421
+ The `withMeta()` method is available on all content types and allows you to attach custom metadata to your responses. This metadata can be used by MCP clients for various purposes such as logging, analytics, or custom processing:
422
+
423
+ ```typescript
424
+ async handle({ args, response }: ToolContext<Schema>) {
425
+ const users = await User.all()
426
+
427
+ return response.text(JSON.stringify(users)).withMeta({
428
+ source: 'database',
429
+ queryTime: Date.now(),
430
+ count: users.length,
431
+ cacheHit: false
432
+ })
433
+ }
434
+ ```
435
+
436
+ Metadata is particularly useful when:
437
+ - You want to provide debugging information
438
+ - You need to track the source of data
439
+ - You want to include performance metrics
440
+ - You need to pass additional context to the client
441
+
442
+ ### Annotations
443
+
444
+ Annotations allow you to provide additional metadata about your tools and resources to help MCP clients better understand their behavior and characteristics.
445
+
446
+ #### Tool Annotations
447
+
448
+ Tools support the following annotations that describe their operational characteristics:
449
+
450
+ ##### `@isReadOnly()`
451
+
452
+ Indicates that a tool only reads data and does not modify any state:
453
+
454
+ ```typescript
455
+ import { Tool } from '@jrmc/adonis-mcp'
456
+ import { isReadOnly } from '@jrmc/adonis-mcp/tool_annotations'
457
+
458
+ @isReadOnly()
459
+ export default class GetUserTool extends Tool {
460
+ name = 'get_user'
461
+
462
+ async handle({ args, response }: ToolContext) {
463
+ const user = await User.find(args.id)
464
+ return response.text(JSON.stringify(user))
465
+ }
466
+ }
467
+ ```
468
+
469
+ You can also explicitly set it to false:
470
+
471
+ ```typescript
472
+ @isReadOnly(false)
473
+ export default class UpdateUserTool extends Tool {
474
+ // ...
475
+ }
476
+ ```
477
+
478
+ ##### `@isOpenWorld()`
479
+
480
+ Indicates that a tool can access information from the internet or external sources:
481
+
482
+ ```typescript
483
+ import { isOpenWorld } from '@jrmc/adonis-mcp/tool_annotations'
484
+
485
+ @isOpenWorld()
486
+ export default class FetchWeatherTool extends Tool {
487
+ name = 'fetch_weather'
488
+
489
+ async handle({ args, response }: ToolContext) {
490
+ const weather = await externalApi.getWeather(args.city)
491
+ return response.text(JSON.stringify(weather))
492
+ }
493
+ }
494
+ ```
495
+
496
+ ##### `@isDestructive()`
497
+
498
+ Indicates that a tool performs destructive operations like deleting data:
499
+
500
+ ```typescript
501
+ import { isDestructive } from '@jrmc/adonis-mcp/tool_annotations'
502
+
503
+ @isDestructive()
504
+ export default class DeleteUserTool extends Tool {
505
+ name = 'delete_user'
506
+
507
+ async handle({ args, response }: ToolContext) {
508
+ await User.query().where('id', args.id).delete()
509
+ return response.text('User deleted successfully')
510
+ }
511
+ }
512
+ ```
513
+
514
+ ##### `@isIdempotent()`
515
+
516
+ Indicates that a tool can be safely called multiple times with the same arguments without causing different effects:
517
+
518
+ ```typescript
519
+ import { isIdempotent } from '@jrmc/adonis-mcp/tool_annotations'
520
+
521
+ @isIdempotent()
522
+ export default class SetUserStatusTool extends Tool {
523
+ name = 'set_user_status'
524
+
525
+ async handle({ args, response }: ToolContext) {
526
+ await User.query().where('id', args.id).update({ status: args.status })
527
+ return response.text('Status updated')
528
+ }
529
+ }
530
+ ```
531
+
532
+ ##### Combining Multiple Annotations
533
+
534
+ You can use multiple annotations on the same tool:
535
+
536
+ ```typescript
537
+ import { isReadOnly, isOpenWorld, isIdempotent } from '@jrmc/adonis-mcp/tool_annotations'
538
+
539
+ @isReadOnly()
540
+ @isOpenWorld()
541
+ @isIdempotent()
542
+ export default class SearchOnlineTool extends Tool {
543
+ name = 'search_online'
544
+
545
+ async handle({ args, response }: ToolContext) {
546
+ const results = await searchEngine.search(args.query)
547
+ return response.text(JSON.stringify(results))
548
+ }
549
+ }
550
+ ```
551
+
552
+ #### Resource Annotations
553
+
554
+ Resources support the following annotations to provide additional context:
555
+
556
+ ##### `@priority()`
557
+
558
+ Specifies the importance of a resource as a number between 0.0 and 1.0:
559
+
560
+ ```typescript
561
+ import { Resource } from '@jrmc/adonis-mcp'
562
+ import { priority } from '@jrmc/adonis-mcp/annotations'
563
+
564
+ @priority(0.9)
565
+ export default class ImportantDocResource extends Resource {
566
+ name = 'important_doc.txt'
567
+ uri = 'file:///important_doc.txt'
568
+
569
+ async handle({ response }: ResourceContext) {
570
+ return response.text('Critical documentation content')
571
+ }
572
+ }
573
+ ```
574
+
575
+ ##### `@audience()`
576
+
577
+ Specifies the intended audience for a resource (user, assistant, or both):
578
+
579
+ ```typescript
580
+ import { Resource } from '@jrmc/adonis-mcp'
581
+ import { audience } from '@jrmc/adonis-mcp/annotations'
582
+ import Role from '@jrmc/adonis-mcp/enums/role'
583
+
584
+ @audience(Role.USER)
585
+ export default class UserManualResource extends Resource {
586
+ name = 'user_manual.txt'
587
+ uri = 'file:///user_manual.txt'
588
+
589
+ async handle({ response }: ResourceContext) {
590
+ return response.text('User manual content')
591
+ }
592
+ }
593
+ ```
594
+
595
+ You can also specify multiple audiences:
596
+
597
+ ```typescript
598
+ @audience([Role.USER, Role.ASSISTANT])
599
+ export default class SharedDocResource extends Resource {
600
+ // ...
601
+ }
602
+ ```
603
+
604
+ ##### `@lastModified()`
605
+
606
+ Indicates when a resource was last updated (ISO 8601 timestamp):
607
+
608
+ ```typescript
609
+ import { Resource } from '@jrmc/adonis-mcp'
610
+ import { lastModified } from '@jrmc/adonis-mcp/annotations'
611
+
612
+ @lastModified('2024-12-12T10:00:00Z')
613
+ export default class DocumentResource extends Resource {
614
+ name = 'document.txt'
615
+ uri = 'file:///document.txt'
616
+
617
+ async handle({ response }: ResourceContext) {
618
+ return response.text('Document content')
619
+ }
620
+ }
621
+ ```
622
+
623
+ ##### Combining Resource Annotations
624
+
625
+ You can use multiple annotations on the same resource:
626
+
627
+ ```typescript
628
+ import { Resource } from '@jrmc/adonis-mcp'
629
+ import { priority, audience, lastModified } from '@jrmc/adonis-mcp/annotations'
630
+ import Role from '@jrmc/adonis-mcp/enums/role'
631
+
632
+ @priority(0.8)
633
+ @audience([Role.USER, Role.ASSISTANT])
634
+ @lastModified('2024-12-12T10:00:00Z')
635
+ export default class ApiDocResource extends Resource {
636
+ name = 'api_docs.txt'
637
+ uri = 'file:///api_docs.txt'
638
+
639
+ async handle({ response }: ResourceContext) {
640
+ return response.text('API documentation content')
641
+ }
642
+ }
643
+ ```
644
+
645
+ ### Creating a Resource
646
+
647
+ To create a new resource, use the Ace command:
648
+
649
+ ```bash
650
+ node ace make:mcp-resource my_resource
651
+ ```
652
+
653
+ This command will create a file in `app/mcp/resources/my_resource.ts` with a base template:
654
+
655
+ ```typescript
656
+ import type { ResourceContext } from '@jrmc/adonis-mcp/types/context'
657
+
658
+ import { Resource } from '@jrmc/adonis-mcp'
659
+
660
+ export default class MyResourceResource extends Resource {
661
+ name = 'example.txt'
662
+ uri = 'file:///example.txt'
663
+ mimeType = 'text/plain'
664
+ title = 'Resource title'
665
+ description = 'Resource description'
666
+ size = 0
667
+
668
+ async handle({ response }: ResourceContext) {
669
+ this.size = 1000
670
+ return response.text('Hello World')
671
+ }
672
+ }
673
+ ```
674
+
675
+ ### Resource Properties
676
+
677
+ Resources have the following properties:
678
+
679
+ - `name` (optional): The name of the resource
680
+ - `uri` (required): The unique identifier for the resource (must be unique)
681
+ - `mimeType` (optional): The MIME type of the resource
682
+ - `title` (optional): A human-readable title
683
+ - `description` (optional): A description of the resource
684
+ - `size` (optional): The size of the resource in bytes
685
+
686
+ ### Resource Handler
687
+
688
+ The `handle` method returns the content of the resource. You can use `response.text()` for text content or `response.blob()` for binary content:
689
+
690
+ ```typescript
691
+ async handle({ response }: ResourceContext) {
692
+ const content = await fs.readFile('path/to/file.txt', 'utf-8')
693
+ this.size = content.length
694
+ return response.text(content)
695
+ }
696
+ ```
697
+
698
+ ### URI Templates
699
+
700
+ Resources support URI templates (RFC 6570) to create dynamic resources. This allows you to define resources with variable parts in their URIs:
701
+
702
+ ```typescript
703
+ import type { ResourceContext } from '@jrmc/adonis-mcp/types/context'
704
+ import { Resource } from '@jrmc/adonis-mcp'
705
+
706
+ type Args = {
707
+ name: string
708
+ }
709
+
710
+ export default class RobotsResource extends Resource<Args> {
711
+ name = 'robots.txt'
712
+ uri = 'file:///{name}.txt'
713
+ mimeType = 'text/plain'
714
+ title = 'Robots file'
715
+ description = 'Dynamic robots.txt file'
716
+
717
+ async handle({ args, response }: ResourceContext<Args>) {
718
+ this.size = 1000
719
+ return response.text(`Hello World ${args?.name}`)
720
+ }
721
+ }
722
+ ```
723
+
724
+ 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`.
725
+
726
+ URI templates support various operators:
727
+ - `{name}` - Simple variable substitution
728
+ - `{/name}` - Path segment
729
+ - `{?name}` - Query parameter
730
+ - `{&name}` - Additional query parameter
731
+ - `{#name}` - Fragment identifier
732
+ - `{+name}` - Reserved characters allowed
733
+ - `{.name}` - Dot-prefixed segment
734
+
735
+ For more information, see [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570).
736
+
737
+ ### Creating a Prompt
738
+
739
+ To create a new prompt, use the Ace command:
740
+
741
+ ```bash
742
+ node ace make:mcp-prompt my_prompt
743
+ ```
744
+
745
+ This command will create a file in `app/mcp/prompts/my_prompt.ts` with a base template:
746
+
747
+ ```typescript
748
+ import type { PromptContext } from '@jrmc/adonis-mcp/types/context'
749
+ import type { BaseSchema } from '@jrmc/adonis-mcp/types/method'
750
+
751
+ import { Prompt } from '@jrmc/adonis-mcp'
752
+
753
+ type Schema = BaseSchema<{
754
+ text: { type: "string" }
755
+ }>
756
+
757
+ export default class MyPromptPrompt extends Prompt<Schema> {
758
+ name = 'my_prompt'
759
+ title = 'Prompt title'
760
+ description = 'Prompt description'
761
+
762
+ async handle({ args, response }: PromptContext<Schema>) {
763
+ return [
764
+ response.text('Hello, world!')
765
+ ]
766
+ }
767
+
768
+ schema() {
769
+ return {
770
+ type: "object",
771
+ properties: {
772
+ text: {
773
+ type: "string",
774
+ description: "Description text argument"
775
+ },
776
+ },
777
+ required: ["text"]
778
+ } as Schema
779
+ }
780
+ }
781
+ ```
782
+
783
+ ### Prompt Schema
784
+
785
+ 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:
786
+
787
+ ```typescript
788
+ import * as z from 'zod'
789
+
790
+ const zodSchema = z.object({
791
+ code: z.string(),
792
+ language: z.string().optional()
793
+ })
794
+
795
+ schema() {
796
+ return z.toJSONSchema(
797
+ zodSchema,
798
+ { io: "input" }
799
+ ) as Schema
800
+ }
801
+ ```
802
+
803
+ ### Prompt Handler
804
+
805
+ The `handle` method for prompts returns an array of content objects. This allows you to return multiple pieces of content, including embedded resources:
806
+
807
+ ```typescript
808
+ async handle({ args, response }: PromptContext<Schema>) {
809
+ return [
810
+ response.text(`Please review this code:\n\n${args.code}`),
811
+ response.embeddedResource('file:///example.txt')
812
+ ]
813
+ }
814
+ ```
815
+
816
+ ### Prompt Response Methods
817
+
818
+ For prompts, you can use the same response methods as tools, but you must return an array:
819
+
820
+ - `response.text(text: string)`: Return plain text content
821
+ - `response.image(data: string, mimeType: string)`: Return image content (base64 encoded)
822
+ - `response.audio(data: string, mimeType: string)`: Return audio content (base64 encoded)
823
+ - `response.embeddedResource(uri: string)`: Embed another resource in the prompt response
824
+
825
+ All response methods also support the `withMeta()` method to add metadata:
826
+
827
+ ```typescript
828
+ async handle({ args, response }: PromptContext<Schema>) {
829
+ return [
830
+ response.text('Here is the code to review:').withMeta({
831
+ language: args.language
832
+ }),
833
+ response.embeddedResource('file:///code.py'),
834
+ response.text('Please provide feedback.')
835
+ ]
836
+ }
837
+ ```
838
+
839
+ ### Complete Prompt Example
840
+
841
+ Here is a complete example of a prompt for code review:
842
+
843
+ ```typescript
844
+ import type { PromptContext } from '@jrmc/adonis-mcp/types/context'
845
+ import type { BaseSchema } from '@jrmc/adonis-mcp/types/method'
846
+
847
+ import { Prompt } from '@jrmc/adonis-mcp'
848
+
849
+ type Schema = BaseSchema<{
850
+ code: { type: "string" }
851
+ language: { type: "string" }
852
+ }>
853
+
854
+ export default class CodeReviewPrompt extends Prompt<Schema> {
855
+ name = 'code_review'
856
+ title = 'Code Review'
857
+ description = 'Review code and provide feedback'
858
+
859
+ async handle({ args, response }: PromptContext<Schema>) {
860
+ return [
861
+ response.text(`Please review this ${args.language} code:\n\n${args.code}`),
862
+ response.text('Provide feedback on code quality, potential bugs, and improvements.')
863
+ ]
864
+ }
865
+
866
+ schema() {
867
+ return {
868
+ type: "object",
869
+ properties: {
870
+ code: {
871
+ type: "string",
872
+ description: "The code to review"
873
+ },
874
+ language: {
875
+ type: "string",
876
+ description: "Programming language"
877
+ }
878
+ },
879
+ required: ["code", "language"]
880
+ } as Schema
881
+ }
882
+ }
883
+ ```
884
+
885
+ ## Completions
886
+
887
+ 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.
888
+
889
+ ### Enabling Completions
890
+
891
+ First, enable completions in your `config/mcp.ts`:
892
+
893
+ ```typescript
894
+ import { defineConfig } from '@jrmc/adonis-mcp'
895
+
896
+ export default defineConfig({
897
+ name: 'adonis-mcp-server',
898
+ version: '1.0.0',
899
+ completions: true, // Enable completions
900
+ })
901
+ ```
902
+
903
+ ### Implementing Completions in Prompts
904
+
905
+ Add a `complete()` method to your prompt to provide argument suggestions:
906
+
907
+ ```typescript
908
+ import type { PromptContext, CompleteContext } from '@jrmc/adonis-mcp/types/context'
909
+ import type { BaseSchema } from '@jrmc/adonis-mcp/types/method'
910
+
911
+ import { Prompt } from '@jrmc/adonis-mcp'
912
+
913
+ type Schema = BaseSchema<{
914
+ language: { type: "string" }
915
+ code: { type: "string" }
916
+ }>
917
+
918
+ export default class CodeReviewPrompt extends Prompt<Schema> {
919
+ name = 'code_review'
920
+ title = 'Code Review'
921
+ description = 'Review code and provide feedback'
922
+
923
+ async handle({ args, response }: PromptContext<Schema>) {
924
+ return [
925
+ response.text(`Please review this ${args.language} code:\n\n${args.code}`)
926
+ ]
927
+ }
928
+
929
+ async complete({ args, response }: CompleteContext<Schema>) {
930
+ // Provide language suggestions when the user types
931
+ if (args?.language !== undefined) {
932
+ return response.complete({
933
+ values: ['python', 'javascript', 'typescript', 'java', 'go', 'rust']
934
+ })
935
+ }
936
+
937
+ return response.complete({ values: [] })
938
+ }
939
+
940
+ schema() {
941
+ return {
942
+ type: "object",
943
+ properties: {
944
+ language: {
945
+ type: "string",
946
+ description: "Programming language"
947
+ },
948
+ code: {
949
+ type: "string",
950
+ description: "Code to review"
951
+ }
952
+ },
953
+ required: ["language", "code"]
954
+ } as Schema
955
+ }
956
+ }
957
+ ```
958
+
959
+ ### Implementing Completions in Resources
960
+
961
+ Resources with URI templates can also provide completions for their path parameters:
962
+
963
+ ```typescript
964
+ import type { ResourceContext, CompleteContext } from '@jrmc/adonis-mcp/types/context'
965
+ import { Resource } from '@jrmc/adonis-mcp'
966
+
967
+ type Args = {
968
+ directory: string
969
+ name: string
970
+ }
971
+
972
+ export default class ConfigFileResource extends Resource<Args> {
973
+ name = 'config_file'
974
+ uri = 'file://{directory}/{name}.txt'
975
+ mimeType = 'text/plain'
976
+ title = 'Configuration File'
977
+ description = 'Access configuration files'
978
+
979
+ async handle({ args, response }: ResourceContext<Args>) {
980
+ const content = await readConfigFile(args.directory, args.name)
981
+ return response.text(content)
982
+ }
983
+
984
+ async complete({ args, response }: CompleteContext<Args>) {
985
+ // Provide suggestions based on available directories and files
986
+ if (args?.name !== undefined) {
987
+ return response.complete({
988
+ values: ['config', 'settings', 'environment', 'database']
989
+ })
990
+ }
991
+
992
+ if (args?.directory !== undefined) {
993
+ return response.complete({
994
+ values: ['production', 'staging', 'development']
995
+ })
996
+ }
997
+
998
+ return response.complete({ values: [] })
999
+ }
1000
+ }
1001
+ ```
1002
+
1003
+ ### Completion Context
1004
+
1005
+ The `complete()` method receives a `CompleteContext` that includes:
1006
+
1007
+ - `args`: The current argument values (partial or complete)
1008
+ - `response`: The response object with a `complete()` method
1009
+
1010
+ The response format includes:
1011
+
1012
+ ```typescript
1013
+ response.complete({
1014
+ values: string[], // Array of suggested values
1015
+ hasMore?: boolean, // Optional: indicates if more values are available
1016
+ total?: number // Optional: total number of available values
1017
+ })
1018
+ ```
1019
+
1020
+ ### Transports
1021
+
1022
+ The package supports multiple transport mechanisms:
1023
+
1024
+ - **HTTP Transport**: Default transport for HTTP-based MCP servers (used when accessing via HTTP routes)
1025
+ - **Stdio Transport**: For command-line MCP servers that communicate via standard input/output
1026
+ - **Fake Transport**: For testing purposes, allows you to capture and inspect MCP messages
1027
+
1028
+ ### Pagination
1029
+
1030
+ 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/2025-06-18/server/utilities/pagination)
1031
+
1032
+ ## Testing & Debugging
1033
+
1034
+ ### MCP Inspector
1035
+
1036
+ 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.
1037
+
1038
+ To open the MCP Inspector, use the following command:
1039
+
1040
+ ```bash
1041
+ node ace mcp:inspector
1042
+ ```
1043
+
1044
+ By default, this command uses HTTP transport. You can specify a different transport type:
1045
+
1046
+ ```bash
1047
+ # Use HTTP transport (default)
1048
+ node ace mcp:inspector http
1049
+
1050
+ # Use stdio transport
1051
+ node ace mcp:inspector stdio
1052
+ ```
1053
+
1054
+ **Important notes:**
1055
+
1056
+ - The inspector can only be used in development environment (not in production)
1057
+ - For HTTP transport, make sure your server is running and the MCP route is configured
1058
+ - The inspector will automatically connect to your MCP server and allow you to:
1059
+ - List and test all available tools
1060
+ - Browse and read resources
1061
+ - Execute prompts with different arguments
1062
+ - Inspect request/response payloads
1063
+ - Debug any issues with your MCP implementation
1064
+
1065
+ ## Support
1066
+
1067
+ For any questions or issues, please open an issue on the [GitHub repository](https://github.com/batosai/adonis-mcp).
1068
+
1069
+ ## Inspiration
1070
+
1071
+ This package is inspired by [laravel/mcp](https://github.com/laravel/mcp).