@ainote/mcp 1.3.2 → 1.3.3

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/lib/auth/oauth.js CHANGED
@@ -1,18 +1,18 @@
1
- export function authenticateWithOAuth(headers = {}) {
2
- const authHeader = headers['authorization'] || headers['Authorization'];
3
-
4
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
5
- throw new Error('Missing or invalid Bearer token for OAuth authentication');
6
- }
7
-
8
- const token = authHeader.substring('Bearer '.length).trim();
9
-
10
- if (!token) {
11
- throw new Error('Bearer token is empty');
12
- }
13
-
14
- return {
15
- type: 'oauth',
16
- token
17
- };
18
- }
1
+ export function authenticateWithOAuth(headers = {}) {
2
+ const authHeader = headers['authorization'] || headers['Authorization'];
3
+
4
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
5
+ throw new Error('Missing or invalid Bearer token for OAuth authentication');
6
+ }
7
+
8
+ const token = authHeader.substring('Bearer '.length).trim();
9
+
10
+ if (!token) {
11
+ throw new Error('Bearer token is empty');
12
+ }
13
+
14
+ return {
15
+ type: 'oauth',
16
+ token
17
+ };
18
+ }
@@ -1,78 +1,78 @@
1
- /**
2
- * Access token rotation against the ainote backend.
3
- *
4
- * Endpoint: POST /api/auth/token/refresh
5
- * Body: { refresh_token: "rt_..." }
6
- * Response: { access_token, refresh_token, expires_in }
7
- *
8
- * Concurrency: an in-process mutex (module-level Promise) ensures that
9
- * parallel callers wait on a single in-flight refresh instead of issuing
10
- * duplicate requests (which would otherwise burn through refresh-token
11
- * rotation and invalidate the family).
12
- */
13
- import axios from 'axios';
14
-
15
- let inFlight = null;
16
-
17
- export class RefreshError extends Error {
18
- constructor(message, { status, body } = {}) {
19
- super(message);
20
- this.name = 'RefreshError';
21
- this.status = status;
22
- this.body = body;
23
- }
24
- }
25
-
26
- async function performRefresh({ apiUrl, refreshToken }) {
27
- let response;
28
- try {
29
- response = await axios.post(
30
- `${apiUrl}/api/auth/token/refresh`,
31
- { refresh_token: refreshToken },
32
- {
33
- headers: { 'Content-Type': 'application/json' },
34
- timeout: 30000,
35
- validateStatus: (s) => s >= 200 && s < 500
36
- }
37
- );
38
- } catch (err) {
39
- throw new RefreshError(`Refresh request failed: ${err.message}`);
40
- }
41
-
42
- if (response.status < 200 || response.status >= 300) {
43
- const body = response.data || {};
44
- const msg = body.error_description || body.error || body.message ||
45
- `Refresh failed with HTTP ${response.status}`;
46
- throw new RefreshError(msg, { status: response.status, body });
47
- }
48
-
49
- const body = response.data || {};
50
- if (!body.access_token) {
51
- throw new RefreshError('Refresh response missing access_token', { body });
52
- }
53
- return {
54
- access_token: body.access_token,
55
- refresh_token: body.refresh_token || refreshToken,
56
- expires_in: Number(body.expires_in) || undefined
57
- };
58
- }
59
-
60
- /**
61
- * Rotate the access token. Concurrent callers share one in-flight request.
62
- *
63
- * @param {Object} args
64
- * @param {string} args.apiUrl
65
- * @param {string} args.refreshToken
66
- * @returns {Promise<{access_token: string, refresh_token: string, expires_in?: number}>}
67
- */
68
- export async function refreshAccessToken({ apiUrl, refreshToken }) {
69
- if (!refreshToken) {
70
- throw new RefreshError('No refresh token available — login required.');
71
- }
72
- if (inFlight) return inFlight;
73
- inFlight = performRefresh({ apiUrl, refreshToken })
74
- .finally(() => {
75
- inFlight = null;
76
- });
77
- return inFlight;
78
- }
1
+ /**
2
+ * Access token rotation against the ainote backend.
3
+ *
4
+ * Endpoint: POST /api/auth/token/refresh
5
+ * Body: { refresh_token: "rt_..." }
6
+ * Response: { access_token, refresh_token, expires_in }
7
+ *
8
+ * Concurrency: an in-process mutex (module-level Promise) ensures that
9
+ * parallel callers wait on a single in-flight refresh instead of issuing
10
+ * duplicate requests (which would otherwise burn through refresh-token
11
+ * rotation and invalidate the family).
12
+ */
13
+ import axios from 'axios';
14
+
15
+ let inFlight = null;
16
+
17
+ export class RefreshError extends Error {
18
+ constructor(message, { status, body } = {}) {
19
+ super(message);
20
+ this.name = 'RefreshError';
21
+ this.status = status;
22
+ this.body = body;
23
+ }
24
+ }
25
+
26
+ async function performRefresh({ apiUrl, refreshToken }) {
27
+ let response;
28
+ try {
29
+ response = await axios.post(
30
+ `${apiUrl}/api/auth/token/refresh`,
31
+ { refresh_token: refreshToken },
32
+ {
33
+ headers: { 'Content-Type': 'application/json' },
34
+ timeout: 30000,
35
+ validateStatus: (s) => s >= 200 && s < 500
36
+ }
37
+ );
38
+ } catch (err) {
39
+ throw new RefreshError(`Refresh request failed: ${err.message}`);
40
+ }
41
+
42
+ if (response.status < 200 || response.status >= 300) {
43
+ const body = response.data || {};
44
+ const msg = body.error_description || body.error || body.message ||
45
+ `Refresh failed with HTTP ${response.status}`;
46
+ throw new RefreshError(msg, { status: response.status, body });
47
+ }
48
+
49
+ const body = response.data || {};
50
+ if (!body.access_token) {
51
+ throw new RefreshError('Refresh response missing access_token', { body });
52
+ }
53
+ return {
54
+ access_token: body.access_token,
55
+ refresh_token: body.refresh_token || refreshToken,
56
+ expires_in: Number(body.expires_in) || undefined
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Rotate the access token. Concurrent callers share one in-flight request.
62
+ *
63
+ * @param {Object} args
64
+ * @param {string} args.apiUrl
65
+ * @param {string} args.refreshToken
66
+ * @returns {Promise<{access_token: string, refresh_token: string, expires_in?: number}>}
67
+ */
68
+ export async function refreshAccessToken({ apiUrl, refreshToken }) {
69
+ if (!refreshToken) {
70
+ throw new RefreshError('No refresh token available — login required.');
71
+ }
72
+ if (inFlight) return inFlight;
73
+ inFlight = performRefresh({ apiUrl, refreshToken })
74
+ .finally(() => {
75
+ inFlight = null;
76
+ });
77
+ return inFlight;
78
+ }
@@ -1,31 +1,31 @@
1
- import { authenticateWithMcpKey } from './mcp-key.js';
2
- import { authenticateWithOAuth } from './oauth.js';
3
- import { getFeatureFlags } from '../core/config.js';
4
-
5
- export function resolveAuthContext(transportType, headers = {}) {
6
- if (transportType === 'stdio') {
7
- try {
8
- return authenticateWithMcpKey();
9
- } catch {
10
- // No API key configured — allow anonymous mode for onboarding tools
11
- return { type: 'anonymous' };
12
- }
13
- }
14
-
15
- const features = getFeatureFlags();
16
-
17
- if (transportType === 'sse' && features.oauthAuth) {
18
- try {
19
- return authenticateWithOAuth(headers);
20
- } catch {
21
- // Fall through to MCP key or anonymous
22
- }
23
- }
24
-
25
- // Default fallback to MCP key, then anonymous for onboarding
26
- try {
27
- return authenticateWithMcpKey();
28
- } catch {
29
- return { type: 'anonymous' };
30
- }
31
- }
1
+ import { authenticateWithMcpKey } from './mcp-key.js';
2
+ import { authenticateWithOAuth } from './oauth.js';
3
+ import { getFeatureFlags } from '../core/config.js';
4
+
5
+ export function resolveAuthContext(transportType, headers = {}) {
6
+ if (transportType === 'stdio') {
7
+ try {
8
+ return authenticateWithMcpKey();
9
+ } catch {
10
+ // No API key configured — allow anonymous mode for onboarding tools
11
+ return { type: 'anonymous' };
12
+ }
13
+ }
14
+
15
+ const features = getFeatureFlags();
16
+
17
+ if (transportType === 'sse' && features.oauthAuth) {
18
+ try {
19
+ return authenticateWithOAuth(headers);
20
+ } catch {
21
+ // Fall through to MCP key or anonymous
22
+ }
23
+ }
24
+
25
+ // Default fallback to MCP key, then anonymous for onboarding
26
+ try {
27
+ return authenticateWithMcpKey();
28
+ } catch {
29
+ return { type: 'anonymous' };
30
+ }
31
+ }