@elnora-ai/mcp-server 0.1.0 → 0.2.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 +2 -2
- package/dist/auth/clients-store.d.ts +13 -0
- package/dist/auth/clients-store.d.ts.map +1 -0
- package/dist/auth/clients-store.js +35 -0
- package/dist/auth/clients-store.js.map +1 -0
- package/dist/auth/provider.d.ts +55 -0
- package/dist/auth/provider.d.ts.map +1 -0
- package/dist/auth/provider.js +234 -0
- package/dist/auth/provider.js.map +1 -0
- package/dist/constants.d.ts +7 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +18 -4
- package/dist/constants.js.map +1 -1
- package/dist/index.js +81 -13
- package/dist/index.js.map +1 -1
- package/dist/middleware/cors.d.ts +11 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +42 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/rate-limiter.d.ts +13 -0
- package/dist/middleware/rate-limiter.d.ts.map +1 -0
- package/dist/middleware/rate-limiter.js +59 -0
- package/dist/middleware/rate-limiter.js.map +1 -0
- package/dist/middleware/tool-logging.d.ts +11 -0
- package/dist/middleware/tool-logging.d.ts.map +1 -0
- package/dist/middleware/tool-logging.js +40 -0
- package/dist/middleware/tool-logging.js.map +1 -0
- package/dist/server.d.ts +6 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +10 -6
- package/dist/server.js.map +1 -1
- package/dist/services/elnora-api-client.d.ts +0 -2
- package/dist/services/elnora-api-client.d.ts.map +1 -1
- package/dist/services/elnora-api-client.js +0 -4
- package/dist/services/elnora-api-client.js.map +1 -1
- package/dist/tools/files.d.ts +2 -1
- package/dist/tools/files.d.ts.map +1 -1
- package/dist/tools/files.js +8 -7
- package/dist/tools/files.js.map +1 -1
- package/dist/tools/messages.d.ts +2 -1
- package/dist/tools/messages.d.ts.map +1 -1
- package/dist/tools/messages.js +5 -4
- package/dist/tools/messages.js.map +1 -1
- package/dist/tools/protocols.d.ts +2 -1
- package/dist/tools/protocols.d.ts.map +1 -1
- package/dist/tools/protocols.js +4 -3
- package/dist/tools/protocols.js.map +1 -1
- package/dist/tools/scope-guard.d.ts +19 -0
- package/dist/tools/scope-guard.d.ts.map +1 -0
- package/dist/tools/scope-guard.js +33 -0
- package/dist/tools/scope-guard.js.map +1 -0
- package/dist/tools/tasks.d.ts +2 -1
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +8 -7
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/with-guard.d.ts +19 -0
- package/dist/tools/with-guard.d.ts.map +1 -0
- package/dist/tools/with-guard.js +32 -0
- package/dist/tools/with-guard.js.map +1 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -4
- package/dist/auth/middleware.d.ts +0 -12
- package/dist/auth/middleware.d.ts.map +0 -1
- package/dist/auth/middleware.js +0 -40
- package/dist/auth/middleware.js.map +0 -1
- package/dist/auth/protected-resource.d.ts +0 -4
- package/dist/auth/protected-resource.d.ts.map +0 -1
- package/dist/auth/protected-resource.js +0 -19
- package/dist/auth/protected-resource.js.map +0 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Add the Elnora MCP server to your AI client. No installation required — just p
|
|
|
15
15
|
### Claude Code
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
claude mcp add elnora --transport
|
|
18
|
+
claude mcp add elnora --transport http https://mcp.elnora.ai/mcp
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
### Cursor
|
|
@@ -40,7 +40,7 @@ Add to your `.vscode/mcp.json`:
|
|
|
40
40
|
{
|
|
41
41
|
"servers": {
|
|
42
42
|
"elnora": {
|
|
43
|
-
"type": "
|
|
43
|
+
"type": "http",
|
|
44
44
|
"url": "https://mcp.elnora.ai/mcp"
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { OAuthRegisteredClientsStore } from "@modelcontextprotocol/sdk/server/auth/clients.js";
|
|
2
|
+
import { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
3
|
+
/**
|
|
4
|
+
* In-memory OAuth client store supporting Dynamic Client Registration (RFC 7591).
|
|
5
|
+
*
|
|
6
|
+
* For single-instance deployments (ECS). Migrate to Redis/DynamoDB for multi-instance.
|
|
7
|
+
*/
|
|
8
|
+
export declare class InMemoryClientsStore implements OAuthRegisteredClientsStore {
|
|
9
|
+
private clients;
|
|
10
|
+
getClient(clientId: string): OAuthClientInformationFull | undefined;
|
|
11
|
+
registerClient(client: Omit<OAuthClientInformationFull, "client_id" | "client_id_issued_at">): OAuthClientInformationFull;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=clients-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clients-store.d.ts","sourceRoot":"","sources":["../../src/auth/clients-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,MAAM,kDAAkD,CAAC;AAC/F,OAAO,EAAE,0BAA0B,EAAE,MAAM,0CAA0C,CAAC;AAGtF;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,2BAA2B;IACtE,OAAO,CAAC,OAAO,CAAiD;IAEhE,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS;IAYnE,cAAc,CACZ,MAAM,EAAE,IAAI,CAAC,0BAA0B,EAAE,WAAW,GAAG,qBAAqB,CAAC,GAC5E,0BAA0B;CAgB9B"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { CLIENT_SECRET_TTL_SECONDS } from "../constants.js";
|
|
3
|
+
/**
|
|
4
|
+
* In-memory OAuth client store supporting Dynamic Client Registration (RFC 7591).
|
|
5
|
+
*
|
|
6
|
+
* For single-instance deployments (ECS). Migrate to Redis/DynamoDB for multi-instance.
|
|
7
|
+
*/
|
|
8
|
+
export class InMemoryClientsStore {
|
|
9
|
+
clients = new Map();
|
|
10
|
+
getClient(clientId) {
|
|
11
|
+
const client = this.clients.get(clientId);
|
|
12
|
+
if (!client)
|
|
13
|
+
return undefined;
|
|
14
|
+
// Check secret expiration (per SDK docs: don't delete, just check)
|
|
15
|
+
if (client.client_secret_expires_at && client.client_secret_expires_at < Math.floor(Date.now() / 1000)) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
return client;
|
|
19
|
+
}
|
|
20
|
+
registerClient(client) {
|
|
21
|
+
const clientId = crypto.randomUUID();
|
|
22
|
+
const clientSecret = crypto.randomBytes(32).toString("base64url");
|
|
23
|
+
const now = Math.floor(Date.now() / 1000);
|
|
24
|
+
const registered = {
|
|
25
|
+
...client,
|
|
26
|
+
client_id: clientId,
|
|
27
|
+
client_id_issued_at: now,
|
|
28
|
+
client_secret: clientSecret,
|
|
29
|
+
client_secret_expires_at: now + CLIENT_SECRET_TTL_SECONDS,
|
|
30
|
+
};
|
|
31
|
+
this.clients.set(clientId, registered);
|
|
32
|
+
return registered;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=clients-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clients-store.js","sourceRoot":"","sources":["../../src/auth/clients-store.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AAGjC,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAE5D;;;;GAIG;AACH,MAAM,OAAO,oBAAoB;IACvB,OAAO,GAAG,IAAI,GAAG,EAAsC,CAAC;IAEhE,SAAS,CAAC,QAAgB;QACxB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1C,IAAI,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;QAE9B,mEAAmE;QACnE,IAAI,MAAM,CAAC,wBAAwB,IAAI,MAAM,CAAC,wBAAwB,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;YACvG,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,cAAc,CACZ,MAA6E;QAE7E,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;QACrC,MAAM,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAClE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAE1C,MAAM,UAAU,GAA+B;YAC7C,GAAG,MAAM;YACT,SAAS,EAAE,QAAQ;YACnB,mBAAmB,EAAE,GAAG;YACxB,aAAa,EAAE,YAAY;YAC3B,wBAAwB,EAAE,GAAG,GAAG,yBAAyB;SAC1D,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACvC,OAAO,UAAU,CAAC;IACpB,CAAC;CACF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Response } from "express";
|
|
2
|
+
import { OAuthServerProvider, AuthorizationParams } from "@modelcontextprotocol/sdk/server/auth/provider.js";
|
|
3
|
+
import { OAuthRegisteredClientsStore } from "@modelcontextprotocol/sdk/server/auth/clients.js";
|
|
4
|
+
import { OAuthClientInformationFull, OAuthTokens, OAuthTokenRevocationRequest } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
5
|
+
import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
|
|
6
|
+
import { ElnoraConfig } from "../types.js";
|
|
7
|
+
/**
|
|
8
|
+
* OAuth 2.1 provider that proxies to the Elnora platform auth system.
|
|
9
|
+
*
|
|
10
|
+
* Flow:
|
|
11
|
+
* 1. MCP client calls /authorize → provider redirects to Elnora platform login
|
|
12
|
+
* 2. User authenticates on platform → platform redirects back with platform auth code
|
|
13
|
+
* 3. MCP server exchanges platform code for platform token
|
|
14
|
+
* 4. MCP server issues its own access + refresh tokens, mapped to the platform token
|
|
15
|
+
* 5. On token validation, the provider checks its own token store and validates
|
|
16
|
+
* the underlying platform token hasn't been revoked
|
|
17
|
+
*
|
|
18
|
+
* CoSAI controls:
|
|
19
|
+
* - MCP-T1: PKCE S256 mandatory (handled by SDK)
|
|
20
|
+
* - MCP-T1: No token passthrough — MCP tokens are separate from platform tokens
|
|
21
|
+
* - MCP-T7: Short-lived access tokens (1h), refresh token rotation
|
|
22
|
+
* - MCP-T9: Audience binding via resource parameter
|
|
23
|
+
* - MCP-T12: Auth event logging
|
|
24
|
+
*/
|
|
25
|
+
export declare class ElnoraOAuthProvider implements OAuthServerProvider {
|
|
26
|
+
private _clientsStore;
|
|
27
|
+
private authSessions;
|
|
28
|
+
private tokenRecords;
|
|
29
|
+
private refreshTokenIndex;
|
|
30
|
+
private config;
|
|
31
|
+
constructor(config: ElnoraConfig);
|
|
32
|
+
get clientsStore(): OAuthRegisteredClientsStore;
|
|
33
|
+
/**
|
|
34
|
+
* Redirect the user to the Elnora platform login page.
|
|
35
|
+
* After authentication, the platform redirects back to our callback,
|
|
36
|
+
* which completes the OAuth flow back to the MCP client.
|
|
37
|
+
*/
|
|
38
|
+
authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise<void>;
|
|
39
|
+
challengeForAuthorizationCode(_client: OAuthClientInformationFull, authorizationCode: string): Promise<string>;
|
|
40
|
+
exchangeAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string, _codeVerifier?: string, _redirectUri?: string, resource?: URL): Promise<OAuthTokens>;
|
|
41
|
+
exchangeRefreshToken(client: OAuthClientInformationFull, refreshToken: string, scopes?: string[], resource?: URL): Promise<OAuthTokens>;
|
|
42
|
+
verifyAccessToken(token: string): Promise<AuthInfo>;
|
|
43
|
+
revokeToken(client: OAuthClientInformationFull, request: OAuthTokenRevocationRequest): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Handle the callback from the Elnora platform login.
|
|
46
|
+
* Called by the /oauth/callback route after the user authenticates.
|
|
47
|
+
*/
|
|
48
|
+
handlePlatformCallback(mcpCode: string, platformCode: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Get redirect URL for MCP client after platform callback.
|
|
51
|
+
*/
|
|
52
|
+
getClientRedirectUrl(mcpCode: string): string;
|
|
53
|
+
private issueTokens;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/auth/provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,mDAAmD,CAAC;AAC7G,OAAO,EAAE,2BAA2B,EAAE,MAAM,kDAAkD,CAAC;AAC/F,OAAO,EACL,0BAA0B,EAC1B,WAAW,EACX,2BAA2B,EAC5B,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,gDAAgD,CAAC;AAE1E,OAAO,EAAE,YAAY,EAAqC,MAAM,aAAa,CAAC;AAQ9E;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,mBAAoB,YAAW,mBAAmB;IAC7D,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,MAAM,CAAe;gBAEjB,MAAM,EAAE,YAAY;IAKhC,IAAI,YAAY,IAAI,2BAA2B,CAE9C;IAED;;;;OAIG;IACG,SAAS,CAAC,MAAM,EAAE,0BAA0B,EAAE,MAAM,EAAE,mBAAmB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BxG,6BAA6B,CACjC,OAAO,EAAE,0BAA0B,EACnC,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,MAAM,CAAC;IAQZ,yBAAyB,CAC7B,MAAM,EAAE,0BAA0B,EAClC,iBAAiB,EAAE,MAAM,EACzB,aAAa,CAAC,EAAE,MAAM,EACtB,YAAY,CAAC,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,GAAG,GACb,OAAO,CAAC,WAAW,CAAC;IAoCjB,oBAAoB,CACxB,MAAM,EAAE,0BAA0B,EAClC,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,MAAM,EAAE,EACjB,QAAQ,CAAC,EAAE,GAAG,GACb,OAAO,CAAC,WAAW,CAAC;IA0BjB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAyCnD,WAAW,CACf,MAAM,EAAE,0BAA0B,EAClC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,IAAI,CAAC;IAwBhB;;;OAGG;IACH,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAmBnE;;OAEG;IACH,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAc7C,OAAO,CAAC,WAAW;CAwCpB"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import { InMemoryClientsStore } from "./clients-store.js";
|
|
4
|
+
import { ACCESS_TOKEN_TTL_SECONDS, REFRESH_TOKEN_TTL_SECONDS, AUTH_CODE_TTL_SECONDS, REQUEST_TIMEOUT_MS, } from "../constants.js";
|
|
5
|
+
/**
|
|
6
|
+
* OAuth 2.1 provider that proxies to the Elnora platform auth system.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. MCP client calls /authorize → provider redirects to Elnora platform login
|
|
10
|
+
* 2. User authenticates on platform → platform redirects back with platform auth code
|
|
11
|
+
* 3. MCP server exchanges platform code for platform token
|
|
12
|
+
* 4. MCP server issues its own access + refresh tokens, mapped to the platform token
|
|
13
|
+
* 5. On token validation, the provider checks its own token store and validates
|
|
14
|
+
* the underlying platform token hasn't been revoked
|
|
15
|
+
*
|
|
16
|
+
* CoSAI controls:
|
|
17
|
+
* - MCP-T1: PKCE S256 mandatory (handled by SDK)
|
|
18
|
+
* - MCP-T1: No token passthrough — MCP tokens are separate from platform tokens
|
|
19
|
+
* - MCP-T7: Short-lived access tokens (1h), refresh token rotation
|
|
20
|
+
* - MCP-T9: Audience binding via resource parameter
|
|
21
|
+
* - MCP-T12: Auth event logging
|
|
22
|
+
*/
|
|
23
|
+
export class ElnoraOAuthProvider {
|
|
24
|
+
_clientsStore;
|
|
25
|
+
authSessions = new Map();
|
|
26
|
+
tokenRecords = new Map();
|
|
27
|
+
refreshTokenIndex = new Map(); // refreshToken -> accessToken
|
|
28
|
+
config;
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.config = config;
|
|
31
|
+
this._clientsStore = new InMemoryClientsStore();
|
|
32
|
+
}
|
|
33
|
+
get clientsStore() {
|
|
34
|
+
return this._clientsStore;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Redirect the user to the Elnora platform login page.
|
|
38
|
+
* After authentication, the platform redirects back to our callback,
|
|
39
|
+
* which completes the OAuth flow back to the MCP client.
|
|
40
|
+
*/
|
|
41
|
+
async authorize(client, params, res) {
|
|
42
|
+
const authCode = crypto.randomBytes(32).toString("base64url");
|
|
43
|
+
// Store session for later exchange
|
|
44
|
+
this.authSessions.set(authCode, {
|
|
45
|
+
clientId: client.client_id,
|
|
46
|
+
codeChallenge: params.codeChallenge,
|
|
47
|
+
redirectUri: params.redirectUri,
|
|
48
|
+
scopes: params.scopes || [],
|
|
49
|
+
state: params.state,
|
|
50
|
+
resource: params.resource?.toString(),
|
|
51
|
+
createdAt: Date.now(),
|
|
52
|
+
});
|
|
53
|
+
// Schedule cleanup of expired sessions
|
|
54
|
+
setTimeout(() => this.authSessions.delete(authCode), AUTH_CODE_TTL_SECONDS * 1000);
|
|
55
|
+
// Redirect to Elnora platform login
|
|
56
|
+
const loginUrl = new URL(this.config.loginUrl);
|
|
57
|
+
loginUrl.searchParams.set("mcp_code", authCode);
|
|
58
|
+
loginUrl.searchParams.set("redirect_uri", `${this.config.publicUrl}/oauth/callback`);
|
|
59
|
+
loginUrl.searchParams.set("client_id", this.config.platformClientId);
|
|
60
|
+
console.error(`[auth] authorize: redirecting client ${client.client_id} to platform login`);
|
|
61
|
+
res.redirect(loginUrl.toString());
|
|
62
|
+
}
|
|
63
|
+
async challengeForAuthorizationCode(_client, authorizationCode) {
|
|
64
|
+
const session = this.authSessions.get(authorizationCode);
|
|
65
|
+
if (!session) {
|
|
66
|
+
throw new Error("Invalid or expired authorization code");
|
|
67
|
+
}
|
|
68
|
+
return session.codeChallenge;
|
|
69
|
+
}
|
|
70
|
+
async exchangeAuthorizationCode(client, authorizationCode, _codeVerifier, _redirectUri, resource) {
|
|
71
|
+
const session = this.authSessions.get(authorizationCode);
|
|
72
|
+
if (!session) {
|
|
73
|
+
throw new Error("Invalid or expired authorization code");
|
|
74
|
+
}
|
|
75
|
+
if (session.clientId !== client.client_id) {
|
|
76
|
+
throw new Error("Authorization code was not issued to this client");
|
|
77
|
+
}
|
|
78
|
+
// Delete the auth code — single use only
|
|
79
|
+
this.authSessions.delete(authorizationCode);
|
|
80
|
+
// Exchange with Elnora platform for a platform token
|
|
81
|
+
let platformToken;
|
|
82
|
+
try {
|
|
83
|
+
const response = await axios.post(this.config.tokenExchangeUrl, {
|
|
84
|
+
grant_type: "authorization_code",
|
|
85
|
+
code: session.platformCode || authorizationCode,
|
|
86
|
+
client_id: this.config.platformClientId,
|
|
87
|
+
client_secret: this.config.platformClientSecret,
|
|
88
|
+
}, { timeout: REQUEST_TIMEOUT_MS });
|
|
89
|
+
platformToken = response.data.access_token;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error("[auth] platform token exchange failed:", error);
|
|
93
|
+
throw new Error("Failed to exchange authorization code with platform");
|
|
94
|
+
}
|
|
95
|
+
// Issue MCP tokens (separate from platform token — CoSAI MCP-T1)
|
|
96
|
+
return this.issueTokens(client.client_id, session.scopes, platformToken, resource?.toString());
|
|
97
|
+
}
|
|
98
|
+
async exchangeRefreshToken(client, refreshToken, scopes, resource) {
|
|
99
|
+
const accessTokenKey = this.refreshTokenIndex.get(refreshToken);
|
|
100
|
+
if (!accessTokenKey) {
|
|
101
|
+
throw new Error("Invalid refresh token");
|
|
102
|
+
}
|
|
103
|
+
const record = this.tokenRecords.get(accessTokenKey);
|
|
104
|
+
if (!record || record.clientId !== client.client_id) {
|
|
105
|
+
throw new Error("Invalid refresh token");
|
|
106
|
+
}
|
|
107
|
+
// Rotate: delete old tokens
|
|
108
|
+
this.tokenRecords.delete(accessTokenKey);
|
|
109
|
+
this.refreshTokenIndex.delete(refreshToken);
|
|
110
|
+
console.error(`[auth] refresh token rotation for client ${client.client_id}`);
|
|
111
|
+
// Issue new tokens with the same platform token
|
|
112
|
+
return this.issueTokens(client.client_id, scopes || record.scopes, record.platformToken, resource?.toString() || record.resource);
|
|
113
|
+
}
|
|
114
|
+
async verifyAccessToken(token) {
|
|
115
|
+
const record = this.tokenRecords.get(token);
|
|
116
|
+
if (!record) {
|
|
117
|
+
throw new Error("Invalid access token");
|
|
118
|
+
}
|
|
119
|
+
if (record.expiresAt < Math.floor(Date.now() / 1000)) {
|
|
120
|
+
this.tokenRecords.delete(token);
|
|
121
|
+
throw new Error("Access token expired");
|
|
122
|
+
}
|
|
123
|
+
// Validate platform token is still valid
|
|
124
|
+
try {
|
|
125
|
+
const validation = await axios.post(this.config.tokenValidationUrl, { token: record.platformToken }, { timeout: REQUEST_TIMEOUT_MS });
|
|
126
|
+
if (!validation.data.valid) {
|
|
127
|
+
this.tokenRecords.delete(token);
|
|
128
|
+
throw new Error("Underlying platform token revoked");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
|
133
|
+
this.tokenRecords.delete(token);
|
|
134
|
+
throw new Error("Underlying platform token revoked");
|
|
135
|
+
}
|
|
136
|
+
// Network errors — don't invalidate, just log
|
|
137
|
+
console.error("[auth] platform token validation failed (non-fatal):", error);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
token,
|
|
141
|
+
clientId: record.clientId,
|
|
142
|
+
scopes: record.scopes,
|
|
143
|
+
expiresAt: record.expiresAt,
|
|
144
|
+
resource: record.resource ? new URL(record.resource) : undefined,
|
|
145
|
+
extra: { platformToken: record.platformToken },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
async revokeToken(client, request) {
|
|
149
|
+
const token = request.token;
|
|
150
|
+
// Check if it's an access token
|
|
151
|
+
const record = this.tokenRecords.get(token);
|
|
152
|
+
if (record && record.clientId === client.client_id) {
|
|
153
|
+
this.refreshTokenIndex.delete(record.refreshToken);
|
|
154
|
+
this.tokenRecords.delete(token);
|
|
155
|
+
console.error(`[auth] revoked access token for client ${client.client_id}`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Check if it's a refresh token
|
|
159
|
+
const accessTokenKey = this.refreshTokenIndex.get(token);
|
|
160
|
+
if (accessTokenKey) {
|
|
161
|
+
const rec = this.tokenRecords.get(accessTokenKey);
|
|
162
|
+
if (rec && rec.clientId === client.client_id) {
|
|
163
|
+
this.tokenRecords.delete(accessTokenKey);
|
|
164
|
+
this.refreshTokenIndex.delete(token);
|
|
165
|
+
console.error(`[auth] revoked refresh token for client ${client.client_id}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Handle the callback from the Elnora platform login.
|
|
171
|
+
* Called by the /oauth/callback route after the user authenticates.
|
|
172
|
+
*/
|
|
173
|
+
handlePlatformCallback(mcpCode, platformCode) {
|
|
174
|
+
const session = this.authSessions.get(mcpCode);
|
|
175
|
+
if (!session) {
|
|
176
|
+
throw new Error("Invalid or expired MCP authorization code");
|
|
177
|
+
}
|
|
178
|
+
// Store the platform code for later exchange
|
|
179
|
+
session.platformCode = platformCode;
|
|
180
|
+
// Build the redirect back to the MCP client
|
|
181
|
+
const redirectUrl = new URL(session.redirectUri);
|
|
182
|
+
redirectUrl.searchParams.set("code", mcpCode);
|
|
183
|
+
if (session.state) {
|
|
184
|
+
redirectUrl.searchParams.set("state", session.state);
|
|
185
|
+
}
|
|
186
|
+
console.error(`[auth] platform callback: completing auth for client ${session.clientId}`);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get redirect URL for MCP client after platform callback.
|
|
190
|
+
*/
|
|
191
|
+
getClientRedirectUrl(mcpCode) {
|
|
192
|
+
const session = this.authSessions.get(mcpCode);
|
|
193
|
+
if (!session) {
|
|
194
|
+
throw new Error("Invalid or expired MCP authorization code");
|
|
195
|
+
}
|
|
196
|
+
const redirectUrl = new URL(session.redirectUri);
|
|
197
|
+
redirectUrl.searchParams.set("code", mcpCode);
|
|
198
|
+
if (session.state) {
|
|
199
|
+
redirectUrl.searchParams.set("state", session.state);
|
|
200
|
+
}
|
|
201
|
+
return redirectUrl.toString();
|
|
202
|
+
}
|
|
203
|
+
issueTokens(clientId, scopes, platformToken, resource) {
|
|
204
|
+
const accessToken = crypto.randomBytes(32).toString("base64url");
|
|
205
|
+
const refreshToken = crypto.randomBytes(32).toString("base64url");
|
|
206
|
+
const now = Math.floor(Date.now() / 1000);
|
|
207
|
+
const record = {
|
|
208
|
+
accessToken,
|
|
209
|
+
refreshToken,
|
|
210
|
+
platformToken,
|
|
211
|
+
clientId,
|
|
212
|
+
scopes,
|
|
213
|
+
resource,
|
|
214
|
+
expiresAt: now + ACCESS_TOKEN_TTL_SECONDS,
|
|
215
|
+
createdAt: now,
|
|
216
|
+
};
|
|
217
|
+
this.tokenRecords.set(accessToken, record);
|
|
218
|
+
this.refreshTokenIndex.set(refreshToken, accessToken);
|
|
219
|
+
// Schedule cleanup of expired tokens (cap at ~24 days to stay within 32-bit setTimeout limit)
|
|
220
|
+
const cleanupMs = Math.min(REFRESH_TOKEN_TTL_SECONDS * 1000, 2_000_000_000);
|
|
221
|
+
setTimeout(() => {
|
|
222
|
+
this.tokenRecords.delete(accessToken);
|
|
223
|
+
this.refreshTokenIndex.delete(refreshToken);
|
|
224
|
+
}, cleanupMs);
|
|
225
|
+
console.error(`[auth] issued tokens for client ${clientId} (expires ${new Date((now + ACCESS_TOKEN_TTL_SECONDS) * 1000).toISOString()})`);
|
|
226
|
+
return {
|
|
227
|
+
access_token: accessToken,
|
|
228
|
+
token_type: "Bearer",
|
|
229
|
+
expires_in: ACCESS_TOKEN_TTL_SECONDS,
|
|
230
|
+
refresh_token: refreshToken,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/auth/provider.ts"],"names":[],"mappings":"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"}
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
export declare const CHARACTER_LIMIT =
|
|
2
|
-
export declare const DEFAULT_PAGE_SIZE =
|
|
1
|
+
export declare const CHARACTER_LIMIT = 100000;
|
|
2
|
+
export declare const DEFAULT_PAGE_SIZE = 50;
|
|
3
3
|
export declare const MAX_PAGE_SIZE = 100;
|
|
4
4
|
export declare const REQUEST_TIMEOUT_MS = 30000;
|
|
5
5
|
export declare const LONG_REQUEST_TIMEOUT_MS = 120000;
|
|
6
|
+
export declare const ACCESS_TOKEN_TTL_SECONDS = 3600;
|
|
7
|
+
export declare const REFRESH_TOKEN_TTL_SECONDS: number;
|
|
8
|
+
export declare const AUTH_CODE_TTL_SECONDS = 300;
|
|
9
|
+
export declare const CLIENT_SECRET_TTL_SECONDS: number;
|
|
10
|
+
export declare const SUPPORTED_SCOPES: readonly ["tasks:read", "tasks:write", "files:read", "files:write", "messages:read", "messages:write"];
|
|
6
11
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,SAAU,CAAC;AACvC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,eAAO,MAAM,aAAa,MAAM,CAAC;AACjC,eAAO,MAAM,kBAAkB,QAAS,CAAC;AACzC,eAAO,MAAM,uBAAuB,SAAU,CAAC;AAG/C,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,eAAO,MAAM,yBAAyB,QAAiB,CAAC;AACxD,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,yBAAyB,QAAiB,CAAC;AAGxD,eAAO,MAAM,gBAAgB,wGAOnB,CAAC"}
|
package/dist/constants.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
export const CHARACTER_LIMIT =
|
|
2
|
-
export const DEFAULT_PAGE_SIZE =
|
|
1
|
+
export const CHARACTER_LIMIT = 100_000;
|
|
2
|
+
export const DEFAULT_PAGE_SIZE = 50;
|
|
3
3
|
export const MAX_PAGE_SIZE = 100;
|
|
4
|
-
export const REQUEST_TIMEOUT_MS =
|
|
5
|
-
export const LONG_REQUEST_TIMEOUT_MS =
|
|
4
|
+
export const REQUEST_TIMEOUT_MS = 30_000;
|
|
5
|
+
export const LONG_REQUEST_TIMEOUT_MS = 120_000;
|
|
6
|
+
// Auth constants
|
|
7
|
+
export const ACCESS_TOKEN_TTL_SECONDS = 3600; // 1 hour
|
|
8
|
+
export const REFRESH_TOKEN_TTL_SECONDS = 30 * 24 * 3600; // 30 days
|
|
9
|
+
export const AUTH_CODE_TTL_SECONDS = 300; // 5 minutes
|
|
10
|
+
export const CLIENT_SECRET_TTL_SECONDS = 90 * 24 * 3600; // 90 days
|
|
11
|
+
// MCP scopes
|
|
12
|
+
export const SUPPORTED_SCOPES = [
|
|
13
|
+
"tasks:read",
|
|
14
|
+
"tasks:write",
|
|
15
|
+
"files:read",
|
|
16
|
+
"files:write",
|
|
17
|
+
"messages:read",
|
|
18
|
+
"messages:write",
|
|
19
|
+
];
|
|
6
20
|
//# sourceMappingURL=constants.js.map
|
package/dist/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC;AACvC,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AACpC,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AACjC,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AACzC,MAAM,CAAC,MAAM,uBAAuB,GAAG,OAAO,CAAC;AAE/C,iBAAiB;AACjB,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC,CAAC,SAAS;AACvD,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,UAAU;AACnE,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC,CAAC,YAAY;AACtD,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,UAAU;AAEnE,aAAa;AACb,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,YAAY;IACZ,aAAa;IACb,YAAY;IACZ,aAAa;IACb,eAAe;IACf,gBAAgB;CACR,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
2
|
+
import { mcpAuthRouter, getOAuthProtectedResourceMetadataUrl } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
3
|
+
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
2
4
|
import express from "express";
|
|
3
5
|
import { ElnoraApiClient } from "./services/elnora-api-client.js";
|
|
4
|
-
import {
|
|
5
|
-
import { protectedResourceMetadataHandler } from "./auth/protected-resource.js";
|
|
6
|
+
import { ElnoraOAuthProvider } from "./auth/provider.js";
|
|
6
7
|
import { createElnoraServer } from "./server.js";
|
|
8
|
+
import { corsMiddleware } from "./middleware/cors.js";
|
|
9
|
+
import { mcpRateLimiter } from "./middleware/rate-limiter.js";
|
|
10
|
+
import { SUPPORTED_SCOPES } from "./constants.js";
|
|
7
11
|
function requireEnv(name) {
|
|
8
12
|
const value = process.env[name];
|
|
9
13
|
if (!value) {
|
|
@@ -17,25 +21,81 @@ function loadConfig() {
|
|
|
17
21
|
authUrl: requireEnv("ELNORA_AUTH_URL"),
|
|
18
22
|
tokenValidationUrl: requireEnv("ELNORA_TOKEN_VALIDATION_URL"),
|
|
19
23
|
port: parseInt(process.env.PORT || "3000", 10),
|
|
24
|
+
publicUrl: requireEnv("ELNORA_PUBLIC_URL"),
|
|
25
|
+
loginUrl: requireEnv("ELNORA_LOGIN_URL"),
|
|
26
|
+
tokenExchangeUrl: requireEnv("ELNORA_TOKEN_EXCHANGE_URL"),
|
|
27
|
+
platformClientId: requireEnv("ELNORA_PLATFORM_CLIENT_ID"),
|
|
28
|
+
platformClientSecret: requireEnv("ELNORA_PLATFORM_CLIENT_SECRET"),
|
|
20
29
|
};
|
|
21
30
|
}
|
|
22
31
|
async function main() {
|
|
23
32
|
const config = loadConfig();
|
|
24
33
|
const app = express();
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
// --- Security middleware (CoSAI MCP-T7) ---
|
|
35
|
+
app.use(corsMiddleware(config));
|
|
36
|
+
app.use(express.json({ limit: "1mb" })); // Payload size limit (CoSAI MCP-T10)
|
|
37
|
+
// Health check (no auth)
|
|
27
38
|
app.get("/health", (_req, res) => {
|
|
28
39
|
res.json({ status: "ok", service: "elnora-mcp-server" });
|
|
29
40
|
});
|
|
30
|
-
// OAuth
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
// --- OAuth 2.1 Authorization Server ---
|
|
42
|
+
const provider = new ElnoraOAuthProvider(config);
|
|
43
|
+
const issuerUrl = new URL(config.publicUrl);
|
|
44
|
+
const mcpServerUrl = new URL(`${config.publicUrl}/mcp`);
|
|
45
|
+
// Install OAuth routes: /.well-known/oauth-authorization-server,
|
|
46
|
+
// /.well-known/oauth-protected-resource, /authorize, /token, /register, /revoke
|
|
47
|
+
app.use(mcpAuthRouter({
|
|
48
|
+
provider,
|
|
49
|
+
issuerUrl,
|
|
50
|
+
resourceServerUrl: mcpServerUrl,
|
|
51
|
+
scopesSupported: [...SUPPORTED_SCOPES],
|
|
52
|
+
resourceName: "Elnora MCP Server",
|
|
53
|
+
serviceDocumentationUrl: new URL("https://github.com/Elnora-AI/elnora-mcp-server"),
|
|
54
|
+
}));
|
|
55
|
+
// Platform OAuth callback — receives the auth code from Elnora platform login
|
|
56
|
+
// CSRF protection: validates mcp_code exists in our session store (CoSAI MCP-T7)
|
|
57
|
+
app.get("/oauth/callback", (req, res) => {
|
|
58
|
+
const mcpCode = req.query.mcp_code;
|
|
59
|
+
const platformCode = req.query.code;
|
|
60
|
+
const error = req.query.error;
|
|
61
|
+
if (error) {
|
|
62
|
+
console.error(`[auth] platform callback error: ${error}`);
|
|
63
|
+
res.status(400).json({ error: "platform_auth_failed", error_description: error });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!mcpCode || !platformCode) {
|
|
67
|
+
res.status(400).json({ error: "invalid_request", error_description: "Missing mcp_code or code parameter" });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
provider.handlePlatformCallback(mcpCode, platformCode);
|
|
72
|
+
const redirectUrl = provider.getClientRedirectUrl(mcpCode);
|
|
73
|
+
res.redirect(redirectUrl);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.error("[auth] platform callback failed:", err);
|
|
77
|
+
res.status(400).json({
|
|
78
|
+
error: "invalid_grant",
|
|
79
|
+
error_description: err instanceof Error ? err.message : "Callback processing failed",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// --- MCP Endpoint (protected by bearer auth + rate limiting) ---
|
|
84
|
+
const authMiddleware = requireBearerAuth({
|
|
85
|
+
verifier: provider,
|
|
86
|
+
requiredScopes: [],
|
|
87
|
+
resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl),
|
|
88
|
+
});
|
|
89
|
+
app.post("/mcp", mcpRateLimiter(), authMiddleware, async (req, res) => {
|
|
34
90
|
try {
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
91
|
+
// Extract auth context (set by provider.verifyAccessToken)
|
|
92
|
+
const platformToken = req.auth?.extra?.platformToken || "";
|
|
93
|
+
const clientId = req.auth?.clientId || "unknown";
|
|
94
|
+
const scopes = req.auth?.scopes || [];
|
|
95
|
+
// Create per-request MCP server and API client with auth context
|
|
96
|
+
const client = new ElnoraApiClient(config, platformToken);
|
|
97
|
+
const getContext = () => ({ client, clientId, scopes });
|
|
98
|
+
const server = createElnoraServer(config, getContext);
|
|
39
99
|
// Stateless transport — new transport per request
|
|
40
100
|
const transport = new StreamableHTTPServerTransport({
|
|
41
101
|
sessionIdGenerator: undefined,
|
|
@@ -52,9 +112,17 @@ async function main() {
|
|
|
52
112
|
}
|
|
53
113
|
}
|
|
54
114
|
});
|
|
115
|
+
// GET and DELETE on /mcp return 405 per MCP Streamable HTTP spec (stateless mode)
|
|
116
|
+
app.get("/mcp", (_req, res) => {
|
|
117
|
+
res.status(405).json({ error: "method_not_allowed", error_description: "Stateless server — use POST" });
|
|
118
|
+
});
|
|
119
|
+
app.delete("/mcp", (_req, res) => {
|
|
120
|
+
res.status(405).json({ error: "method_not_allowed", error_description: "Stateless server — sessions not supported" });
|
|
121
|
+
});
|
|
55
122
|
app.listen(config.port, () => {
|
|
56
123
|
console.error(`Elnora MCP server running on http://localhost:${config.port}/mcp`);
|
|
57
|
-
console.error(`
|
|
124
|
+
console.error(`OAuth AS Metadata: ${config.publicUrl}/.well-known/oauth-authorization-server`);
|
|
125
|
+
console.error(`Protected Resource Metadata: ${getOAuthProtectedResourceMetadataUrl(mcpServerUrl)}`);
|
|
58
126
|
console.error(`Health check: http://localhost:${config.port}/health`);
|
|
59
127
|
});
|
|
60
128
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { RequestHandler } from "express";
|
|
2
|
+
import { ElnoraConfig } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* CORS middleware restricting origins to the Elnora platform domain.
|
|
5
|
+
* CoSAI MCP-T7: Prevent cross-origin data leaks and CORS policy bypass.
|
|
6
|
+
*
|
|
7
|
+
* MCP clients communicate via direct HTTP (not browser), so CORS is primarily
|
|
8
|
+
* defense-in-depth against browser-based attacks targeting the MCP endpoint.
|
|
9
|
+
*/
|
|
10
|
+
export declare function corsMiddleware(config: ElnoraConfig): RequestHandler;
|
|
11
|
+
//# sourceMappingURL=cors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../src/middleware/cors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CAsCnE"}
|