toolception 0.5.5 → 0.6.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.
package/README.md CHANGED
@@ -11,6 +11,7 @@
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:
@@ -1,6 +1,7 @@
1
1
  import { FastifyInstance } from 'fastify';
2
2
  import { DynamicToolManager } from '../core/DynamicToolManager.js';
3
3
  import { ServerOrchestrator } from '../core/ServerOrchestrator.js';
4
+ import { SessionContextResolver } from '../session/SessionContextResolver.js';
4
5
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
6
  import { CustomEndpointDefinition } from './customEndpoints.js';
6
7
  export interface FastifyTransportOptions {
@@ -16,17 +17,24 @@ export interface FastifyTransportOptions {
16
17
  */
17
18
  customEndpoints?: CustomEndpointDefinition[];
18
19
  }
20
+ /**
21
+ * Callback type for creating a server bundle.
22
+ * Accepts an optional merged context for per-session context support.
23
+ */
24
+ export type CreateBundleCallback = (mergedContext?: unknown) => {
25
+ server: McpServer;
26
+ orchestrator: ServerOrchestrator;
27
+ };
19
28
  export declare class FastifyTransport {
20
29
  private readonly options;
21
30
  private readonly defaultManager;
22
31
  private readonly createBundle;
32
+ private readonly sessionContextResolver?;
33
+ private readonly baseContext?;
23
34
  private app;
24
35
  private readonly configSchema?;
25
36
  private readonly clientCache;
26
- constructor(defaultManager: DynamicToolManager, createBundle: () => {
27
- server: McpServer;
28
- orchestrator: ServerOrchestrator;
29
- }, options?: FastifyTransportOptions, configSchema?: object);
37
+ constructor(defaultManager: DynamicToolManager, createBundle: CreateBundleCallback, options?: FastifyTransportOptions, configSchema?: object, sessionContextResolver?: SessionContextResolver, baseContext?: unknown);
30
38
  start(): Promise<void>;
31
39
  /**
32
40
  * Stops the Fastify server and cleans up all resources.
@@ -40,5 +48,33 @@ export declare class FastifyTransport {
40
48
  * @private
41
49
  */
42
50
  private cleanupBundle;
51
+ /**
52
+ * Resolves the session context and generates a cache key for the request.
53
+ * If a session context resolver is configured, it extracts query parameters
54
+ * and merges session-specific context with the base context.
55
+ *
56
+ * @param req - The Fastify request
57
+ * @param clientId - The client identifier
58
+ * @returns Object with cache key and merged context
59
+ * @private
60
+ */
61
+ private resolveSessionContext;
62
+ /**
63
+ * Extracts headers from a Fastify request as a Record.
64
+ * Normalizes header names to lowercase.
65
+ *
66
+ * @param req - The Fastify request
67
+ * @returns Headers as a string record
68
+ * @private
69
+ */
70
+ private extractHeaders;
71
+ /**
72
+ * Extracts query parameters from a Fastify request as a Record.
73
+ *
74
+ * @param req - The Fastify request
75
+ * @returns Query parameters as a string record
76
+ * @private
77
+ */
78
+ private extractQuery;
43
79
  }
44
80
  //# sourceMappingURL=FastifyTransport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FastifyTransport.d.ts","sourceRoot":"","sources":["../../src/http/FastifyTransport.ts"],"names":[],"mappings":"AAAA,OAAgB,EACd,KAAK,eAAe,EAGrB,MAAM,SAAS,CAAC;AAGjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAIxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAGrE,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC9C;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAQtB;IACF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAG3B;IACF,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAS;IAGvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CASzB;gBAGD,cAAc,EAAE,kBAAkB,EAClC,YAAY,EAAE,MAAM;QAAE,MAAM,EAAE,SAAS,CAAC;QAAC,YAAY,EAAE,kBAAkB,CAAA;KAAE,EAC3E,OAAO,GAAE,uBAA4B,EACrC,YAAY,CAAC,EAAE,MAAM;IAgBV,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoMnC;;;OAGG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAYlC;;;;;OAKG;IACH,OAAO,CAAC,aAAa;CAkBtB"}
1
+ {"version":3,"file":"FastifyTransport.d.ts","sourceRoot":"","sources":["../../src/http/FastifyTransport.ts"],"names":[],"mappings":"AAAA,OAAgB,EACd,KAAK,eAAe,EAGrB,MAAM,SAAS,CAAC;AAGjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAExE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAInF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAGrE,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC9C;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,aAAa,CAAC,EAAE,OAAO,KAAK;IAC9D,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,EAAE,kBAAkB,CAAC;CAClC,CAAC;AAEF,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAQtB;IACF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAuB;IACpD,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAyB;IACjE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAU;IACvC,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAS;IAGvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CASzB;gBAGD,cAAc,EAAE,kBAAkB,EAClC,YAAY,EAAE,oBAAoB,EAClC,OAAO,GAAE,uBAA4B,EACrC,YAAY,CAAC,EAAE,MAAM,EACrB,sBAAsB,CAAC,EAAE,sBAAsB,EAC/C,WAAW,CAAC,EAAE,OAAO;IAkBV,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwMnC;;;OAGG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAYlC;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAmBrB;;;;;;;;;OASG;IACH,OAAO,CAAC,qBAAqB;IAqC7B;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAYtB;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;CAYrB"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export { createMcpServer } from './server/createMcpServer.js';
2
2
  export type { CreateMcpServerOptions } from './server/createMcpServer.js';
3
3
  export { createPermissionBasedMcpServer } from './server/createPermissionBasedMcpServer.js';
4
- export type { ToolSetCatalog, ToolSetDefinition, McpToolDefinition, ExposurePolicy, Mode, ModuleLoader, PermissionConfig, CreatePermissionBasedMcpServerOptions, } from './types/index.js';
4
+ export type { ToolSetCatalog, ToolSetDefinition, McpToolDefinition, ExposurePolicy, Mode, ModuleLoader, PermissionConfig, CreatePermissionBasedMcpServerOptions, SessionContextConfig, SessionRequestContext, } from './types/index.js';
5
+ export { SessionContextResolver } from './session/SessionContextResolver.js';
6
+ export type { SessionContextResult } from './session/SessionContextResolver.js';
5
7
  export type { CustomEndpointDefinition, CustomEndpointRequest, PermissionAwareEndpointRequest, CustomEndpointHandler, PermissionAwareEndpointHandler, HttpMethod, EndpointErrorResponse, } from './http/customEndpoints.js';
6
8
  export { defineEndpoint, definePermissionAwareEndpoint, } from './http/customEndpoints.js';
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,YAAY,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAG1E,OAAO,EAAE,8BAA8B,EAAE,MAAM,4CAA4C,CAAC;AAG5F,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,IAAI,EACJ,YAAY,EACZ,gBAAgB,EAChB,qCAAqC,GACtC,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,wBAAwB,EACxB,qBAAqB,EACrB,8BAA8B,EAC9B,qBAAqB,EACrB,8BAA8B,EAC9B,UAAU,EACV,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,cAAc,EACd,6BAA6B,GAC9B,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,YAAY,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAG1E,OAAO,EAAE,8BAA8B,EAAE,MAAM,4CAA4C,CAAC;AAG5F,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,IAAI,EACJ,YAAY,EACZ,gBAAgB,EAChB,qCAAqC,EACrC,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,YAAY,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAGhF,YAAY,EACV,wBAAwB,EACxB,qBAAqB,EACrB,8BAA8B,EAC9B,qBAAqB,EACrB,8BAA8B,EAC9B,UAAU,EACV,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,cAAc,EACd,6BAA6B,GAC9B,MAAM,2BAA2B,CAAC"}