mcp-macos 2.0.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/LICENSE +21 -0
- package/README.md +360 -0
- package/bin/dev.cjs +10 -0
- package/bin/run.cjs +15 -0
- package/dist/config/index.d.ts +36 -0
- package/dist/config/index.js +127 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +161 -0
- package/dist/config/schema.js +45 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +75 -0
- package/dist/index.js.map +1 -0
- package/dist/server/handlers.d.ts +10 -0
- package/dist/server/handlers.js +43 -0
- package/dist/server/handlers.js.map +1 -0
- package/dist/server/promptAbstractions.d.ts +74 -0
- package/dist/server/promptAbstractions.js +150 -0
- package/dist/server/promptAbstractions.js.map +1 -0
- package/dist/server/prompts.d.ts +8 -0
- package/dist/server/prompts.js +480 -0
- package/dist/server/prompts.js.map +1 -0
- package/dist/server/server.d.ts +29 -0
- package/dist/server/server.js +52 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/transports/http/auth.d.ts +34 -0
- package/dist/server/transports/http/auth.js +148 -0
- package/dist/server/transports/http/auth.js.map +1 -0
- package/dist/server/transports/http/health.d.ts +35 -0
- package/dist/server/transports/http/health.js +93 -0
- package/dist/server/transports/http/health.js.map +1 -0
- package/dist/server/transports/http/index.d.ts +43 -0
- package/dist/server/transports/http/index.js +141 -0
- package/dist/server/transports/http/index.js.map +1 -0
- package/dist/server/transports/http/middleware.d.ts +53 -0
- package/dist/server/transports/http/middleware.js +133 -0
- package/dist/server/transports/http/middleware.js.map +1 -0
- package/dist/tools/definitions.d.ts +10 -0
- package/dist/tools/definitions.js +633 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/handlers/calendarHandlers.d.ts +11 -0
- package/dist/tools/handlers/calendarHandlers.js +123 -0
- package/dist/tools/handlers/calendarHandlers.js.map +1 -0
- package/dist/tools/handlers/contactsHandlers.d.ts +17 -0
- package/dist/tools/handlers/contactsHandlers.js +397 -0
- package/dist/tools/handlers/contactsHandlers.js.map +1 -0
- package/dist/tools/handlers/index.d.ts +11 -0
- package/dist/tools/handlers/index.js +12 -0
- package/dist/tools/handlers/index.js.map +1 -0
- package/dist/tools/handlers/listHandlers.d.ts +10 -0
- package/dist/tools/handlers/listHandlers.js +40 -0
- package/dist/tools/handlers/listHandlers.js.map +1 -0
- package/dist/tools/handlers/mailHandlers.d.ts +11 -0
- package/dist/tools/handlers/mailHandlers.js +301 -0
- package/dist/tools/handlers/mailHandlers.js.map +1 -0
- package/dist/tools/handlers/messagesHandlers.d.ts +17 -0
- package/dist/tools/handlers/messagesHandlers.js +350 -0
- package/dist/tools/handlers/messagesHandlers.js.map +1 -0
- package/dist/tools/handlers/notesHandlers.d.ts +12 -0
- package/dist/tools/handlers/notesHandlers.js +305 -0
- package/dist/tools/handlers/notesHandlers.js.map +1 -0
- package/dist/tools/handlers/reminderHandlers.d.ts +10 -0
- package/dist/tools/handlers/reminderHandlers.js +96 -0
- package/dist/tools/handlers/reminderHandlers.js.map +1 -0
- package/dist/tools/handlers/shared.d.ts +51 -0
- package/dist/tools/handlers/shared.js +107 -0
- package/dist/tools/handlers/shared.js.map +1 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.js +125 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +265 -0
- package/dist/types/index.js +67 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/prompts.d.ts +84 -0
- package/dist/types/prompts.js +6 -0
- package/dist/types/prompts.js.map +1 -0
- package/dist/types/repository.d.ts +102 -0
- package/dist/types/repository.js +6 -0
- package/dist/types/repository.js.map +1 -0
- package/dist/utils/binaryValidator.d.ts +52 -0
- package/dist/utils/binaryValidator.js +152 -0
- package/dist/utils/binaryValidator.js.map +1 -0
- package/dist/utils/calendarRepository.d.ts +25 -0
- package/dist/utils/calendarRepository.js +100 -0
- package/dist/utils/calendarRepository.js.map +1 -0
- package/dist/utils/cliExecutor.d.ts +28 -0
- package/dist/utils/cliExecutor.js +196 -0
- package/dist/utils/cliExecutor.js.map +1 -0
- package/dist/utils/constants.d.ts +96 -0
- package/dist/utils/constants.js +97 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/contactResolver.d.ts +142 -0
- package/dist/utils/contactResolver.js +386 -0
- package/dist/utils/contactResolver.js.map +1 -0
- package/dist/utils/dateFiltering.d.ts +22 -0
- package/dist/utils/dateFiltering.js +72 -0
- package/dist/utils/dateFiltering.js.map +1 -0
- package/dist/utils/dateUtils.d.ts +20 -0
- package/dist/utils/dateUtils.js +36 -0
- package/dist/utils/dateUtils.js.map +1 -0
- package/dist/utils/errorHandling.d.ts +30 -0
- package/dist/utils/errorHandling.js +101 -0
- package/dist/utils/errorHandling.js.map +1 -0
- package/dist/utils/helpers.d.ts +35 -0
- package/dist/utils/helpers.js +59 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/jxaExecutor.d.ts +47 -0
- package/dist/utils/jxaExecutor.js +194 -0
- package/dist/utils/jxaExecutor.js.map +1 -0
- package/dist/utils/logging.d.ts +31 -0
- package/dist/utils/logging.js +98 -0
- package/dist/utils/logging.js.map +1 -0
- package/dist/utils/permissionPrompt.d.ts +16 -0
- package/dist/utils/permissionPrompt.js +42 -0
- package/dist/utils/permissionPrompt.js.map +1 -0
- package/dist/utils/preflight.d.ts +30 -0
- package/dist/utils/preflight.js +196 -0
- package/dist/utils/preflight.js.map +1 -0
- package/dist/utils/projectUtils.d.ts +11 -0
- package/dist/utils/projectUtils.js +76 -0
- package/dist/utils/projectUtils.js.map +1 -0
- package/dist/utils/reminderDateParser.d.ts +8 -0
- package/dist/utils/reminderDateParser.js +77 -0
- package/dist/utils/reminderDateParser.js.map +1 -0
- package/dist/utils/reminderRepository.d.ts +23 -0
- package/dist/utils/reminderRepository.js +91 -0
- package/dist/utils/reminderRepository.js.map +1 -0
- package/dist/utils/sqliteContactReader.d.ts +51 -0
- package/dist/utils/sqliteContactReader.js +216 -0
- package/dist/utils/sqliteContactReader.js.map +1 -0
- package/dist/utils/sqliteMailReader.d.ts +97 -0
- package/dist/utils/sqliteMailReader.js +310 -0
- package/dist/utils/sqliteMailReader.js.map +1 -0
- package/dist/utils/sqliteMessageReader.d.ts +71 -0
- package/dist/utils/sqliteMessageReader.js +400 -0
- package/dist/utils/sqliteMessageReader.js.map +1 -0
- package/dist/utils/timeHelpers.d.ts +40 -0
- package/dist/utils/timeHelpers.js +136 -0
- package/dist/utils/timeHelpers.js.map +1 -0
- package/dist/utils/timezone.d.ts +24 -0
- package/dist/utils/timezone.js +39 -0
- package/dist/utils/timezone.js.map +1 -0
- package/dist/validation/schemas.d.ts +610 -0
- package/dist/validation/schemas.js +354 -0
- package/dist/validation/schemas.js.map +1 -0
- package/package.json +97 -0
- package/scripts/build-swift.mjs +86 -0
- package/src/swift/EventKitCLI.swift +778 -0
- package/src/swift/Info.plist +38 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Cloudflare Access JWT verification for HTTP transport
|
|
3
|
+
* @module server/transports/http/auth
|
|
4
|
+
* @description Verifies JWTs from Cloudflare Access for defense-in-depth security
|
|
5
|
+
*/
|
|
6
|
+
import * as jose from 'jose';
|
|
7
|
+
/** JWKS cache TTL in milliseconds (1 hour) */
|
|
8
|
+
const JWKS_CACHE_TTL = 60 * 60 * 1000;
|
|
9
|
+
/** JWKS cache keyed by team domain */
|
|
10
|
+
const jwksCache = new Map();
|
|
11
|
+
/**
|
|
12
|
+
* Gets the JWKS URL for a Cloudflare Access team domain
|
|
13
|
+
* @param teamDomain - Cloudflare Access team domain
|
|
14
|
+
* @returns JWKS URL
|
|
15
|
+
*/
|
|
16
|
+
function getJwksUrl(teamDomain) {
|
|
17
|
+
// Normalize domain - remove protocol if present and ensure .cloudflareaccess.com suffix
|
|
18
|
+
let domain = teamDomain.replace(/^https?:\/\//, '');
|
|
19
|
+
// If just the team name is provided, add the full domain
|
|
20
|
+
if (!domain.includes('.')) {
|
|
21
|
+
domain = `${domain}.cloudflareaccess.com`;
|
|
22
|
+
}
|
|
23
|
+
return `https://${domain}/cdn-cgi/access/certs`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Gets or creates a cached JWKS getter for the team domain
|
|
27
|
+
* @param teamDomain - Cloudflare Access team domain
|
|
28
|
+
* @returns JWKS getter function
|
|
29
|
+
*/
|
|
30
|
+
async function getJwks(teamDomain) {
|
|
31
|
+
const cached = jwksCache.get(teamDomain);
|
|
32
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
33
|
+
return cached.jwks;
|
|
34
|
+
}
|
|
35
|
+
const jwksUrl = getJwksUrl(teamDomain);
|
|
36
|
+
const jwks = jose.createRemoteJWKSet(new URL(jwksUrl));
|
|
37
|
+
jwksCache.set(teamDomain, {
|
|
38
|
+
jwks,
|
|
39
|
+
expiresAt: Date.now() + JWKS_CACHE_TTL,
|
|
40
|
+
});
|
|
41
|
+
return jwks;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Verifies a Cloudflare Access JWT
|
|
45
|
+
*
|
|
46
|
+
* @param token - JWT from Cf-Access-Jwt-Assertion header
|
|
47
|
+
* @param config - Cloudflare Access configuration
|
|
48
|
+
* @returns Verification result
|
|
49
|
+
*/
|
|
50
|
+
export async function verifyCloudflareAccessJwt(token, config) {
|
|
51
|
+
try {
|
|
52
|
+
const jwks = await getJwks(config.teamDomain);
|
|
53
|
+
// Normalize team domain for issuer validation
|
|
54
|
+
let normalizedDomain = config.teamDomain.replace(/^https?:\/\//, '');
|
|
55
|
+
if (!normalizedDomain.includes('.')) {
|
|
56
|
+
normalizedDomain = `${normalizedDomain}.cloudflareaccess.com`;
|
|
57
|
+
}
|
|
58
|
+
const expectedIssuer = `https://${normalizedDomain}`;
|
|
59
|
+
const { payload } = await jose.jwtVerify(token, jwks, {
|
|
60
|
+
audience: config.policyAUD,
|
|
61
|
+
issuer: expectedIssuer,
|
|
62
|
+
});
|
|
63
|
+
// Extract email from payload
|
|
64
|
+
const email = typeof payload.email === 'string' ? payload.email : undefined;
|
|
65
|
+
// Validate email against allowed list if configured
|
|
66
|
+
if (config.allowedEmails && config.allowedEmails.length > 0) {
|
|
67
|
+
if (!email) {
|
|
68
|
+
return {
|
|
69
|
+
valid: false,
|
|
70
|
+
error: 'JWT missing email claim',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (!config.allowedEmails.includes(email)) {
|
|
74
|
+
return {
|
|
75
|
+
valid: false,
|
|
76
|
+
error: `Email ${email} not in allowed list`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
valid: true,
|
|
82
|
+
email,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof jose.errors.JWTExpired) {
|
|
87
|
+
return {
|
|
88
|
+
valid: false,
|
|
89
|
+
error: 'JWT has expired',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (error instanceof jose.errors.JWTClaimValidationFailed) {
|
|
93
|
+
return {
|
|
94
|
+
valid: false,
|
|
95
|
+
error: `JWT claim validation failed: ${error.message}`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (error instanceof jose.errors.JWSSignatureVerificationFailed) {
|
|
99
|
+
return {
|
|
100
|
+
valid: false,
|
|
101
|
+
error: 'JWT signature verification failed',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
105
|
+
return {
|
|
106
|
+
valid: false,
|
|
107
|
+
error: `JWT verification failed: ${errorMessage}`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Express middleware that verifies Cloudflare Access JWTs
|
|
113
|
+
* Returns 401 if no valid JWT is present
|
|
114
|
+
*
|
|
115
|
+
* @param config - Cloudflare Access configuration
|
|
116
|
+
* @returns Express middleware function
|
|
117
|
+
*/
|
|
118
|
+
export function createAuthMiddleware(config) {
|
|
119
|
+
return async (req, res, next) => {
|
|
120
|
+
// Get JWT from Cloudflare Access header
|
|
121
|
+
const token = req.headers['cf-access-jwt-assertion'];
|
|
122
|
+
if (!token || typeof token !== 'string') {
|
|
123
|
+
res.status(401).json({
|
|
124
|
+
error: 'Unauthorized',
|
|
125
|
+
message: 'Missing Cf-Access-Jwt-Assertion header',
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const result = await verifyCloudflareAccessJwt(token, config);
|
|
130
|
+
if (!result.valid) {
|
|
131
|
+
res.status(401).json({
|
|
132
|
+
error: 'Unauthorized',
|
|
133
|
+
message: result.error ?? 'JWT verification failed',
|
|
134
|
+
});
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Attach verified email to request for logging
|
|
138
|
+
req.cfAccessEmail = result.email;
|
|
139
|
+
next();
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Clears the JWKS cache (useful for testing)
|
|
144
|
+
*/
|
|
145
|
+
export function clearJwksCache() {
|
|
146
|
+
jwksCache.clear();
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../../src/server/transports/http/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Health check endpoints for HTTP transport
|
|
3
|
+
* @module server/transports/http/health
|
|
4
|
+
* @description Provides /health and /health/ready endpoints for monitoring
|
|
5
|
+
*/
|
|
6
|
+
import type { Request, Response, Router } from 'express';
|
|
7
|
+
import type { FullServerConfig } from '../../../config/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Marks the server as started (call when HTTP server begins listening)
|
|
10
|
+
*/
|
|
11
|
+
export declare function markServerStarted(): void;
|
|
12
|
+
/**
|
|
13
|
+
* Basic health check handler
|
|
14
|
+
* Returns 200 if server is running
|
|
15
|
+
*
|
|
16
|
+
* @param config - Server configuration
|
|
17
|
+
* @returns Express request handler
|
|
18
|
+
*/
|
|
19
|
+
export declare function createHealthHandler(config: FullServerConfig): (req: Request, res: Response) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Readiness check handler
|
|
22
|
+
* Returns 200 with subsystem status if server is ready to accept requests
|
|
23
|
+
*
|
|
24
|
+
* @param config - Server configuration
|
|
25
|
+
* @returns Express request handler
|
|
26
|
+
*/
|
|
27
|
+
export declare function createReadinessHandler(config: FullServerConfig): (req: Request, res: Response) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Registers health check routes on the provided router
|
|
30
|
+
* These endpoints do NOT require authentication
|
|
31
|
+
*
|
|
32
|
+
* @param router - Express router to register routes on
|
|
33
|
+
* @param config - Server configuration
|
|
34
|
+
*/
|
|
35
|
+
export declare function registerHealthRoutes(router: Router, config: FullServerConfig): void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Health check endpoints for HTTP transport
|
|
3
|
+
* @module server/transports/http/health
|
|
4
|
+
* @description Provides /health and /health/ready endpoints for monitoring
|
|
5
|
+
*/
|
|
6
|
+
/** Server start time for uptime calculation */
|
|
7
|
+
let serverStartTime = null;
|
|
8
|
+
/**
|
|
9
|
+
* Marks the server as started (call when HTTP server begins listening)
|
|
10
|
+
*/
|
|
11
|
+
export function markServerStarted() {
|
|
12
|
+
serverStartTime = Date.now();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Gets the server uptime in seconds
|
|
16
|
+
* @returns Uptime in seconds, or 0 if not started
|
|
17
|
+
*/
|
|
18
|
+
function getUptime() {
|
|
19
|
+
if (serverStartTime === null) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
return Math.floor((Date.now() - serverStartTime) / 1000);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Basic health check handler
|
|
26
|
+
* Returns 200 if server is running
|
|
27
|
+
*
|
|
28
|
+
* @param config - Server configuration
|
|
29
|
+
* @returns Express request handler
|
|
30
|
+
*/
|
|
31
|
+
export function createHealthHandler(config) {
|
|
32
|
+
return (_req, res) => {
|
|
33
|
+
const response = {
|
|
34
|
+
status: 'healthy',
|
|
35
|
+
service: config.name,
|
|
36
|
+
version: config.version,
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
uptime: getUptime(),
|
|
39
|
+
};
|
|
40
|
+
res.status(200).json(response);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Readiness check handler
|
|
45
|
+
* Returns 200 with subsystem status if server is ready to accept requests
|
|
46
|
+
*
|
|
47
|
+
* @param config - Server configuration
|
|
48
|
+
* @returns Express request handler
|
|
49
|
+
*/
|
|
50
|
+
export function createReadinessHandler(config) {
|
|
51
|
+
return (_req, res) => {
|
|
52
|
+
const subsystems = [
|
|
53
|
+
{
|
|
54
|
+
name: 'mcp-server',
|
|
55
|
+
status: 'healthy',
|
|
56
|
+
message: 'MCP server is accepting connections',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'http-transport',
|
|
60
|
+
status: 'healthy',
|
|
61
|
+
message: 'HTTP transport is operational',
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
// Determine overall status based on subsystems
|
|
65
|
+
const overallStatus = subsystems.every((s) => s.status === 'healthy')
|
|
66
|
+
? 'healthy'
|
|
67
|
+
: subsystems.some((s) => s.status === 'unhealthy')
|
|
68
|
+
? 'unhealthy'
|
|
69
|
+
: 'degraded';
|
|
70
|
+
const response = {
|
|
71
|
+
status: overallStatus,
|
|
72
|
+
service: config.name,
|
|
73
|
+
version: config.version,
|
|
74
|
+
timestamp: new Date().toISOString(),
|
|
75
|
+
uptime: getUptime(),
|
|
76
|
+
subsystems,
|
|
77
|
+
};
|
|
78
|
+
const statusCode = overallStatus === 'healthy' ? 200 : 503;
|
|
79
|
+
res.status(statusCode).json(response);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Registers health check routes on the provided router
|
|
84
|
+
* These endpoints do NOT require authentication
|
|
85
|
+
*
|
|
86
|
+
* @param router - Express router to register routes on
|
|
87
|
+
* @param config - Server configuration
|
|
88
|
+
*/
|
|
89
|
+
export function registerHealthRoutes(router, config) {
|
|
90
|
+
router.get('/health', createHealthHandler(config));
|
|
91
|
+
router.get('/health/ready', createReadinessHandler(config));
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../../../src/server/transports/http/health.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA0BH,+CAA+C;AAC/C,IAAI,eAAe,GAAkB,IAAI,CAAC;AAE1C;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;AAC/B,CAAC;AAED;;;GAGG;AACH,SAAS,SAAS;IAChB,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC7B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC,GAAG,IAAI,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAwB;IAExB,OAAO,CAAC,IAAa,EAAE,GAAa,EAAQ,EAAE;QAC5C,MAAM,QAAQ,GAAmB;YAC/B,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,MAAM,CAAC,IAAI;YACpB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,MAAM,EAAE,SAAS,EAAE;SACpB,CAAC;QAEF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAwB;IAExB,OAAO,CAAC,IAAa,EAAE,GAAa,EAAQ,EAAE;QAC5C,MAAM,UAAU,GAAsB;YACpC;gBACE,IAAI,EAAE,YAAY;gBAClB,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,qCAAqC;aAC/C;YACD;gBACE,IAAI,EAAE,gBAAgB;gBACtB,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,+BAA+B;aACzC;SACF,CAAC;QAEF,+CAA+C;QAC/C,MAAM,aAAa,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC;YACnE,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC;gBAChD,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,UAAU,CAAC;QAEjB,MAAM,QAAQ,GAAmB;YAC/B,MAAM,EAAE,aAAa;YACrB,OAAO,EAAE,MAAM,CAAC,IAAI;YACpB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,MAAM,EAAE,SAAS,EAAE;YACnB,UAAU;SACX,CAAC;QAEF,MAAM,UAAU,GAAG,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QAC3D,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAc,EACd,MAAwB;IAExB,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC;IACnD,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview HTTP transport layer using MCP SDK's StreamableHTTPServerTransport
|
|
3
|
+
* @module server/transports/http
|
|
4
|
+
* @description Express server providing HTTP transport for MCP protocol messages
|
|
5
|
+
*/
|
|
6
|
+
import type { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
|
+
import { type Express } from 'express';
|
|
8
|
+
import type { FullServerConfig, HttpConfig } from '../../../config/index.js';
|
|
9
|
+
/**
|
|
10
|
+
* HTTP transport instance
|
|
11
|
+
*/
|
|
12
|
+
export interface HttpTransportInstance {
|
|
13
|
+
/** Express application */
|
|
14
|
+
app: Express;
|
|
15
|
+
/** Start listening on configured host:port */
|
|
16
|
+
start: () => Promise<void>;
|
|
17
|
+
/** Stop the HTTP server */
|
|
18
|
+
stop: () => Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Creates an HTTP transport for the MCP server
|
|
22
|
+
*
|
|
23
|
+
* SDK 1.26.0 requires stateless transports to be created fresh per request.
|
|
24
|
+
* Each POST creates a new MCP Server + StreamableHTTPServerTransport pair,
|
|
25
|
+
* handles the request, then cleans up on response close.
|
|
26
|
+
*
|
|
27
|
+
* @param _mcpServer - Unused (kept for API compatibility). Per-request servers are created internally.
|
|
28
|
+
* @param config - Full server configuration
|
|
29
|
+
* @param httpConfig - HTTP-specific configuration
|
|
30
|
+
* @returns HTTP transport instance
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const config = loadConfig();
|
|
35
|
+
* const mcpServer = createServer(config);
|
|
36
|
+
* const httpTransport = createHttpTransport(mcpServer, config, config.http!);
|
|
37
|
+
* await httpTransport.start();
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function createHttpTransport(_mcpServer: McpServer, config: FullServerConfig, httpConfig: HttpConfig): HttpTransportInstance;
|
|
41
|
+
export { createAuthMiddleware, verifyCloudflareAccessJwt } from './auth.js';
|
|
42
|
+
export { createHealthHandler, createReadinessHandler, registerHealthRoutes, } from './health.js';
|
|
43
|
+
export { corsMiddleware, createRateLimiter, errorHandler, requestLogging, requestTiming, } from './middleware.js';
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview HTTP transport layer using MCP SDK's StreamableHTTPServerTransport
|
|
3
|
+
* @module server/transports/http
|
|
4
|
+
* @description Express server providing HTTP transport for MCP protocol messages
|
|
5
|
+
*/
|
|
6
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
7
|
+
import express, { Router, } from 'express';
|
|
8
|
+
import { createServer } from '../../server.js';
|
|
9
|
+
import { createAuthMiddleware } from './auth.js';
|
|
10
|
+
import { markServerStarted, registerHealthRoutes } from './health.js';
|
|
11
|
+
import { corsMiddleware, createRateLimiter, errorHandler, requestLogging, requestTiming, } from './middleware.js';
|
|
12
|
+
/**
|
|
13
|
+
* Creates an HTTP transport for the MCP server
|
|
14
|
+
*
|
|
15
|
+
* SDK 1.26.0 requires stateless transports to be created fresh per request.
|
|
16
|
+
* Each POST creates a new MCP Server + StreamableHTTPServerTransport pair,
|
|
17
|
+
* handles the request, then cleans up on response close.
|
|
18
|
+
*
|
|
19
|
+
* @param _mcpServer - Unused (kept for API compatibility). Per-request servers are created internally.
|
|
20
|
+
* @param config - Full server configuration
|
|
21
|
+
* @param httpConfig - HTTP-specific configuration
|
|
22
|
+
* @returns HTTP transport instance
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const config = loadConfig();
|
|
27
|
+
* const mcpServer = createServer(config);
|
|
28
|
+
* const httpTransport = createHttpTransport(mcpServer, config, config.http!);
|
|
29
|
+
* await httpTransport.start();
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function createHttpTransport(_mcpServer, config, httpConfig) {
|
|
33
|
+
const app = express();
|
|
34
|
+
// Trust proxy for rate limiting behind Cloudflare Tunnel
|
|
35
|
+
app.set('trust proxy', true);
|
|
36
|
+
// Apply global middleware
|
|
37
|
+
// express.json() is required to pre-parse the body for the SDK's parsedBody parameter.
|
|
38
|
+
// SDK 1.26.0 explicitly supports this pattern: transport.handleRequest(req, res, req.body)
|
|
39
|
+
app.use(express.json());
|
|
40
|
+
app.use(corsMiddleware());
|
|
41
|
+
app.use(requestTiming());
|
|
42
|
+
app.use(requestLogging());
|
|
43
|
+
app.use(createRateLimiter());
|
|
44
|
+
// Register health endpoints (no auth required)
|
|
45
|
+
const healthRouter = Router();
|
|
46
|
+
registerHealthRoutes(healthRouter, config);
|
|
47
|
+
app.use(healthRouter);
|
|
48
|
+
// Apply auth middleware to MCP endpoint if Cloudflare Access is configured
|
|
49
|
+
if (httpConfig.cloudflareAccess) {
|
|
50
|
+
app.use('/mcp', createAuthMiddleware(httpConfig.cloudflareAccess));
|
|
51
|
+
}
|
|
52
|
+
// MCP endpoint handler — creates a fresh server + transport per request
|
|
53
|
+
// SDK 1.26.0 stateless mode throws "Stateless transport cannot be reused across requests"
|
|
54
|
+
// if a transport with sessionIdGenerator=undefined handles more than one request.
|
|
55
|
+
// The official SDK pattern (simpleStatelessStreamableHttp.ts) creates new instances per request.
|
|
56
|
+
const mcpHandler = async (req, res) => {
|
|
57
|
+
const perRequestServer = createServer(config);
|
|
58
|
+
const transport = new StreamableHTTPServerTransport({
|
|
59
|
+
sessionIdGenerator: undefined,
|
|
60
|
+
enableJsonResponse: true,
|
|
61
|
+
});
|
|
62
|
+
// Log transport-level errors to stderr for visibility
|
|
63
|
+
transport.onerror = (error) => {
|
|
64
|
+
process.stderr.write(`${JSON.stringify({ timestamp: new Date().toISOString(), error: 'MCP transport error', message: error.message, stack: error.stack })}\n`);
|
|
65
|
+
};
|
|
66
|
+
try {
|
|
67
|
+
await perRequestServer.connect(transport);
|
|
68
|
+
await transport.handleRequest(req, res, req.body);
|
|
69
|
+
// Clean up when the response closes
|
|
70
|
+
res.on('close', () => {
|
|
71
|
+
transport.close().catch(() => { });
|
|
72
|
+
perRequestServer.close().catch(() => { });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
77
|
+
process.stderr.write(`${JSON.stringify({ timestamp: new Date().toISOString(), error: 'MCP request failed', message: errorMessage })}\n`);
|
|
78
|
+
if (!res.headersSent) {
|
|
79
|
+
res.status(500).json({
|
|
80
|
+
error: 'Internal Server Error',
|
|
81
|
+
message: 'MCP request processing failed',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
// Register MCP routes - handle all methods for session management
|
|
87
|
+
// Support both /mcp (explicit) and root / (Claude.ai expects this)
|
|
88
|
+
app.all('/mcp', mcpHandler);
|
|
89
|
+
app.all('/', mcpHandler);
|
|
90
|
+
// Error handler must be last
|
|
91
|
+
app.use(errorHandler());
|
|
92
|
+
// HTTP server reference for cleanup
|
|
93
|
+
let server = null;
|
|
94
|
+
return {
|
|
95
|
+
app,
|
|
96
|
+
async start() {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
try {
|
|
99
|
+
server = app.listen(httpConfig.port, httpConfig.host, () => {
|
|
100
|
+
markServerStarted();
|
|
101
|
+
process.stderr.write(`${JSON.stringify({
|
|
102
|
+
timestamp: new Date().toISOString(),
|
|
103
|
+
event: 'http_server_started',
|
|
104
|
+
host: httpConfig.host,
|
|
105
|
+
port: httpConfig.port,
|
|
106
|
+
authEnabled: !!httpConfig.cloudflareAccess,
|
|
107
|
+
})}\n`);
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
server.on('error', (error) => {
|
|
111
|
+
process.stderr.write(`${JSON.stringify({ timestamp: new Date().toISOString(), error: 'HTTP server error', message: error.message })}\n`);
|
|
112
|
+
reject(error);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
reject(error);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
async stop() {
|
|
121
|
+
if (server) {
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
server?.close((error) => {
|
|
124
|
+
if (error) {
|
|
125
|
+
reject(error);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
process.stderr.write(`${JSON.stringify({ timestamp: new Date().toISOString(), event: 'http_server_stopped' })}\n`);
|
|
129
|
+
resolve();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
// Re-export components for testing and extensibility
|
|
138
|
+
export { createAuthMiddleware, verifyCloudflareAccessJwt } from './auth.js';
|
|
139
|
+
export { createHealthHandler, createReadinessHandler, registerHealthRoutes, } from './health.js';
|
|
140
|
+
export { corsMiddleware, createRateLimiter, errorHandler, requestLogging, requestTiming, } from './middleware.js';
|
|
141
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/server/transports/http/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview HTTP middleware for rate limiting, logging, and CORS
|
|
3
|
+
* @module server/transports/http/middleware
|
|
4
|
+
* @description Express middleware stack for the HTTP transport
|
|
5
|
+
*/
|
|
6
|
+
import type { NextFunction, Request, Response } from 'express';
|
|
7
|
+
import rateLimit from 'express-rate-limit';
|
|
8
|
+
/**
|
|
9
|
+
* Extended request type with timing and auth info
|
|
10
|
+
*/
|
|
11
|
+
export interface TimedRequest extends Request {
|
|
12
|
+
/** Request start time for duration calculation */
|
|
13
|
+
startTime?: number;
|
|
14
|
+
/** Verified Cloudflare Access email */
|
|
15
|
+
cfAccessEmail?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Creates a rate limiter middleware
|
|
19
|
+
* Default: 100 requests per minute per IP
|
|
20
|
+
*
|
|
21
|
+
* @param maxRequests - Maximum requests per window (default: 100)
|
|
22
|
+
* @param windowMs - Window size in milliseconds (default: 60000 = 1 minute)
|
|
23
|
+
* @returns Express rate limiting middleware
|
|
24
|
+
*/
|
|
25
|
+
export declare function createRateLimiter(maxRequests?: number, windowMs?: number): ReturnType<typeof rateLimit>;
|
|
26
|
+
/**
|
|
27
|
+
* Request timing middleware
|
|
28
|
+
* Adds startTime to request for duration calculation
|
|
29
|
+
*
|
|
30
|
+
* @returns Express middleware function
|
|
31
|
+
*/
|
|
32
|
+
export declare function requestTiming(): (req: TimedRequest, res: Response, next: NextFunction) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Request logging middleware
|
|
35
|
+
* Logs request details after response is sent
|
|
36
|
+
*
|
|
37
|
+
* @returns Express middleware function
|
|
38
|
+
*/
|
|
39
|
+
export declare function requestLogging(): (req: TimedRequest, res: Response, next: NextFunction) => void;
|
|
40
|
+
/**
|
|
41
|
+
* CORS middleware for handling preflight requests
|
|
42
|
+
* Configured for Cloudflare Tunnel / Access
|
|
43
|
+
*
|
|
44
|
+
* @returns Express middleware function
|
|
45
|
+
*/
|
|
46
|
+
export declare function corsMiddleware(): (req: Request, res: Response, next: NextFunction) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Error handling middleware
|
|
49
|
+
* Catches unhandled errors and returns appropriate response
|
|
50
|
+
*
|
|
51
|
+
* @returns Express error handling middleware
|
|
52
|
+
*/
|
|
53
|
+
export declare function errorHandler(): (err: Error, req: Request, res: Response, next: NextFunction) => void;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview HTTP middleware for rate limiting, logging, and CORS
|
|
3
|
+
* @module server/transports/http/middleware
|
|
4
|
+
* @description Express middleware stack for the HTTP transport
|
|
5
|
+
*/
|
|
6
|
+
import rateLimit from 'express-rate-limit';
|
|
7
|
+
/**
|
|
8
|
+
* Creates a rate limiter middleware
|
|
9
|
+
* Default: 100 requests per minute per IP
|
|
10
|
+
*
|
|
11
|
+
* @param maxRequests - Maximum requests per window (default: 100)
|
|
12
|
+
* @param windowMs - Window size in milliseconds (default: 60000 = 1 minute)
|
|
13
|
+
* @returns Express rate limiting middleware
|
|
14
|
+
*/
|
|
15
|
+
export function createRateLimiter(maxRequests = 100, windowMs = 60000) {
|
|
16
|
+
return rateLimit({
|
|
17
|
+
windowMs,
|
|
18
|
+
max: maxRequests,
|
|
19
|
+
// Suppress ERR_ERL_KEY_GEN_IPV6 ValidationError — we're behind Cloudflare
|
|
20
|
+
// Tunnel which always provides X-Forwarded-For, so IPv6 validation is irrelevant
|
|
21
|
+
validate: { keyGeneratorIpFallback: false },
|
|
22
|
+
message: {
|
|
23
|
+
error: 'Too Many Requests',
|
|
24
|
+
message: `Rate limit exceeded. Maximum ${maxRequests} requests per ${windowMs / 1000} seconds.`,
|
|
25
|
+
},
|
|
26
|
+
standardHeaders: true,
|
|
27
|
+
legacyHeaders: false,
|
|
28
|
+
// Use X-Forwarded-For header if behind proxy (Cloudflare Tunnel)
|
|
29
|
+
keyGenerator: (req) => {
|
|
30
|
+
const forwarded = req.headers['x-forwarded-for'];
|
|
31
|
+
if (typeof forwarded === 'string') {
|
|
32
|
+
// Take the first IP if there are multiple
|
|
33
|
+
return forwarded.split(',')[0].trim();
|
|
34
|
+
}
|
|
35
|
+
return req.ip ?? req.socket.remoteAddress ?? 'unknown';
|
|
36
|
+
},
|
|
37
|
+
// Skip rate limiting for health checks
|
|
38
|
+
skip: (req) => {
|
|
39
|
+
return req.path === '/health' || req.path === '/health/ready';
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Request timing middleware
|
|
45
|
+
* Adds startTime to request for duration calculation
|
|
46
|
+
*
|
|
47
|
+
* @returns Express middleware function
|
|
48
|
+
*/
|
|
49
|
+
export function requestTiming() {
|
|
50
|
+
return (req, _res, next) => {
|
|
51
|
+
req.startTime = Date.now();
|
|
52
|
+
next();
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Request logging middleware
|
|
57
|
+
* Logs request details after response is sent
|
|
58
|
+
*
|
|
59
|
+
* @returns Express middleware function
|
|
60
|
+
*/
|
|
61
|
+
export function requestLogging() {
|
|
62
|
+
return (req, res, next) => {
|
|
63
|
+
// Log after response is finished
|
|
64
|
+
res.on('finish', () => {
|
|
65
|
+
const duration = req.startTime ? Date.now() - req.startTime : 0;
|
|
66
|
+
const logEntry = {
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
method: req.method,
|
|
69
|
+
path: req.path,
|
|
70
|
+
status: res.statusCode,
|
|
71
|
+
duration,
|
|
72
|
+
ip: (typeof req.headers['x-forwarded-for'] === 'string'
|
|
73
|
+
? req.headers['x-forwarded-for'].split(',')[0].trim()
|
|
74
|
+
: undefined) ??
|
|
75
|
+
req.ip ??
|
|
76
|
+
req.socket.remoteAddress ??
|
|
77
|
+
'unknown',
|
|
78
|
+
};
|
|
79
|
+
// Add user if available from Cloudflare Access
|
|
80
|
+
if (req.cfAccessEmail) {
|
|
81
|
+
logEntry.user = req.cfAccessEmail;
|
|
82
|
+
}
|
|
83
|
+
// Log to stderr to avoid interfering with stdio transport
|
|
84
|
+
// Use structured JSON for easy parsing
|
|
85
|
+
process.stderr.write(`${JSON.stringify(logEntry)}\n`);
|
|
86
|
+
});
|
|
87
|
+
next();
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* CORS middleware for handling preflight requests
|
|
92
|
+
* Configured for Cloudflare Tunnel / Access
|
|
93
|
+
*
|
|
94
|
+
* @returns Express middleware function
|
|
95
|
+
*/
|
|
96
|
+
export function corsMiddleware() {
|
|
97
|
+
return (req, res, next) => {
|
|
98
|
+
// Allow requests from same origin or Cloudflare
|
|
99
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
100
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
101
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Cf-Access-Jwt-Assertion, Mcp-Session-Id, Last-Event-Id');
|
|
102
|
+
res.setHeader('Access-Control-Expose-Headers', 'Mcp-Session-Id');
|
|
103
|
+
res.setHeader('Access-Control-Max-Age', '86400');
|
|
104
|
+
// Handle preflight requests
|
|
105
|
+
if (req.method === 'OPTIONS') {
|
|
106
|
+
res.status(204).end();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
next();
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Error handling middleware
|
|
114
|
+
* Catches unhandled errors and returns appropriate response
|
|
115
|
+
*
|
|
116
|
+
* @returns Express error handling middleware
|
|
117
|
+
*/
|
|
118
|
+
export function errorHandler() {
|
|
119
|
+
return (err, _req, res, _next) => {
|
|
120
|
+
// Log error to stderr
|
|
121
|
+
process.stderr.write(`${JSON.stringify({ timestamp: new Date().toISOString(), error: err.message, stack: err.stack })}\n`);
|
|
122
|
+
// Send error response if not already sent
|
|
123
|
+
if (!res.headersSent) {
|
|
124
|
+
res.status(500).json({
|
|
125
|
+
error: 'Internal Server Error',
|
|
126
|
+
message: process.env.NODE_ENV === 'production'
|
|
127
|
+
? 'An unexpected error occurred'
|
|
128
|
+
: err.message,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=middleware.js.map
|