-
Notifications
You must be signed in to change notification settings - Fork 4
feat: Add serverless deployment support for Vercel, AWS Lambda, and Cloudflare Workers #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…loudflare Workers
WalkthroughAdds serverless deployment support: new CLI adapter option, serverless handler registry and exports, Server/adapter lifecycle hooks ( Changes
Sequence DiagramsequenceDiagram
participant Decorator as "@frontmcp" Decorator
participant DynamicImport as Dynamic Import (SDK)
participant FrontMcp as FrontMcpInstance
participant Registry as Serverless Registry
participant HostAdapter as Host Adapter (Express)
participant HTTP as HTTP Handler
Decorator->>Decorator: check FRONTMCP_SERVERLESS==1
alt serverless mode
Decorator->>DynamicImport: import "@frontmcp/sdk"
DynamicImport-->>Decorator: SDK module
Decorator->>FrontMcp: FrontMcpInstance.createHandler(metadata)
FrontMcp->>FrontMcp: instantiate & await readiness
FrontMcp->>HostAdapter: getHandler()
HostAdapter->>HostAdapter: prepare() (register routes, idempotent)
HostAdapter-->>FrontMcp: return handler (HTTP)
FrontMcp-->>Decorator: resolve handler
Decorator->>Registry: setServerlessHandlerPromise(promise)
Registry->>Registry: promise.then -> setServerlessHandler(handler)
Registry->>Registry: promise.catch -> setServerlessHandlerError(err)
else normal mode
Decorator->>FrontMcp: bootstrap(metadata)
FrontMcp->>HostAdapter: start(port)
HostAdapter->>HostAdapter: prepare()
HostAdapter->>HTTP: listen(port)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (1)
docs/draft/docs/deployment/serverless.mdx (1)
322-348: Verify consistency of module format claims.The API reference states Cloudflare uses CommonJS (line 325, matching line 15), while Vercel/Lambda use ESM. This should be verified for accuracy (see earlier comment on lines 11-15).
🧹 Nitpick comments (9)
libs/cli/src/args.ts (1)
33-33: Validate adapter value before casting.The unsafe cast to
DeploymentAdapterallows invalid values to pass through. Consider validating against the allowed set of adapters to provide early, clear error messages.Apply this diff to add validation:
- else if (a === '--adapter' || a === '-a') out.adapter = argv[++i] as DeploymentAdapter; + else if (a === '--adapter' || a === '-a') { + const value = argv[++i]; + const validAdapters: DeploymentAdapter[] = ['node', 'vercel', 'lambda', 'cloudflare']; + if (!validAdapters.includes(value as DeploymentAdapter)) { + throw new Error(`Invalid adapter: ${value}. Must be one of: ${validAdapters.join(', ')}`); + } + out.adapter = value as DeploymentAdapter; + }libs/sdk/src/common/interfaces/server.interface.ts (1)
57-66: Consider allowing asyncprepare()to avoid painting adapters into a corner.Some hosts will need async setup (e.g., async middleware/route init, platform bindings). Consider:
- abstract prepare(): void; + abstract prepare(): Promise<void> | void;
getHandler(): unknownas the base type is OK, but if you want better DX later, a genericgetHandler<T = unknown>(): Tcan be introduced carefully (would be a public API change). Based on learnings, keep handler typing aligned with the platform’s actual handler shape wherever possible.libs/sdk/src/server/adapters/base.host.adapter.ts (1)
4-13: KeepHostServerAdapter.prepare()capable of async, consistent with lifecycle intent.If
FrontMcpServer.prepare()is updated toPromise<void> | void, mirror it here too so host adapters can safely do async wiring without changing the public API later.libs/sdk/src/front-mcp/front-mcp.ts (3)
21-21: Unnecessary array spread.
createMcpGlobalProvidersalready returns an array (perfront-mcp.providers.ts), so spreading into a new array creates an unnecessary copy.- this.providers = new ProviderRegistry([...createMcpGlobalProviders(this.config)]); + this.providers = new ProviderRegistry(createMcpGlobalProviders(this.config));
32-36: Use a specific error class instead of genericError.Per coding guidelines, use specific error classes with MCP error codes. The same pattern applies to line 63 in
createHandler. Consider creating aServerNotFoundErroror similar that extends a base MCP error class with appropriate error codes.
57-68: Consider adding a generic type parameter for better type inference.The
unknownreturn type is reasonable given different serverless platforms have different handler signatures. However, a generic could allow consumers to specify their expected handler type:- public static async createHandler(options: FrontMcpConfigType): Promise<unknown> { + public static async createHandler<T = unknown>(options: FrontMcpConfigType): Promise<T> { const frontMcp = new FrontMcpInstance(options); await frontMcp.ready; const server = frontMcp.providers.get(FrontMcpServer); if (!server) { throw new Error('Server not found'); } server.prepare(); - return server.getHandler(); + return server.getHandler() as T; }This enables typed usage:
createHandler<VercelHandler>(config).libs/sdk/src/front-mcp/serverless-handler.ts (3)
6-8: Consider adding a reset function for testability.Global mutable state is appropriate for this singleton pattern, but without a reset mechanism, tests may leak state between test cases.
let globalHandler: unknown = null; let globalHandlerPromise: Promise<unknown> | null = null; let globalHandlerError: Error | null = null; + +/** + * Reset all serverless handler state (for testing purposes) + * @internal + */ +export function resetServerlessHandler(): void { + globalHandler = null; + globalHandlerPromise = null; + globalHandlerError = null; +}
55-59: Use a specific error class instead of genericError.Per coding guidelines, throw specific error classes with MCP error codes. Consider creating a
ServerlessNotInitializedErroror similar:+// In a shared errors file: +export class ServerlessNotInitializedError extends Error { + constructor() { + super('Serverless handler not initialized. Ensure @FrontMcp decorator ran and FRONTMCP_SERVERLESS=1 is set.'); + this.name = 'ServerlessNotInitializedError'; + } +} // Then in this file: if (!globalHandler) { - throw new Error( - 'Serverless handler not initialized. Ensure @FrontMcp decorator ran and FRONTMCP_SERVERLESS=1 is set.', - ); + throw new ServerlessNotInitializedError(); }
48-61: Logic is sound, but consider clearing the promise on error.The async getter correctly prioritizes error checking. However, if initialization fails,
globalHandlerPromisestill exists and would re-throw on await rather than throwing the cached error immediately. Consider clearing the promise when setting an error for consistent behavior.export function setServerlessHandlerError(error: Error): void { globalHandlerError = error; + globalHandlerPromise = null; // Clear promise so cached error is thrown immediately }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (8)
libs/cli/src/commands/build/README.mdis excluded by!**/build/**libs/cli/src/commands/build/adapters/cloudflare.tsis excluded by!**/build/**libs/cli/src/commands/build/adapters/index.tsis excluded by!**/build/**libs/cli/src/commands/build/adapters/lambda.tsis excluded by!**/build/**libs/cli/src/commands/build/adapters/node.tsis excluded by!**/build/**libs/cli/src/commands/build/adapters/vercel.tsis excluded by!**/build/**libs/cli/src/commands/build/index.tsis excluded by!**/build/**libs/cli/src/commands/build/types.tsis excluded by!**/build/**
📒 Files selected for processing (13)
docs/draft/docs.json(1 hunks)docs/draft/docs/deployment/production-build.mdx(1 hunks)docs/draft/docs/deployment/serverless.mdx(1 hunks)libs/cli/src/args.ts(3 hunks)libs/cli/src/commands/build.ts(0 hunks)libs/sdk/src/common/decorators/front-mcp.decorator.ts(1 hunks)libs/sdk/src/common/interfaces/server.interface.ts(1 hunks)libs/sdk/src/front-mcp/front-mcp.ts(3 hunks)libs/sdk/src/front-mcp/serverless-handler.ts(1 hunks)libs/sdk/src/index.ts(1 hunks)libs/sdk/src/server/adapters/base.host.adapter.ts(1 hunks)libs/sdk/src/server/adapters/express.host.adapter.ts(1 hunks)libs/sdk/src/server/server.instance.ts(1 hunks)
💤 Files with no reviewable changes (1)
- libs/cli/src/commands/build.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.ts: Enable strict TypeScript mode with noanytypes without strong justification - useunknowninstead for generic type defaults
Avoid non-null assertions (!) - use proper error handling and throw specific errors instead
Use specific error classes with MCP error codes instead of generic errors
Use type parameters with constraints instead of unconstrained generics, and preferunknownoveranyfor generic type defaults
Follow the preset pattern for hierarchical configurations across the codebase
Files:
libs/sdk/src/server/adapters/express.host.adapter.tslibs/sdk/src/front-mcp/serverless-handler.tslibs/sdk/src/index.tslibs/cli/src/args.tslibs/sdk/src/common/decorators/front-mcp.decorator.tslibs/sdk/src/server/adapters/base.host.adapter.tslibs/sdk/src/front-mcp/front-mcp.tslibs/sdk/src/common/interfaces/server.interface.tslibs/sdk/src/server/server.instance.ts
libs/{sdk,adapters,plugins,cli}/src/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
libs/{sdk,adapters,plugins,cli}/src/**/*.ts: Return strictly typed MCP protocol responses (GetPromptResult, ReadResourceResult, etc.) instead ofunknownforexecute()andread()methods
Validate URIs per RFC 3986 at metadata level using Zod validation with custom refinements
UsegetCapabilities()for dynamic capability exposure instead of hardcoding capabilities in adapters
UsechangeScopeinstead ofscopefor change event properties to avoid confusion with the Scope class
Validate hooks match their entry type and fail fast with InvalidHookFlowError for unsupported flows
Don't mutate rawInput in flows - use state.set() for managing flow state instead
Files:
libs/sdk/src/server/adapters/express.host.adapter.tslibs/sdk/src/front-mcp/serverless-handler.tslibs/sdk/src/index.tslibs/cli/src/args.tslibs/sdk/src/common/decorators/front-mcp.decorator.tslibs/sdk/src/server/adapters/base.host.adapter.tslibs/sdk/src/front-mcp/front-mcp.tslibs/sdk/src/common/interfaces/server.interface.tslibs/sdk/src/server/server.instance.ts
libs/**
⚙️ CodeRabbit configuration file
libs/**: Contains publishable SDK libraries. Review for API correctness, breaking changes, and consistency with docs. When public APIs change, ensure there is a matching docs/draft/docs/** update (not direct edits under docs/docs/**).
Files:
libs/sdk/src/server/adapters/express.host.adapter.tslibs/sdk/src/front-mcp/serverless-handler.tslibs/sdk/src/index.tslibs/cli/src/args.tslibs/sdk/src/common/decorators/front-mcp.decorator.tslibs/sdk/src/server/adapters/base.host.adapter.tslibs/sdk/src/front-mcp/front-mcp.tslibs/sdk/src/common/interfaces/server.interface.tslibs/sdk/src/server/server.instance.ts
docs/**
⚙️ CodeRabbit configuration file
docs/**: Repository documentation for the SDK, using MDX and hosted by Mintlify. See more specific rules for: - docs/docs/** (latest rendered docs, automation-only) - docs/v/** (archived versions, read-only) - docs/draft/docs/** (human-editable drafts) - docs/blogs/** (blogs, human edited) - docs/docs.json (Mintlify navigation)
Files:
docs/draft/docs.jsondocs/draft/docs/deployment/serverless.mdxdocs/draft/docs/deployment/production-build.mdx
libs/*/src/index.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Use barrel exports (index.ts) to export all public API surface without legacy exports or aliases
Files:
libs/sdk/src/index.ts
docs/draft/docs/**
⚙️ CodeRabbit configuration file
docs/draft/docs/**: This folder holds the draft/source docs that humans are expected to edit. When authors want to add or change documentation, they should do it here. The Codex workflow uses these drafts, together with the code diff, to generate the latest docs under docs/docs/. As a reviewer: - Encourage contributors to add/update content here instead of docs/docs/. - It is fine to do structural/content feedback here (clarity, examples, etc).
Files:
docs/draft/docs/deployment/serverless.mdxdocs/draft/docs/deployment/production-build.mdx
🧠 Learnings (3)
📚 Learning: 2025-12-01T00:33:33.644Z
Learnt from: CR
Repo: agentfront/frontmcp PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T00:33:33.644Z
Learning: Applies to libs/{sdk,adapters,plugins,cli}/src/**/*.ts : Use `getCapabilities()` for dynamic capability exposure instead of hardcoding capabilities in adapters
Applied to files:
libs/sdk/src/server/adapters/express.host.adapter.tslibs/cli/src/args.tslibs/sdk/src/server/adapters/base.host.adapter.ts
📚 Learning: 2025-12-01T00:33:33.644Z
Learnt from: CR
Repo: agentfront/frontmcp PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T00:33:33.644Z
Learning: Applies to libs/*/src/index.ts : Use barrel exports (index.ts) to export all public API surface without legacy exports or aliases
Applied to files:
libs/sdk/src/index.ts
📚 Learning: 2025-12-01T00:33:33.644Z
Learnt from: CR
Repo: agentfront/frontmcp PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T00:33:33.644Z
Learning: Use FrontMCP's TypeScript-first schema validation framework philosophy - all types should align with MCP protocol definitions
Applied to files:
libs/sdk/src/common/decorators/front-mcp.decorator.tslibs/sdk/src/front-mcp/front-mcp.tslibs/sdk/src/common/interfaces/server.interface.tslibs/sdk/src/server/server.instance.ts
🧬 Code graph analysis (2)
libs/sdk/src/common/decorators/front-mcp.decorator.ts (3)
libs/sdk/src/front-mcp/front-mcp.ts (1)
FrontMcpInstance(7-69)libs/sdk/src/index.ts (4)
FrontMcpInstance(4-4)setServerlessHandlerPromise(9-9)setServerlessHandler(8-8)setServerlessHandlerError(10-10)libs/sdk/src/front-mcp/serverless-handler.ts (3)
setServerlessHandlerPromise(20-22)setServerlessHandler(13-15)setServerlessHandlerError(27-29)
libs/sdk/src/front-mcp/front-mcp.ts (3)
libs/sdk/src/provider/provider.registry.ts (1)
ProviderRegistry(26-649)libs/sdk/src/front-mcp/front-mcp.providers.ts (1)
createMcpGlobalProviders(26-28)libs/sdk/src/common/metadata/front-mcp.metadata.ts (1)
FrontMcpConfigType(90-90)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (10)
docs/draft/docs/deployment/serverless.mdx (5)
96-106: LGTM!The generated handler example clearly demonstrates the serverless initialization flow and is consistent with the architecture described later in the document.
176-185: LGTM!The ESM requirements note accurately documents Lambda's ESM support options and provides clear guidance for users.
197-209: LGTM!The experimental warning and limitations are clearly documented, and the suggestion to consider Hono for production is appropriate guidance.
243-267: LGTM!The architecture diagram and flow explanation clearly document the serverless initialization lifecycle and provide valuable insight into how the decorator detects and handles serverless mode.
11-15: The table entry is accurate. The Cloudflare adapter is intentionally configured to generate CommonJS (as confirmed inlibs/cli/src/commands/build/adapters/cloudflare.tswithmoduleFormat: 'commonjs'), and this is documented consistently throughout the codebase at lines 325 and 343-344. The CommonJS format supports the Express-to-Cloudflare Workers compatibility layer. No changes needed.libs/cli/src/args.ts (1)
13-13: LGTM!The DeploymentAdapter type correctly defines the supported serverless platforms.
docs/draft/docs.json (1)
166-170: LGTM!The navigation update correctly adds the new serverless deployment page to the Deployment group.
docs/draft/docs/deployment/production-build.mdx (1)
9-12: LGTM!The tip block effectively guides users to the serverless deployment documentation when appropriate, improving discoverability.
libs/sdk/src/index.ts (1)
5-11: Barrel re-exports look good and keep the serverless API discoverable.libs/sdk/src/front-mcp/serverless-handler.ts (1)
13-29: LGTM!The setter functions are clean and straightforward. The separation of concerns (handler, promise, error) provides flexibility for the decorator to wire up the initialization flow properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
libs/sdk/src/server/server.instance.ts (1)
49-51: Consider adding JSDoc documentation forgetHandler().The
unknownreturn type is appropriate given the polymorphic nature of serverless handlers (Express app, Lambda handler, Cloudflare Worker, etc.). However, adding JSDoc comments would help callers understand what handler types to expect from different adapters.Example documentation:
+ /** + * Retrieves the underlying serverless handler. + * The handler type depends on the host adapter: + * - ExpressHostAdapter returns an Express Application + * - AWS Lambda adapter returns RequestHandler + * - Cloudflare Workers adapter returns ExportedHandler + * Callers must cast to the appropriate type based on their deployment target. + */ getHandler(): unknown { return this.host.getHandler(); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
libs/sdk/src/common/decorators/front-mcp.decorator.ts(1 hunks)libs/sdk/src/server/adapters/express.host.adapter.ts(2 hunks)libs/sdk/src/server/server.instance.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- libs/sdk/src/server/adapters/express.host.adapter.ts
- libs/sdk/src/common/decorators/front-mcp.decorator.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.ts: Enable strict TypeScript mode with noanytypes without strong justification - useunknowninstead for generic type defaults
Avoid non-null assertions (!) - use proper error handling and throw specific errors instead
Use specific error classes with MCP error codes instead of generic errors
Use type parameters with constraints instead of unconstrained generics, and preferunknownoveranyfor generic type defaults
Follow the preset pattern for hierarchical configurations across the codebase
Files:
libs/sdk/src/server/server.instance.ts
libs/{sdk,adapters,plugins,cli}/src/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
libs/{sdk,adapters,plugins,cli}/src/**/*.ts: Return strictly typed MCP protocol responses (GetPromptResult, ReadResourceResult, etc.) instead ofunknownforexecute()andread()methods
Validate URIs per RFC 3986 at metadata level using Zod validation with custom refinements
UsegetCapabilities()for dynamic capability exposure instead of hardcoding capabilities in adapters
UsechangeScopeinstead ofscopefor change event properties to avoid confusion with the Scope class
Validate hooks match their entry type and fail fast with InvalidHookFlowError for unsupported flows
Don't mutate rawInput in flows - use state.set() for managing flow state instead
Files:
libs/sdk/src/server/server.instance.ts
libs/**
⚙️ CodeRabbit configuration file
libs/**: Contains publishable SDK libraries. Review for API correctness, breaking changes, and consistency with docs. When public APIs change, ensure there is a matching docs/draft/docs/** update (not direct edits under docs/docs/**).
Files:
libs/sdk/src/server/server.instance.ts
🧬 Code graph analysis (1)
libs/sdk/src/server/server.instance.ts (1)
libs/sdk/src/common/interfaces/server.interface.ts (2)
HttpMethod(4-4)ServerRequestHandler(30-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (2)
libs/sdk/src/server/server.instance.ts (2)
8-8: Idempotency implementation addresses past review concerns effectively.The
healthRouteRegisteredflag prevents duplicate/healthroute registrations whenprepare()is called multiple times. This implementation cleanly resolves the issue flagged in the past review comment.Also applies to: 39-47
53-56: Excellent lifecycle separation.Calling
prepare()before starting the host ensures routes are properly registered before the server begins accepting requests. This design also supports the serverless deployment pattern whereprepare()andgetHandler()can be invoked without callingstart().
Summary by CodeRabbit
New Features
Documentation
Revert
✏️ Tip: You can customize this high-level summary in your review settings.