|
10 | 10 | - [Static startup](#static-startup) |
11 | 11 | - [Permission-based starter guide](#permission-based-starter-guide) |
12 | 12 | - [Permission configuration approaches](#permission-configuration-approaches) |
| 13 | +- [Custom HTTP endpoints](#custom-http-endpoints) |
13 | 14 | - [API](#api) |
14 | 15 | - [createMcpServer](#createmcpserveroptions) |
15 | 16 | - [createPermissionBasedMcpServer](#createpermissionbasedmcpserveroptions) |
@@ -589,6 +590,135 @@ await start(); |
589 | 590 |
|
590 | 591 | **Note:** Resolver functions must be synchronous. If you need to fetch permissions from external sources, do so before server creation and cache the results. |
591 | 592 |
|
| 593 | +## Custom HTTP endpoints |
| 594 | + |
| 595 | +Toolception supports custom HTTP endpoints alongside MCP protocol endpoints, enabling REST-like APIs with Zod validation and type inference. |
| 596 | + |
| 597 | +### Basic usage |
| 598 | + |
| 599 | +```ts |
| 600 | +import { createMcpServer, defineEndpoint } from "toolception"; |
| 601 | +import { z } from "zod"; |
| 602 | + |
| 603 | +const { start } = await createMcpServer({ |
| 604 | + // ... standard options |
| 605 | + http: { |
| 606 | + port: 3000, |
| 607 | + customEndpoints: [ |
| 608 | + defineEndpoint({ |
| 609 | + method: "GET", |
| 610 | + path: "/api/users", |
| 611 | + querySchema: z.object({ |
| 612 | + limit: z.coerce.number().int().positive().default(10), |
| 613 | + role: z.enum(["admin", "user"]).optional(), |
| 614 | + }), |
| 615 | + responseSchema: z.object({ |
| 616 | + users: z.array(z.object({ id: z.string(), name: z.string() })), |
| 617 | + }), |
| 618 | + handler: async (req) => { |
| 619 | + // req.query is typed: { limit: number, role?: "admin" | "user" } |
| 620 | + // req.clientId is available from mcp-client-id header |
| 621 | + return { users: [{ id: "1", name: "Alice" }] }; |
| 622 | + }, |
| 623 | + }), |
| 624 | + ], |
| 625 | + }, |
| 626 | +}); |
| 627 | +``` |
| 628 | + |
| 629 | +### Validation schemas |
| 630 | + |
| 631 | +- **querySchema**: Validates URL query parameters (use `z.coerce` for type conversion) |
| 632 | +- **bodySchema**: Validates request body (POST/PUT/PATCH) |
| 633 | +- **paramsSchema**: Validates path parameters (e.g., `/users/:userId`) |
| 634 | +- **responseSchema**: Validates handler response (prevents invalid data leakage) |
| 635 | + |
| 636 | +### Request context |
| 637 | + |
| 638 | +Handlers receive a typed request object: |
| 639 | + |
| 640 | +```ts |
| 641 | +{ |
| 642 | + body: TBody, // Validated from bodySchema |
| 643 | + query: TQuery, // Validated from querySchema |
| 644 | + params: TParams, // Validated from paramsSchema |
| 645 | + headers: Record<string, string | string[] | undefined>, |
| 646 | + clientId: string, // From mcp-client-id header or auto-generated |
| 647 | +} |
| 648 | +``` |
| 649 | + |
| 650 | +### Permission-aware endpoints |
| 651 | + |
| 652 | +Use `definePermissionAwareEndpoint` in permission-based servers to access client permissions: |
| 653 | + |
| 654 | +```ts |
| 655 | +import { createPermissionBasedMcpServer, definePermissionAwareEndpoint } from "toolception"; |
| 656 | + |
| 657 | +const { start } = await createPermissionBasedMcpServer({ |
| 658 | + // ... permission config |
| 659 | + http: { |
| 660 | + customEndpoints: [ |
| 661 | + definePermissionAwareEndpoint({ |
| 662 | + method: "GET", |
| 663 | + path: "/api/me", |
| 664 | + responseSchema: z.object({ |
| 665 | + clientId: z.string(), |
| 666 | + allowedToolsets: z.array(z.string()), |
| 667 | + isAdmin: z.boolean(), |
| 668 | + }), |
| 669 | + handler: async (req) => { |
| 670 | + // req.allowedToolsets and req.failedToolsets are available |
| 671 | + return { |
| 672 | + clientId: req.clientId, |
| 673 | + allowedToolsets: req.allowedToolsets, |
| 674 | + isAdmin: req.allowedToolsets.includes("admin-tools"), |
| 675 | + }; |
| 676 | + }, |
| 677 | + }), |
| 678 | + ], |
| 679 | + }, |
| 680 | +}); |
| 681 | +``` |
| 682 | + |
| 683 | +### Error handling |
| 684 | + |
| 685 | +Validation failures return standardized error responses: |
| 686 | + |
| 687 | +- **400 VALIDATION_ERROR**: Request validation failed (body, query, or params) |
| 688 | +- **500 INTERNAL_ERROR**: Handler threw an exception |
| 689 | +- **500 RESPONSE_VALIDATION_ERROR**: Response validation failed |
| 690 | + |
| 691 | +Example error response: |
| 692 | + |
| 693 | +```json |
| 694 | +{ |
| 695 | + "error": { |
| 696 | + "code": "VALIDATION_ERROR", |
| 697 | + "message": "Validation failed for query", |
| 698 | + "details": [ |
| 699 | + { |
| 700 | + "code": "invalid_type", |
| 701 | + "path": ["limit"], |
| 702 | + "message": "Expected number, received string" |
| 703 | + } |
| 704 | + ] |
| 705 | + } |
| 706 | +} |
| 707 | +``` |
| 708 | + |
| 709 | +### Reserved paths |
| 710 | + |
| 711 | +Custom endpoints cannot override built-in MCP paths: |
| 712 | + |
| 713 | +- `/mcp` - MCP JSON-RPC endpoint |
| 714 | +- `/healthz` - Health check |
| 715 | +- `/tools` - Tool listing |
| 716 | +- `/.well-known/mcp-config` - Configuration schema |
| 717 | + |
| 718 | +### Complete example |
| 719 | + |
| 720 | +See `examples/custom-endpoints-demo.ts` for a full working example with GET, POST, PUT, DELETE endpoints, pagination, and permission-aware handlers. |
| 721 | + |
592 | 722 | ## API |
593 | 723 |
|
594 | 724 | ### createMcpServer(options) |
@@ -736,9 +866,10 @@ const moduleLoaders = { |
736 | 866 |
|
737 | 867 | #### options.http (optional) |
738 | 868 |
|
739 | | -`{ host?: string; port?: number; basePath?: string; cors?: boolean; logger?: boolean }` |
| 869 | +`{ host?: string; port?: number; basePath?: string; cors?: boolean; logger?: boolean; customEndpoints?: CustomEndpointDefinition[] }` |
740 | 870 |
|
741 | 871 | - Fastify transport configuration. Defaults: host `0.0.0.0`, port `3000`, basePath `/`, CORS enabled, logger disabled. |
| 872 | +- `customEndpoints`: Optional array of custom HTTP endpoints to register alongside MCP protocol endpoints. See [Custom HTTP endpoints](#custom-http-endpoints) for details. |
742 | 873 |
|
743 | 874 | #### options.createServer (optional) |
744 | 875 |
|
|
0 commit comments