rhombus-node-mcp 0.1.34 → 0.1.36
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/dist/api/get-accessible-apps.js +40 -0
- package/dist/createServer.js +80 -12
- package/dist/filtering-utils.js +11 -0
- package/dist/tools/getTools.js +33 -10
- package/dist/transports/streamable-http.js +74 -70
- package/package.json +1 -1
- /package/dist/{tools → tools-console}/access-control-tool.js +0 -0
- /package/dist/{tools → tools-console}/alarm-monitoring-tool.js +0 -0
- /package/dist/{tools → tools-console}/analytics-tool.js +0 -0
- /package/dist/{tools → tools-console}/automated-prompts-tool.js +0 -0
- /package/dist/{tools → tools-console}/camera-tool.js +0 -0
- /package/dist/{tools → tools-console}/camera-uptime-tool.js +0 -0
- /package/dist/{tools → tools-console}/clips-tool.js +0 -0
- /package/dist/{tools → tools-console}/count-tool.js +0 -0
- /package/dist/{tools → tools-console}/create-camera-policy-tool.js +0 -0
- /package/dist/{tools → tools-console}/door-schedule-exception-tool.js +0 -0
- /package/dist/{tools → tools-console}/door-tool.js +0 -0
- /package/dist/{tools → tools-console}/events-tool.js +0 -0
- /package/dist/{tools → tools-console}/faces-tool.js +0 -0
- /package/dist/{tools → tools-console}/guest-management-tool.js +0 -0
- /package/dist/{tools → tools-console}/location-tool.js +0 -0
- /package/dist/{tools → tools-console}/lpr-tool.js +0 -0
- /package/dist/{tools → tools-console}/policy-alerts-tool.js +0 -0
- /package/dist/{tools → tools-console}/reboot-cameras-tool.js +0 -0
- /package/dist/{tools → tools-console}/report-tool.js +0 -0
- /package/dist/{tools → tools-console}/rules-tool.js +0 -0
- /package/dist/{tools → tools-console}/search-tool.js +0 -0
- /package/dist/{tools → tools-console}/update-tool.js +0 -0
- /package/dist/{tools → tools-console}/user-access-trail-tool.js +0 -0
- /package/dist/{tools → tools-console}/user-audit-tool.js +0 -0
- /package/dist/{tools → tools-console}/video-walls-tool.js +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { logger } from "../logger.js";
|
|
2
|
+
import { postApi } from "../network/network.js";
|
|
3
|
+
/**
|
|
4
|
+
* Fetches `user.accessibleRhombusApps` from getCurrentUser for the given session.
|
|
5
|
+
* Successful results are cached in-memory for the lifetime of the session;
|
|
6
|
+
* failures are NOT cached so transient errors don't poison the session.
|
|
7
|
+
* Returns null on error or missing session; callers should fall back to a
|
|
8
|
+
* permissive default.
|
|
9
|
+
*/
|
|
10
|
+
const cache = new Map();
|
|
11
|
+
export async function resolveAccessibleApps(sessionId) {
|
|
12
|
+
if (!sessionId)
|
|
13
|
+
return null;
|
|
14
|
+
const cached = cache.get(sessionId);
|
|
15
|
+
if (cached !== undefined)
|
|
16
|
+
return cached;
|
|
17
|
+
try {
|
|
18
|
+
const res = await postApi({
|
|
19
|
+
route: "/customer/getCurrentUser",
|
|
20
|
+
body: {},
|
|
21
|
+
sessionId,
|
|
22
|
+
});
|
|
23
|
+
if (res.error) {
|
|
24
|
+
logger.warn(`resolveAccessibleApps: getCurrentUser failed for session ${sessionId}`);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const apps = (res.user?.accessibleRhombusApps ?? []).filter((a) => a !== null && a !== undefined);
|
|
28
|
+
cache.set(sessionId, apps);
|
|
29
|
+
logger.info(`resolveAccessibleApps: session ${sessionId} -> [${apps.join(", ")}]`);
|
|
30
|
+
return apps;
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
logger.warn(`resolveAccessibleApps: error for session ${sessionId}: ${String(e)}`);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Drop the cached entry — call when a session ends. */
|
|
38
|
+
export function clearAccessibleAppsCache(sessionId) {
|
|
39
|
+
cache.delete(sessionId);
|
|
40
|
+
}
|
package/dist/createServer.js
CHANGED
|
@@ -1,29 +1,93 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { resolveAccessibleApps } from "./api/get-accessible-apps.js";
|
|
2
4
|
import { logger } from "./logger.js";
|
|
3
5
|
import { createFilteringProxy } from "./util.js";
|
|
4
6
|
import getResources from "./resources/getResources.js";
|
|
5
|
-
import
|
|
7
|
+
import { getConsoleTools, getPartnerTools, getSharedTools, } from "./tools/getTools.js";
|
|
8
|
+
import { RhombusAppEnum } from "./types/schema.js";
|
|
6
9
|
let initiated = false;
|
|
7
10
|
let resources;
|
|
8
|
-
let
|
|
11
|
+
let sharedTools;
|
|
12
|
+
let consoleTools;
|
|
13
|
+
let partnerTools;
|
|
9
14
|
export async function serverInit() {
|
|
10
15
|
resources = await getResources();
|
|
11
16
|
logger.info(`📚 Found ${resources.length} resources`);
|
|
12
17
|
for (const resource of resources) {
|
|
13
18
|
logger.debug(`📕 - ${resource.name}`);
|
|
14
19
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
sharedTools = await getSharedTools();
|
|
21
|
+
consoleTools = await getConsoleTools();
|
|
22
|
+
partnerTools = await getPartnerTools();
|
|
23
|
+
logger.info(`🛠️ Found ${sharedTools.length} shared, ${consoleTools.length} console, ${partnerTools.length} partner tools`);
|
|
24
|
+
for (const tool of sharedTools)
|
|
25
|
+
logger.debug(`🔧 shared - ${tool.name}`);
|
|
26
|
+
for (const tool of consoleTools)
|
|
27
|
+
logger.debug(`🔧 console - ${tool.name}`);
|
|
28
|
+
for (const tool of partnerTools)
|
|
29
|
+
logger.debug(`🔧 partner - ${tool.name}`);
|
|
20
30
|
initiated = true;
|
|
21
31
|
}
|
|
22
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Compose the tool set to register based on the caller's accessibleRhombusApps.
|
|
34
|
+
* Shared tools are always included.
|
|
35
|
+
*
|
|
36
|
+
* If **PARTNER** is among the caller's apps (including alongside CONSOLE), only
|
|
37
|
+
* **partner** tools are added — never the console-only set (partner capability
|
|
38
|
+
* is stricter).
|
|
39
|
+
*
|
|
40
|
+
* Else if **CONSOLE** is present, add **console** tools only.
|
|
41
|
+
*
|
|
42
|
+
* Otherwise (unresolved session, empty apps, or only other enums like
|
|
43
|
+
* RHOMBUS_KEY), fall back to the permissive union of console + partner sets.
|
|
44
|
+
*/
|
|
45
|
+
function pickToolsForSession(apps) {
|
|
46
|
+
if (apps !== null && apps.length > 0 && apps.includes(RhombusAppEnum.PARTNER)) {
|
|
47
|
+
return [...sharedTools, ...partnerTools];
|
|
48
|
+
}
|
|
49
|
+
if (apps !== null && apps.length > 0 && apps.includes(RhombusAppEnum.CONSOLE)) {
|
|
50
|
+
return [...sharedTools, ...consoleTools];
|
|
51
|
+
}
|
|
52
|
+
return [...sharedTools, ...consoleTools, ...partnerTools];
|
|
53
|
+
}
|
|
54
|
+
function isNodeDevEnvironment() {
|
|
55
|
+
return process.env.NODE_ENV === "development";
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Human-readable caller + tool-set summary for dev logs (must stay aligned with
|
|
59
|
+
* {@link pickToolsForSession}).
|
|
60
|
+
*/
|
|
61
|
+
function describeCallerForDevLogs(apps) {
|
|
62
|
+
if (apps === null) {
|
|
63
|
+
return "caller=unknown (no session or unresolved apps); tool sets=shared + console + partner (permissive)";
|
|
64
|
+
}
|
|
65
|
+
if (apps.length === 0) {
|
|
66
|
+
return "caller=unknown (empty accessibleRhombusApps); tool sets=shared + console + partner (permissive)";
|
|
67
|
+
}
|
|
68
|
+
if (apps.includes(RhombusAppEnum.PARTNER)) {
|
|
69
|
+
const alsoConsole = apps.includes(RhombusAppEnum.CONSOLE);
|
|
70
|
+
return alsoConsole
|
|
71
|
+
? `caller=partner (PARTNER in [${apps.join(", ")}], console tools suppressed); tool sets=shared + partner`
|
|
72
|
+
: "caller=partner user; tool sets=shared + partner";
|
|
73
|
+
}
|
|
74
|
+
if (apps.includes(RhombusAppEnum.CONSOLE)) {
|
|
75
|
+
return "caller=console user; tool sets=shared + console";
|
|
76
|
+
}
|
|
77
|
+
return `caller=other (accessibleRhombusApps=[${apps.join(", ")}]); tool sets=shared + console + partner (permissive)`;
|
|
78
|
+
}
|
|
79
|
+
function logDevToolRegistration(sessionId, apps, toolsToRegister) {
|
|
80
|
+
if (!isNodeDevEnvironment())
|
|
81
|
+
return;
|
|
82
|
+
const names = toolsToRegister.map((t) => path.basename(t.name, ".js")).sort();
|
|
83
|
+
logger.info(`[dev MCP tools] session=${sessionId ?? "(none)"} — ${describeCallerForDevLogs(apps)} — registering ${names.length} tools`);
|
|
84
|
+
logger.info(`[dev MCP tools] tool names: ${names.join(", ")}`);
|
|
85
|
+
}
|
|
86
|
+
export default async function createServer({ sessionId } = {}) {
|
|
23
87
|
if (!initiated) {
|
|
24
88
|
await serverInit();
|
|
25
89
|
}
|
|
26
|
-
logger.info(`🖥️ Creating Server`);
|
|
90
|
+
logger.info(`🖥️ Creating Server for session ${sessionId ?? "(stateless)"}`);
|
|
27
91
|
const server = new McpServer({
|
|
28
92
|
name: "rhombus-node-mcp",
|
|
29
93
|
version: "1.0.0",
|
|
@@ -36,9 +100,13 @@ export default async function createServer() {
|
|
|
36
100
|
for (const resource of resources) {
|
|
37
101
|
resource.create(server);
|
|
38
102
|
}
|
|
39
|
-
logger.info(
|
|
103
|
+
logger.info(`📚 Registered ${resources.length} resources`);
|
|
104
|
+
const apps = await resolveAccessibleApps(sessionId);
|
|
105
|
+
const toolsToRegister = pickToolsForSession(apps);
|
|
106
|
+
logDevToolRegistration(sessionId, apps, toolsToRegister);
|
|
107
|
+
logger.info(`🔒 Session ${sessionId ?? "(none)"}: apps=[${apps?.join(", ") ?? "unknown"}] — registering ${toolsToRegister.length} tools`);
|
|
40
108
|
const filteredServer = createFilteringProxy(server, new Set(["time-tool", "count-tool", "time-conversion-tool"]));
|
|
41
|
-
for (const tool of
|
|
109
|
+
for (const tool of toolsToRegister) {
|
|
42
110
|
try {
|
|
43
111
|
await tool.create(filteredServer);
|
|
44
112
|
}
|
|
@@ -47,7 +115,7 @@ export default async function createServer() {
|
|
|
47
115
|
// Continue with other tools instead of failing completely
|
|
48
116
|
}
|
|
49
117
|
}
|
|
50
|
-
logger.info(`🛠️ Registered ${
|
|
118
|
+
logger.info(`🛠️ Registered ${toolsToRegister.length} tools`);
|
|
51
119
|
logger.info(`✅ Server created`);
|
|
52
120
|
return server;
|
|
53
121
|
}
|
package/dist/filtering-utils.js
CHANGED
|
@@ -168,6 +168,17 @@ export function applyFilterBy(obj, conditions) {
|
|
|
168
168
|
result[topKey] = result[topKey].filter((item) => keyConds.every((c) => matchesCondition(item, c)));
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
+
// If the result has a sibling `count: number` and exactly one top-level
|
|
172
|
+
// array, sync count to the (now-filtered) array length. This makes `count`
|
|
173
|
+
// reflect what the model is looking at — pre-filter total when no filterBy
|
|
174
|
+
// was applied (handler computed it), post-filter total when one was. Any
|
|
175
|
+
// tool returning {count, items[]} gets this for free.
|
|
176
|
+
if (typeof result.count === "number") {
|
|
177
|
+
const arrayKeys = Object.keys(result).filter((k) => Array.isArray(result[k]));
|
|
178
|
+
if (arrayKeys.length === 1) {
|
|
179
|
+
result.count = result[arrayKeys[0]].length;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
171
182
|
return result;
|
|
172
183
|
}
|
|
173
184
|
// ---------------------------------------------------------------------------
|
package/dist/tools/getTools.js
CHANGED
|
@@ -1,22 +1,45 @@
|
|
|
1
|
-
import { fileURLToPath } from "node:url";
|
|
2
1
|
import path from "path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { getFilePathsInDirectory } from "../util.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const SELF_BASENAME = "getTools.js";
|
|
5
|
+
async function loadFrom(dir) {
|
|
6
|
+
let filePaths;
|
|
7
|
+
try {
|
|
8
|
+
filePaths = getFilePathsInDirectory(dir);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// Directory doesn't exist (e.g. an empty `tools-partner` for a project that
|
|
12
|
+
// hasn't filled it in yet) — return empty list rather than failing.
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
7
15
|
const tools = [];
|
|
8
16
|
for (const filePath of filePaths) {
|
|
9
|
-
// ensure it's a js file
|
|
10
17
|
if (!filePath.endsWith(".js"))
|
|
11
18
|
continue;
|
|
19
|
+
if (path.basename(filePath) === SELF_BASENAME)
|
|
20
|
+
continue;
|
|
12
21
|
const imported = (await import(filePath));
|
|
13
22
|
if (imported.createTool !== undefined) {
|
|
14
|
-
tools.push({
|
|
15
|
-
name: filePath,
|
|
16
|
-
create: imported.createTool,
|
|
17
|
-
});
|
|
23
|
+
tools.push({ name: filePath, create: imported.createTool });
|
|
18
24
|
}
|
|
19
25
|
}
|
|
20
26
|
return tools;
|
|
21
27
|
}
|
|
22
|
-
|
|
28
|
+
/** Tools visible to every caller (shared / universal — time, identity, lookups). */
|
|
29
|
+
export async function getSharedTools() {
|
|
30
|
+
// This file lives in `src/tools/`, so its directory IS the shared tools dir.
|
|
31
|
+
return loadFrom(path.dirname(fileURLToPath(import.meta.url)));
|
|
32
|
+
}
|
|
33
|
+
/** Tools visible only to console (non-partner) callers. From `src/tools-console/`. */
|
|
34
|
+
export async function getConsoleTools() {
|
|
35
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
return loadFrom(path.resolve(here, "..", "tools-console"));
|
|
37
|
+
}
|
|
38
|
+
/** Tools visible only to partner callers. From `src/tools-partner/`. */
|
|
39
|
+
export async function getPartnerTools() {
|
|
40
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
return loadFrom(path.resolve(here, "..", "tools-partner"));
|
|
42
|
+
}
|
|
43
|
+
// Backwards-compatible default — returns the shared set so existing callers
|
|
44
|
+
// don't break, but new code should pick the explicit functions.
|
|
45
|
+
export default getSharedTools;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
2
3
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
3
4
|
import cors from "cors";
|
|
4
5
|
import express from "express";
|
|
5
|
-
import
|
|
6
|
+
import { clearAccessibleAppsCache } from "../api/get-accessible-apps.js";
|
|
6
7
|
import createServer from "../createServer.js";
|
|
7
8
|
import { logger } from "../logger.js";
|
|
8
9
|
var AuthScheme;
|
|
@@ -14,6 +15,54 @@ var AuthScheme;
|
|
|
14
15
|
})(AuthScheme || (AuthScheme = {}));
|
|
15
16
|
export const authStore = new Map();
|
|
16
17
|
const transports = new Map();
|
|
18
|
+
/**
|
|
19
|
+
* Populate authStore for the given sessionId based on the request headers.
|
|
20
|
+
* Done up-front (before createServer) so resolveAccessibleApps can read auth
|
|
21
|
+
* during tool registration. Returns true on success.
|
|
22
|
+
*/
|
|
23
|
+
function populateAuthStore(req, sessionId) {
|
|
24
|
+
const oauthToken = req.headers["x-auth-access-token"];
|
|
25
|
+
const authScheme = req.headers["x-auth-scheme"] ?? AuthScheme.API_TOKEN;
|
|
26
|
+
if (oauthToken && typeof oauthToken === "string") {
|
|
27
|
+
authStore.set(sessionId, { oauthToken, createdMs: Date.now() });
|
|
28
|
+
logger.info(`🔒 MCP request authenticated with oauth token (session ${sessionId})`);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (authScheme === AuthScheme.API_TOKEN) {
|
|
32
|
+
const apiKey = "x-auth-apikey" in req.headers
|
|
33
|
+
? req.headers["x-auth-apikey"]
|
|
34
|
+
: process.env.RHOMBUS_API_KEY;
|
|
35
|
+
if (!apiKey) {
|
|
36
|
+
logger.warn("populateAuthStore: API_TOKEN scheme but no api key found");
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
logger.info(`🔒 MCP request authenticated with api key (session ${sessionId})`);
|
|
40
|
+
authStore.set(sessionId, { apiKey, createdMs: Date.now() });
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (authScheme === AuthScheme.CHATBOT &&
|
|
44
|
+
"x-auth-session" in req.headers &&
|
|
45
|
+
"x-auth-chat" in req.headers) {
|
|
46
|
+
logger.info(`🔒 MCP request authenticated with x-auth-session: ${req.headers["x-auth-session"]} and x-auth-chat: ${req.headers["x-auth-chat"]}`);
|
|
47
|
+
authStore.set(sessionId, {
|
|
48
|
+
sessionId: req.headers["x-auth-session"],
|
|
49
|
+
latestRecordUuid: req.headers["x-auth-chat"],
|
|
50
|
+
createdMs: Date.now(),
|
|
51
|
+
});
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (authScheme === AuthScheme.WEB2 && "x-auth-cookie" in req.headers) {
|
|
55
|
+
logger.info(`🔒 MCP request authenticated with x-auth-cookie (session ${sessionId})`);
|
|
56
|
+
authStore.set(sessionId, {
|
|
57
|
+
cookie: req.headers["x-auth-cookie"],
|
|
58
|
+
sessionAlias: req.headers["x-auth-session-alias"],
|
|
59
|
+
createdMs: Date.now(),
|
|
60
|
+
});
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
logger.warn(`populateAuthStore: invalid auth scheme. x-auth-scheme: ${req.headers["x-auth-scheme"]}, x-auth-session: ${req.headers["x-auth-session"]}, x-auth-chat: ${req.headers["x-auth-chat"]}`);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
17
66
|
export default function streamableHttpTransport() {
|
|
18
67
|
const app = express();
|
|
19
68
|
app.use(express.json());
|
|
@@ -26,7 +75,6 @@ export default function streamableHttpTransport() {
|
|
|
26
75
|
/**
|
|
27
76
|
* STATEFUL ENDPOINT
|
|
28
77
|
*/
|
|
29
|
-
const authRequired = ["tools/call"];
|
|
30
78
|
app.post("/mcp", async (req, res) => {
|
|
31
79
|
logger.info(`Received MCP request`, JSON.stringify(req.body, null, 2));
|
|
32
80
|
// Check for existing session ID
|
|
@@ -41,76 +89,29 @@ export default function streamableHttpTransport() {
|
|
|
41
89
|
transport = _transport;
|
|
42
90
|
}
|
|
43
91
|
else if (!sessionId && isInitializeRequest(req.body)) {
|
|
44
|
-
// New initialization request
|
|
92
|
+
// New initialization request — mint our sessionId up front so we can
|
|
93
|
+
// populate authStore and gate tool registration BEFORE the SDK calls
|
|
94
|
+
// onsessioninitialized.
|
|
95
|
+
const newSessionId = crypto.randomUUID();
|
|
96
|
+
const authOk = populateAuthStore(req, newSessionId);
|
|
97
|
+
// Reject the initialize itself when auth couldn't be populated. Letting
|
|
98
|
+
// the session through without an authStore entry only defers the failure:
|
|
99
|
+
// any later /customer/getCurrentUser (during tool registration) or tool
|
|
100
|
+
// call will throw "No auth found for sessionId" from network.ts, which
|
|
101
|
+
// is harder to diagnose than an upfront 401 here.
|
|
102
|
+
if (!authOk) {
|
|
103
|
+
logger.error(`Auth could not be populated for ${req.body.method}; rejecting`);
|
|
104
|
+
res
|
|
105
|
+
.status(401)
|
|
106
|
+
.setHeader("WWW-Authenticate", `Bearer realm="${process.env.REALM}", error="invalid_token", error_description="The access token is missing or invalid"`)
|
|
107
|
+
.send("Unauthorized");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
45
110
|
transport = new StreamableHTTPServerTransport({
|
|
46
|
-
sessionIdGenerator:
|
|
111
|
+
sessionIdGenerator: () => newSessionId,
|
|
47
112
|
onsessioninitialized: sessionId => {
|
|
48
|
-
// Store the transport by session ID
|
|
49
113
|
transports.set(sessionId, transport);
|
|
50
114
|
logger.info(`🔒 MCP request initialized with sessionId: ${sessionId}`);
|
|
51
|
-
try {
|
|
52
|
-
const oauthToken = req.headers["x-auth-access-token"];
|
|
53
|
-
const authScheme = req.headers["x-auth-scheme"] ?? AuthScheme.API_TOKEN; // otherwise, read 'x-auth-scheme'
|
|
54
|
-
// if oauth token is provided
|
|
55
|
-
if (oauthToken && typeof oauthToken === "string") {
|
|
56
|
-
authStore.set(sessionId, {
|
|
57
|
-
oauthToken: oauthToken,
|
|
58
|
-
createdMs: Date.now(),
|
|
59
|
-
});
|
|
60
|
-
logger.info(`🔒 MCP request authenticated with oauth token: ${oauthToken}`);
|
|
61
|
-
}
|
|
62
|
-
else if (authScheme === AuthScheme.API_TOKEN) {
|
|
63
|
-
const apiKey = "x-auth-apikey" in req.headers
|
|
64
|
-
? req.headers["x-auth-apikey"]
|
|
65
|
-
: process.env.RHOMBUS_API_KEY;
|
|
66
|
-
if (!apiKey) {
|
|
67
|
-
throw new Error("Invalid API Key provided! Please check the headers or environment variables");
|
|
68
|
-
}
|
|
69
|
-
logger.info(`🔒 MCP request authenticated with api key: ${apiKey}`);
|
|
70
|
-
authStore.set(sessionId, {
|
|
71
|
-
apiKey: apiKey,
|
|
72
|
-
createdMs: Date.now(),
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
else if (authScheme === AuthScheme.CHATBOT &&
|
|
76
|
-
"x-auth-session" in req.headers &&
|
|
77
|
-
"x-auth-chat" in req.headers) {
|
|
78
|
-
logger.info(`🔒 MCP request authenticated with x-auth-session: ${req.headers["x-auth-session"]} and x-auth-chat: ${req.headers["x-auth-chat"]}`);
|
|
79
|
-
// otherwise, store the sessionId and latestRecordUuid in authStore
|
|
80
|
-
authStore.set(sessionId, {
|
|
81
|
-
sessionId: req.headers["x-auth-session"],
|
|
82
|
-
latestRecordUuid: req.headers["x-auth-chat"],
|
|
83
|
-
createdMs: Date.now(),
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
else if (authScheme === AuthScheme.WEB2 && "x-auth-cookie" in req.headers) {
|
|
87
|
-
authStore.set(sessionId, {
|
|
88
|
-
cookie: req.headers["x-auth-cookie"],
|
|
89
|
-
sessionAlias: req.headers["x-auth-session-alias"],
|
|
90
|
-
createdMs: Date.now(),
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
throw new Error(`Invalid auth scheme provided! x-auth-scheme: ${req.headers["x-auth-scheme"]}, x-auth-session: ${req.headers["x-auth-session"]}, x-auth-chat: ${req.headers["x-auth-chat"]}`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
catch (e) {
|
|
98
|
-
// only throw if this is an auth required call
|
|
99
|
-
if (authRequired.includes(req.body.method)) {
|
|
100
|
-
if (e instanceof Error) {
|
|
101
|
-
logger.error(e.message);
|
|
102
|
-
res
|
|
103
|
-
.status(401)
|
|
104
|
-
.setHeader("WWW-Authenticate", `Bearer realm="${process.env.REALM}", error="invalid_token", error_description="The access token is missing or invalid"`)
|
|
105
|
-
.send(e.message);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
logger.error(e);
|
|
110
|
-
throw e;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
115
|
},
|
|
115
116
|
// DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
|
|
116
117
|
// locally, make sure to set:
|
|
@@ -122,12 +123,15 @@ export default function streamableHttpTransport() {
|
|
|
122
123
|
if (transport.sessionId) {
|
|
123
124
|
transports.delete(transport.sessionId);
|
|
124
125
|
authStore.delete(transport.sessionId);
|
|
126
|
+
clearAccessibleAppsCache(transport.sessionId);
|
|
125
127
|
}
|
|
126
128
|
};
|
|
127
|
-
|
|
129
|
+
// newSessionId is already in authStore; createServer can resolve
|
|
130
|
+
// accessibleRhombusApps and pick the right tool set.
|
|
131
|
+
const server = await createServer({ sessionId: newSessionId });
|
|
128
132
|
// Connect to the MCP server
|
|
129
133
|
await server.connect(transport);
|
|
130
|
-
logger.info(`🔗 Transport connected with sessionId: ${
|
|
134
|
+
logger.info(`🔗 Transport connected with sessionId: ${newSessionId}`);
|
|
131
135
|
}
|
|
132
136
|
else {
|
|
133
137
|
// Invalid request
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|