Skip to content

Commit 3dc4a92

Browse files
docs(core): readme update
1 parent 5546c74 commit 3dc4a92

1 file changed

Lines changed: 1 addition & 321 deletions

File tree

README.md

Lines changed: 1 addition & 321 deletions
Original file line numberDiff line numberDiff line change
@@ -94,19 +94,10 @@ export class McpPostHookProvider implements Provider<Function> {
9494
Update your `src/application.ts` to bind the component and hooks:
9595

9696
```typescript
97-
import {BootMixin, ServiceMixin} from '@loopback/core';
98-
import {RepositoryMixin, RestApplication} from '@loopback/rest';
99-
import {McpComponent} from 'loopback4-mcp';
100-
import {McpHookBindings} from './keys';
101-
import {McpPreHookProvider} from './providers/mcp-pre-hook.provider';
102-
import {McpPostHookProvider} from './providers/mcp-post-hook.provider';
103-
10497
export class MyApplication extends BootMixin(
10598
ServiceMixin(RepositoryMixin(RestApplication)),
10699
) {
107100
constructor(options: ApplicationConfig = {}) {
108-
super(options);
109-
110101
// Bind MCP component
111102
this.component(McpComponent);
112103

@@ -122,26 +113,6 @@ export class MyApplication extends BootMixin(
122113
Add the `@mcpTool()` decorator to controller methods you want to expose as MCP tools. Here's a complete example showing the decorator stack with authorization and authentication:
123114

124115
```typescript
125-
import {
126-
Count,
127-
CountSchema,
128-
Filter,
129-
repository,
130-
Where,
131-
} from '@loopback/repository';
132-
import {param, post, get, patch, put, del, requestBody} from '@loopback/rest';
133-
import {authorize} from 'loopback4-authorization';
134-
import {authenticate, STRATEGY} from 'loopback4-authentication';
135-
import {PermissionKey} from '../permissions';
136-
import {
137-
OPERATION_SECURITY_SPEC,
138-
STATUS_CODE,
139-
getModelSchemaRefSF,
140-
} from '@sourceloop/core';
141-
import {mcpTool} from 'loopback4-mcp';
142-
import {HookBindings} from '../keys';
143-
import {User, UserRepository} from '../models';
144-
145116
export class UserController {
146117
constructor(
147118
@repository(UserRepository)
@@ -183,209 +154,6 @@ export class UserController {
183154
}]
184155
};
185156
}
186-
187-
@authorize({
188-
permissions: [PermissionKey.ViewUser],
189-
})
190-
@authenticate(STRATEGY.BEARER, {
191-
passReqToCallback: true,
192-
})
193-
@mcpTool({
194-
name: 'getUserById',
195-
description: 'Get a user by ID',
196-
preHook: {binding: HookBindings.PRE_HOOK},
197-
postHook: {binding: HookBindings.POST_HOOK},
198-
})
199-
@get('/users/{id}', {
200-
security: OPERATION_SECURITY_SPEC,
201-
responses: {
202-
[STATUS_CODE.OK]: {
203-
description: 'User model instance',
204-
content: {
205-
'application/json': {
206-
schema: getModelSchemaRefSF(User, {includeRelations: true}),
207-
},
208-
},
209-
},
210-
},
211-
})
212-
async findById(
213-
@param.path.string('id') id: string,
214-
): Promise<User> {
215-
return this.userRepository.findById(id);
216-
}
217-
218-
@authorize({
219-
permissions: [PermissionKey.ViewUser],
220-
})
221-
@authenticate(STRATEGY.BEARER, {
222-
passReqToCallback: true,
223-
})
224-
@mcpTool({
225-
name: 'listUsers',
226-
description: 'List all users',
227-
preHook: {binding: HookBindings.PRE_HOOK},
228-
postHook: {binding: HookBindings.POST_HOOK},
229-
})
230-
@get('/users', {
231-
security: OPERATION_SECURITY_SPEC,
232-
responses: {
233-
[STATUS_CODE.OK]: {
234-
description: 'Array of User model instances',
235-
content: {
236-
'application/json': {
237-
schema: {
238-
type: 'array',
239-
items: getModelSchemaRefSF(User, {includeRelations: true}),
240-
},
241-
},
242-
},
243-
},
244-
},
245-
})
246-
async find(
247-
@param.filter(User) filter?: Filter<User>,
248-
): Promise<User[]> {
249-
return this.userRepository.find(filter);
250-
}
251-
252-
@authorize({
253-
permissions: [PermissionKey.UpdateUser],
254-
})
255-
@authenticate(STRATEGY.BEARER, {
256-
passReqToCallback: true,
257-
})
258-
@mcpTool({
259-
name: 'updateUserById',
260-
description: 'Update a user by ID',
261-
preHook: {binding: HookBindings.PRE_HOOK},
262-
postHook: {binding: HookBindings.POST_HOOK},
263-
})
264-
@patch('/users/{id}', {
265-
security: OPERATION_SECURITY_SPEC,
266-
responses: {
267-
[STATUS_CODE.NO_CONTENT]: {
268-
description: 'User PATCH success',
269-
},
270-
},
271-
})
272-
async updateById(
273-
@param.path.string('id') id: string,
274-
@param.query.object('user') user: User,
275-
): Promise<object> {
276-
await this.userRepository.updateById(id, user);
277-
278-
return {
279-
content: [{
280-
type: 'text',
281-
text: `Successfully updated user with id: ${id}`
282-
}]
283-
};
284-
}
285-
286-
@authorize({
287-
permissions: [PermissionKey.DeleteUser],
288-
})
289-
@authenticate(STRATEGY.BEARER, {
290-
passReqToCallback: true,
291-
})
292-
@mcpTool({
293-
name: 'deleteUser',
294-
description: 'Delete a user by ID',
295-
preHook: {binding: HookBindings.PRE_HOOK},
296-
postHook: {binding: HookBindings.POST_HOOK},
297-
})
298-
@del('/users/{id}', {
299-
security: OPERATION_SECURITY_SPEC,
300-
responses: {
301-
[STATUS_CODE.NO_CONTENT]: {
302-
description: 'User DELETE success',
303-
},
304-
},
305-
})
306-
async deleteById(@param.path.string('id') id: string): Promise<object> {
307-
// Verify user exists first (will throw 404 if not found)
308-
await this.userRepository.findById(id);
309-
310-
await this.userRepository.deleteById(id);
311-
312-
return {
313-
content: [{
314-
type: 'text',
315-
text: `Successfully deleted user with id: ${id}`
316-
}]
317-
};
318-
}
319-
}
320-
```
321-
322-
## Component Interaction Flow
323-
324-
**Request Flow:**
325-
```
326-
Client Request (POST /mcp?tool=toolName)
327-
328-
McpController receives request and creates MCP server
329-
330-
McpServerFactory generates per-request server instance
331-
332-
McpToolRegistry looks up tool by name
333-
334-
Authorization Check validates JWT and permissions
335-
336-
Pre-Hook (if configured) performs validation/sanitization
337-
338-
Controller Method executes business logic
339-
340-
Post-Hook (if configured) performs logging/audit trails
341-
342-
Response Formatting wraps result in MCP format
343-
344-
Client receives MCP-formatted response
345-
```
346-
347-
**Error Flow:**
348-
```
349-
Authorization Check Failed → 403 Forbidden Response → Client receives error
350-
Controller Method Error → Error Formatting → Client receives MCP-formatted error
351-
Hook Execution Error → Error Handling → Client receives MCP-formatted error
352-
```
353-
354-
## MCP Endpoint Usage
355-
356-
### Endpoint Format
357-
358-
**POST** `/mcp?tool=toolName`
359-
360-
**Required Headers:**
361-
- `Content-Type: application/json`
362-
- `Authorization: Bearer YOUR_JWT_TOKEN`
363-
364-
### Example Request
365-
366-
```bash
367-
curl -X POST http://localhost:3000/mcp?tool=create-user \
368-
-H "Content-Type: application/json" \
369-
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
370-
-d '{
371-
"user": {
372-
"email": "john@example.com",
373-
"name": "John Doe",
374-
"age": 30
375-
}
376-
}'
377-
```
378-
379-
### Example Response
380-
381-
```json
382-
{
383-
"content": [
384-
{
385-
"type": "text",
386-
"text": "User created with ID: 550e8400-e29b-41d4-a716-446655440000"
387-
}
388-
]
389157
}
390158
```
391159

@@ -474,95 +242,6 @@ npx @modelcontextprotocol/inspector http://localhost:3000/mcp
474242
5. Monitor hook execution and responses
475243
6. Debug issues using the detailed logs
476244

477-
## Troubleshooting
478-
479-
### Common Issues
480-
481-
**Parameter extraction failures**
482-
- **Cause:** Missing or incorrect `@param` decorators
483-
- **Solution:** Ensure all parameters have appropriate decorators based on route structure
484-
485-
**"Invalid tools/call result: expected object, received undefined"**
486-
- **Cause:** Method returns `void` instead of object
487-
- **Solution:** Always return explicit MCP-formatted response for non-read operations
488-
489-
**Hook not executing**
490-
- **Cause:** Hook not bound in application.ts or binding key mismatch
491-
- **Solution:** Verify hook providers are bound and binding keys match decorator configuration
492-
493-
**Authorization failures**
494-
- **Cause:** Missing `@authorize()` decorator or invalid JWT token
495-
- **Solution:** Add appropriate authorization decorators and ensure valid authentication
496-
497-
## Best Practices
498-
499-
1. **Always use `@param` decorators** - MCP tool will fail without them
500-
2. **Return MCP-formatted responses** for write operations (CREATE, UPDATE, DELETE)
501-
3. **Implement proper error handling** in controller methods and hooks
502-
4. **Use hooks for cross-cutting concerns** - validation, logging, audit trails
503-
5. **Test with curl first** before integrating with MCP clients
504-
6. **Monitor hook execution time** - hooks should be fast and non-blocking
505-
7. **Keep controller methods focused** - move complex logic to services
506-
8. **Use descriptive tool names** and detailed descriptions for better discovery
507-
508-
## Advanced Features
509-
510-
### Zod Schema Validation
511-
512-
Add input validation using Zod schemas:
513-
514-
```typescript
515-
import {z} from 'zod';
516-
517-
@mcpTool({
518-
name: 'create-user',
519-
description: 'Create a new user',
520-
schema: {
521-
email: z.string().email('Invalid email format'),
522-
name: z.string().min(2, 'Name must be at least 2 characters'),
523-
age: z.number().min(18, 'User must be 18 or older'),
524-
},
525-
})
526-
```
527-
528-
### Custom Hook Implementations
529-
530-
Create more sophisticated hooks for business logic:
531-
532-
```typescript
533-
export class ValidationHookProvider implements Provider<Function> {
534-
value(): Function {
535-
return async (context: McpHookContext) => {
536-
if (context.toolName === 'create-user') {
537-
const user = context.args.user as any;
538-
539-
// Validate email uniqueness
540-
const existing = await this.userRepository.findOne({
541-
where: {email: user.email}
542-
});
543-
544-
if (existing) {
545-
throw new Error('User with this email already exists');
546-
}
547-
548-
// Sanitize input
549-
user.name = user.name.trim();
550-
user.email = user.email.toLowerCase();
551-
}
552-
};
553-
}
554-
}
555-
```
556-
557-
## Complete Example
558-
559-
For a complete working example, refer to the test suite in `src/__tests__/` which demonstrates:
560-
561-
- Controller setup with `@mcpTool` decorators
562-
- Hook provider implementations
563-
- Integration with authorization system
564-
- Request/response handling
565-
- Error management
566245

567246
## License
568247

@@ -572,3 +251,4 @@ For a complete working example, refer to the test suite in `src/__tests__/` whic
572251

573252
- GitHub Issues: [sourcefuse/loopback4-mcp/issues](https://github.com/sourcefuse/loopback4-mcp/issues)
574253
- Documentation: [loopback.io](https://loopback.io/)
254+

0 commit comments

Comments
 (0)