loopback4-mcp 0.0.1

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 (87) hide show
  1. package/README.md +87 -0
  2. package/dist/component.d.ts +11 -0
  3. package/dist/component.js +22 -0
  4. package/dist/component.js.map +1 -0
  5. package/dist/constants/index.d.ts +1 -0
  6. package/dist/constants/index.js +5 -0
  7. package/dist/constants/index.js.map +1 -0
  8. package/dist/constants/mcp-tool.constant.d.ts +1 -0
  9. package/dist/constants/mcp-tool.constant.js +6 -0
  10. package/dist/constants/mcp-tool.constant.js.map +1 -0
  11. package/dist/controllers/index.d.ts +1 -0
  12. package/dist/controllers/index.js +5 -0
  13. package/dist/controllers/index.js.map +1 -0
  14. package/dist/controllers/mcp.controller.d.ts +10 -0
  15. package/dist/controllers/mcp.controller.js +121 -0
  16. package/dist/controllers/mcp.controller.js.map +1 -0
  17. package/dist/decorators/index.d.ts +1 -0
  18. package/dist/decorators/index.js +5 -0
  19. package/dist/decorators/index.js.map +1 -0
  20. package/dist/decorators/mcp-tool.decorator.d.ts +2 -0
  21. package/dist/decorators/mcp-tool.decorator.js +45 -0
  22. package/dist/decorators/mcp-tool.decorator.js.map +1 -0
  23. package/dist/index.d.ts +5 -0
  24. package/dist/index.js +9 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/interfaces/index.d.ts +1 -0
  27. package/dist/interfaces/index.js +5 -0
  28. package/dist/interfaces/index.js.map +1 -0
  29. package/dist/interfaces/mcp-hook-provider.interface.d.ts +12 -0
  30. package/dist/interfaces/mcp-hook-provider.interface.js +3 -0
  31. package/dist/interfaces/mcp-hook-provider.interface.js.map +1 -0
  32. package/dist/keys.d.ts +1 -0
  33. package/dist/keys.js +3 -0
  34. package/dist/keys.js.map +1 -0
  35. package/dist/observers/index.d.ts +1 -0
  36. package/dist/observers/index.js +5 -0
  37. package/dist/observers/index.js.map +1 -0
  38. package/dist/observers/mcp-tool-registry-boot.observer.d.ts +19 -0
  39. package/dist/observers/mcp-tool-registry-boot.observer.js +42 -0
  40. package/dist/observers/mcp-tool-registry-boot.observer.js.map +1 -0
  41. package/dist/services/index.d.ts +3 -0
  42. package/dist/services/index.js +7 -0
  43. package/dist/services/index.js.map +1 -0
  44. package/dist/services/mcp-schema-generator-service.service.d.ts +20 -0
  45. package/dist/services/mcp-schema-generator-service.service.js +73 -0
  46. package/dist/services/mcp-schema-generator-service.service.js.map +1 -0
  47. package/dist/services/mcp-server-factory.service.d.ts +13 -0
  48. package/dist/services/mcp-server-factory.service.js +51 -0
  49. package/dist/services/mcp-server-factory.service.js.map +1 -0
  50. package/dist/services/mcp-tool-registry.service.d.ts +49 -0
  51. package/dist/services/mcp-tool-registry.service.js +245 -0
  52. package/dist/services/mcp-tool-registry.service.js.map +1 -0
  53. package/dist/types.d.ts +46 -0
  54. package/dist/types.js +17 -0
  55. package/dist/types.js.map +1 -0
  56. package/dist/utils/index.d.ts +1 -0
  57. package/dist/utils/index.js +5 -0
  58. package/dist/utils/index.js.map +1 -0
  59. package/dist/utils/mcp-parameter-extractor.d.ts +9 -0
  60. package/dist/utils/mcp-parameter-extractor.js +51 -0
  61. package/dist/utils/mcp-parameter-extractor.js.map +1 -0
  62. package/package.json +139 -0
  63. package/src/component.ts +18 -0
  64. package/src/constants/index.ts +1 -0
  65. package/src/constants/mcp-tool.constant.ts +2 -0
  66. package/src/controllers/README.md +6 -0
  67. package/src/controllers/index.ts +1 -0
  68. package/src/controllers/mcp.controller.ts +112 -0
  69. package/src/decorators/README.md +34 -0
  70. package/src/decorators/index.ts +1 -0
  71. package/src/decorators/mcp-tool.decorator.ts +68 -0
  72. package/src/index.ts +5 -0
  73. package/src/interfaces/index.ts +1 -0
  74. package/src/interfaces/mcp-hook-provider.interface.ts +11 -0
  75. package/src/keys.ts +1 -0
  76. package/src/mixins/README.md +216 -0
  77. package/src/observers/index.ts +1 -0
  78. package/src/observers/mcp-tool-registry-boot.observer.ts +40 -0
  79. package/src/providers/README.md +129 -0
  80. package/src/repositories/README.md +5 -0
  81. package/src/services/index.ts +3 -0
  82. package/src/services/mcp-schema-generator-service.service.ts +80 -0
  83. package/src/services/mcp-server-factory.service.ts +71 -0
  84. package/src/services/mcp-tool-registry.service.ts +368 -0
  85. package/src/types.ts +59 -0
  86. package/src/utils/index.ts +1 -0
  87. package/src/utils/mcp-parameter-extractor.ts +67 -0
package/package.json ADDED
@@ -0,0 +1,139 @@
1
+ {
2
+ "name": "loopback4-mcp",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "description": "Desktop",
6
+ "keywords": [
7
+ "loopback-extension",
8
+ "loopback"
9
+ ],
10
+ "main": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "engines": {
13
+ "node": ">=20"
14
+ },
15
+ "scripts": {
16
+ "build": "lb-tsc",
17
+ "build:watch": "lb-tsc --watch",
18
+ "lint": "npm run eslint && npm run prettier:check",
19
+ "lint:fix": "npm run eslint:fix && npm run prettier:fix",
20
+ "prettier:cli": "lb-prettier \"**/*.ts\" \"**/*.js\"",
21
+ "prettier:check": "npm run prettier:cli -- -l",
22
+ "prettier:fix": "npm run prettier:cli -- --write",
23
+ "eslint": "lb-eslint --report-unused-disable-directives .",
24
+ "eslint:fix": "npm run eslint -- --fix",
25
+ "pretest": "npm run rebuild",
26
+ "test": "lb-mocha --allow-console-logs \"dist/__tests__\"",
27
+ "posttest": "npm run lint",
28
+ "test:dev": "lb-mocha --allow-console-logs dist/__tests__/**/*.js && npm run posttest",
29
+ "clean": "lb-clean dist *.tsbuildinfo .eslintcache",
30
+ "rebuild": "npm run clean && npm run build",
31
+ "prepublishOnly": "npm run test",
32
+ "prepare": "husky install"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/sourcefuse/loopback4-mcp"
37
+ },
38
+ "author": "SourceFuse",
39
+ "license": "MIT",
40
+ "files": [
41
+ "README.md",
42
+ "dist",
43
+ "src",
44
+ "!*/__tests__"
45
+ ],
46
+ "peerDependencies": {
47
+ "@loopback/core": "^7.0.3"
48
+ },
49
+ "dependencies": {
50
+ "@loopback/rest": "^15.0.7",
51
+ "@modelcontextprotocol/sdk": "^1.24.0",
52
+ "@sourceloop/core": "^20.0.0",
53
+ "tslib": "^2.6.2",
54
+ "zod": "^4.2.1"
55
+ },
56
+ "devDependencies": {
57
+ "@commitlint/cli": "^17.7.1",
58
+ "@commitlint/config-conventional": "^17.7.0",
59
+ "@loopback/build": "^12.0.3",
60
+ "@loopback/core": "^7.0.3",
61
+ "@loopback/eslint-config": "^16.0.1",
62
+ "@loopback/testlab": "^8.0.3",
63
+ "@semantic-release/changelog": "^6.0.1",
64
+ "@semantic-release/commit-analyzer": "^9.0.2",
65
+ "@semantic-release/git": "^10.0.1",
66
+ "@semantic-release/npm": "^13.1.3",
67
+ "@semantic-release/release-notes-generator": "^10.0.3",
68
+ "@types/jsonwebtoken": "^9.0.10",
69
+ "@types/node": "^16.18.126",
70
+ "commitizen": "^4.2.4",
71
+ "cz-conventional-changelog": "^3.3.0",
72
+ "cz-customizable": "^6.3.0",
73
+ "cz-customizable-ghooks": "^2.0.0",
74
+ "eslint": "^8.57.1",
75
+ "husky": "^7.0.4",
76
+ "loopback4-authentication": "^13.0.4",
77
+ "loopback4-authorization": "^8.1.3",
78
+ "semantic-release": "^25.0.2",
79
+ "source-map-support": "^0.5.21",
80
+ "typescript": "~5.2.2"
81
+ },
82
+ "overrides": {
83
+ "jws": "3.2.3",
84
+ "node-forge": "1.3.2"
85
+ },
86
+ "config": {
87
+ "commitizen": {
88
+ "path": "./node_modules/cz-customizable"
89
+ },
90
+ "cz-customizable": {
91
+ "config": "./.cz-config.js"
92
+ }
93
+ },
94
+ "publishConfig": {
95
+ "registry": "https://registry.npmjs.org/",
96
+ "access": "public"
97
+ },
98
+ "release": {
99
+ "branches": [
100
+ "master"
101
+ ],
102
+ "plugins": [
103
+ [
104
+ "@semantic-release/commit-analyzer",
105
+ {
106
+ "preset": "angular",
107
+ "releaseRules": [
108
+ {
109
+ "type": "chore",
110
+ "scope": "deps",
111
+ "release": "patch"
112
+ }
113
+ ]
114
+ }
115
+ ],
116
+ "@semantic-release/release-notes-generator",
117
+ [
118
+ "@semantic-release/npm",
119
+ {
120
+ "npmPublish": true,
121
+ "pkgRoot": ".",
122
+ "tarballDir": "dist"
123
+ }
124
+ ],
125
+ [
126
+ "@semantic-release/git",
127
+ {
128
+ "assets": [
129
+ "package.json",
130
+ "CHANGELOG.md"
131
+ ],
132
+ "message": "chore(release): ${nextRelease.version} semantic"
133
+ }
134
+ ],
135
+ "@semantic-release/github"
136
+ ],
137
+ "repositoryUrl": "https://github.com/sourcefuse/loopback4-mcp.git"
138
+ }
139
+ }
@@ -0,0 +1,18 @@
1
+ import {Application, Component, CoreBindings, inject} from '@loopback/core';
2
+ import {
3
+ McpSchemaGeneratorService,
4
+ McpServerFactory,
5
+ McpToolRegistry,
6
+ } from './services';
7
+ import {McpController} from './controllers';
8
+ import {McpToolRegistryBootObserver} from './observers';
9
+
10
+ export class McpComponent implements Component {
11
+ services = [McpSchemaGeneratorService, McpServerFactory, McpToolRegistry];
12
+ controllers = [McpController];
13
+ lifeCycleObservers = [McpToolRegistryBootObserver];
14
+ constructor(
15
+ @inject(CoreBindings.APPLICATION_INSTANCE)
16
+ private application: Application,
17
+ ) {}
18
+ }
@@ -0,0 +1 @@
1
+ export * from './mcp-tool.constant';
@@ -0,0 +1,2 @@
1
+ // Metadata key for MCP tools
2
+ export const MCP_TOOL_METADATA_KEY = 'mcp:tools';
@@ -0,0 +1,6 @@
1
+ # Controllers
2
+
3
+ This directory contains source files for the controllers exported by this
4
+ extension.
5
+
6
+ For more information, see <http://loopback.io/doc/en/lb4/Controllers.html>.
@@ -0,0 +1 @@
1
+ export * from './mcp.controller';
@@ -0,0 +1,112 @@
1
+ import {inject, service} from '@loopback/core';
2
+ import {post, Request, Response, RestBindings} from '@loopback/rest';
3
+ import {StreamableHTTPServerTransport} from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
+ import {CONTENT_TYPE, ILogger, LOGGER, STATUS_CODE} from '@sourceloop/core';
5
+ import {authenticate, STRATEGY} from 'loopback4-authentication';
6
+ import {authorize} from 'loopback4-authorization';
7
+ import {McpServerFactory} from '../services';
8
+
9
+ export class McpController {
10
+ constructor(
11
+ @inject(LOGGER.LOGGER_INJECT)
12
+ private readonly logger: ILogger,
13
+ @service(McpServerFactory)
14
+ private readonly serverFactory: McpServerFactory,
15
+ ) {}
16
+
17
+ @authenticate(STRATEGY.BEARER, {
18
+ passReqToCallback: true,
19
+ })
20
+ @authorize({
21
+ permissions: ['*'],
22
+ })
23
+ @post('/mcp', {
24
+ summary: 'MCP HTTP Message',
25
+ description: 'Handle MCP message via StreamableHTTP transport',
26
+ requestBody: {
27
+ content: {
28
+ [CONTENT_TYPE.JSON]: {
29
+ schema: {
30
+ type: 'object',
31
+ description: 'MCP message payload',
32
+ },
33
+ },
34
+ },
35
+ },
36
+ responses: {
37
+ [STATUS_CODE.OK]: {
38
+ description: 'MCP message processed successfully',
39
+ content: {
40
+ [CONTENT_TYPE.JSON]: {
41
+ schema: {
42
+ type: 'object',
43
+ description: 'MCP response message',
44
+ },
45
+ },
46
+ },
47
+ },
48
+ [STATUS_CODE.INTERNAL_SERVER_ERROR]: {
49
+ description: 'Internal server error',
50
+ content: {
51
+ [CONTENT_TYPE.JSON]: {
52
+ schema: {
53
+ type: 'object',
54
+ properties: {
55
+ jsonrpc: {type: 'string'},
56
+ error: {
57
+ type: 'object',
58
+ properties: {
59
+ code: {type: 'number'},
60
+ message: {type: 'string'},
61
+ },
62
+ },
63
+ id: {type: 'null'},
64
+ },
65
+ },
66
+ },
67
+ },
68
+ },
69
+ },
70
+ })
71
+ async handleMCPRequest(
72
+ @inject(RestBindings.Http.REQUEST) req: Request,
73
+ @inject(RestBindings.Http.RESPONSE) res: Response,
74
+ ): Promise<void> {
75
+ try {
76
+ // Server creation using factory service
77
+ const server = this.serverFactory.createServer();
78
+ const transport = new StreamableHTTPServerTransport({
79
+ sessionIdGenerator: undefined,
80
+ });
81
+
82
+ // Set up cleanup handlers
83
+ /* eslint-disable @typescript-eslint/no-misused-promises */
84
+ res.on('close', async () => {
85
+ await transport.close();
86
+ await server.close();
87
+ this.logger.info('Session closed.');
88
+ });
89
+
90
+ res.on('error', async () => {
91
+ await transport.close();
92
+ await server.close();
93
+ this.logger.info('Closing Session as it errorred out.');
94
+ });
95
+
96
+ await server.connect(transport);
97
+ await transport.handleRequest(req, res, req.body);
98
+ } catch (err) {
99
+ this.logger.error('Failed to establish MCP connection:', err);
100
+ if (!res.headersSent) {
101
+ res.status(STATUS_CODE.INTERNAL_SERVER_ERROR).json({
102
+ jsonrpc: '2.0',
103
+ error: {
104
+ code: -32603,
105
+ message: 'Internal server error',
106
+ },
107
+ id: null,
108
+ });
109
+ }
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,34 @@
1
+ # Decorators
2
+
3
+ ## Overview
4
+
5
+ Decorators provide annotations for class methods and arguments. Decorators use
6
+ the form `@decorator` where `decorator` is the name of the function that will be
7
+ called at runtime.
8
+
9
+ ## Basic Usage
10
+
11
+ ### txIdFromHeader
12
+
13
+ This simple decorator allows you to annotate a `Controller` method argument. The
14
+ decorator will annotate the method argument with the value of the header
15
+ `X-Transaction-Id` from the request.
16
+
17
+ **Example**
18
+
19
+ ```ts
20
+ class MyController {
21
+ @get('/')
22
+ getHandler(@txIdFromHeader() txId: string) {
23
+ return `Your transaction id is: ${txId}`;
24
+ }
25
+ }
26
+ ```
27
+
28
+ ## Related Resources
29
+
30
+ You can check out the following resource to learn more about decorators and how
31
+ they are used in LoopBack Next.
32
+
33
+ - [TypeScript Handbook: Decorators](https://www.typescriptlang.org/docs/handbook/decorators.html)
34
+ - [Decorators in LoopBack](http://loopback.io/doc/en/lb4/Decorators.html)
@@ -0,0 +1 @@
1
+ export * from './mcp-tool.decorator';
@@ -0,0 +1,68 @@
1
+ import {BindingKey, MetadataInspector} from '@loopback/core';
2
+ import {MCP_TOOL_METADATA_KEY} from '../constants';
3
+ import {McpHookFunction} from '../interfaces';
4
+ import {
5
+ McpHookConfig,
6
+ McpToolDecoratorOptions,
7
+ McpToolMetadata,
8
+ } from '../types';
9
+
10
+ /**
11
+ * Process hook config to normalize binding keys
12
+ */
13
+ function processHookConfig(
14
+ hookConfig?: McpHookConfig,
15
+ ): McpHookConfig | undefined {
16
+ if (!hookConfig) return undefined;
17
+
18
+ const binding =
19
+ typeof hookConfig.binding === 'string'
20
+ ? BindingKey.create<McpHookFunction>(hookConfig.binding)
21
+ : hookConfig.binding;
22
+
23
+ return {
24
+ binding,
25
+ config: hookConfig.config,
26
+ };
27
+ }
28
+
29
+ export function mcpTool(options: McpToolDecoratorOptions) {
30
+ return function (
31
+ target: Object,
32
+ propertyKey: string,
33
+ descriptor: PropertyDescriptor,
34
+ ) {
35
+ // Create basic schema - parameter extraction happens in the service
36
+ const basicSchema = options.schema ?? {};
37
+
38
+ const metadata: McpToolMetadata = {
39
+ name: options.name,
40
+ description: options.description,
41
+ schema: basicSchema,
42
+ controllerFunction: descriptor.value,
43
+ preHook: processHookConfig(options.preHook),
44
+ postHook: processHookConfig(options.postHook),
45
+ parameterNames: [], // Will be populated by service
46
+ };
47
+
48
+ // Store metadata using LoopBack's metadata system
49
+ // Get existing metadata for the class
50
+ const existingMethodsMetadata =
51
+ MetadataInspector.getAllMethodMetadata<McpToolMetadata>(
52
+ MCP_TOOL_METADATA_KEY,
53
+ target,
54
+ ) ?? {};
55
+
56
+ // Add this method's metadata
57
+ existingMethodsMetadata[propertyKey] = metadata;
58
+
59
+ // Store the complete metadata map back
60
+ MetadataInspector.defineMetadata(
61
+ MCP_TOOL_METADATA_KEY,
62
+ existingMethodsMetadata,
63
+ target,
64
+ );
65
+
66
+ return descriptor;
67
+ };
68
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './component';
2
+ export * from './types';
3
+ export * from './decorators';
4
+ export * from './observers';
5
+ export * from './keys';
@@ -0,0 +1 @@
1
+ export * from './mcp-hook-provider.interface';
@@ -0,0 +1,11 @@
1
+ export interface McpHookContext {
2
+ toolName: string;
3
+ args: {[key: string]: unknown};
4
+ result?: unknown;
5
+ error?: Error;
6
+ metadata?: {[key: string]: unknown};
7
+ }
8
+
9
+ export type McpHookFunction = (
10
+ context: McpHookContext,
11
+ ) => Promise<McpHookContext | void> | McpHookContext | void;
package/src/keys.ts ADDED
@@ -0,0 +1 @@
1
+ export namespace McpBindings {}
@@ -0,0 +1,216 @@
1
+ # Mixins
2
+
3
+ This directory contains source files for the mixins exported by this extension.
4
+
5
+ ## Overview
6
+
7
+ Sometimes it's helpful to write partial classes and then combining them together
8
+ to build more powerful classes. This pattern is called Mixins (mixing in partial
9
+ classes) and is supported by LoopBack 4.
10
+
11
+ LoopBack 4 supports mixins at an `Application` level. Your partial class can
12
+ then be mixed into the `Application` class. A mixin class can modify or override
13
+ existing methods of the class or add new ones! It is also possible to mixin
14
+ multiple classes together as needed.
15
+
16
+ ### High level example
17
+
18
+ ```ts
19
+ class MyApplication extends MyMixinClass(Application) {
20
+ // Your code
21
+ }
22
+
23
+ // Multiple Classes mixed together
24
+ class MyApp extends MyMixinClass(MyMixinClass2(Application)) {
25
+ // Your code
26
+ }
27
+ ```
28
+
29
+ ## Getting Started
30
+
31
+ For hello-extensions we write a simple Mixin that allows the `Application` class
32
+ to bind a `Logger` class from ApplicationOptions, Components, or `.logger()`
33
+ method that is mixed in. `Logger` instances are bound to the key
34
+ `loggers.${Logger.name}`. Once a Logger has been bound, the user can retrieve it
35
+ by using
36
+ [Dependency Injection](http://loopback.io/doc/en/lb4/Dependency-injection.html)
37
+ and the key for the `Logger`.
38
+
39
+ ### What is a Logger?
40
+
41
+ > A Logger class is provides a mechanism for logging messages of varying
42
+ > priority by providing an implementation for `Logger.info()` &
43
+ > `Logger.error()`. An example of a Logger is `console` which has
44
+ > `console.log()` and `console.error()`.
45
+
46
+ #### An example Logger
47
+
48
+ ```ts
49
+ class ColorLogger implements Logger {
50
+ log(...args: LogArgs) {
51
+ console.log('log :', ...args);
52
+ }
53
+
54
+ error(...args: LogArgs) {
55
+ // log in red color
56
+ console.log('\x1b[31m error: ', ...args, '\x1b[0m');
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## LoggerMixin
62
+
63
+ A complete & functional implementation can be found in `logger.mixin.ts`. _Here
64
+ are some key things to keep in mind when writing your own Mixin_.
65
+
66
+ ### constructor()
67
+
68
+ A Mixin constructor must take an array of any type as it's argument. This would
69
+ represent `ApplicationOptions` for our base class `Application` as well as any
70
+ properties we would like for our Mixin.
71
+
72
+ It is also important for the constructor to call `super(args)` so `Application`
73
+ continues to work as expected.
74
+
75
+ ```ts
76
+ constructor(...args: any[]) {
77
+ super(args);
78
+ }
79
+ ```
80
+
81
+ ### Binding via `ApplicationOptions`
82
+
83
+ As mentioned earlier, since our `args` represents `ApplicationOptions`, we can
84
+ make it possible for users to pass in their `Logger` implementations in a
85
+ `loggers` array on `ApplicationOptions`. We can then read the array and
86
+ automatically bind these for the user.
87
+
88
+ #### Example user experience
89
+
90
+ ```ts
91
+ class MyApp extends LoggerMixin(Application) {
92
+ constructor(...args: any[]) {
93
+ super(...args);
94
+ }
95
+ }
96
+
97
+ const app = new MyApp({
98
+ loggers: [ColorLogger],
99
+ });
100
+ ```
101
+
102
+ #### Example Implementation
103
+
104
+ To implement this, we would check `this.options` to see if it has a `loggers`
105
+ array and if so, bind it by calling the `.logger()` method. (More on that
106
+ below).
107
+
108
+ ```ts
109
+ if (this.options.loggers) {
110
+ for (const logger of this.options.loggers) {
111
+ this.logger(logger);
112
+ }
113
+ }
114
+ ```
115
+
116
+ ### Binding via `.logger()`
117
+
118
+ As mentioned earlier, we can add a new function to our `Application` class
119
+ called `.logger()` into which a user would pass in their `Logger` implementation
120
+ so we can bind it to the `loggers.*` key for them. We just add this new method
121
+ on our partial Mixin class.
122
+
123
+ ```ts
124
+ logger(logClass: Logger) {
125
+ const loggerKey = `loggers.${logClass.name}`;
126
+ this.bind(loggerKey).toClass(logClass);
127
+ }
128
+ ```
129
+
130
+ ### Binding a `Logger` from a `Component`
131
+
132
+ Our base class of `Application` already has a method that binds components. We
133
+ can modify this method to continue binding a `Component` as usual but also
134
+ binding any `Logger` instances provided by that `Component`. When modifying
135
+ behavior of an existing method, we can ensure existing behavior by calling the
136
+ `super.method()`. In our case the method is `.component()`.
137
+
138
+ ```ts
139
+ component(component: Constructor<any>) {
140
+ super.component(component); // ensures existing behavior from Application
141
+ this.mountComponentLoggers(component);
142
+ }
143
+ ```
144
+
145
+ We have now modified `.component()` to do it's thing and then call our method
146
+ `mountComponentLoggers()`. In this method is where we check for `Logger`
147
+ implementations declared by the component in a `loggers` array by retrieving the
148
+ instance of the `Component`. Then if `loggers` array exists, we bind the
149
+ `Logger` instances as normal (by leveraging our `.logger()` method).
150
+
151
+ ```ts
152
+ mountComponentLoggers(component: Constructor<any>) {
153
+ const componentKey = `components.${component.name}`;
154
+ const compInstance = this.getSync(componentKey);
155
+
156
+ if (compInstance.loggers) {
157
+ for (const logger of compInstance.loggers) {
158
+ this.logger(logger);
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ ## Retrieving the Logger instance
165
+
166
+ Now that we have bound a Logger to our Application via one of the many ways made
167
+ possible by `LoggerMixin`, we need to be able to retrieve it so we can use it.
168
+ Let's say we want to use it in a controller. Here's an example to retrieving it
169
+ so we can use it.
170
+
171
+ ```ts
172
+ class MyController {
173
+ constructor(@inject('loggers.ColorLogger') protected log: Logger) {}
174
+
175
+ helloWorld() {
176
+ this.log.log('hello log');
177
+ this.log.error('hello error');
178
+ }
179
+ }
180
+ ```
181
+
182
+ ## Examples for using LoggerMixin
183
+
184
+ ### Using the app's `.logger()` method
185
+
186
+ ```ts
187
+ class LoggingApplication extends LoggerMixin(Application) {
188
+ constructor(...args: any[]) {
189
+ super(...args);
190
+ this.logger(ColorLogger);
191
+ }
192
+ }
193
+ ```
194
+
195
+ ### Using the app's constructor
196
+
197
+ ```ts
198
+ class LoggerApplication extends LoggerMixin(Application) {
199
+ constructor() {
200
+ super({
201
+ loggers: [ColorLogger],
202
+ });
203
+ }
204
+ }
205
+ ```
206
+
207
+ ### Binding a Logger provided by a component
208
+
209
+ ```ts
210
+ class LoggingComponent implements Component {
211
+ loggers: [ColorLogger];
212
+ }
213
+
214
+ const app = new LoggingApplication();
215
+ app.component(LoggingComponent); // Logger from MyComponent will be bound to loggers.ColorLogger
216
+ ```
@@ -0,0 +1 @@
1
+ export * from './mcp-tool-registry-boot.observer';
@@ -0,0 +1,40 @@
1
+ import {
2
+ inject,
3
+ lifeCycleObserver,
4
+ LifeCycleObserver,
5
+ service,
6
+ } from '@loopback/core';
7
+ import {ILogger, LOGGER} from '@sourceloop/core';
8
+ import {McpToolRegistry} from '../services';
9
+
10
+ /**
11
+ * Lifecycle observer to initialize MCP tool registry after application boot
12
+ */
13
+ @lifeCycleObserver('mcpToolRegistryInit')
14
+ export class McpToolRegistryBootObserver implements LifeCycleObserver {
15
+ constructor(
16
+ @service(McpToolRegistry)
17
+ private readonly mcpToolRegistry: McpToolRegistry,
18
+ @inject(LOGGER.LOGGER_INJECT)
19
+ private readonly logger: ILogger,
20
+ ) {}
21
+
22
+ /**
23
+ * Initialize MCP tool registry after application starts
24
+ */
25
+ async start(): Promise<void> {
26
+ try {
27
+ await this.mcpToolRegistry.initialize();
28
+ } catch (error) {
29
+ this.logger.error('Failed to initialize MCP tool registry:', error);
30
+ throw error;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Called when application stops
36
+ */
37
+ async stop(): Promise<void> {
38
+ this.logger.info('MCP tool registry stopping...');
39
+ }
40
+ }