@hed-hog/core 0.0.349 → 0.0.350

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 (126) hide show
  1. package/dist/auth/auth.controller.d.ts +3 -3
  2. package/dist/auth/auth.service.d.ts +8 -8
  3. package/dist/core.module.d.ts.map +1 -1
  4. package/dist/core.module.js +3 -0
  5. package/dist/core.module.js.map +1 -1
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +4 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/mcp/guards/mcp-auth.guard.d.ts +6 -1
  11. package/dist/mcp/guards/mcp-auth.guard.d.ts.map +1 -1
  12. package/dist/mcp/guards/mcp-auth.guard.js +29 -2
  13. package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
  14. package/dist/mcp/mcp-api-key.controller.d.ts +32 -0
  15. package/dist/mcp/mcp-api-key.controller.d.ts.map +1 -0
  16. package/dist/mcp/mcp-api-key.controller.js +64 -0
  17. package/dist/mcp/mcp-api-key.controller.js.map +1 -0
  18. package/dist/mcp/mcp-api-key.service.d.ts +27 -0
  19. package/dist/mcp/mcp-api-key.service.d.ts.map +1 -0
  20. package/dist/mcp/mcp-api-key.service.js +74 -0
  21. package/dist/mcp/mcp-api-key.service.js.map +1 -0
  22. package/dist/mcp/mcp-registry.service.d.ts +1 -2
  23. package/dist/mcp/mcp-registry.service.d.ts.map +1 -1
  24. package/dist/mcp/mcp-registry.service.js +6 -2
  25. package/dist/mcp/mcp-registry.service.js.map +1 -1
  26. package/dist/mcp/mcp.module.d.ts.map +1 -1
  27. package/dist/mcp/mcp.module.js +8 -1
  28. package/dist/mcp/mcp.module.js.map +1 -1
  29. package/dist/mcp-chat/mcp-chat.controller.d.ts +4 -0
  30. package/dist/mcp-chat/mcp-chat.controller.d.ts.map +1 -1
  31. package/dist/mcp-chat/mcp-chat.controller.js +46 -0
  32. package/dist/mcp-chat/mcp-chat.controller.js.map +1 -1
  33. package/dist/mcp-chat/mcp-chat.service.d.ts +6 -1
  34. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  35. package/dist/mcp-chat/mcp-chat.service.js +125 -17
  36. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  37. package/dist/notification/dto/create-notification.dto.d.ts +26 -0
  38. package/dist/notification/dto/create-notification.dto.d.ts.map +1 -0
  39. package/dist/notification/dto/create-notification.dto.js +86 -0
  40. package/dist/notification/dto/create-notification.dto.js.map +1 -0
  41. package/dist/notification/dto/update-progress.dto.d.ts +6 -0
  42. package/dist/notification/dto/update-progress.dto.d.ts.map +1 -0
  43. package/dist/notification/dto/update-progress.dto.js +33 -0
  44. package/dist/notification/dto/update-progress.dto.js.map +1 -0
  45. package/dist/notification/notification.controller.d.ts +108 -0
  46. package/dist/notification/notification.controller.d.ts.map +1 -0
  47. package/dist/notification/notification.controller.js +118 -0
  48. package/dist/notification/notification.controller.js.map +1 -0
  49. package/dist/notification/notification.module.d.ts +3 -0
  50. package/dist/notification/notification.module.d.ts.map +1 -0
  51. package/dist/notification/notification.module.js +29 -0
  52. package/dist/notification/notification.module.js.map +1 -0
  53. package/dist/notification/notification.service.d.ts +98 -0
  54. package/dist/notification/notification.service.d.ts.map +1 -0
  55. package/dist/notification/notification.service.js +123 -0
  56. package/dist/notification/notification.service.js.map +1 -0
  57. package/dist/profile/profile.controller.d.ts +1 -1
  58. package/dist/profile/profile.service.d.ts +1 -1
  59. package/dist/session/session.controller.d.ts +1 -1
  60. package/dist/session/session.service.d.ts +3 -3
  61. package/dist/setting/dto/update-setting-list.dto.d.ts +9 -0
  62. package/dist/setting/dto/update-setting-list.dto.d.ts.map +1 -0
  63. package/dist/setting/dto/update-setting-list.dto.js +44 -0
  64. package/dist/setting/dto/update-setting-list.dto.js.map +1 -0
  65. package/dist/setting/setting.controller.d.ts +19 -0
  66. package/dist/setting/setting.controller.d.ts.map +1 -1
  67. package/dist/setting/setting.controller.js +12 -0
  68. package/dist/setting/setting.controller.js.map +1 -1
  69. package/dist/setting/setting.service.d.ts +19 -0
  70. package/dist/setting/setting.service.d.ts.map +1 -1
  71. package/dist/setting/setting.service.js +79 -0
  72. package/dist/setting/setting.service.js.map +1 -1
  73. package/dist/task/task.service.d.ts +12 -0
  74. package/dist/task/task.service.d.ts.map +1 -1
  75. package/dist/task/task.service.js +60 -0
  76. package/dist/task/task.service.js.map +1 -1
  77. package/dist/user/user.controller.d.ts +2 -2
  78. package/dist/user/user.service.d.ts +6 -6
  79. package/dist/webhook-integration/webhook-integration.service.d.ts +4 -0
  80. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  81. package/dist/webhook-integration/webhook-integration.service.js +184 -34
  82. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  83. package/dist/webhook-integration/webhook-utils.d.ts +1 -0
  84. package/dist/webhook-integration/webhook-utils.d.ts.map +1 -1
  85. package/dist/webhook-integration/webhook-utils.js +38 -0
  86. package/dist/webhook-integration/webhook-utils.js.map +1 -1
  87. package/hedhog/data/menu.yaml +48 -4
  88. package/hedhog/data/route.yaml +133 -0
  89. package/hedhog/data/setting_group.yaml +45 -0
  90. package/hedhog/data/setting_subgroup.yaml +16 -0
  91. package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +285 -0
  92. package/hedhog/frontend/app/account/layout.tsx.ejs +32 -55
  93. package/hedhog/frontend/app/account/mcp-tokens/page.tsx.ejs +5 -0
  94. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +2098 -1835
  95. package/hedhog/frontend/app/mcp_chat/chat-components.tsx.ejs +595 -0
  96. package/hedhog/frontend/app/mcp_chat/chat-types.ts.ejs +16 -0
  97. package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +554 -383
  98. package/hedhog/frontend/app/setting-list/page.tsx.ejs +649 -0
  99. package/hedhog/frontend/app/users/page.tsx.ejs +445 -203
  100. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +16 -37
  101. package/hedhog/frontend/messages/en.json +241 -2
  102. package/hedhog/frontend/messages/pt.json +241 -2
  103. package/hedhog/table/mcp_api_key.yaml +30 -0
  104. package/hedhog/table/notification.yaml +72 -0
  105. package/hedhog/table/webhook_action_log.yaml +49 -0
  106. package/package.json +4 -4
  107. package/src/core.module.ts +3 -0
  108. package/src/index.ts +5 -0
  109. package/src/mcp/guards/mcp-auth.guard.ts +64 -32
  110. package/src/mcp/mcp-api-key.controller.ts +42 -0
  111. package/src/mcp/mcp-api-key.service.ts +66 -0
  112. package/src/mcp/mcp-registry.service.ts +75 -69
  113. package/src/mcp/mcp.module.ts +43 -36
  114. package/src/mcp-chat/mcp-chat.controller.ts +113 -60
  115. package/src/mcp-chat/mcp-chat.service.ts +494 -332
  116. package/src/notification/dto/create-notification.dto.ts +71 -0
  117. package/src/notification/dto/update-progress.dto.ts +16 -0
  118. package/src/notification/notification.controller.ts +71 -0
  119. package/src/notification/notification.module.ts +16 -0
  120. package/src/notification/notification.service.ts +124 -0
  121. package/src/setting/dto/update-setting-list.dto.ts +32 -0
  122. package/src/setting/setting.controller.ts +9 -0
  123. package/src/setting/setting.service.ts +91 -0
  124. package/src/task/task.service.ts +72 -0
  125. package/src/webhook-integration/webhook-integration.service.ts +934 -735
  126. package/src/webhook-integration/webhook-utils.ts +136 -79
@@ -0,0 +1,30 @@
1
+ columns:
2
+ - type: pk
3
+ - name: user_id
4
+ type: fk
5
+ references:
6
+ table: user
7
+ column: id
8
+ onDelete: CASCADE
9
+ - name: name
10
+ type: varchar
11
+ - name: token_hash
12
+ type: varchar
13
+ - name: token_prefix
14
+ type: varchar
15
+ - name: last_used_at
16
+ type: datetime
17
+ isNullable: true
18
+ - name: expires_at
19
+ type: datetime
20
+ isNullable: true
21
+ - name: revoked_at
22
+ type: datetime
23
+ isNullable: true
24
+ - type: created_at
25
+ - type: updated_at
26
+ indices:
27
+ - columns:
28
+ - user_id
29
+ - columns:
30
+ - token_hash
@@ -0,0 +1,72 @@
1
+ columns:
2
+ - type: pk
3
+ - name: user_id
4
+ type: fk
5
+ references:
6
+ table: user
7
+ column: id
8
+ onDelete: CASCADE
9
+ onUpdate: CASCADE
10
+ - name: title
11
+ type: varchar
12
+ length: 255
13
+ - name: body
14
+ type: text
15
+ isNullable: true
16
+ - name: type
17
+ type: enum
18
+ values:
19
+ - info
20
+ - success
21
+ - warning
22
+ - error
23
+ - progress
24
+ default: info
25
+ - name: status
26
+ type: enum
27
+ values:
28
+ - unread
29
+ - read
30
+ default: unread
31
+ - name: icon
32
+ type: varchar
33
+ length: 64
34
+ isNullable: true
35
+ - name: action_type
36
+ type: enum
37
+ values:
38
+ - url
39
+ - sheet
40
+ - modal
41
+ default: url
42
+ isNullable: true
43
+ - name: action_data
44
+ type: json
45
+ isNullable: true
46
+ - name: action_url
47
+ type: varchar
48
+ length: 512
49
+ isNullable: true
50
+ - name: auto_remove
51
+ type: boolean
52
+ default: false
53
+ - name: progress
54
+ type: int
55
+ isNullable: true
56
+ - name: started_at
57
+ type: timestamptz
58
+ isNullable: true
59
+ - name: finished_at
60
+ type: timestamptz
61
+ isNullable: true
62
+ - type: created_at
63
+ - type: updated_at
64
+ indices:
65
+ - columns:
66
+ - user_id
67
+ - columns:
68
+ - user_id
69
+ - status
70
+ - columns:
71
+ - user_id
72
+ - created_at
@@ -0,0 +1,49 @@
1
+ columns:
2
+ - type: pk
3
+ - type: fk
4
+ name: webhook_call_log_id
5
+ references:
6
+ table: webhook_call_log
7
+ column: id
8
+ onDelete: cascade
9
+ onUpdate: cascade
10
+ - type: fk
11
+ name: webhook_action_id
12
+ isNullable: true
13
+ references:
14
+ table: webhook_action
15
+ column: id
16
+ onDelete: set null
17
+ onUpdate: cascade
18
+ - name: action_name
19
+ type: varchar
20
+ length: 127
21
+ - name: action_type
22
+ type: varchar
23
+ length: 64
24
+ - name: status
25
+ type: varchar
26
+ length: 16
27
+ - name: duration_ms
28
+ type: int
29
+ default: 0
30
+ - name: request_payload
31
+ type: json
32
+ isNullable: true
33
+ - name: response_payload
34
+ type: json
35
+ isNullable: true
36
+ - name: error_message
37
+ type: text
38
+ isNullable: true
39
+ - type: created_at
40
+ - type: updated_at
41
+ indices:
42
+ - columns:
43
+ - webhook_call_log_id
44
+ - columns:
45
+ - webhook_action_id
46
+ - columns:
47
+ - status
48
+ - columns:
49
+ - created_at
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/core",
3
- "version": "0.0.349",
3
+ "version": "0.0.350",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -32,11 +32,11 @@
32
32
  "uuid": "^11.1.0",
33
33
  "@modelcontextprotocol/sdk": "^1.12.0",
34
34
  "@hed-hog/api-locale": "0.0.14",
35
+ "@hed-hog/api-mail": "0.0.9",
36
+ "@hed-hog/api-types": "0.0.1",
35
37
  "@hed-hog/api-pagination": "0.0.7",
36
- "@hed-hog/api-prisma": "0.0.6",
37
38
  "@hed-hog/api": "0.0.8",
38
- "@hed-hog/api-mail": "0.0.9",
39
- "@hed-hog/api-types": "0.0.1"
39
+ "@hed-hog/api-prisma": "0.0.6"
40
40
  },
41
41
  "exports": {
42
42
  ".": {
@@ -37,6 +37,7 @@ import { IsEmailWithSettingsConstraint } from './validators/is-email-with-settin
37
37
  import { IsPinCodeWithSettingConstraint } from './validators/is-pin-code-with-setting.validator';
38
38
  import { IsStrongPasswordWithSettingsConstraint } from './validators/is-strong-password-with-settings.validator';
39
39
  import { ValidatorServiceLocator } from './validators/service-locator';
40
+ import { NotificationModule } from './notification/notification.module';
40
41
  import { WebhookIntegrationModule } from './webhook-integration/webhook-integration.module';
41
42
 
42
43
  @Global()
@@ -73,6 +74,7 @@ import { WebhookIntegrationModule } from './webhook-integration/webhook-integrat
73
74
  forwardRef(() => McpModule),
74
75
  forwardRef(() => McpChatModule),
75
76
  forwardRef(() => PdfModule),
77
+ forwardRef(() => NotificationModule),
76
78
  ],
77
79
  exports: [
78
80
  UserModule,
@@ -94,6 +96,7 @@ import { WebhookIntegrationModule } from './webhook-integration/webhook-integrat
94
96
  IsEmailWithSettingsConstraint,
95
97
  IsPinCodeWithSettingConstraint,
96
98
  PdfModule,
99
+ NotificationModule,
97
100
  ],
98
101
  providers: [
99
102
  Reflector,
package/src/index.ts CHANGED
@@ -70,3 +70,8 @@ export * from './mcp/types/mcp-context.type';
70
70
  // Utilities
71
71
  export * from './utils/locale-context';
72
72
 
73
+
74
+ // Notification Module
75
+ export * from './notification/notification.module';
76
+ export * from './notification/notification.service';
77
+ export * from './notification/dto/create-notification.dto';
@@ -1,32 +1,64 @@
1
- import {
2
- CanActivate,
3
- ExecutionContext,
4
- Injectable,
5
- UnauthorizedException,
6
- } from '@nestjs/common';
7
- import { Request } from 'express';
8
- import { TokenService } from '../../token/token.service';
9
-
10
- @Injectable()
11
- export class McpAuthGuard implements CanActivate {
12
- constructor(private readonly tokenService: TokenService) {}
13
-
14
- async canActivate(context: ExecutionContext): Promise<boolean> {
15
- const request = context.switchToHttp().getRequest<Request>();
16
- const locale =
17
- request.headers['accept-language']?.split(',')[0]?.split('-')[0] ?? 'en';
18
-
19
- const token = this.extractToken(request);
20
- if (!token) {
21
- throw new UnauthorizedException('MCP: Bearer token required');
22
- }
23
-
24
- request['auth'] = await this.tokenService.verify(locale, token);
25
- return true;
26
- }
27
-
28
- private extractToken(request: Request): string | undefined {
29
- const [type, token] = request.headers.authorization?.split(' ') ?? [];
30
- return type === 'Bearer' ? token : undefined;
31
- }
32
- }
1
+ import { PrismaService } from '@hed-hog/api-prisma';
2
+ import {
3
+ CanActivate,
4
+ ExecutionContext,
5
+ Injectable,
6
+ UnauthorizedException,
7
+ } from '@nestjs/common';
8
+ import { Request } from 'express';
9
+ import { SecurityService } from '../../security/security.service';
10
+ import { TokenService } from '../../token/token.service';
11
+
12
+ @Injectable()
13
+ export class McpAuthGuard implements CanActivate {
14
+ constructor(
15
+ private readonly tokenService: TokenService,
16
+ private readonly prisma: PrismaService,
17
+ private readonly security: SecurityService,
18
+ ) {}
19
+
20
+ async canActivate(context: ExecutionContext): Promise<boolean> {
21
+ const request = context.switchToHttp().getRequest<Request>();
22
+ const locale =
23
+ request.headers['accept-language']?.split(',')[0]?.split('-')[0] ?? 'en';
24
+
25
+ const token = this.extractToken(request);
26
+ if (!token) {
27
+ throw new UnauthorizedException('MCP: Bearer token required');
28
+ }
29
+
30
+ if (token.startsWith('hedhog_mcp_')) {
31
+ return this.verifyApiKey(request, token);
32
+ }
33
+
34
+ request['auth'] = await this.tokenService.verify(locale, token);
35
+ return true;
36
+ }
37
+
38
+ private async verifyApiKey(request: Request, token: string): Promise<boolean> {
39
+ const hash = this.security.hashWithPepper(token);
40
+ const apiKey = await this.prisma.mcp_api_key.findFirst({
41
+ where: { token_hash: hash, revoked_at: null },
42
+ });
43
+
44
+ if (!apiKey) {
45
+ throw new UnauthorizedException('MCP: Invalid API key');
46
+ }
47
+ if (apiKey.expires_at && apiKey.expires_at <= new Date()) {
48
+ throw new UnauthorizedException('MCP: API key expired');
49
+ }
50
+
51
+ this.prisma.mcp_api_key.update({
52
+ where: { id: apiKey.id },
53
+ data: { last_used_at: new Date() },
54
+ }).catch(() => {});
55
+
56
+ request['auth'] = { sub: apiKey.user_id, sessionId: null };
57
+ return true;
58
+ }
59
+
60
+ private extractToken(request: Request): string | undefined {
61
+ const [type, token] = request.headers.authorization?.split(' ') ?? [];
62
+ return type === 'Bearer' ? token : undefined;
63
+ }
64
+ }
@@ -0,0 +1,42 @@
1
+ import { Role, User } from '@hed-hog/api';
2
+ import { Pagination, PaginationDTO } from '@hed-hog/api-pagination';
3
+ import {
4
+ Body,
5
+ Controller,
6
+ Delete,
7
+ Get,
8
+ Param,
9
+ ParseIntPipe,
10
+ Post,
11
+ } from '@nestjs/common';
12
+ import { McpApiKeyService } from './mcp-api-key.service';
13
+
14
+ @Role()
15
+ @Controller('mcp/api-keys')
16
+ export class McpApiKeyController {
17
+ constructor(private readonly mcpApiKeyService: McpApiKeyService) {}
18
+
19
+ @Get()
20
+ list(
21
+ @Pagination() paginationParams: PaginationDTO,
22
+ @User() { id }: { id: number },
23
+ ) {
24
+ return this.mcpApiKeyService.list(id, paginationParams);
25
+ }
26
+
27
+ @Post()
28
+ create(
29
+ @User() { id }: { id: number },
30
+ @Body() body: { name: string },
31
+ ) {
32
+ return this.mcpApiKeyService.create(id, body.name);
33
+ }
34
+
35
+ @Delete(':id')
36
+ revoke(
37
+ @User() { id: userId }: { id: number },
38
+ @Param('id', ParseIntPipe) id: number,
39
+ ) {
40
+ return this.mcpApiKeyService.revoke(userId, id);
41
+ }
42
+ }
@@ -0,0 +1,66 @@
1
+ import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
2
+ import { PrismaService } from '@hed-hog/api-prisma';
3
+ import { Injectable, NotFoundException } from '@nestjs/common';
4
+ import { SecurityService } from '../security/security.service';
5
+
6
+ @Injectable()
7
+ export class McpApiKeyService {
8
+ constructor(
9
+ private readonly prisma: PrismaService,
10
+ private readonly security: SecurityService,
11
+ private readonly pagination: PaginationService,
12
+ ) {}
13
+
14
+ async list(userId: number, paginationParams: PaginationDTO) {
15
+ return this.pagination.paginatePrismaModel(this.prisma.mcp_api_key, {
16
+ ...paginationParams,
17
+ where: { user_id: userId, revoked_at: null },
18
+ select: {
19
+ id: true,
20
+ name: true,
21
+ token_prefix: true,
22
+ last_used_at: true,
23
+ expires_at: true,
24
+ created_at: true,
25
+ },
26
+ orderBy: { created_at: 'desc' },
27
+ });
28
+ }
29
+
30
+ async create(userId: number, name: string) {
31
+ const rawToken = 'hedhog_mcp_' + this.security.randomOpaque(36);
32
+ const tokenHash = this.security.hashWithPepper(rawToken);
33
+ const tokenPrefix = rawToken.substring(0, 16);
34
+
35
+ const record = await this.prisma.mcp_api_key.create({
36
+ data: {
37
+ user_id: userId,
38
+ name,
39
+ token_hash: tokenHash,
40
+ token_prefix: tokenPrefix,
41
+ },
42
+ select: {
43
+ id: true,
44
+ name: true,
45
+ token_prefix: true,
46
+ created_at: true,
47
+ },
48
+ });
49
+
50
+ return { ...record, rawToken };
51
+ }
52
+
53
+ async revoke(userId: number, id: number) {
54
+ const key = await this.prisma.mcp_api_key.findFirst({
55
+ where: { id, user_id: userId },
56
+ });
57
+ if (!key) {
58
+ throw new NotFoundException(`MCP API key ${id} not found`);
59
+ }
60
+ await this.prisma.mcp_api_key.update({
61
+ where: { id },
62
+ data: { revoked_at: new Date() },
63
+ });
64
+ return { success: true };
65
+ }
66
+ }
@@ -1,69 +1,75 @@
1
- import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
2
- import { DiscoveryService } from '@nestjs/core';
3
- import { Reflector } from '@nestjs/core';
4
- import { MCP_TOOL_METADATA } from './decorators/mcp-tool.decorator';
5
- import { McpToolDefinition } from './interfaces/mcp-tool-definition.interface';
6
- import { McpContext } from './types/mcp-context.type';
7
-
8
- export interface McpToolEntry {
9
- definition: McpToolDefinition;
10
- handler: (args: Record<string, any>, context: McpContext) => Promise<any>;
11
- }
12
-
13
- @Injectable()
14
- export class McpRegistryService implements OnModuleInit {
15
- private readonly logger = new Logger(McpRegistryService.name);
16
- private readonly tools = new Map<string, McpToolEntry>();
17
-
18
- constructor(
19
- private readonly discovery: DiscoveryService,
20
- private readonly reflector: Reflector,
21
- ) {}
22
-
23
- onModuleInit(): void {
24
- const providers = this.discovery.getProviders();
25
-
26
- for (const wrapper of providers) {
27
- const { instance } = wrapper;
28
- if (!instance || typeof instance !== 'object') continue;
29
-
30
- const prototype = Object.getPrototypeOf(instance);
31
- if (!prototype) continue;
32
-
33
- const methodNames = Object.getOwnPropertyNames(prototype).filter((key) => {
34
- if (key === 'constructor') return false;
35
- const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
36
- return descriptor !== undefined && typeof descriptor.value === 'function';
37
- });
38
-
39
- for (const methodName of methodNames) {
40
- const definition = this.reflector.get<McpToolDefinition>(
41
- MCP_TOOL_METADATA,
42
- prototype[methodName],
43
- );
44
-
45
- if (!definition) continue;
46
-
47
- this.logger.log(`Registered MCP tool: ${definition.name}`);
48
- this.tools.set(definition.name, {
49
- definition,
50
- handler: (instance as any)[methodName].bind(instance),
51
- });
52
- }
53
- }
54
-
55
- this.logger.log(`McpRegistryService: ${this.tools.size} tools registered`);
56
- }
57
-
58
- getAll(): McpToolEntry[] {
59
- return Array.from(this.tools.values());
60
- }
61
-
62
- get(toolName: string): McpToolEntry | undefined {
63
- return this.tools.get(toolName);
64
- }
65
-
66
- has(toolName: string): boolean {
67
- return this.tools.has(toolName);
68
- }
69
- }
1
+ import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
2
+ import { DiscoveryService, Reflector } from '@nestjs/core';
3
+ import { MCP_TOOL_METADATA } from './decorators/mcp-tool.decorator';
4
+ import { McpToolDefinition } from './interfaces/mcp-tool-definition.interface';
5
+ import { McpContext } from './types/mcp-context.type';
6
+
7
+ export interface McpToolEntry {
8
+ definition: McpToolDefinition;
9
+ handler: (args: Record<string, any>, context: McpContext) => Promise<any>;
10
+ }
11
+
12
+ @Injectable()
13
+ export class McpRegistryService implements OnModuleInit {
14
+ private readonly logger = new Logger(McpRegistryService.name);
15
+ private readonly tools = new Map<string, McpToolEntry>();
16
+
17
+ constructor(
18
+ private readonly discovery: DiscoveryService,
19
+ private readonly reflector: Reflector,
20
+ ) {}
21
+
22
+ onModuleInit(): void {
23
+ const providers = this.discovery.getProviders();
24
+
25
+ for (const wrapper of providers) {
26
+ const { instance } = wrapper;
27
+ if (!instance || typeof instance !== 'object') continue;
28
+
29
+ const prototype = Object.getPrototypeOf(instance);
30
+ if (!prototype) continue;
31
+
32
+ const methodNames = Object.getOwnPropertyNames(prototype).filter((key) => {
33
+ if (key === 'constructor') return false;
34
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
35
+ return descriptor !== undefined && typeof descriptor.value === 'function';
36
+ });
37
+
38
+ for (const methodName of methodNames) {
39
+ const definition = this.reflector.get<McpToolDefinition>(
40
+ MCP_TOOL_METADATA,
41
+ prototype[methodName],
42
+ );
43
+
44
+ if (!definition) continue;
45
+
46
+ if (this.tools.has(definition.name)) {
47
+ this.logger.warn(
48
+ `Duplicate MCP tool ignored: ${definition.name} from ${prototype.constructor?.name}.${methodName}`,
49
+ );
50
+ continue;
51
+ }
52
+
53
+ this.logger.log(`Registered MCP tool: ${definition.name}`);
54
+ this.tools.set(definition.name, {
55
+ definition,
56
+ handler: (instance as any)[methodName].bind(instance),
57
+ });
58
+ }
59
+ }
60
+
61
+ this.logger.log(`McpRegistryService: ${this.tools.size} tools registered`);
62
+ }
63
+
64
+ getAll(): McpToolEntry[] {
65
+ return Array.from(this.tools.values());
66
+ }
67
+
68
+ get(toolName: string): McpToolEntry | undefined {
69
+ return this.tools.get(toolName);
70
+ }
71
+
72
+ has(toolName: string): boolean {
73
+ return this.tools.has(toolName);
74
+ }
75
+ }
@@ -1,36 +1,43 @@
1
- import { PrismaModule } from '@hed-hog/api-prisma';
2
- import { Module, forwardRef } from '@nestjs/common';
3
- import { DiscoveryModule } from '@nestjs/core';
4
- import { SettingModule } from '../setting/setting.module';
5
- import { TokenModule } from '../token/token.module';
6
- import { UserModule } from '../user/user.module';
7
- import { McpAuthGuard } from './guards/mcp-auth.guard';
8
- import { McpPermissionGuard } from './guards/mcp-permission.guard';
9
- import { McpAuditService } from './mcp-audit.service';
10
- import { McpRegistryService } from './mcp-registry.service';
11
- import { McpServerService } from './mcp-server.service';
12
- import { McpController } from './mcp.controller';
13
- import { CoreSettingsMcpTools } from './tools/core-settings.mcp-tools';
14
- import { CoreUsersMcpTools } from './tools/core-users.mcp-tools';
15
-
16
- @Module({
17
- imports: [
18
- DiscoveryModule,
19
- forwardRef(() => PrismaModule),
20
- forwardRef(() => TokenModule),
21
- forwardRef(() => SettingModule),
22
- forwardRef(() => UserModule),
23
- ],
24
- controllers: [McpController],
25
- providers: [
26
- McpRegistryService,
27
- McpServerService,
28
- McpAuditService,
29
- McpAuthGuard,
30
- McpPermissionGuard,
31
- CoreSettingsMcpTools,
32
- CoreUsersMcpTools,
33
- ],
34
- exports: [McpRegistryService, McpServerService],
35
- })
36
- export class McpModule {}
1
+ import { PaginationModule } from '@hed-hog/api-pagination';
2
+ import { PrismaModule } from '@hed-hog/api-prisma';
3
+ import { Module, forwardRef } from '@nestjs/common';
4
+ import { DiscoveryModule } from '@nestjs/core';
5
+ import { SecurityModule } from '../security/security.module';
6
+ import { SettingModule } from '../setting/setting.module';
7
+ import { TokenModule } from '../token/token.module';
8
+ import { UserModule } from '../user/user.module';
9
+ import { McpAuthGuard } from './guards/mcp-auth.guard';
10
+ import { McpPermissionGuard } from './guards/mcp-permission.guard';
11
+ import { McpApiKeyController } from './mcp-api-key.controller';
12
+ import { McpApiKeyService } from './mcp-api-key.service';
13
+ import { McpAuditService } from './mcp-audit.service';
14
+ import { McpRegistryService } from './mcp-registry.service';
15
+ import { McpServerService } from './mcp-server.service';
16
+ import { McpController } from './mcp.controller';
17
+ import { CoreSettingsMcpTools } from './tools/core-settings.mcp-tools';
18
+ import { CoreUsersMcpTools } from './tools/core-users.mcp-tools';
19
+
20
+ @Module({
21
+ imports: [
22
+ DiscoveryModule,
23
+ forwardRef(() => PrismaModule),
24
+ forwardRef(() => PaginationModule),
25
+ forwardRef(() => TokenModule),
26
+ forwardRef(() => SettingModule),
27
+ forwardRef(() => UserModule),
28
+ forwardRef(() => SecurityModule),
29
+ ],
30
+ controllers: [McpController, McpApiKeyController],
31
+ providers: [
32
+ McpRegistryService,
33
+ McpServerService,
34
+ McpAuditService,
35
+ McpAuthGuard,
36
+ McpPermissionGuard,
37
+ McpApiKeyService,
38
+ CoreSettingsMcpTools,
39
+ CoreUsersMcpTools,
40
+ ],
41
+ exports: [McpRegistryService, McpServerService],
42
+ })
43
+ export class McpModule {}