|
11 | 11 | - [Permission-based starter guide](#permission-based-starter-guide) |
12 | 12 | - [Permission configuration approaches](#permission-configuration-approaches) |
13 | 13 | - [Custom HTTP endpoints](#custom-http-endpoints) |
| 14 | +- [Per-session context](#per-session-context) |
14 | 15 | - [API](#api) |
15 | 16 | - [createMcpServer](#createmcpserveroptions) |
16 | 17 | - [createPermissionBasedMcpServer](#createpermissionbasedmcpserveroptions) |
@@ -39,6 +40,7 @@ Toolception addresses this by grouping tools into toolsets and letting you expos |
39 | 40 | - **Large or multi-domain catalogs**: You have >20–50 tools or multiple domains (e.g., search, data, billing) and don’t want to expose them all at once. |
40 | 41 | - **Task-specific workflows**: You want the client/agent to enable only the tools relevant to the current task. |
41 | 42 | - **Multi-tenant or policy needs**: Different users/tenants require different tool access or limits. |
| 43 | +- **Per-session context**: You need different context values (API tokens, user IDs) for each client session passed to module loaders. |
42 | 44 | - **Permission-based access control**: You need to enforce client-specific toolset permissions for security, compliance, or multi-tenant isolation. Each client should only see and access the toolsets they're authorized to use, with server-side or header-based permission enforcement. |
43 | 45 | - **Collision-safe naming**: You need predictable, namespaced tool names to avoid conflicts. |
44 | 46 | - **Lazy loading**: Some tools are heavy and should be loaded on demand. |
@@ -719,6 +721,88 @@ Custom endpoints cannot override built-in MCP paths: |
719 | 721 |
|
720 | 722 | See `examples/custom-endpoints-demo.ts` for a full working example with GET, POST, PUT, DELETE endpoints, pagination, and permission-aware handlers. |
721 | 723 |
|
| 724 | +## Per-session context |
| 725 | + |
| 726 | +Use the `sessionContext` option to enable per-client context values extracted from query parameters. This is useful for multi-tenant scenarios where each client needs different configuration (API tokens, user IDs, etc.) passed to module loaders. |
| 727 | + |
| 728 | +### Basic usage |
| 729 | + |
| 730 | +```ts |
| 731 | +import { createMcpServer } from "toolception"; |
| 732 | +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 733 | + |
| 734 | +const { start } = await createMcpServer({ |
| 735 | + catalog: { /* ... */ }, |
| 736 | + moduleLoaders: { /* ... */ }, |
| 737 | + context: { baseValue: 'shared' }, // Base context for all sessions |
| 738 | + sessionContext: { |
| 739 | + enabled: true, |
| 740 | + queryParam: { |
| 741 | + name: 'config', |
| 742 | + encoding: 'base64', |
| 743 | + allowedKeys: ['API_TOKEN', 'USER_ID'], // Security: always specify |
| 744 | + }, |
| 745 | + merge: 'shallow', |
| 746 | + }, |
| 747 | + createServer: () => new McpServer({ |
| 748 | + name: "my-server", |
| 749 | + version: "1.0.0", |
| 750 | + capabilities: { tools: { listChanged: true } }, |
| 751 | + }), |
| 752 | + http: { port: 3000 }, |
| 753 | +}); |
| 754 | + |
| 755 | +await start(); |
| 756 | +``` |
| 757 | + |
| 758 | +### Client connection |
| 759 | + |
| 760 | +```bash |
| 761 | +# Encode session config as base64 |
| 762 | +CONFIG=$(echo -n '{"API_TOKEN":"user-secret-token","USER_ID":"123"}' | base64) |
| 763 | + |
| 764 | +# Connect with session config |
| 765 | +curl -X POST "http://localhost:3000/mcp?config=$CONFIG" \ |
| 766 | + -H "mcp-client-id: my-client" \ |
| 767 | + -H "Content-Type: application/json" \ |
| 768 | + -d '{"jsonrpc":"2.0","method":"initialize",...}' |
| 769 | +``` |
| 770 | + |
| 771 | +### Module loader receives merged context |
| 772 | + |
| 773 | +```ts |
| 774 | +const moduleLoaders = { |
| 775 | + tenant: async (ctx: any) => { |
| 776 | + // ctx = { baseValue: 'shared', API_TOKEN: 'user-secret-token', USER_ID: '123' } |
| 777 | + return [/* tools using ctx.API_TOKEN */]; |
| 778 | + }, |
| 779 | +}; |
| 780 | +``` |
| 781 | + |
| 782 | +### Custom context resolver |
| 783 | + |
| 784 | +For advanced use cases, provide a custom resolver function: |
| 785 | + |
| 786 | +```ts |
| 787 | +sessionContext: { |
| 788 | + enabled: true, |
| 789 | + queryParam: { allowedKeys: ['tenant_id'] }, |
| 790 | + contextResolver: (request, baseContext, parsedConfig) => ({ |
| 791 | + ...baseContext, |
| 792 | + ...parsedConfig, |
| 793 | + clientId: request.clientId, |
| 794 | + timestamp: Date.now(), |
| 795 | + }), |
| 796 | +} |
| 797 | +``` |
| 798 | + |
| 799 | +### Security considerations |
| 800 | + |
| 801 | +- **Always specify `allowedKeys`**: Without a whitelist, any key in the query config is accepted |
| 802 | +- **Fail-secure**: Invalid encoding silently falls back to base context |
| 803 | +- **No logging of values**: Session config values are never logged |
| 804 | +- **Filtered silently**: Disallowed keys are filtered without error messages |
| 805 | + |
722 | 806 | ## API |
723 | 807 |
|
724 | 808 | ### createMcpServer(options) |
@@ -864,6 +948,28 @@ const moduleLoaders = { |
864 | 948 | }; |
865 | 949 | ``` |
866 | 950 |
|
| 951 | +#### options.sessionContext (optional) |
| 952 | + |
| 953 | +`SessionContextConfig` |
| 954 | + |
| 955 | +Configuration for per-session context extraction from query parameters. Enables multi-tenant use cases where each client session can have its own context values passed to module loaders. See [Per-session context](#per-session-context) for detailed usage examples. |
| 956 | + |
| 957 | +| Field | Type | Default | Description | |
| 958 | +|-------|------|---------|-------------| |
| 959 | +| `enabled` | `boolean` | `true` | Whether session context extraction is enabled | |
| 960 | +| `queryParam.name` | `string` | `'config'` | Query parameter name | |
| 961 | +| `queryParam.encoding` | `'base64' \| 'json'` | `'base64'` | Encoding format | |
| 962 | +| `queryParam.allowedKeys` | `string[]` | - | Whitelist of allowed keys (recommended for security) | |
| 963 | +| `contextResolver` | `function` | - | Custom context resolver function | |
| 964 | +| `merge` | `'shallow' \| 'deep'` | `'shallow'` | How to merge with base context | |
| 965 | + |
| 966 | +Notes |
| 967 | + |
| 968 | +- Session context is extracted per-request and merged with the base `context` option |
| 969 | +- Each unique session config generates a different cache key, enabling per-tenant module caching |
| 970 | +- Invalid encoding or parsing errors silently fall back to base context (fail-secure) |
| 971 | +- Only applies to DYNAMIC mode servers; STATIC mode uses a single shared server instance |
| 972 | + |
867 | 973 | #### options.http (optional) |
868 | 974 |
|
869 | 975 | `{ host?: string; port?: number; basePath?: string; cors?: boolean; logger?: boolean; customEndpoints?: CustomEndpointDefinition[] }` |
@@ -970,6 +1076,14 @@ Same as `createMcpServer` - see [options.configSchema](#optionsconfigschema-opti |
970 | 1076 |
|
971 | 1077 | Same as `createMcpServer` - see [options.context](#optionscontext-optional). |
972 | 1078 |
|
| 1079 | +#### options.sessionContext (optional) |
| 1080 | + |
| 1081 | +`SessionContextConfig` |
| 1082 | + |
| 1083 | +Session context is available in permission-based servers but has limited support. Because permission-based servers determine toolsets at connection time based on permissions, the session context cannot affect which toolsets are loaded. However, the merged context is still passed to module loaders. |
| 1084 | + |
| 1085 | +**Note:** A warning is issued if `sessionContext` is used with `createPermissionBasedMcpServer`. For full session context support with per-session toolset caching, use `createMcpServer` with DYNAMIC mode. |
| 1086 | + |
973 | 1087 | ### Meta-tools |
974 | 1088 |
|
975 | 1089 | Meta-tools are registered based on mode: |
|
0 commit comments