@tmcp/session-manager 0.2.1 → 0.3.0-next.0
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 +27 -3
- package/package.json +13 -3
- package/src/index.js +256 -3
- package/src/types/index.d.ts +59 -2
- package/src/types/index.d.ts.map +7 -2
- package/CHANGELOG.md +0 -43
- package/scripts/abstractify.js +0 -3
- package/tsconfig.json +0 -16
package/README.md
CHANGED
|
@@ -10,10 +10,11 @@ pnpm add @tmcp/session-manager
|
|
|
10
10
|
|
|
11
11
|
## Overview
|
|
12
12
|
|
|
13
|
-
Session management is split into
|
|
13
|
+
Session management is split into three concerns:
|
|
14
14
|
|
|
15
15
|
- **Stream session managers** handle the storage of long-lived streaming connections (SSE/HTTP) and the fan-out of notifications back to the right session.
|
|
16
16
|
- **Info session managers** persist metadata that MCP transports need across requests, such as client capabilities, client info, requested log level, and resource subscriptions.
|
|
17
|
+
- **Subscription managers** route MCP `2026-07-28` per-request change notifications to long-lived `subscriptions/listen` streams.
|
|
17
18
|
|
|
18
19
|
Together they manage:
|
|
19
20
|
|
|
@@ -23,6 +24,7 @@ Together they manage:
|
|
|
23
24
|
- **Message Delivery**: Sending messages to specific sessions or everyone
|
|
24
25
|
- **Client Metadata**: Persisting capabilities, `clientInfo`, and log level between requests
|
|
25
26
|
- **Resource Subscriptions**: Tracking which sessions subscribed to which URIs
|
|
27
|
+
- **Per-Request Subscriptions**: Acknowledging, filtering, ordering, and closing sessionless notification streams
|
|
26
28
|
|
|
27
29
|
## Usage
|
|
28
30
|
|
|
@@ -42,6 +44,8 @@ const sessionManagers = {
|
|
|
42
44
|
};
|
|
43
45
|
```
|
|
44
46
|
|
|
47
|
+
`InMemorySubscriptionManager` is the default for transports that support the per-request protocol. It preserves JSON-RPC ID types, buffers changes until acknowledgement completes, and serializes delivery per subscription.
|
|
48
|
+
|
|
45
49
|
### Custom Session Managers
|
|
46
50
|
|
|
47
51
|
You can implement your own managers by extending the base classes that ship with this package.
|
|
@@ -108,6 +112,10 @@ class CustomInfoSessionManager extends InfoSessionManager {
|
|
|
108
112
|
// Track that the session subscribed to the URI
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
removeSubscription(id, uri) {
|
|
116
|
+
// Stop tracking this resource subscription
|
|
117
|
+
}
|
|
118
|
+
|
|
111
119
|
delete(id) {
|
|
112
120
|
// Remove all metadata for the session (client info, capabilities, subscriptions, etc.)
|
|
113
121
|
}
|
|
@@ -116,6 +124,17 @@ class CustomInfoSessionManager extends InfoSessionManager {
|
|
|
116
124
|
|
|
117
125
|
## API
|
|
118
126
|
|
|
127
|
+
### `SubscriptionManager` (Abstract Base Class)
|
|
128
|
+
|
|
129
|
+
Transport-owned manager for `subscriptions/listen` registrations.
|
|
130
|
+
|
|
131
|
+
- `create(subscription, callbacks)` – atomically register `{ id, origin, filters }` and acknowledge before delivering buffered changes
|
|
132
|
+
- `send(notification)` – route one change to every matching registration
|
|
133
|
+
- `close(id, origin, reason)` – close one registration without conflating numeric and string IDs
|
|
134
|
+
- `closeAll(origin?, reason?)` – close all registrations, optionally for one transport origin
|
|
135
|
+
|
|
136
|
+
Only the descriptor is suitable for persistence. Callback functions remain on the instance serving the response stream; distributed implementations should use pub/sub only to fan notifications out to that instance. Registration and closure stay local to the process that owns the response stream.
|
|
137
|
+
|
|
119
138
|
### `StreamSessionManager` (Abstract Base Class)
|
|
120
139
|
|
|
121
140
|
Responsible for creating and managing streaming controllers.
|
|
@@ -134,6 +153,7 @@ Stores session metadata that needs to survive across HTTP requests or reconnects
|
|
|
134
153
|
- `getLogLevel(id)` / `setLogLevel(id, level)`
|
|
135
154
|
- `getSubscriptions(uri)` – return all session IDs that subscribed to a resource
|
|
136
155
|
- `addSubscription(id, uri)` – record a new resource subscription
|
|
156
|
+
- `removeSubscription(id, uri)` – remove one resource subscription
|
|
137
157
|
- `delete(id)` – remove all metadata for a session when it disconnects
|
|
138
158
|
|
|
139
159
|
### `InMemoryStreamSessionManager` & `InMemoryInfoSessionManager`
|
|
@@ -153,8 +173,12 @@ const sessionManagers = {
|
|
|
153
173
|
info: new InMemoryInfoSessionManager(),
|
|
154
174
|
};
|
|
155
175
|
|
|
156
|
-
const httpTransport = new HttpTransport(server, {
|
|
157
|
-
|
|
176
|
+
const httpTransport = new HttpTransport(server, {
|
|
177
|
+
sessionManager: sessionManagers,
|
|
178
|
+
});
|
|
179
|
+
const sseTransport = new SseTransport(server, {
|
|
180
|
+
sessionManager: sessionManagers,
|
|
181
|
+
});
|
|
158
182
|
```
|
|
159
183
|
|
|
160
184
|
## Related Packages
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmcp/session-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-next.0",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"description": "",
|
|
5
6
|
"main": "src/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"src/"
|
|
9
|
+
],
|
|
6
10
|
"repository": {
|
|
7
11
|
"type": "git",
|
|
8
12
|
"url": "git+https://github.com/paoloricciuti/tmcp.git",
|
|
@@ -24,7 +28,7 @@
|
|
|
24
28
|
},
|
|
25
29
|
"type": "module",
|
|
26
30
|
"peerDependencies": {
|
|
27
|
-
"tmcp": "^1.
|
|
31
|
+
"tmcp": "^1.20.0-next.0 || ^1.20.0"
|
|
28
32
|
},
|
|
29
33
|
"publishConfig": {
|
|
30
34
|
"access": "public"
|
|
@@ -32,10 +36,16 @@
|
|
|
32
36
|
"devDependencies": {
|
|
33
37
|
"dts-buddy": "^0.6.2",
|
|
34
38
|
"publint": "^0.3.12",
|
|
39
|
+
"vitest": "^4.0.0",
|
|
40
|
+
"tmcp": "^1.20.0-next.0",
|
|
35
41
|
"@tmcp/internal-ts-helpers": "^1.0.0"
|
|
36
42
|
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"json-rpc-2.0": "^1.7.1"
|
|
45
|
+
},
|
|
37
46
|
"scripts": {
|
|
38
47
|
"generate:types": "dts-buddy && node scripts/abstractify.js && publint",
|
|
39
|
-
"prepublish": "pnpm generate:types"
|
|
48
|
+
"prepublish": "pnpm generate:types",
|
|
49
|
+
"test": "vitest --run"
|
|
40
50
|
}
|
|
41
51
|
}
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,255 @@
|
|
|
1
1
|
/* eslint-disable no-unused-vars */
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @import { Context } from "tmcp";
|
|
4
|
+
* @import { Context, SubscriptionFilter } from "tmcp";
|
|
5
|
+
* @import { JSONRPCRequest } from "json-rpc-2.0";
|
|
5
6
|
*/
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {{ id: string | number, origin: string, filters: SubscriptionFilter }} Subscription
|
|
10
|
+
* @typedef {{ acknowledge: () => void | Promise<void>, send: (notification: JSONRPCRequest) => void | Promise<void>, close: (reason: 'closed' | 'cancelled') => void | Promise<void> }} SubscriptionCallbacks
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Determine whether a notification belongs on a subscription stream.
|
|
15
|
+
* Distributed managers can use this directly or maintain equivalent indexes.
|
|
16
|
+
* @param {SubscriptionFilter} filters
|
|
17
|
+
* @param {JSONRPCRequest} notification
|
|
18
|
+
*/
|
|
19
|
+
export function matchesSubscription(filters, notification) {
|
|
20
|
+
if (notification.method === 'notifications/tools/list_changed') {
|
|
21
|
+
return filters.toolsListChanged === true;
|
|
22
|
+
}
|
|
23
|
+
if (notification.method === 'notifications/prompts/list_changed') {
|
|
24
|
+
return filters.promptsListChanged === true;
|
|
25
|
+
}
|
|
26
|
+
if (notification.method === 'notifications/resources/list_changed') {
|
|
27
|
+
return filters.resourcesListChanged === true;
|
|
28
|
+
}
|
|
29
|
+
if (notification.method === 'notifications/resources/updated') {
|
|
30
|
+
const uri = /** @type {Record<string, unknown> | undefined} */ (
|
|
31
|
+
notification.params
|
|
32
|
+
)?.uri;
|
|
33
|
+
return (
|
|
34
|
+
typeof uri === 'string' &&
|
|
35
|
+
filters.resourceSubscriptions?.includes(uri) === true
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Routes notifications to long-lived per-request subscription streams.
|
|
43
|
+
* Callbacks remain local to the process serving the response stream. Brokered
|
|
44
|
+
* implementations can distribute notifications without persisting descriptors.
|
|
45
|
+
* @abstract
|
|
46
|
+
*/
|
|
47
|
+
export class SubscriptionManager {
|
|
48
|
+
/**
|
|
49
|
+
* @abstract
|
|
50
|
+
* Atomically register a subscription. Matching notifications must be
|
|
51
|
+
* buffered until acknowledgement completes and then delivered in order.
|
|
52
|
+
* @param {Subscription} subscription
|
|
53
|
+
* @param {SubscriptionCallbacks} callbacks
|
|
54
|
+
* @returns {boolean | Promise<boolean>}
|
|
55
|
+
*/
|
|
56
|
+
create(subscription, callbacks) {
|
|
57
|
+
void subscription;
|
|
58
|
+
void callbacks;
|
|
59
|
+
throw new Error('Method not implemented.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @abstract
|
|
64
|
+
* @param {JSONRPCRequest} notification
|
|
65
|
+
* @returns {void | Promise<void>}
|
|
66
|
+
*/
|
|
67
|
+
send(notification) {
|
|
68
|
+
void notification;
|
|
69
|
+
throw new Error('Method not implemented.');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @abstract
|
|
74
|
+
* Request closure of one subscription. Implementations must preserve the
|
|
75
|
+
* JSON-RPC ID type when identifying a registration.
|
|
76
|
+
* @param {string | number} id
|
|
77
|
+
* @param {string} origin
|
|
78
|
+
* @param {'closed' | 'cancelled'} reason
|
|
79
|
+
* @returns {boolean | Promise<boolean>}
|
|
80
|
+
*/
|
|
81
|
+
close(id, origin, reason) {
|
|
82
|
+
void id;
|
|
83
|
+
void origin;
|
|
84
|
+
void reason;
|
|
85
|
+
throw new Error('Method not implemented.');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @abstract
|
|
90
|
+
* Close every subscription, optionally limited to one transport origin.
|
|
91
|
+
* @param {string} [origin]
|
|
92
|
+
* @param {'closed' | 'cancelled'} [reason]
|
|
93
|
+
* @returns {void | Promise<void>}
|
|
94
|
+
*/
|
|
95
|
+
closeAll(origin, reason = 'cancelled') {
|
|
96
|
+
void origin;
|
|
97
|
+
void reason;
|
|
98
|
+
throw new Error('Method not implemented.');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Process-local subscription manager. Distributed implementations should
|
|
104
|
+
* mirror its acknowledgement buffering and per-subscription ordering.
|
|
105
|
+
*/
|
|
106
|
+
export class InMemorySubscriptionManager extends SubscriptionManager {
|
|
107
|
+
/** @type {Map<string, Map<string | number, { subscription: Subscription, callbacks: SubscriptionCallbacks, queue: Promise<'active' | 'failed'>, close_promise?: Promise<boolean> }>>} */
|
|
108
|
+
#subscriptions = new Map();
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {{ subscription: Subscription, callbacks: SubscriptionCallbacks, queue: Promise<'active' | 'failed'>, close_promise?: Promise<boolean> }} record
|
|
112
|
+
* @param {JSONRPCRequest} notification
|
|
113
|
+
*/
|
|
114
|
+
#enqueue(record, notification) {
|
|
115
|
+
const delivery = record.queue.then(async (state) => {
|
|
116
|
+
if (state === 'failed' || record.close_promise) return state;
|
|
117
|
+
await record.callbacks.send(notification);
|
|
118
|
+
return /** @type {const} */ ('active');
|
|
119
|
+
});
|
|
120
|
+
// A failed send rejects its caller without poisoning later queue entries.
|
|
121
|
+
record.queue = delivery.catch(() => 'active');
|
|
122
|
+
return delivery.then(() => {});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {Subscription} subscription
|
|
127
|
+
* @param {SubscriptionCallbacks} callbacks
|
|
128
|
+
* @returns {Promise<boolean>}
|
|
129
|
+
*/
|
|
130
|
+
async create(subscription, callbacks) {
|
|
131
|
+
let subscriptions = this.#subscriptions.get(subscription.origin);
|
|
132
|
+
if (!subscriptions) {
|
|
133
|
+
subscriptions = new Map();
|
|
134
|
+
this.#subscriptions.set(subscription.origin, subscriptions);
|
|
135
|
+
}
|
|
136
|
+
if (subscriptions.has(subscription.id)) return false;
|
|
137
|
+
/** @type {(value?: void) => void} */
|
|
138
|
+
let resolve_acknowledgement = () => {};
|
|
139
|
+
/** @type {(reason?: unknown) => void} */
|
|
140
|
+
let reject_acknowledgement = () => {};
|
|
141
|
+
const acknowledge_promise = new Promise((resolve, reject) => {
|
|
142
|
+
resolve_acknowledgement = resolve;
|
|
143
|
+
reject_acknowledgement = reject;
|
|
144
|
+
});
|
|
145
|
+
/** @type {{ subscription: Subscription, callbacks: SubscriptionCallbacks, queue: Promise<'active' | 'failed'>, close_promise?: Promise<boolean> }} */
|
|
146
|
+
const record = {
|
|
147
|
+
subscription,
|
|
148
|
+
callbacks,
|
|
149
|
+
// Starting behind acknowledgement replaces both active and pending state.
|
|
150
|
+
queue: acknowledge_promise.then(
|
|
151
|
+
() => /** @type {const} */ ('active'),
|
|
152
|
+
() => /** @type {const} */ ('failed'),
|
|
153
|
+
),
|
|
154
|
+
};
|
|
155
|
+
subscriptions.set(subscription.id, record);
|
|
156
|
+
try {
|
|
157
|
+
Promise.resolve(callbacks.acknowledge()).then(
|
|
158
|
+
resolve_acknowledgement,
|
|
159
|
+
reject_acknowledgement,
|
|
160
|
+
);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
reject_acknowledgement(error);
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
await acknowledge_promise;
|
|
166
|
+
if (record.close_promise) return true;
|
|
167
|
+
await record.queue;
|
|
168
|
+
return true;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (!record.close_promise) {
|
|
171
|
+
subscriptions.delete(subscription.id);
|
|
172
|
+
if (subscriptions.size === 0) {
|
|
173
|
+
this.#subscriptions.delete(subscription.origin);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {JSONRPCRequest} notification
|
|
182
|
+
* @returns {Promise<void>}
|
|
183
|
+
*/
|
|
184
|
+
async send(notification) {
|
|
185
|
+
/** @type {Promise<void>[]} */
|
|
186
|
+
const deliveries = [];
|
|
187
|
+
for (const subscriptions of this.#subscriptions.values()) {
|
|
188
|
+
for (const record of subscriptions.values()) {
|
|
189
|
+
if (
|
|
190
|
+
record.close_promise ||
|
|
191
|
+
!matchesSubscription(
|
|
192
|
+
record.subscription.filters,
|
|
193
|
+
notification,
|
|
194
|
+
)
|
|
195
|
+
) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
deliveries.push(this.#enqueue(record, notification));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
await Promise.all(deliveries);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @param {string | number} id
|
|
206
|
+
* @param {string} origin
|
|
207
|
+
* @param {'closed' | 'cancelled'} reason
|
|
208
|
+
* @returns {Promise<boolean>}
|
|
209
|
+
*/
|
|
210
|
+
async close(id, origin, reason) {
|
|
211
|
+
const subscriptions = this.#subscriptions.get(origin);
|
|
212
|
+
if (!subscriptions) return false;
|
|
213
|
+
const record = subscriptions.get(id);
|
|
214
|
+
if (!record) return false;
|
|
215
|
+
if (record.close_promise) return record.close_promise;
|
|
216
|
+
|
|
217
|
+
record.close_promise = (async () => {
|
|
218
|
+
try {
|
|
219
|
+
await record.queue;
|
|
220
|
+
await record.callbacks.close(reason);
|
|
221
|
+
return true;
|
|
222
|
+
} finally {
|
|
223
|
+
if (subscriptions.get(id) === record) subscriptions.delete(id);
|
|
224
|
+
if (subscriptions.size === 0)
|
|
225
|
+
this.#subscriptions.delete(origin);
|
|
226
|
+
}
|
|
227
|
+
})();
|
|
228
|
+
return record.close_promise;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @param {string} [origin]
|
|
233
|
+
* @param {'closed' | 'cancelled'} [reason]
|
|
234
|
+
* @returns {Promise<void>}
|
|
235
|
+
*/
|
|
236
|
+
async closeAll(origin, reason = 'cancelled') {
|
|
237
|
+
/** @type {Array<[string | number, string]>} */
|
|
238
|
+
const registrations = [];
|
|
239
|
+
for (const [stored_origin, subscriptions] of this.#subscriptions) {
|
|
240
|
+
if (origin !== undefined && stored_origin !== origin) continue;
|
|
241
|
+
for (const id of subscriptions.keys()) {
|
|
242
|
+
registrations.push([id, stored_origin]);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
await Promise.all(
|
|
246
|
+
registrations.map(([id, stored_origin]) =>
|
|
247
|
+
this.close(id, stored_origin, reason),
|
|
248
|
+
),
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
7
253
|
/**
|
|
8
254
|
* @abstract
|
|
9
255
|
*/
|
|
@@ -92,7 +338,11 @@ export class InMemoryStreamSessionManager extends StreamSessionManager {
|
|
|
92
338
|
send(sessions, data) {
|
|
93
339
|
for (const [id, controller] of this.#sessions.entries()) {
|
|
94
340
|
if (sessions == null || sessions.includes(id)) {
|
|
95
|
-
|
|
341
|
+
try {
|
|
342
|
+
controller.enqueue(this.#text_encoder.encode(data));
|
|
343
|
+
} catch {
|
|
344
|
+
this.#sessions.delete(id);
|
|
345
|
+
}
|
|
96
346
|
}
|
|
97
347
|
}
|
|
98
348
|
}
|
|
@@ -301,7 +551,10 @@ export class InMemoryInfoSessionManager extends InfoSessionManager {
|
|
|
301
551
|
* @type {InfoSessionManager["delete"]}
|
|
302
552
|
*/
|
|
303
553
|
delete(id) {
|
|
304
|
-
this.#subscriptions
|
|
554
|
+
for (const [uri, subscriptions] of this.#subscriptions) {
|
|
555
|
+
subscriptions.delete(id);
|
|
556
|
+
if (subscriptions.size === 0) this.#subscriptions.delete(uri);
|
|
557
|
+
}
|
|
305
558
|
this.#log_level.delete(id);
|
|
306
559
|
this.#client_capabilities.delete(id);
|
|
307
560
|
this.#client_info.delete(id);
|
package/src/types/index.d.ts
CHANGED
|
@@ -1,8 +1,55 @@
|
|
|
1
1
|
declare module '@tmcp/session-manager' {
|
|
2
|
-
import type { Context } from 'tmcp';
|
|
2
|
+
import type { SubscriptionFilter, Context } from 'tmcp';
|
|
3
|
+
import type { JSONRPCRequest } from 'json-rpc-2.0';
|
|
3
4
|
/**
|
|
4
|
-
* @import { Context } from "tmcp";
|
|
5
|
+
* @import { Context, SubscriptionFilter } from "tmcp";
|
|
6
|
+
* @import { JSONRPCRequest } from "json-rpc-2.0";
|
|
5
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Determine whether a notification belongs on a subscription stream.
|
|
10
|
+
* Distributed managers can use this directly or maintain equivalent indexes.
|
|
11
|
+
* */
|
|
12
|
+
export function matchesSubscription(filters: SubscriptionFilter, notification: JSONRPCRequest): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Routes notifications to long-lived per-request subscription streams.
|
|
15
|
+
* Callbacks remain local to the process serving the response stream. Brokered
|
|
16
|
+
* implementations can distribute notifications without persisting descriptors.
|
|
17
|
+
* @abstract
|
|
18
|
+
*/
|
|
19
|
+
export abstract class SubscriptionManager {
|
|
20
|
+
/**
|
|
21
|
+
* @abstract
|
|
22
|
+
* Atomically register a subscription. Matching notifications must be
|
|
23
|
+
* buffered until acknowledgement completes and then delivered in order.
|
|
24
|
+
* */
|
|
25
|
+
abstract create(subscription: Subscription, callbacks: SubscriptionCallbacks): boolean | Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* @abstract
|
|
28
|
+
* */
|
|
29
|
+
abstract send(notification: JSONRPCRequest): void | Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* @abstract
|
|
32
|
+
* Request closure of one subscription. Implementations must preserve the
|
|
33
|
+
* JSON-RPC ID type when identifying a registration.
|
|
34
|
+
* */
|
|
35
|
+
abstract close(id: string | number, origin: string, reason: "closed" | "cancelled"): boolean | Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* @abstract
|
|
38
|
+
* Close every subscription, optionally limited to one transport origin.
|
|
39
|
+
* */
|
|
40
|
+
abstract closeAll(origin?: string, reason?: "closed" | "cancelled"): void | Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Process-local subscription manager. Distributed implementations should
|
|
44
|
+
* mirror its acknowledgement buffering and per-subscription ordering.
|
|
45
|
+
*/
|
|
46
|
+
export class InMemorySubscriptionManager extends SubscriptionManager {
|
|
47
|
+
create(subscription: Subscription, callbacks: SubscriptionCallbacks): Promise<boolean>;
|
|
48
|
+
send(notification: JSONRPCRequest): Promise<void>;
|
|
49
|
+
close(id: string | number, origin: string, reason: "closed" | "cancelled"): Promise<boolean>;
|
|
50
|
+
closeAll(origin?: string, reason?: "closed" | "cancelled"): Promise<void>;
|
|
51
|
+
#private;
|
|
52
|
+
}
|
|
6
53
|
/**
|
|
7
54
|
* @abstract
|
|
8
55
|
*/
|
|
@@ -79,6 +126,16 @@ declare module '@tmcp/session-manager' {
|
|
|
79
126
|
export class InMemoryInfoSessionManager extends InfoSessionManager {
|
|
80
127
|
#private;
|
|
81
128
|
}
|
|
129
|
+
export type Subscription = {
|
|
130
|
+
id: string | number;
|
|
131
|
+
origin: string;
|
|
132
|
+
filters: SubscriptionFilter;
|
|
133
|
+
};
|
|
134
|
+
export type SubscriptionCallbacks = {
|
|
135
|
+
acknowledge: () => void | Promise<void>;
|
|
136
|
+
send: (notification: JSONRPCRequest) => void | Promise<void>;
|
|
137
|
+
close: (reason: "closed" | "cancelled") => void | Promise<void>;
|
|
138
|
+
};
|
|
82
139
|
export {};
|
|
83
140
|
}
|
|
84
141
|
//# sourceMappingURL=index.d.ts.map
|
package/src/types/index.d.ts.map
CHANGED
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"file": "index.d.ts",
|
|
4
4
|
"names": [
|
|
5
|
+
"matchesSubscription",
|
|
6
|
+
"SubscriptionManager",
|
|
7
|
+
"InMemorySubscriptionManager",
|
|
5
8
|
"StreamSessionManager",
|
|
6
9
|
"InMemoryStreamSessionManager",
|
|
7
10
|
"InfoSessionManager",
|
|
8
|
-
"InMemoryInfoSessionManager"
|
|
11
|
+
"InMemoryInfoSessionManager",
|
|
12
|
+
"Subscription",
|
|
13
|
+
"SubscriptionCallbacks"
|
|
9
14
|
],
|
|
10
15
|
"sources": [
|
|
11
16
|
"../index.js"
|
|
@@ -13,6 +18,6 @@
|
|
|
13
18
|
"sourcesContent": [
|
|
14
19
|
null
|
|
15
20
|
],
|
|
16
|
-
"mappings": "
|
|
21
|
+
"mappings": ";;;;;;;;;;;;iBAkBgBA,mBAAmBA;;;;;;;cA4BtBC,mBAAmBA;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2DnBC,2BAA2BA;;;;;;;;;;;;;;cAsJ3BC,oBAAoBA;;;;;;;;;;;;;;;;;;cAwCpBC,4BAA4BA;;;;;;;;;;;;;;cA0D5BC,kBAAkBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2FlBC,0BAA0BA;;;aApb4CC,YAAYA;;;;;aAC0FC,qBAAqBA",
|
|
17
22
|
"ignoreList": []
|
|
18
23
|
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# @tmcp/session-manager
|
|
2
|
-
|
|
3
|
-
## 0.2.1
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- 5613d43: fix: handle `resources/unsubscribe`
|
|
8
|
-
- Updated dependencies [5613d43]
|
|
9
|
-
- tmcp@1.16.3
|
|
10
|
-
|
|
11
|
-
## 0.2.0
|
|
12
|
-
|
|
13
|
-
### Minor Changes
|
|
14
|
-
|
|
15
|
-
- 8a04ee2: breaking: move sessions out of core into the transports and allow for persistent mcp state
|
|
16
|
-
|
|
17
|
-
This release moves the session management out of the core package into the SSE and HTTP transport separately.
|
|
18
|
-
While technically a breaking change if you update both `tmcp` and your transport (`@tmcp/transport-http`,
|
|
19
|
-
`@tmcp/transport-sse`, or `@tmcp/transport-stdio`), you will not face a breaking change unless you were using a
|
|
20
|
-
session manager.
|
|
21
|
-
|
|
22
|
-
If you were testing your `McpServer` instance manually you might need to update them to pass the `sessionInfo`
|
|
23
|
-
in the context parameter (only if you were reading them in the tool/resource/prompt).
|
|
24
|
-
|
|
25
|
-
Sorry for the "breaking" but this was a necessary step to unlock persistent state. 🧡
|
|
26
|
-
|
|
27
|
-
### Patch Changes
|
|
28
|
-
|
|
29
|
-
- Updated dependencies [8a04ee2]
|
|
30
|
-
- Updated dependencies [a9254cb]
|
|
31
|
-
- tmcp@1.16.0
|
|
32
|
-
|
|
33
|
-
## 0.1.2
|
|
34
|
-
|
|
35
|
-
### Patch Changes
|
|
36
|
-
|
|
37
|
-
- c0f7d7f: fix: try catch closing controller in case it's already closed
|
|
38
|
-
|
|
39
|
-
## 0.1.1
|
|
40
|
-
|
|
41
|
-
### Patch Changes
|
|
42
|
-
|
|
43
|
-
- 957805b: fix: await session manager calls
|
package/scripts/abstractify.js
DELETED
package/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "esnext",
|
|
4
|
-
"module": "nodenext",
|
|
5
|
-
"moduleResolution": "nodenext",
|
|
6
|
-
"allowJs": true,
|
|
7
|
-
"checkJs": true,
|
|
8
|
-
"declaration": true,
|
|
9
|
-
"emitDeclarationOnly": false,
|
|
10
|
-
"strict": true,
|
|
11
|
-
"noEmit": true,
|
|
12
|
-
"skipLibCheck": true
|
|
13
|
-
},
|
|
14
|
-
"include": ["src/**/*"],
|
|
15
|
-
"exclude": ["node_modules"]
|
|
16
|
-
}
|