Skip to content

Commit 12ca118

Browse files
authored
Merge pull request #19 from code-rabi/benr/support-session-config
feat: support session context config
2 parents bae42cb + edc1856 commit 12ca118

18 files changed

+2518
-57
lines changed

AGENTS.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ For `enable_toolset`/`disable_toolset`, read `message` to adapt decisions (e.g.,
4343

4444
- `GET /healthz` - Health check
4545
- `GET /tools` - List available toolsets and tools
46-
- `POST /mcp` - MCP JSON-RPC requests
47-
- `GET /mcp` - Server-sent events stream
46+
- `POST /mcp` - MCP JSON-RPC requests (accepts `?config=` query parameter for session context)
47+
- `GET /mcp` - Server-sent events stream (accepts `?config=` query parameter for session context)
4848
- `DELETE /mcp` - Close session
4949
- `GET /.well-known/mcp-config` - Configuration schema
5050

@@ -65,3 +65,7 @@ Check `GET /tools` or server documentation to discover available custom endpoint
6565
- `mcp-client-id`: Client identifier (reuse for per-client sessions)
6666
- `mcp-session-id`: Session identifier (managed by MCP transport after initialize)
6767
- `mcp-toolset-permissions`: Comma-separated toolset list (permission-based servers with header-based permissions)
68+
69+
### Query parameters
70+
71+
- `config`: Base64-encoded JSON containing session-specific context (if server has `sessionContext` enabled). Used for multi-tenant scenarios where each client needs different context values (API tokens, user IDs) passed to module loaders.

CLAUDE.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,23 @@ Two main factory functions in `src/server/`:
7878
- Extends FastifyTransport with permission checking
7979
- Supports header-based or config-based permissions
8080

81+
### Session Context
82+
83+
**SessionContextResolver** (`src/session/SessionContextResolver.ts`)
84+
- Parses query parameter (base64/json encoding)
85+
- Filters allowed keys (whitelist enforcement)
86+
- Merges session context with base context (shallow or deep)
87+
- Generates cache key suffix for session differentiation
88+
8189
### Key Types (`src/types/index.ts`)
8290

8391
- `McpToolDefinition` - Tool with name, description, inputSchema, handler, optional annotations
8492
- `ToolSetDefinition` - Groups tools with name, description, optional modules
8593
- `ToolSetCatalog` - Record of toolset key to definition
8694
- `ExposurePolicy` - Controls maxActiveToolsets, allowlist, denylist, namespacing
8795
- `PermissionConfig` - Header or config-based permission source
96+
- `SessionContextConfig` - Per-session context configuration (query params, encoding, merge strategy)
97+
- `SessionRequestContext` - Request context (clientId, headers, query) for context resolvers
8898

8999
### Meta-tools (DYNAMIC mode)
90100

@@ -98,8 +108,18 @@ Registered in `src/meta/registerMetaTools.ts`:
98108
Tests use Vitest with in-memory mocks. Key patterns:
99109
- Fake MCP server in `tests/helpers/fakes.ts`
100110
- Unit tests alongside integration tests in `tests/`
111+
- E2E tests in `tests/e2e/` for full server/client flows
101112
- Smoke E2E tests in `tests/smoke-e2e/` for manual server/client testing
102113

114+
### Key Test Files
115+
116+
- `tests/sessionContextResolver.test.ts` - Unit tests for SessionContextResolver (parsing, filtering, merging)
117+
- `tests/validateSessionContextConfig.test.ts` - Validation tests for SessionContextConfig
118+
- `tests/sessionContext.integration.test.ts` - Integration tests for session context with HTTP transport
119+
- `tests/e2e/dynamicMode.e2e.test.ts` - E2E tests for DYNAMIC mode and session context
120+
- `tests/e2e/staticMode.e2e.test.ts` - E2E tests for STATIC mode
121+
- `tests/e2e/permissionBased.e2e.test.ts` - E2E tests for permission-based servers
122+
103123
## Build System
104124

105125
- Vite for bundling (`vite.config.ts`)

README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- [Permission-based starter guide](#permission-based-starter-guide)
1212
- [Permission configuration approaches](#permission-configuration-approaches)
1313
- [Custom HTTP endpoints](#custom-http-endpoints)
14+
- [Per-session context](#per-session-context)
1415
- [API](#api)
1516
- [createMcpServer](#createmcpserveroptions)
1617
- [createPermissionBasedMcpServer](#createpermissionbasedmcpserveroptions)
@@ -39,6 +40,7 @@ Toolception addresses this by grouping tools into toolsets and letting you expos
3940
- **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.
4041
- **Task-specific workflows**: You want the client/agent to enable only the tools relevant to the current task.
4142
- **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.
4244
- **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.
4345
- **Collision-safe naming**: You need predictable, namespaced tool names to avoid conflicts.
4446
- **Lazy loading**: Some tools are heavy and should be loaded on demand.
@@ -719,6 +721,88 @@ Custom endpoints cannot override built-in MCP paths:
719721

720722
See `examples/custom-endpoints-demo.ts` for a full working example with GET, POST, PUT, DELETE endpoints, pagination, and permission-aware handlers.
721723

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+
722806
## API
723807

724808
### createMcpServer(options)
@@ -864,6 +948,28 @@ const moduleLoaders = {
864948
};
865949
```
866950

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+
867973
#### options.http (optional)
868974

869975
`{ host?: string; port?: number; basePath?: string; cors?: boolean; logger?: boolean; customEndpoints?: CustomEndpointDefinition[] }`
@@ -970,6 +1076,14 @@ Same as `createMcpServer` - see [options.configSchema](#optionsconfigschema-opti
9701076

9711077
Same as `createMcpServer` - see [options.context](#optionscontext-optional).
9721078

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+
9731087
### Meta-tools
9741088

9751089
Meta-tools are registered based on mode:

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "toolception",
3-
"version": "0.5.5",
3+
"version": "0.6.0",
44
"private": false,
55
"type": "module",
66
"main": "dist/index.js",

0 commit comments

Comments
 (0)