@jrmc/adonis-mcp 1.0.0-beta.8 → 1.0.0

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.
package/README.md CHANGED
@@ -28,16 +28,15 @@ AdonisJS MCP - Server MCP for your AdonisJS applications.
28
28
  - [x] Documentation
29
29
  - [x] Inject support
30
30
  - [x] VineJs integration
31
- - [ ] JSON Schema with VineJs
32
- - [ ] Bounce integration (WIP)
33
- - [ ] Auth helpers (WIP)
34
- - [ ] Events
35
- - [ ] Logger
31
+ - [x] JSON Schema with VineJs
32
+ - [x] Bounce integration
33
+ - [x] Auth helpers
34
+ - [x] Starter kit
35
+ - [x] Demo applications
36
+ - [x] Events
37
+ - [x] Logger
36
38
  - [ ] Alternative transports (SSE)
37
39
  - [ ] Output tool
38
- - [ ] Login flow
39
- - [ ] Starter kit
40
- - [ ] Demo applications
41
40
 
42
41
  ## Installation & Configuration
43
42
 
@@ -192,10 +191,10 @@ To use `auth` and `bouncer` in your MCP tools, prompts, and resources, add the f
192
191
  ```typescript
193
192
  declare module '@jrmc/adonis-mcp/types/context' {
194
193
  export interface McpContext {
195
- auth?: {
194
+ auth: {
196
195
  user?: HttpContext['auth']['user']
197
196
  }
198
- bouncer?: Bouncer<
197
+ bouncer: McpBouncer<
199
198
  Exclude<HttpContext['auth']['user'], undefined>,
200
199
  typeof abilities,
201
200
  typeof policies
@@ -1 +1 @@
1
- {"commands":[{"commandName":"mcp:inspector","description":"Open the MCP Inspector tool to debug and test MCP Servers.","help":"","namespace":"mcp","aliases":[],"flags":[],"args":[{"name":"transport","argumentName":"transport","required":true,"description":"Transport type","default":"http","type":"string"}],"options":{"startApp":true},"filePath":"inspector.js"},{"commandName":"make:mcp-prompt","description":"Create a new MCP prompt","help":"","namespace":"make","aliases":[],"flags":[],"args":[{"name":"name","argumentName":"name","required":true,"description":"Name of the prompt","type":"string"}],"options":{"startApp":true},"filePath":"make/prompt.js"},{"commandName":"make:mcp-resource","description":"Create a new MCP resource","help":"","namespace":"make","aliases":[],"flags":[],"args":[{"name":"name","argumentName":"name","required":true,"description":"Name of the resource","type":"string"}],"options":{"startApp":true},"filePath":"make/resource.js"},{"commandName":"make:mcp-tool","description":"Create a new MCP tool","help":"","namespace":"make","aliases":[],"flags":[],"args":[{"name":"name","argumentName":"name","required":true,"description":"Name of the tool","type":"string"}],"options":{"startApp":true},"filePath":"make/tool.js"},{"commandName":"mcp:start","description":"Start the MCP server stdio transport","help":"","namespace":"mcp","aliases":[],"flags":[],"args":[],"options":{"startApp":true},"filePath":"start.js"}],"version":1}
1
+ {"commands":[{"commandName":"mcp:inspector","description":"Open the MCP Inspector tool to debug and test MCP Servers.","help":"","namespace":"mcp","aliases":["mcp:inspect"],"flags":[],"args":[{"name":"transport","argumentName":"transport","required":true,"description":"Transport type","default":"http","type":"string"}],"options":{"startApp":true},"filePath":"inspector.js"},{"commandName":"make:mcp-prompt","description":"Create a new MCP prompt","help":"","namespace":"make","aliases":[],"flags":[],"args":[{"name":"name","argumentName":"name","required":true,"description":"Name of the prompt","type":"string"}],"options":{"startApp":true},"filePath":"make/prompt.js"},{"commandName":"make:mcp-resource","description":"Create a new MCP resource","help":"","namespace":"make","aliases":[],"flags":[],"args":[{"name":"name","argumentName":"name","required":true,"description":"Name of the resource","type":"string"}],"options":{"startApp":true},"filePath":"make/resource.js"},{"commandName":"make:mcp-tool","description":"Create a new MCP tool","help":"","namespace":"make","aliases":[],"flags":[],"args":[{"name":"name","argumentName":"name","required":true,"description":"Name of the tool","type":"string"}],"options":{"startApp":true},"filePath":"make/tool.js"},{"commandName":"mcp:start","description":"Start the MCP server stdio transport","help":"","namespace":"mcp","aliases":[],"flags":[],"args":[],"options":{"startApp":true},"filePath":"start.js"}],"version":1}
@@ -9,6 +9,7 @@ import { BaseCommand } from '@adonisjs/core/ace';
9
9
  export default class Inspector extends BaseCommand {
10
10
  static commandName: string;
11
11
  static description: string;
12
+ static aliases: string[];
12
13
  static options: CommandOptions;
13
14
  transport: string;
14
15
  run(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"inspector.d.ts","sourceRoot":"","sources":["../../commands/inspector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAG9D,OAAO,EAAQ,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEtD,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,MAAM,CAAC,WAAW,SAAkB;IACpC,MAAM,CAAC,WAAW,SAA+D;IAEjF,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAGO,SAAS,EAAE,MAAM,CAAA;IAEnB,GAAG;CAoCV"}
1
+ {"version":3,"file":"inspector.d.ts","sourceRoot":"","sources":["../../commands/inspector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAG9D,OAAO,EAAQ,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEtD,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,MAAM,CAAC,WAAW,SAAkB;IACpC,MAAM,CAAC,WAAW,SAA+D;IACjF,MAAM,CAAC,OAAO,WAAkB;IAEhC,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAGO,SAAS,EAAE,MAAM,CAAA;IAEnB,GAAG;CAoCV"}
@@ -15,6 +15,7 @@ import { args, BaseCommand } from '@adonisjs/core/ace';
15
15
  export default class Inspector extends BaseCommand {
16
16
  static commandName = 'mcp:inspector';
17
17
  static description = 'Open the MCP Inspector tool to debug and test MCP Servers.';
18
+ static aliases = ['mcp:inspect'];
18
19
  static options = {
19
20
  startApp: true,
20
21
  };
@@ -1 +1 @@
1
- {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../../commands/make/prompt.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAE9D,OAAO,EAAQ,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAItD,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;IACjD,MAAM,CAAC,WAAW,SAAoB;IACtC,MAAM,CAAC,WAAW,SAA4B;IAE9C,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAGO,IAAI,EAAE,MAAM,CAAA;IAEd,GAAG;CAUV"}
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../../commands/make/prompt.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAE9D,OAAO,EAAQ,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAItD,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;IACjD,MAAM,CAAC,WAAW,SAAoB;IACtC,MAAM,CAAC,WAAW,SAA4B;IAE9C,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAGO,IAAI,EAAE,MAAM,CAAA;IAEd,GAAG;CAcV"}
@@ -21,7 +21,10 @@ export default class MakePrompt extends BaseCommand {
21
21
  };
22
22
  async run() {
23
23
  const codemods = await this.createCodemods();
24
- const stubPath = `make/mcp/prompts/main.ts.stub`;
24
+ let stubPath = `make/mcp/prompts/main.ts.stub`;
25
+ if (this.app.usingVineJS) {
26
+ stubPath = `make/mcp/prompts/with_vine.ts.stub`;
27
+ }
25
28
  await codemods.makeUsingStub(stubsRoot, stubPath, {
26
29
  name: string.pascalCase(this.name),
27
30
  });
@@ -1 +1 @@
1
- {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../../commands/make/tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAE9D,OAAO,EAAQ,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAItD,MAAM,CAAC,OAAO,OAAO,QAAS,SAAQ,WAAW;IAC/C,MAAM,CAAC,WAAW,SAAkB;IACpC,MAAM,CAAC,WAAW,SAA0B;IAE5C,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAGO,IAAI,EAAE,MAAM,CAAA;IAEd,GAAG;CAUV"}
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../../commands/make/tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAE9D,OAAO,EAAQ,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAItD,MAAM,CAAC,OAAO,OAAO,QAAS,SAAQ,WAAW;IAC/C,MAAM,CAAC,WAAW,SAAkB;IACpC,MAAM,CAAC,WAAW,SAA0B;IAE5C,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAGO,IAAI,EAAE,MAAM,CAAA;IAEd,GAAG;CAcV"}
@@ -21,7 +21,10 @@ export default class MakeTool extends BaseCommand {
21
21
  };
22
22
  async run() {
23
23
  const codemods = await this.createCodemods();
24
- const stubPath = `make/mcp/tools/main.ts.stub`;
24
+ let stubPath = `make/mcp/tools/main.ts.stub`;
25
+ if (this.app.usingVineJS) {
26
+ stubPath = `make/mcp/tools/with_vine.ts.stub`;
27
+ }
25
28
  await codemods.makeUsingStub(stubsRoot, stubPath, {
26
29
  name: string.pascalCase(this.name),
27
30
  });
@@ -1 +1 @@
1
- {"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../configure.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,SAAS,MAAM,mCAAmC,CAAA;AAG9D,wBAAsB,SAAS,CAAC,OAAO,EAAE,SAAS,iBA4BjD"}
1
+ {"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../configure.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,SAAS,MAAM,mCAAmC,CAAA;AAI9D,wBAAsB,SAAS,CAAC,OAAO,EAAE,SAAS,iBAsCjD"}
@@ -4,9 +4,19 @@
4
4
  * @license MIT
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
+ import { mkdir } from 'node:fs/promises';
7
8
  import { stubsRoot } from './stubs/main.js';
8
9
  export async function configure(command) {
9
10
  const codemods = await command.createCodemods();
11
+ /**
12
+ * Create mcp directory if it doesn't exist
13
+ */
14
+ const mcpDirectory = command.app.rcFile.directories['mcp'] || 'app/mcp/';
15
+ await Promise.all([
16
+ mkdir(command.app.makePath(mcpDirectory, 'tools'), { recursive: true }),
17
+ mkdir(command.app.makePath(mcpDirectory, 'resources'), { recursive: true }),
18
+ mkdir(command.app.makePath(mcpDirectory, 'prompts'), { recursive: true }),
19
+ ]);
10
20
  /**
11
21
  * Create default config file
12
22
  */
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { ApplicationService } from '@adonisjs/core/types';
8
8
  import type { RouteGroup } from '@adonisjs/core/http';
9
+ import type { JsonRpcRequest, JsonRpcResponse } from '../src/types/jsonrpc.js';
9
10
  import McpServer from '../src/server.js';
10
11
  export default class McpProvider {
11
12
  protected app: ApplicationService;
@@ -26,5 +27,9 @@ declare module '@adonisjs/core/types' {
26
27
  interface ContainerBindings {
27
28
  'jrmc.mcp': McpServer;
28
29
  }
30
+ interface EventsList {
31
+ 'mcp:request': JsonRpcRequest;
32
+ 'mcp:response': JsonRpcResponse;
33
+ }
29
34
  }
30
35
  //# sourceMappingURL=mcp_provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp_provider.d.ts","sourceRoot":"","sources":["../../providers/mcp_provider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAMrD,OAAO,SAAS,MAAM,kBAAkB,CAAA;AAExC,MAAM,CAAC,OAAO,OAAO,WAAW;IAClB,SAAS,CAAC,GAAG,EAAE,kBAAkB;gBAAvB,GAAG,EAAE,kBAAkB;IAE7C,QAAQ;IAQF,KAAK;IAeL,aAAa;IAIb,iBAAiB;IAIjB,eAAe;IAIf,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,QAAQ;CA6C3D;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,UAAU,CAAA;KACtC;CACF;AAED,OAAO,QAAQ,sBAAsB,CAAC;IACpC,UAAU,iBAAiB;QACzB,UAAU,EAAE,SAAS,CAAA;KACtB;CACF"}
1
+ {"version":3,"file":"mcp_provider.d.ts","sourceRoot":"","sources":["../../providers/mcp_provider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAErD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAK9E,OAAO,SAAS,MAAM,kBAAkB,CAAA;AAExC,MAAM,CAAC,OAAO,OAAO,WAAW;IAClB,SAAS,CAAC,GAAG,EAAE,kBAAkB;gBAAvB,GAAG,EAAE,kBAAkB;IAE7C,QAAQ;IAUF,KAAK;IAeL,aAAa;IAIb,iBAAiB;IAIjB,eAAe;IAIf,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,QAAQ;CA6C3D;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,UAAU,CAAA;KACtC;CACF;AAED,OAAO,QAAQ,sBAAsB,CAAC;IACpC,UAAU,iBAAiB;QACzB,UAAU,EAAE,SAAS,CAAA;KACtB;IAED,UAAU,UAAU;QAClB,aAAa,EAAE,cAAc,CAAA;QAC7B,cAAc,EAAE,eAAe,CAAA;KAChC;CACF"}
@@ -16,7 +16,9 @@ export default class McpProvider {
16
16
  register() {
17
17
  this.app.container.singleton('jrmc.mcp', async () => {
18
18
  const config = this.app.config.get('mcp', {});
19
- return new McpServer(config);
19
+ const logger = await this.app.container.make('logger');
20
+ const emitter = await this.app.container.make('emitter');
21
+ return new McpServer(config, { logger, emitter });
20
22
  });
21
23
  }
22
24
  async start() {
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @jrmc/adonis-mcp
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
7
+ import { type Bouncer, AuthorizationResponse } from '@adonisjs/bouncer';
8
+ import type { LazyImport, Constructor, BouncerAbility, AuthorizerResponse, NarrowAbilitiesForAUser } from '@adonisjs/bouncer/types';
9
+ /**
10
+ * Given a Bouncer instance type, extract the return type of `.with(policy)`
11
+ * so we can wrap it without importing the unexported PolicyAuthorizer class.
12
+ */
13
+ type PolicyAuthorizerOf<B extends Bouncer<any, any, any>, Policy> = B extends {
14
+ with(policy: Policy): infer R;
15
+ } ? R : never;
16
+ /**
17
+ * Replaces each async method's return‑type with one that converts
18
+ * E_AUTHORIZATION_FAILURE → JsonRpcException, while keeping the
19
+ * original parameter signatures intact.
20
+ */
21
+ type WrappedPolicyAuthorizer<T> = {
22
+ [K in keyof T]: T[K] extends (...args: infer A) => Promise<infer R> ? (...args: A) => Promise<R> : T[K];
23
+ };
24
+ /**
25
+ * McpBouncer wraps the AdonisJS Bouncer instance and converts
26
+ * authorization exceptions (`E_AUTHORIZATION_FAILURE`) into
27
+ * `JsonRpcException` so they are properly serialized as JSON-RPC
28
+ * error responses instead of leaking HTTP-centric error handling.
29
+ *
30
+ * Every public method signature is kept identical to the original
31
+ * Bouncer class so that consumers get full type-safety.
32
+ */
33
+ export default class McpBouncer<User extends Record<any, any>, Abilities extends Record<string, BouncerAbility<any>> | undefined = undefined, Policies extends Record<string, LazyImport<Constructor<any>>> | undefined = undefined> {
34
+ #private;
35
+ constructor(bouncer: Bouncer<User, Abilities, Policies>, requestId: string | number);
36
+ /**
37
+ * Access the underlying bouncer abilities.
38
+ */
39
+ get abilities(): Abilities | undefined;
40
+ /**
41
+ * Access the underlying bouncer policies.
42
+ */
43
+ get policies(): Policies | undefined;
44
+ /**
45
+ * Returns a wrapped PolicyAuthorizer for a pre-registered policy name.
46
+ */
47
+ with<Policy extends keyof Policies>(policy: Policy): Policies extends Record<string, LazyImport<Constructor<any>>> ? WrappedPolicyAuthorizer<PolicyAuthorizerOf<Bouncer<User, Abilities, Policies>, Policy>> : never;
48
+ /**
49
+ * Returns a wrapped PolicyAuthorizer for a policy class constructor.
50
+ */
51
+ with<Policy extends Constructor<any>>(policy: Policy): WrappedPolicyAuthorizer<PolicyAuthorizerOf<Bouncer<User, Abilities, Policies>, Policy>>;
52
+ /**
53
+ * Execute an ability by reference.
54
+ */
55
+ execute<Ability extends BouncerAbility<User>>(ability: Ability, ...args: Ability extends {
56
+ original: (user: User, ...args: infer Args) => AuthorizerResponse | Promise<AuthorizerResponse>;
57
+ } ? Args : never): Promise<AuthorizationResponse>;
58
+ /**
59
+ * Execute an ability from the list of pre-defined abilities.
60
+ */
61
+ execute<Ability extends NarrowAbilitiesForAUser<User, Abilities>>(ability: Ability, ...args: Abilities[Ability] extends {
62
+ original: (user: User, ...args: infer Args) => AuthorizerResponse | Promise<AuthorizerResponse>;
63
+ } ? Args : never): Promise<AuthorizationResponse>;
64
+ /**
65
+ * Check if a user is allowed to perform an action using
66
+ * the ability provided by reference.
67
+ */
68
+ allows<Ability extends BouncerAbility<User>>(ability: Ability, ...args: Ability extends {
69
+ original: (user: User, ...args: infer Args) => AuthorizerResponse | Promise<AuthorizerResponse>;
70
+ } ? Args : never): Promise<boolean>;
71
+ /**
72
+ * Check if a user is allowed to perform an action using
73
+ * the ability from the pre-defined list of abilities.
74
+ */
75
+ allows<Ability extends NarrowAbilitiesForAUser<User, Abilities>>(ability: Ability, ...args: Abilities[Ability] extends {
76
+ original: (user: User, ...args: infer Args) => AuthorizerResponse | Promise<AuthorizerResponse>;
77
+ } ? Args : never): Promise<boolean>;
78
+ /**
79
+ * Check if a user is denied from performing an action using
80
+ * the ability provided by reference.
81
+ */
82
+ denies<Action extends BouncerAbility<User>>(action: Action, ...args: Action extends {
83
+ original: (user: User, ...args: infer Args) => AuthorizerResponse | Promise<AuthorizerResponse>;
84
+ } ? Args : never): Promise<boolean>;
85
+ /**
86
+ * Check if a user is denied from performing an action using
87
+ * the ability from the pre-defined list of abilities.
88
+ */
89
+ denies<Action extends NarrowAbilitiesForAUser<User, Abilities>>(action: Action, ...args: Abilities[Action] extends {
90
+ original: (user: User, ...args: infer Args) => AuthorizerResponse | Promise<AuthorizerResponse>;
91
+ } ? Args : never): Promise<boolean>;
92
+ /**
93
+ * Authorize a user against a given ability.
94
+ *
95
+ * @throws {JsonRpcException}
96
+ */
97
+ authorize<Action extends BouncerAbility<User>>(action: Action, ...args: Action extends {
98
+ original: (user: User, ...args: infer Args) => AuthorizerResponse | Promise<AuthorizerResponse>;
99
+ } ? Args : never): Promise<void>;
100
+ /**
101
+ * Authorize a user against a given ability.
102
+ *
103
+ * @throws {JsonRpcException}
104
+ */
105
+ authorize<Ability extends NarrowAbilitiesForAUser<User, Abilities>>(ability: Ability, ...args: Abilities[Ability] extends {
106
+ original: (user: User, ...args: infer Args) => AuthorizerResponse | Promise<AuthorizerResponse>;
107
+ } ? Args : never): Promise<void>;
108
+ /**
109
+ * Create AuthorizationResponse to deny access.
110
+ * This is a pass-through since it doesn't throw.
111
+ */
112
+ deny(message: string, status?: number): AuthorizationResponse;
113
+ }
114
+ export {};
115
+ //# sourceMappingURL=mcp_bouncer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp_bouncer.d.ts","sourceRoot":"","sources":["../../../src/server/mcp_bouncer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @jrmc/adonis-mcp
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
7
+ import { errors } from '@adonisjs/bouncer';
8
+ import { ErrorCode } from '../enums/error.js';
9
+ import JsonRpcException from './exceptions/jsonrpc_exception.js';
10
+ /**
11
+ * Convert any caught error to a JsonRpcException.
12
+ */
13
+ function handleError(error, requestId) {
14
+ if (error instanceof errors.E_AUTHORIZATION_FAILURE) {
15
+ const message = error.response?.message || error.message || 'Access denied';
16
+ throw new JsonRpcException(message, ErrorCode.InvalidRequest, requestId);
17
+ }
18
+ if (error instanceof JsonRpcException) {
19
+ throw error;
20
+ }
21
+ const message = error instanceof Error ? error.message : 'Authorization error';
22
+ throw new JsonRpcException(message, ErrorCode.InternalError, requestId);
23
+ }
24
+ /**
25
+ * Wrap a PolicyAuthorizer so that every async method (`execute`,
26
+ * `allows`, `denies`, `authorize`) converts authorization failures
27
+ * into JsonRpcException.
28
+ */
29
+ function wrapPolicyAuthorizer(authorizer, requestId) {
30
+ const wrapped = ['execute', 'allows', 'denies', 'authorize'];
31
+ return new Proxy(authorizer, {
32
+ get(target, prop, receiver) {
33
+ const value = Reflect.get(target, prop, receiver);
34
+ if (typeof value === 'function' && typeof prop === 'string' && wrapped.includes(prop)) {
35
+ const fn = value;
36
+ return async (...args) => {
37
+ try {
38
+ return await fn.apply(target, args);
39
+ }
40
+ catch (error) {
41
+ handleError(error, requestId);
42
+ }
43
+ };
44
+ }
45
+ return value;
46
+ },
47
+ });
48
+ }
49
+ /**
50
+ * McpBouncer wraps the AdonisJS Bouncer instance and converts
51
+ * authorization exceptions (`E_AUTHORIZATION_FAILURE`) into
52
+ * `JsonRpcException` so they are properly serialized as JSON-RPC
53
+ * error responses instead of leaking HTTP-centric error handling.
54
+ *
55
+ * Every public method signature is kept identical to the original
56
+ * Bouncer class so that consumers get full type-safety.
57
+ */
58
+ export default class McpBouncer {
59
+ #bouncer;
60
+ #requestId;
61
+ constructor(bouncer, requestId) {
62
+ this.#bouncer = bouncer;
63
+ this.#requestId = requestId;
64
+ }
65
+ /**
66
+ * Access the underlying bouncer abilities.
67
+ */
68
+ get abilities() {
69
+ return this.#bouncer.abilities;
70
+ }
71
+ /**
72
+ * Access the underlying bouncer policies.
73
+ */
74
+ get policies() {
75
+ return this.#bouncer.policies;
76
+ }
77
+ with(policy) {
78
+ const authorizer = this.#bouncer.with(policy);
79
+ return wrapPolicyAuthorizer(authorizer, this.#requestId);
80
+ }
81
+ async execute(ability, ...args) {
82
+ try {
83
+ return await this.#bouncer.execute(ability, ...args);
84
+ }
85
+ catch (error) {
86
+ handleError(error, this.#requestId);
87
+ }
88
+ }
89
+ async allows(ability, ...args) {
90
+ try {
91
+ return await this.#bouncer.allows(ability, ...args);
92
+ }
93
+ catch (error) {
94
+ handleError(error, this.#requestId);
95
+ }
96
+ }
97
+ async denies(action, ...args) {
98
+ try {
99
+ return await this.#bouncer.denies(action, ...args);
100
+ }
101
+ catch (error) {
102
+ handleError(error, this.#requestId);
103
+ }
104
+ }
105
+ async authorize(ability, ...args) {
106
+ try {
107
+ return await this.#bouncer.authorize(ability, ...args);
108
+ }
109
+ catch (error) {
110
+ handleError(error, this.#requestId);
111
+ }
112
+ }
113
+ // ---------------------------------------------------------------------------
114
+ // deny()
115
+ // ---------------------------------------------------------------------------
116
+ /**
117
+ * Create AuthorizationResponse to deny access.
118
+ * This is a pass-through since it doesn't throw.
119
+ */
120
+ deny(message, status) {
121
+ return this.#bouncer.deny(message, status);
122
+ }
123
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"http_transport.d.ts","sourceRoot":"","sources":["../../../../src/server/transports/http_transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D,MAAM,CAAC,OAAO,OAAO,aAAc,YAAW,SAAS;;gBAIzC,GAAG,EAAE,WAAW;IAK5B,IAAI,SAAS,uBAEZ;IAED,WAAW,CAAC,UAAU,EAAE,UAAU;IAOlC,QAAQ,CAAC,UAAU,EAAE,UAAU;IAO/B,IAAI,CAAC,OAAO,EAAE,eAAe;CAG9B"}
1
+ {"version":3,"file":"http_transport.d.ts","sourceRoot":"","sources":["../../../../src/server/transports/http_transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAI7D,MAAM,CAAC,OAAO,OAAO,aAAc,YAAW,SAAS;;gBAIzC,GAAG,EAAE,WAAW;IAK5B,IAAI,SAAS,uBAEZ;IAED,WAAW,CAAC,UAAU,EAAE,UAAU;IAOlC,QAAQ,CAAC,UAAU,EAAE,UAAU;IAM/B,IAAI,CAAC,OAAO,EAAE,eAAe;CAG9B"}
@@ -4,6 +4,7 @@
4
4
  * @license MIT
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
+ import McpBouncer from '../mcp_bouncer.js';
7
8
  export default class HttpTransport {
8
9
  #ctx;
9
10
  #sessionId;
@@ -16,13 +17,13 @@ export default class HttpTransport {
16
17
  }
17
18
  bindBouncer(mcpContext) {
18
19
  if ('bouncer' in this.#ctx) {
19
- // @ts-ignore
20
- mcpContext.bouncer = this.#ctx.bouncer;
20
+ const bouncer = this.#ctx.bouncer;
21
+ mcpContext.bouncer = new McpBouncer(bouncer, mcpContext.request.id);
21
22
  }
22
23
  }
23
24
  bindAuth(mcpContext) {
24
25
  if ('auth' in this.#ctx) {
25
- // @ts-ignore
26
+ ;
26
27
  mcpContext.auth = this.#ctx.auth;
27
28
  }
28
29
  }
@@ -8,7 +8,12 @@ import type { McpConfig } from './types/config.js';
8
8
  import type { JsonRpcRequest } from './types/jsonrpc.js';
9
9
  import type { ToolList, ResourceList, PromptList } from './types/method.js';
10
10
  import type { Transport } from './server/contracts/transport.js';
11
+ import type { LoggerService, EmitterService } from '@adonisjs/core/types';
11
12
  import ServerContext from './server/context.js';
13
+ type Services = {
14
+ logger: LoggerService;
15
+ emitter: EmitterService;
16
+ };
12
17
  export default class Server {
13
18
  #private;
14
19
  config: McpConfig;
@@ -35,7 +40,7 @@ export default class Server {
35
40
  'completion/complete': () => Promise<typeof import("./server/methods/completion.js")>;
36
41
  ping: () => Promise<typeof import("./server/methods/ping.js")>;
37
42
  };
38
- constructor(config: McpConfig);
43
+ constructor(config: McpConfig, services?: Services);
39
44
  addCapability(key: string, value?: boolean): void;
40
45
  addTool(item: ToolList): void;
41
46
  addResource(item: ResourceList): void;
@@ -45,4 +50,5 @@ export default class Server {
45
50
  handle(jsonRequest: JsonRpcRequest): Promise<void | null>;
46
51
  createContext(jsonRpcRequest: JsonRpcRequest): ServerContext;
47
52
  }
53
+ export {};
48
54
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,UAAU,EAEX,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAA;AAIhE,OAAO,aAAa,MAAM,qBAAqB,CAAA;AAG/C,MAAM,CAAC,OAAO,OAAO,MAAM;;IAGzB,MAAM,EAAE,SAAS,CAAA;IACjB,IAAI,EAAE,MAAM,CAAwB;IACpC,OAAO,EAAE,MAAM,CAAU;IACzB,YAAY,EAAE,MAAM,CAA2E;IAE/F,wBAAwB,EAAE,MAAM,EAAE,CAA+B;IAEjE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAUhC;IAED,KAAK,EAAE,QAAQ,CAAK;IACpB,SAAS,EAAE,YAAY,CAAK;IAC5B,iBAAiB,EAAE,YAAY,CAAK;IACpC,OAAO,EAAE,UAAU,CAAK;IAExB,mBAAmB,EAAE,MAAM,CAAK;IAChC,uBAAuB,EAAE,MAAM,CAAK;IAEpC,OAAO;;;;;;;;;;;MAWN;gBAEW,MAAM,EAAE,SAAS;IA4B7B,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,OAAc;IAchD,OAAO,CAAC,IAAI,EAAE,QAAQ;IAGtB,WAAW,CAAC,IAAI,EAAE,YAAY;IAG9B,mBAAmB,CAAC,IAAI,EAAE,YAAY;IAGtC,SAAS,CAAC,IAAI,EAAE,UAAU;IAIpB,OAAO,CAAC,SAAS,EAAE,SAAS;IAI5B,MAAM,CAAC,WAAW,EAAE,cAAc;IA6CxC,aAAa,CAAC,cAAc,EAAE,cAAc;CAgB7C"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,UAAU,EAEX,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAIzE,OAAO,aAAa,MAAM,qBAAqB,CAAA;AAG/C,KAAK,QAAQ,GAAG;IACd,MAAM,EAAE,aAAa,CAAA;IACrB,OAAO,EAAE,cAAc,CAAA;CACxB,CAAA;AAED,MAAM,CAAC,OAAO,OAAO,MAAM;;IAKzB,MAAM,EAAE,SAAS,CAAA;IACjB,IAAI,EAAE,MAAM,CAAwB;IACpC,OAAO,EAAE,MAAM,CAAU;IACzB,YAAY,EAAE,MAAM,CAA2E;IAE/F,wBAAwB,EAAE,MAAM,EAAE,CAA+B;IAEjE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAUhC;IAED,KAAK,EAAE,QAAQ,CAAK;IACpB,SAAS,EAAE,YAAY,CAAK;IAC5B,iBAAiB,EAAE,YAAY,CAAK;IACpC,OAAO,EAAE,UAAU,CAAK;IAExB,mBAAmB,EAAE,MAAM,CAAK;IAChC,uBAAuB,EAAE,MAAM,CAAK;IAEpC,OAAO;;;;;;;;;;;MAWN;gBAEW,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,QAAQ;IA+BlD,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,OAAc;IAchD,OAAO,CAAC,IAAI,EAAE,QAAQ;IAGtB,WAAW,CAAC,IAAI,EAAE,YAAY;IAG9B,mBAAmB,CAAC,IAAI,EAAE,YAAY;IAGtC,SAAS,CAAC,IAAI,EAAE,UAAU;IAIpB,OAAO,CAAC,SAAS,EAAE,SAAS;IAI5B,MAAM,CAAC,WAAW,EAAE,cAAc;IAyDxC,aAAa,CAAC,cAAc,EAAE,cAAc;CAgB7C"}
@@ -9,6 +9,8 @@ import { ErrorCode } from './enums/error.js';
9
9
  import ServerContext from './server/context.js';
10
10
  import JsonRpcException from './server/exceptions/jsonrpc_exception.js';
11
11
  export default class Server {
12
+ #logger;
13
+ #emitter;
12
14
  #transport;
13
15
  config;
14
16
  name = 'AdonisJS MCP Server';
@@ -44,7 +46,7 @@ export default class Server {
44
46
  'completion/complete': () => import('./server/methods/completion.js'),
45
47
  'ping': () => import('./server/methods/ping.js'),
46
48
  };
47
- constructor(config) {
49
+ constructor(config, services) {
48
50
  this.config = config;
49
51
  if (config.name) {
50
52
  this.name = config.name;
@@ -64,6 +66,8 @@ export default class Server {
64
66
  if (config.completions) {
65
67
  this.addCapability('completions');
66
68
  }
69
+ this.#logger = services?.logger;
70
+ this.#emitter = services?.emitter;
67
71
  }
68
72
  addCapability(key, value = true) {
69
73
  if (key.includes('.')) {
@@ -91,6 +95,7 @@ export default class Server {
91
95
  this.#transport = transport;
92
96
  }
93
97
  async handle(jsonRequest) {
98
+ this.#emitter?.emit('mcp:request', jsonRequest);
94
99
  // INGORE NOTIFICATIONS FOR NOW
95
100
  if (jsonRequest.method.startsWith('notifications/')) {
96
101
  return null;
@@ -111,19 +116,31 @@ export default class Server {
111
116
  const instance = new MethodClass();
112
117
  // Ensure the instance is treated as MethodInterface when calling handle to satisfy TS
113
118
  const response = await instance.handle(mcpContext);
119
+ this.#emitter?.emit('mcp:response', response);
114
120
  this.#transport.send(response);
115
121
  }
116
122
  else {
117
- throw new JsonRpcException(`The method ${jsonRequest.method} was not found.`, ErrorCode.MethodNotFound, jsonRequest.id);
123
+ const jsonRpcException = new JsonRpcException(`The method ${jsonRequest.method} was not found.`, ErrorCode.MethodNotFound, jsonRequest.id);
124
+ throw jsonRpcException;
118
125
  }
119
126
  }
120
127
  catch (error) {
128
+ let response;
121
129
  if (error instanceof JsonRpcException) {
122
- return this.#transport.send(error.toJsonRpcResponse());
130
+ response = error.toJsonRpcResponse();
131
+ this.#logger?.error(response, error.message);
123
132
  }
124
- return this.#transport.send(new JsonRpcException('Internal error', ErrorCode.InternalError, jsonRequest.id, {
125
- error,
126
- }).toJsonRpcResponse());
133
+ else {
134
+ response = new JsonRpcException('Internal error', ErrorCode.InternalError, jsonRequest.id, {
135
+ error: {
136
+ name: error.name,
137
+ message: error.message,
138
+ },
139
+ }).toJsonRpcResponse();
140
+ this.#logger?.error(error);
141
+ }
142
+ this.#emitter?.emit('mcp:response', response);
143
+ return this.#transport.send(response);
127
144
  }
128
145
  }
129
146
  createContext(jsonRpcRequest) {
@@ -24,7 +24,7 @@ export default class {{ className }} extends Prompt<Schema> {
24
24
  async handle({ args, response }: PromptContext<Schema>) {
25
25
  return [
26
26
  response.text('Hello, world!'),
27
- response.text(args?.text)
27
+ response.text(args?.text!)
28
28
  ]
29
29
  }
30
30
 
@@ -0,0 +1,40 @@
1
+ {{#var className = `${name}Prompt`}}
2
+ {{#var promptName = string(name).snakeCase()}}
3
+ {{#var resourceFileName = string(className).snakeCase().ext('.ts').toString()}}
4
+ {{{
5
+ exports({
6
+ to: app.makePath(app.rcFile.directories['mcp'] || 'app/mcp/', `prompts/`, resourceFileName)
7
+ })
8
+ }}}
9
+ import type { PromptContext } from '@jrmc/adonis-mcp/types/context'
10
+ import type { BaseSchema } from '@jrmc/adonis-mcp/types/method'
11
+
12
+ import { Prompt } from '@jrmc/adonis-mcp'
13
+ import vine from '@vinejs/vine'
14
+
15
+ type Schema = BaseSchema<{
16
+ text: { type: "string" }
17
+ }>
18
+
19
+ const vineSchema = vine.object({
20
+ text: vine.string().meta({
21
+ description: 'Description text argument',
22
+ }),
23
+ })
24
+
25
+ export default class {{ className }} extends Prompt<Schema> {
26
+ name = '{{ promptName }}'
27
+ title = 'Prompt title'
28
+ description = 'Prompt description'
29
+
30
+ async handle({ args, response }: PromptContext<Schema>) {
31
+ return [
32
+ response.text('Hello, world!'),
33
+ response.text(args?.text!)
34
+ ]
35
+ }
36
+
37
+ schema() {
38
+ return vine.create(vineSchema).toJSONSchema() as Schema
39
+ }
40
+ }