@mcp-ts/sdk 1.0.0 → 1.1.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 +25 -13
- package/dist/adapters/agui-adapter.d.mts +21 -44
- package/dist/adapters/agui-adapter.d.ts +21 -44
- package/dist/adapters/agui-adapter.js +93 -67
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +93 -68
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +32 -134
- package/dist/adapters/agui-middleware.d.ts +32 -134
- package/dist/adapters/agui-middleware.js +314 -350
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs +314 -351
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/adapters/ai-adapter.d.mts +2 -2
- package/dist/adapters/ai-adapter.d.ts +2 -2
- package/dist/adapters/langchain-adapter.d.mts +2 -2
- package/dist/adapters/langchain-adapter.d.ts +2 -2
- package/dist/adapters/mastra-adapter.d.mts +2 -2
- package/dist/adapters/mastra-adapter.d.ts +2 -2
- package/dist/client/index.d.mts +184 -57
- package/dist/client/index.d.ts +184 -57
- package/dist/client/index.js +535 -130
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +535 -131
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +40 -6
- package/dist/client/react.d.ts +40 -6
- package/dist/client/react.js +587 -142
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +586 -143
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +5 -5
- package/dist/client/vue.d.ts +5 -5
- package/dist/client/vue.js +545 -140
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +545 -141
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{events-BP6WyRNh.d.mts → events-BgeztGYZ.d.mts} +12 -1
- package/dist/{events-BP6WyRNh.d.ts → events-BgeztGYZ.d.ts} +12 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +779 -248
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +775 -245
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-DMF3ED2O.d.mts → multi-session-client-CxogNckF.d.mts} +1 -1
- package/dist/{multi-session-client-BOFgPypS.d.ts → multi-session-client-cox_WXUj.d.ts} +1 -1
- package/dist/server/index.d.mts +44 -40
- package/dist/server/index.d.ts +44 -40
- package/dist/server/index.js +242 -116
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +238 -112
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +2 -2
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{types-SbDlA2VX.d.mts → types-CLccx9wW.d.mts} +1 -1
- package/dist/{types-SbDlA2VX.d.ts → types-CLccx9wW.d.ts} +1 -1
- package/package.json +8 -1
- package/src/adapters/agui-adapter.ts +121 -107
- package/src/adapters/agui-middleware.ts +474 -512
- package/src/client/core/app-host.ts +417 -0
- package/src/client/core/sse-client.ts +365 -212
- package/src/client/core/types.ts +31 -0
- package/src/client/index.ts +1 -0
- package/src/client/react/index.ts +1 -0
- package/src/client/react/use-mcp-app.ts +73 -0
- package/src/client/react/useMcp.ts +18 -0
- package/src/server/handlers/nextjs-handler.ts +8 -7
- package/src/server/handlers/sse-handler.ts +131 -164
- package/src/server/mcp/oauth-client.ts +32 -2
- package/src/server/storage/index.ts +17 -1
- package/src/server/storage/sqlite-backend.ts +185 -0
- package/src/server/storage/types.ts +1 -1
- package/src/shared/events.ts +12 -0
- package/src/shared/types.ts +4 -2
package/dist/server/index.js
CHANGED
|
@@ -6,7 +6,7 @@ var streamableHttp_js = require('@modelcontextprotocol/sdk/client/streamableHttp
|
|
|
6
6
|
var sse_js = require('@modelcontextprotocol/sdk/client/sse.js');
|
|
7
7
|
var auth_js = require('@modelcontextprotocol/sdk/client/auth.js');
|
|
8
8
|
var types_js = require('@modelcontextprotocol/sdk/types.js');
|
|
9
|
-
var
|
|
9
|
+
var fs2 = require('fs');
|
|
10
10
|
var path = require('path');
|
|
11
11
|
|
|
12
12
|
function _interopNamespace(e) {
|
|
@@ -27,6 +27,7 @@ function _interopNamespace(e) {
|
|
|
27
27
|
return Object.freeze(n);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
|
|
30
31
|
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
31
32
|
|
|
32
33
|
var __defProp = Object.defineProperty;
|
|
@@ -424,8 +425,8 @@ var FileStorageBackend = class {
|
|
|
424
425
|
if (this.initialized) return;
|
|
425
426
|
try {
|
|
426
427
|
const dir = path__namespace.dirname(this.filePath);
|
|
427
|
-
await
|
|
428
|
-
const data = await
|
|
428
|
+
await fs2.promises.mkdir(dir, { recursive: true });
|
|
429
|
+
const data = await fs2.promises.readFile(this.filePath, "utf-8");
|
|
429
430
|
const json = JSON.parse(data);
|
|
430
431
|
this.memoryCache = /* @__PURE__ */ new Map();
|
|
431
432
|
if (Array.isArray(json)) {
|
|
@@ -450,7 +451,7 @@ var FileStorageBackend = class {
|
|
|
450
451
|
async flush() {
|
|
451
452
|
if (!this.memoryCache) return;
|
|
452
453
|
const sessions = Array.from(this.memoryCache.values());
|
|
453
|
-
await
|
|
454
|
+
await fs2.promises.writeFile(this.filePath, JSON.stringify(sessions, null, 2), "utf-8");
|
|
454
455
|
}
|
|
455
456
|
getSessionKey(identity, sessionId) {
|
|
456
457
|
return `${identity}:${sessionId}`;
|
|
@@ -519,6 +520,148 @@ var FileStorageBackend = class {
|
|
|
519
520
|
async disconnect() {
|
|
520
521
|
}
|
|
521
522
|
};
|
|
523
|
+
var SqliteStorage = class {
|
|
524
|
+
constructor(options = {}) {
|
|
525
|
+
__publicField(this, "db", null);
|
|
526
|
+
__publicField(this, "table");
|
|
527
|
+
__publicField(this, "initialized", false);
|
|
528
|
+
__publicField(this, "dbPath");
|
|
529
|
+
this.dbPath = options.path || "./sessions.db";
|
|
530
|
+
this.table = options.table || "mcp_sessions";
|
|
531
|
+
}
|
|
532
|
+
async init() {
|
|
533
|
+
if (this.initialized) return;
|
|
534
|
+
try {
|
|
535
|
+
const DatabaseConstructor = (await import('better-sqlite3')).default;
|
|
536
|
+
const dir = path__namespace.dirname(this.dbPath);
|
|
537
|
+
if (!fs2__namespace.existsSync(dir)) {
|
|
538
|
+
fs2__namespace.mkdirSync(dir, { recursive: true });
|
|
539
|
+
}
|
|
540
|
+
this.db = new DatabaseConstructor(this.dbPath);
|
|
541
|
+
this.db.exec(`
|
|
542
|
+
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
543
|
+
sessionId TEXT PRIMARY KEY,
|
|
544
|
+
identity TEXT NOT NULL,
|
|
545
|
+
data TEXT NOT NULL,
|
|
546
|
+
expiresAt INTEGER
|
|
547
|
+
);
|
|
548
|
+
CREATE INDEX IF NOT EXISTS idx_${this.table}_identity ON ${this.table}(identity);
|
|
549
|
+
`);
|
|
550
|
+
this.initialized = true;
|
|
551
|
+
} catch (error) {
|
|
552
|
+
if (error.code === "MODULE_NOT_FOUND" || error.message?.includes("better-sqlite3")) {
|
|
553
|
+
throw new Error(
|
|
554
|
+
"better-sqlite3 is not installed. Please install it with: npm install better-sqlite3"
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
ensureInitialized() {
|
|
561
|
+
if (!this.initialized) {
|
|
562
|
+
throw new Error("SqliteStorage not initialized. Call init() first.");
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
generateSessionId() {
|
|
566
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
567
|
+
let result = "";
|
|
568
|
+
for (let i = 0; i < 32; i++) {
|
|
569
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
570
|
+
}
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
async createSession(session, ttl) {
|
|
574
|
+
this.ensureInitialized();
|
|
575
|
+
const { sessionId, identity } = session;
|
|
576
|
+
if (!sessionId || !identity) {
|
|
577
|
+
throw new Error("identity and sessionId required");
|
|
578
|
+
}
|
|
579
|
+
const expiresAt = ttl ? Date.now() + ttl * 1e3 : null;
|
|
580
|
+
try {
|
|
581
|
+
const stmt = this.db.prepare(
|
|
582
|
+
`INSERT INTO ${this.table} (sessionId, identity, data, expiresAt) VALUES (?, ?, ?, ?)`
|
|
583
|
+
);
|
|
584
|
+
stmt.run(sessionId, identity, JSON.stringify(session), expiresAt);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
587
|
+
throw new Error(`Session ${sessionId} already exists`);
|
|
588
|
+
}
|
|
589
|
+
throw error;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async updateSession(identity, sessionId, data, ttl) {
|
|
593
|
+
this.ensureInitialized();
|
|
594
|
+
if (!sessionId || !identity) {
|
|
595
|
+
throw new Error("identity and sessionId required");
|
|
596
|
+
}
|
|
597
|
+
const currentSession = await this.getSession(identity, sessionId);
|
|
598
|
+
if (!currentSession) {
|
|
599
|
+
throw new Error(`Session ${sessionId} not found for identity ${identity}`);
|
|
600
|
+
}
|
|
601
|
+
const updatedSession = { ...currentSession, ...data };
|
|
602
|
+
const expiresAt = ttl ? Date.now() + ttl * 1e3 : null;
|
|
603
|
+
const stmt = this.db.prepare(
|
|
604
|
+
`UPDATE ${this.table} SET data = ?, expiresAt = ? WHERE sessionId = ? AND identity = ?`
|
|
605
|
+
);
|
|
606
|
+
stmt.run(JSON.stringify(updatedSession), expiresAt, sessionId, identity);
|
|
607
|
+
}
|
|
608
|
+
async getSession(identity, sessionId) {
|
|
609
|
+
this.ensureInitialized();
|
|
610
|
+
const stmt = this.db.prepare(
|
|
611
|
+
`SELECT data FROM ${this.table} WHERE sessionId = ? AND identity = ?`
|
|
612
|
+
);
|
|
613
|
+
const row = stmt.get(sessionId, identity);
|
|
614
|
+
if (!row) return null;
|
|
615
|
+
return JSON.parse(row.data);
|
|
616
|
+
}
|
|
617
|
+
async getIdentitySessionsData(identity) {
|
|
618
|
+
this.ensureInitialized();
|
|
619
|
+
const stmt = this.db.prepare(
|
|
620
|
+
`SELECT data FROM ${this.table} WHERE identity = ?`
|
|
621
|
+
);
|
|
622
|
+
const rows = stmt.all(identity);
|
|
623
|
+
return rows.map((row) => JSON.parse(row.data));
|
|
624
|
+
}
|
|
625
|
+
async getIdentityMcpSessions(identity) {
|
|
626
|
+
this.ensureInitialized();
|
|
627
|
+
const stmt = this.db.prepare(
|
|
628
|
+
`SELECT sessionId FROM ${this.table} WHERE identity = ?`
|
|
629
|
+
);
|
|
630
|
+
const rows = stmt.all(identity);
|
|
631
|
+
return rows.map((row) => row.sessionId);
|
|
632
|
+
}
|
|
633
|
+
async removeSession(identity, sessionId) {
|
|
634
|
+
this.ensureInitialized();
|
|
635
|
+
const stmt = this.db.prepare(
|
|
636
|
+
`DELETE FROM ${this.table} WHERE sessionId = ? AND identity = ?`
|
|
637
|
+
);
|
|
638
|
+
stmt.run(sessionId, identity);
|
|
639
|
+
}
|
|
640
|
+
async getAllSessionIds() {
|
|
641
|
+
this.ensureInitialized();
|
|
642
|
+
const stmt = this.db.prepare(`SELECT sessionId FROM ${this.table}`);
|
|
643
|
+
const rows = stmt.all();
|
|
644
|
+
return rows.map((row) => row.sessionId);
|
|
645
|
+
}
|
|
646
|
+
async clearAll() {
|
|
647
|
+
this.ensureInitialized();
|
|
648
|
+
const stmt = this.db.prepare(`DELETE FROM ${this.table}`);
|
|
649
|
+
stmt.run();
|
|
650
|
+
}
|
|
651
|
+
async cleanupExpiredSessions() {
|
|
652
|
+
this.ensureInitialized();
|
|
653
|
+
const now = Date.now();
|
|
654
|
+
const stmt = this.db.prepare(
|
|
655
|
+
`DELETE FROM ${this.table} WHERE expiresAt IS NOT NULL AND expiresAt < ?`
|
|
656
|
+
);
|
|
657
|
+
stmt.run(now);
|
|
658
|
+
}
|
|
659
|
+
async disconnect() {
|
|
660
|
+
if (this.db) {
|
|
661
|
+
this.db.close();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
};
|
|
522
665
|
|
|
523
666
|
// src/server/storage/index.ts
|
|
524
667
|
var storageInstance = null;
|
|
@@ -550,6 +693,13 @@ async function createStorage() {
|
|
|
550
693
|
store.init().catch((err) => console.error("[Storage] Failed to initialize file storage:", err));
|
|
551
694
|
return store;
|
|
552
695
|
}
|
|
696
|
+
if (type === "sqlite") {
|
|
697
|
+
const dbPath = process.env.MCP_TS_STORAGE_SQLITE_PATH;
|
|
698
|
+
console.log(`[Storage] Using SQLite storage (${dbPath || "default"}) (Explicit)`);
|
|
699
|
+
const store = new SqliteStorage({ path: dbPath });
|
|
700
|
+
store.init().catch((err) => console.error("[Storage] Failed to initialize SQLite storage:", err));
|
|
701
|
+
return store;
|
|
702
|
+
}
|
|
553
703
|
if (type === "memory") {
|
|
554
704
|
console.log("[Storage] Using In-Memory storage (Explicit)");
|
|
555
705
|
return new MemoryStorageBackend();
|
|
@@ -572,6 +722,12 @@ async function createStorage() {
|
|
|
572
722
|
store.init().catch((err) => console.error("[Storage] Failed to initialize file storage:", err));
|
|
573
723
|
return store;
|
|
574
724
|
}
|
|
725
|
+
if (process.env.MCP_TS_STORAGE_SQLITE_PATH) {
|
|
726
|
+
console.log(`[Storage] Auto-detected MCP_TS_STORAGE_SQLITE_PATH. Using SQLite storage (${process.env.MCP_TS_STORAGE_SQLITE_PATH}).`);
|
|
727
|
+
const store = new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH });
|
|
728
|
+
store.init().catch((err) => console.error("[Storage] Failed to initialize SQLite storage:", err));
|
|
729
|
+
return store;
|
|
730
|
+
}
|
|
575
731
|
console.log("[Storage] No storage configured. Using In-Memory storage (Default).");
|
|
576
732
|
return new MemoryStorageBackend();
|
|
577
733
|
}
|
|
@@ -1079,7 +1235,15 @@ var MCPClient = class _MCPClient {
|
|
|
1079
1235
|
name: "mcp-ts-oauth-client",
|
|
1080
1236
|
version: "2.0"
|
|
1081
1237
|
},
|
|
1082
|
-
{
|
|
1238
|
+
{
|
|
1239
|
+
capabilities: {
|
|
1240
|
+
extensions: {
|
|
1241
|
+
"io.modelcontextprotocol/ui": {
|
|
1242
|
+
mimeTypes: ["text/html+mcp"]
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1083
1247
|
);
|
|
1084
1248
|
}
|
|
1085
1249
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
@@ -1261,7 +1425,15 @@ var MCPClient = class _MCPClient {
|
|
|
1261
1425
|
name: "mcp-ts-oauth-client",
|
|
1262
1426
|
version: "2.0"
|
|
1263
1427
|
},
|
|
1264
|
-
{
|
|
1428
|
+
{
|
|
1429
|
+
capabilities: {
|
|
1430
|
+
extensions: {
|
|
1431
|
+
"io.modelcontextprotocol/ui": {
|
|
1432
|
+
mimeTypes: ["text/html+mcp"]
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1265
1437
|
);
|
|
1266
1438
|
this.emitStateChange("CONNECTING");
|
|
1267
1439
|
await this.client.connect(this.transport);
|
|
@@ -1817,6 +1989,7 @@ var MultiSessionClient = class {
|
|
|
1817
1989
|
};
|
|
1818
1990
|
|
|
1819
1991
|
// src/server/handlers/sse-handler.ts
|
|
1992
|
+
var DEFAULT_HEARTBEAT_INTERVAL = 3e4;
|
|
1820
1993
|
var SSEConnectionManager = class {
|
|
1821
1994
|
constructor(options, sendEvent) {
|
|
1822
1995
|
this.options = options;
|
|
@@ -1846,7 +2019,7 @@ var SSEConnectionManager = class {
|
|
|
1846
2019
|
* Start heartbeat to keep connection alive
|
|
1847
2020
|
*/
|
|
1848
2021
|
startHeartbeat() {
|
|
1849
|
-
const interval = this.options.heartbeatInterval
|
|
2022
|
+
const interval = this.options.heartbeatInterval ?? DEFAULT_HEARTBEAT_INTERVAL;
|
|
1850
2023
|
this.heartbeatTimer = setInterval(() => {
|
|
1851
2024
|
if (this.isActive) {
|
|
1852
2025
|
this.sendEvent({
|
|
@@ -1859,6 +2032,7 @@ var SSEConnectionManager = class {
|
|
|
1859
2032
|
}
|
|
1860
2033
|
/**
|
|
1861
2034
|
* Handle incoming RPC requests
|
|
2035
|
+
* Returns the RPC response directly for immediate HTTP response (bypassing SSE latency)
|
|
1862
2036
|
*/
|
|
1863
2037
|
async handleRequest(request) {
|
|
1864
2038
|
try {
|
|
@@ -1900,39 +2074,29 @@ var SSEConnectionManager = class {
|
|
|
1900
2074
|
default:
|
|
1901
2075
|
throw new Error(`Unknown method: ${request.method}`);
|
|
1902
2076
|
}
|
|
1903
|
-
|
|
2077
|
+
const response = {
|
|
1904
2078
|
id: request.id,
|
|
1905
2079
|
result
|
|
1906
|
-
}
|
|
2080
|
+
};
|
|
2081
|
+
this.sendEvent(response);
|
|
2082
|
+
return response;
|
|
1907
2083
|
} catch (error) {
|
|
1908
|
-
|
|
2084
|
+
const errorResponse = {
|
|
1909
2085
|
id: request.id,
|
|
1910
2086
|
error: {
|
|
1911
2087
|
code: RpcErrorCodes.EXECUTION_ERROR,
|
|
1912
2088
|
message: error instanceof Error ? error.message : "Unknown error"
|
|
1913
2089
|
}
|
|
1914
|
-
}
|
|
2090
|
+
};
|
|
2091
|
+
this.sendEvent(errorResponse);
|
|
2092
|
+
return errorResponse;
|
|
1915
2093
|
}
|
|
1916
2094
|
}
|
|
1917
2095
|
/**
|
|
1918
|
-
* Get all
|
|
2096
|
+
* Get all sessions for the current identity
|
|
1919
2097
|
*/
|
|
1920
2098
|
async getSessions() {
|
|
1921
2099
|
const sessions = await storage.getIdentitySessionsData(this.identity);
|
|
1922
|
-
this.sendEvent({
|
|
1923
|
-
level: "debug",
|
|
1924
|
-
message: `Retrieved ${sessions.length} sessions for identity ${this.identity}`,
|
|
1925
|
-
timestamp: Date.now(),
|
|
1926
|
-
metadata: {
|
|
1927
|
-
identity: this.identity,
|
|
1928
|
-
sessionCount: sessions.length,
|
|
1929
|
-
sessions: sessions.map((s) => ({
|
|
1930
|
-
sessionId: s.sessionId,
|
|
1931
|
-
serverId: s.serverId,
|
|
1932
|
-
serverName: s.serverName
|
|
1933
|
-
}))
|
|
1934
|
-
}
|
|
1935
|
-
});
|
|
1936
2100
|
return {
|
|
1937
2101
|
sessions: sessions.map((s) => ({
|
|
1938
2102
|
sessionId: s.sessionId,
|
|
@@ -1947,7 +2111,8 @@ var SSEConnectionManager = class {
|
|
|
1947
2111
|
* Connect to an MCP server
|
|
1948
2112
|
*/
|
|
1949
2113
|
async connect(params) {
|
|
1950
|
-
const {
|
|
2114
|
+
const { serverName, serverUrl, callbackUrl, transportType } = params;
|
|
2115
|
+
const serverId = params.serverId && params.serverId.length <= 12 ? params.serverId : await storage.generateSessionId();
|
|
1951
2116
|
const existingSessions = await storage.getIdentitySessionsData(this.identity);
|
|
1952
2117
|
const duplicate = existingSessions.find(
|
|
1953
2118
|
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
@@ -1996,10 +2161,6 @@ var SSEConnectionManager = class {
|
|
|
1996
2161
|
});
|
|
1997
2162
|
await client.connect();
|
|
1998
2163
|
const tools = await client.listTools();
|
|
1999
|
-
const sessionAfterConnect = await storage.getSession(this.identity, sessionId);
|
|
2000
|
-
console.log(`[SSE Handler] After connect() - Session ${sessionId}:`, {
|
|
2001
|
-
serverId: sessionAfterConnect?.serverId
|
|
2002
|
-
});
|
|
2003
2164
|
this.emitConnectionEvent({
|
|
2004
2165
|
type: "tools_discovered",
|
|
2005
2166
|
sessionId,
|
|
@@ -2041,24 +2202,21 @@ var SSEConnectionManager = class {
|
|
|
2041
2202
|
return { success: true };
|
|
2042
2203
|
}
|
|
2043
2204
|
/**
|
|
2044
|
-
*
|
|
2205
|
+
* Get an existing client or create and connect a new one for the session.
|
|
2045
2206
|
*/
|
|
2046
2207
|
async getOrCreateClient(sessionId) {
|
|
2047
|
-
|
|
2048
|
-
if (
|
|
2049
|
-
|
|
2050
|
-
identity: this.identity,
|
|
2051
|
-
sessionId
|
|
2052
|
-
});
|
|
2053
|
-
client.onConnectionEvent((event) => {
|
|
2054
|
-
this.emitConnectionEvent(event);
|
|
2055
|
-
});
|
|
2056
|
-
client.onObservabilityEvent((event) => {
|
|
2057
|
-
this.sendEvent(event);
|
|
2058
|
-
});
|
|
2059
|
-
await client.connect();
|
|
2060
|
-
this.clients.set(sessionId, client);
|
|
2208
|
+
const existing = this.clients.get(sessionId);
|
|
2209
|
+
if (existing) {
|
|
2210
|
+
return existing;
|
|
2061
2211
|
}
|
|
2212
|
+
const client = new MCPClient({
|
|
2213
|
+
identity: this.identity,
|
|
2214
|
+
sessionId
|
|
2215
|
+
});
|
|
2216
|
+
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
2217
|
+
client.onObservabilityEvent((event) => this.sendEvent(event));
|
|
2218
|
+
await client.connect();
|
|
2219
|
+
this.clients.set(sessionId, client);
|
|
2062
2220
|
return client;
|
|
2063
2221
|
}
|
|
2064
2222
|
/**
|
|
@@ -2071,51 +2229,35 @@ var SSEConnectionManager = class {
|
|
|
2071
2229
|
return { tools: result.tools };
|
|
2072
2230
|
}
|
|
2073
2231
|
/**
|
|
2074
|
-
* Call a tool
|
|
2232
|
+
* Call a tool on the MCP server
|
|
2075
2233
|
*/
|
|
2076
2234
|
async callTool(params) {
|
|
2077
2235
|
const { sessionId, toolName, toolArgs } = params;
|
|
2078
2236
|
const client = await this.getOrCreateClient(sessionId);
|
|
2079
|
-
|
|
2237
|
+
const result = await client.callTool(toolName, toolArgs);
|
|
2238
|
+
const meta = result._meta || {};
|
|
2239
|
+
return {
|
|
2240
|
+
...result,
|
|
2241
|
+
_meta: {
|
|
2242
|
+
...meta,
|
|
2243
|
+
sessionId
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2080
2246
|
}
|
|
2081
2247
|
/**
|
|
2082
|
-
*
|
|
2248
|
+
* Restore and validate an existing session
|
|
2083
2249
|
*/
|
|
2084
2250
|
async restoreSession(params) {
|
|
2085
2251
|
const { sessionId } = params;
|
|
2086
|
-
this.sendEvent({
|
|
2087
|
-
level: "debug",
|
|
2088
|
-
message: `Starting session refresh for ${sessionId}`,
|
|
2089
|
-
timestamp: Date.now(),
|
|
2090
|
-
metadata: { sessionId, identity: this.identity }
|
|
2091
|
-
});
|
|
2092
2252
|
const session = await storage.getSession(this.identity, sessionId);
|
|
2093
2253
|
if (!session) {
|
|
2094
|
-
this.sendEvent({
|
|
2095
|
-
level: "error",
|
|
2096
|
-
message: `Session not found: ${sessionId}`,
|
|
2097
|
-
timestamp: Date.now(),
|
|
2098
|
-
metadata: { sessionId, identity: this.identity }
|
|
2099
|
-
});
|
|
2100
2254
|
throw new Error("Session not found");
|
|
2101
2255
|
}
|
|
2102
|
-
this.sendEvent({
|
|
2103
|
-
level: "debug",
|
|
2104
|
-
message: `Session found in Redis`,
|
|
2105
|
-
timestamp: Date.now(),
|
|
2106
|
-
metadata: {
|
|
2107
|
-
sessionId,
|
|
2108
|
-
serverId: session.serverId,
|
|
2109
|
-
serverName: session.serverName,
|
|
2110
|
-
serverUrl: session.serverUrl,
|
|
2111
|
-
transportType: session.transportType
|
|
2112
|
-
}
|
|
2113
|
-
});
|
|
2114
2256
|
this.emitConnectionEvent({
|
|
2115
2257
|
type: "state_changed",
|
|
2116
2258
|
sessionId,
|
|
2117
|
-
serverId: session.serverId
|
|
2118
|
-
serverName: session.serverName
|
|
2259
|
+
serverId: session.serverId ?? "unknown",
|
|
2260
|
+
serverName: session.serverName ?? "Unknown",
|
|
2119
2261
|
state: "VALIDATING",
|
|
2120
2262
|
previousState: "DISCONNECTED",
|
|
2121
2263
|
timestamp: Date.now()
|
|
@@ -2126,21 +2268,16 @@ var SSEConnectionManager = class {
|
|
|
2126
2268
|
identity: this.identity,
|
|
2127
2269
|
sessionId,
|
|
2128
2270
|
...clientMetadata
|
|
2129
|
-
// Include metadata for consistency
|
|
2130
|
-
});
|
|
2131
|
-
client.onConnectionEvent((event) => {
|
|
2132
|
-
this.emitConnectionEvent(event);
|
|
2133
|
-
});
|
|
2134
|
-
client.onObservabilityEvent((event) => {
|
|
2135
|
-
this.sendEvent(event);
|
|
2136
2271
|
});
|
|
2272
|
+
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
2273
|
+
client.onObservabilityEvent((event) => this.sendEvent(event));
|
|
2137
2274
|
await client.connect();
|
|
2138
2275
|
this.clients.set(sessionId, client);
|
|
2139
2276
|
const tools = await client.listTools();
|
|
2140
2277
|
this.emitConnectionEvent({
|
|
2141
2278
|
type: "tools_discovered",
|
|
2142
2279
|
sessionId,
|
|
2143
|
-
serverId: session.serverId
|
|
2280
|
+
serverId: session.serverId ?? "unknown",
|
|
2144
2281
|
toolCount: tools.tools.length,
|
|
2145
2282
|
tools: tools.tools,
|
|
2146
2283
|
timestamp: Date.now()
|
|
@@ -2150,7 +2287,7 @@ var SSEConnectionManager = class {
|
|
|
2150
2287
|
this.emitConnectionEvent({
|
|
2151
2288
|
type: "error",
|
|
2152
2289
|
sessionId,
|
|
2153
|
-
serverId: session.serverId
|
|
2290
|
+
serverId: session.serverId ?? "unknown",
|
|
2154
2291
|
error: error instanceof Error ? error.message : "Validation failed",
|
|
2155
2292
|
errorType: "validation",
|
|
2156
2293
|
timestamp: Date.now()
|
|
@@ -2159,16 +2296,10 @@ var SSEConnectionManager = class {
|
|
|
2159
2296
|
}
|
|
2160
2297
|
}
|
|
2161
2298
|
/**
|
|
2162
|
-
* Complete OAuth authorization
|
|
2299
|
+
* Complete OAuth authorization flow
|
|
2163
2300
|
*/
|
|
2164
2301
|
async finishAuth(params) {
|
|
2165
2302
|
const { sessionId, code } = params;
|
|
2166
|
-
this.sendEvent({
|
|
2167
|
-
level: "debug",
|
|
2168
|
-
message: `Completing OAuth for session ${sessionId}`,
|
|
2169
|
-
timestamp: Date.now(),
|
|
2170
|
-
metadata: { sessionId, identity: this.identity }
|
|
2171
|
-
});
|
|
2172
2303
|
const session = await storage.getSession(this.identity, sessionId);
|
|
2173
2304
|
if (!session) {
|
|
2174
2305
|
throw new Error("Session not found");
|
|
@@ -2176,8 +2307,8 @@ var SSEConnectionManager = class {
|
|
|
2176
2307
|
this.emitConnectionEvent({
|
|
2177
2308
|
type: "state_changed",
|
|
2178
2309
|
sessionId,
|
|
2179
|
-
serverId: session.serverId
|
|
2180
|
-
serverName: session.serverName
|
|
2310
|
+
serverId: session.serverId ?? "unknown",
|
|
2311
|
+
serverName: session.serverName ?? "Unknown",
|
|
2181
2312
|
state: "AUTHENTICATING",
|
|
2182
2313
|
previousState: "DISCONNECTED",
|
|
2183
2314
|
timestamp: Date.now()
|
|
@@ -2187,16 +2318,14 @@ var SSEConnectionManager = class {
|
|
|
2187
2318
|
identity: this.identity,
|
|
2188
2319
|
sessionId
|
|
2189
2320
|
});
|
|
2190
|
-
client.onConnectionEvent((event) =>
|
|
2191
|
-
this.emitConnectionEvent(event);
|
|
2192
|
-
});
|
|
2321
|
+
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
2193
2322
|
await client.finishAuth(code);
|
|
2194
2323
|
this.clients.set(sessionId, client);
|
|
2195
2324
|
const tools = await client.listTools();
|
|
2196
2325
|
this.emitConnectionEvent({
|
|
2197
2326
|
type: "tools_discovered",
|
|
2198
2327
|
sessionId,
|
|
2199
|
-
serverId: session.serverId
|
|
2328
|
+
serverId: session.serverId ?? "unknown",
|
|
2200
2329
|
toolCount: tools.tools.length,
|
|
2201
2330
|
tools: tools.tools,
|
|
2202
2331
|
timestamp: Date.now()
|
|
@@ -2206,7 +2335,7 @@ var SSEConnectionManager = class {
|
|
|
2206
2335
|
this.emitConnectionEvent({
|
|
2207
2336
|
type: "error",
|
|
2208
2337
|
sessionId,
|
|
2209
|
-
serverId: session.serverId
|
|
2338
|
+
serverId: session.serverId ?? "unknown",
|
|
2210
2339
|
error: error instanceof Error ? error.message : "OAuth completion failed",
|
|
2211
2340
|
errorType: "auth",
|
|
2212
2341
|
timestamp: Date.now()
|
|
@@ -2246,7 +2375,7 @@ var SSEConnectionManager = class {
|
|
|
2246
2375
|
async readResource(params) {
|
|
2247
2376
|
const { sessionId, uri } = params;
|
|
2248
2377
|
const client = await this.getOrCreateClient(sessionId);
|
|
2249
|
-
return
|
|
2378
|
+
return client.readResource(uri);
|
|
2250
2379
|
}
|
|
2251
2380
|
/**
|
|
2252
2381
|
* Emit connection event
|
|
@@ -2276,19 +2405,17 @@ function createSSEHandler(options) {
|
|
|
2276
2405
|
"Connection": "keep-alive",
|
|
2277
2406
|
"Access-Control-Allow-Origin": "*"
|
|
2278
2407
|
});
|
|
2279
|
-
|
|
2408
|
+
writeSSEEvent(res, "connected", { timestamp: Date.now() });
|
|
2280
2409
|
const manager = new SSEConnectionManager(options, (event) => {
|
|
2281
2410
|
if ("id" in event) {
|
|
2282
|
-
|
|
2411
|
+
writeSSEEvent(res, "rpc-response", event);
|
|
2283
2412
|
} else if ("type" in event && "sessionId" in event) {
|
|
2284
|
-
|
|
2413
|
+
writeSSEEvent(res, "connection", event);
|
|
2285
2414
|
} else {
|
|
2286
|
-
|
|
2415
|
+
writeSSEEvent(res, "observability", event);
|
|
2287
2416
|
}
|
|
2288
2417
|
});
|
|
2289
|
-
req.on("close", () =>
|
|
2290
|
-
manager.dispose();
|
|
2291
|
-
});
|
|
2418
|
+
req.on("close", () => manager.dispose());
|
|
2292
2419
|
if (req.method === "POST") {
|
|
2293
2420
|
let body = "";
|
|
2294
2421
|
req.on("data", (chunk) => {
|
|
@@ -2298,14 +2425,13 @@ function createSSEHandler(options) {
|
|
|
2298
2425
|
try {
|
|
2299
2426
|
const request = JSON.parse(body);
|
|
2300
2427
|
await manager.handleRequest(request);
|
|
2301
|
-
} catch
|
|
2302
|
-
console.error("[SSE] Error handling request:", error);
|
|
2428
|
+
} catch {
|
|
2303
2429
|
}
|
|
2304
2430
|
});
|
|
2305
2431
|
}
|
|
2306
2432
|
};
|
|
2307
2433
|
}
|
|
2308
|
-
function
|
|
2434
|
+
function writeSSEEvent(res, event, data) {
|
|
2309
2435
|
res.write(`event: ${event}
|
|
2310
2436
|
`);
|
|
2311
2437
|
res.write(`data: ${JSON.stringify(data)}
|
|
@@ -2340,7 +2466,7 @@ function createNextMcpHandler(options = {}) {
|
|
|
2340
2466
|
const stream = new TransformStream();
|
|
2341
2467
|
const writer = stream.writable.getWriter();
|
|
2342
2468
|
const encoder = new TextEncoder();
|
|
2343
|
-
const
|
|
2469
|
+
const sendSSE = (event, data) => {
|
|
2344
2470
|
const message = `event: ${event}
|
|
2345
2471
|
data: ${JSON.stringify(data)}
|
|
2346
2472
|
|
|
@@ -2348,7 +2474,6 @@ data: ${JSON.stringify(data)}
|
|
|
2348
2474
|
writer.write(encoder.encode(message)).catch(() => {
|
|
2349
2475
|
});
|
|
2350
2476
|
};
|
|
2351
|
-
sendSSE2("connected", { timestamp: Date.now() });
|
|
2352
2477
|
const previousManager = managers.get(identity);
|
|
2353
2478
|
if (previousManager) {
|
|
2354
2479
|
previousManager.dispose();
|
|
@@ -2363,15 +2488,16 @@ data: ${JSON.stringify(data)}
|
|
|
2363
2488
|
},
|
|
2364
2489
|
(event) => {
|
|
2365
2490
|
if ("id" in event) {
|
|
2366
|
-
|
|
2491
|
+
sendSSE("rpc-response", event);
|
|
2367
2492
|
} else if ("type" in event && "sessionId" in event) {
|
|
2368
|
-
|
|
2493
|
+
sendSSE("connection", event);
|
|
2369
2494
|
} else {
|
|
2370
|
-
|
|
2495
|
+
sendSSE("observability", event);
|
|
2371
2496
|
}
|
|
2372
2497
|
}
|
|
2373
2498
|
);
|
|
2374
2499
|
managers.set(identity, manager);
|
|
2500
|
+
sendSSE("connected", { timestamp: Date.now() });
|
|
2375
2501
|
const abortController = new AbortController();
|
|
2376
2502
|
request.signal?.addEventListener("abort", () => {
|
|
2377
2503
|
manager.dispose();
|
|
@@ -2414,8 +2540,8 @@ data: ${JSON.stringify(data)}
|
|
|
2414
2540
|
{ status: 400 }
|
|
2415
2541
|
);
|
|
2416
2542
|
}
|
|
2417
|
-
await manager.handleRequest(body);
|
|
2418
|
-
return Response.json(
|
|
2543
|
+
const response = await manager.handleRequest(body);
|
|
2544
|
+
return Response.json(response);
|
|
2419
2545
|
} catch (error) {
|
|
2420
2546
|
return Response.json(
|
|
2421
2547
|
{
|