Initial commit: Complete Hive distributed AI orchestration platform

This comprehensive implementation includes:
- FastAPI backend with MCP server integration
- React/TypeScript frontend with Vite
- PostgreSQL database with Redis caching
- Grafana/Prometheus monitoring stack
- Docker Compose orchestration
- Full MCP protocol support for Claude Code integration

Features:
- Agent discovery and management across network
- Visual workflow editor and execution engine
- Real-time task coordination and monitoring
- Multi-model support with specialized agents
- Distributed development task allocation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-07-07 21:44:31 +10:00
commit d7ad321176
2631 changed files with 870175 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
import { OAuthClientInformationFull } from "../../shared/auth.js";
/**
* Stores information about registered OAuth clients for this server.
*/
export interface OAuthRegisteredClientsStore {
/**
* Returns information about a registered client, based on its ID.
*/
getClient(clientId: string): OAuthClientInformationFull | undefined | Promise<OAuthClientInformationFull | undefined>;
/**
* Registers a new client with the server. The client ID and secret will be automatically generated by the library. A modified version of the client information can be returned to reflect specific values enforced by the server.
*
* NOTE: Implementations should NOT delete expired client secrets in-place. Auth middleware provided by this library will automatically check the `client_secret_expires_at` field and reject requests with expired secrets. Any custom logic for authenticating clients should check the `client_secret_expires_at` field as well.
*
* If unimplemented, dynamic client registration is unsupported.
*/
registerClient?(client: OAuthClientInformationFull): OAuthClientInformationFull | Promise<OAuthClientInformationFull>;
}
//# sourceMappingURL=clients.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"clients.d.ts","sourceRoot":"","sources":["../../../../src/server/auth/clients.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS,GAAG,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC,CAAC;IAEtH;;;;;;OAMG;IACH,cAAc,CAAC,CAAC,MAAM,EAAE,0BAA0B,GAAG,0BAA0B,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;CACvH"}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=clients.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"clients.js","sourceRoot":"","sources":["../../../../src/server/auth/clients.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,126 @@
import { OAuthErrorResponse } from "../../shared/auth.js";
/**
* Base class for all OAuth errors
*/
export declare class OAuthError extends Error {
readonly errorCode: string;
readonly errorUri?: string | undefined;
constructor(errorCode: string, message: string, errorUri?: string | undefined);
/**
* Converts the error to a standard OAuth error response object
*/
toResponseObject(): OAuthErrorResponse;
}
/**
* Invalid request error - The request is missing a required parameter,
* includes an invalid parameter value, includes a parameter more than once,
* or is otherwise malformed.
*/
export declare class InvalidRequestError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Invalid client error - Client authentication failed (e.g., unknown client, no client
* authentication included, or unsupported authentication method).
*/
export declare class InvalidClientError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Invalid grant error - The provided authorization grant or refresh token is
* invalid, expired, revoked, does not match the redirection URI used in the
* authorization request, or was issued to another client.
*/
export declare class InvalidGrantError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Unauthorized client error - The authenticated client is not authorized to use
* this authorization grant type.
*/
export declare class UnauthorizedClientError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Unsupported grant type error - The authorization grant type is not supported
* by the authorization server.
*/
export declare class UnsupportedGrantTypeError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Invalid scope error - The requested scope is invalid, unknown, malformed, or
* exceeds the scope granted by the resource owner.
*/
export declare class InvalidScopeError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Access denied error - The resource owner or authorization server denied the request.
*/
export declare class AccessDeniedError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Server error - The authorization server encountered an unexpected condition
* that prevented it from fulfilling the request.
*/
export declare class ServerError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Temporarily unavailable error - The authorization server is currently unable to
* handle the request due to a temporary overloading or maintenance of the server.
*/
export declare class TemporarilyUnavailableError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Unsupported response type error - The authorization server does not support
* obtaining an authorization code using this method.
*/
export declare class UnsupportedResponseTypeError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Unsupported token type error - The authorization server does not support
* the requested token type.
*/
export declare class UnsupportedTokenTypeError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Invalid token error - The access token provided is expired, revoked, malformed,
* or invalid for other reasons.
*/
export declare class InvalidTokenError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Method not allowed error - The HTTP method used is not allowed for this endpoint.
* (Custom, non-standard error)
*/
export declare class MethodNotAllowedError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Too many requests error - Rate limit exceeded.
* (Custom, non-standard error based on RFC 6585)
*/
export declare class TooManyRequestsError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Invalid client metadata error - The client metadata is invalid.
* (Custom error for dynamic client registration - RFC 7591)
*/
export declare class InvalidClientMetadataError extends OAuthError {
constructor(message: string, errorUri?: string);
}
/**
* Insufficient scope error - The request requires higher privileges than provided by the access token.
*/
export declare class InsufficientScopeError extends OAuthError {
constructor(message: string, errorUri?: string);
}
//# sourceMappingURL=errors.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../../src/server/auth/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;GAEG;AACH,qBAAa,UAAW,SAAQ,KAAK;aAEjB,SAAS,EAAE,MAAM;aAEjB,QAAQ,CAAC,EAAE,MAAM;gBAFjB,SAAS,EAAE,MAAM,EACjC,OAAO,EAAE,MAAM,EACC,QAAQ,CAAC,EAAE,MAAM,YAAA;IAMnC;;OAEG;IACH,gBAAgB,IAAI,kBAAkB;CAYvC;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,UAAU;gBACrC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;gBACpC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,UAAU;gBACzC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;GAGG;AACH,qBAAa,yBAA0B,SAAQ,UAAU;gBAC3C,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;GAGG;AACH,qBAAa,WAAY,SAAQ,UAAU;gBAC7B,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;GAGG;AACH,qBAAa,2BAA4B,SAAQ,UAAU;gBAC7C,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;GAGG;AACH,qBAAa,4BAA6B,SAAQ,UAAU;gBAC9C,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;GAGG;AACH,qBAAa,yBAA0B,SAAQ,UAAU;gBAC3C,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,UAAU;gBACnC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,UAAU;gBACvC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;gBACtC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;;GAGG;AACH,qBAAa,0BAA2B,SAAQ,UAAU;gBAC5C,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,UAAU;gBACxC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAG/C"}

View File

@@ -0,0 +1,189 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InsufficientScopeError = exports.InvalidClientMetadataError = exports.TooManyRequestsError = exports.MethodNotAllowedError = exports.InvalidTokenError = exports.UnsupportedTokenTypeError = exports.UnsupportedResponseTypeError = exports.TemporarilyUnavailableError = exports.ServerError = exports.AccessDeniedError = exports.InvalidScopeError = exports.UnsupportedGrantTypeError = exports.UnauthorizedClientError = exports.InvalidGrantError = exports.InvalidClientError = exports.InvalidRequestError = exports.OAuthError = void 0;
/**
* Base class for all OAuth errors
*/
class OAuthError extends Error {
constructor(errorCode, message, errorUri) {
super(message);
this.errorCode = errorCode;
this.errorUri = errorUri;
this.name = this.constructor.name;
}
/**
* Converts the error to a standard OAuth error response object
*/
toResponseObject() {
const response = {
error: this.errorCode,
error_description: this.message
};
if (this.errorUri) {
response.error_uri = this.errorUri;
}
return response;
}
}
exports.OAuthError = OAuthError;
/**
* Invalid request error - The request is missing a required parameter,
* includes an invalid parameter value, includes a parameter more than once,
* or is otherwise malformed.
*/
class InvalidRequestError extends OAuthError {
constructor(message, errorUri) {
super("invalid_request", message, errorUri);
}
}
exports.InvalidRequestError = InvalidRequestError;
/**
* Invalid client error - Client authentication failed (e.g., unknown client, no client
* authentication included, or unsupported authentication method).
*/
class InvalidClientError extends OAuthError {
constructor(message, errorUri) {
super("invalid_client", message, errorUri);
}
}
exports.InvalidClientError = InvalidClientError;
/**
* Invalid grant error - The provided authorization grant or refresh token is
* invalid, expired, revoked, does not match the redirection URI used in the
* authorization request, or was issued to another client.
*/
class InvalidGrantError extends OAuthError {
constructor(message, errorUri) {
super("invalid_grant", message, errorUri);
}
}
exports.InvalidGrantError = InvalidGrantError;
/**
* Unauthorized client error - The authenticated client is not authorized to use
* this authorization grant type.
*/
class UnauthorizedClientError extends OAuthError {
constructor(message, errorUri) {
super("unauthorized_client", message, errorUri);
}
}
exports.UnauthorizedClientError = UnauthorizedClientError;
/**
* Unsupported grant type error - The authorization grant type is not supported
* by the authorization server.
*/
class UnsupportedGrantTypeError extends OAuthError {
constructor(message, errorUri) {
super("unsupported_grant_type", message, errorUri);
}
}
exports.UnsupportedGrantTypeError = UnsupportedGrantTypeError;
/**
* Invalid scope error - The requested scope is invalid, unknown, malformed, or
* exceeds the scope granted by the resource owner.
*/
class InvalidScopeError extends OAuthError {
constructor(message, errorUri) {
super("invalid_scope", message, errorUri);
}
}
exports.InvalidScopeError = InvalidScopeError;
/**
* Access denied error - The resource owner or authorization server denied the request.
*/
class AccessDeniedError extends OAuthError {
constructor(message, errorUri) {
super("access_denied", message, errorUri);
}
}
exports.AccessDeniedError = AccessDeniedError;
/**
* Server error - The authorization server encountered an unexpected condition
* that prevented it from fulfilling the request.
*/
class ServerError extends OAuthError {
constructor(message, errorUri) {
super("server_error", message, errorUri);
}
}
exports.ServerError = ServerError;
/**
* Temporarily unavailable error - The authorization server is currently unable to
* handle the request due to a temporary overloading or maintenance of the server.
*/
class TemporarilyUnavailableError extends OAuthError {
constructor(message, errorUri) {
super("temporarily_unavailable", message, errorUri);
}
}
exports.TemporarilyUnavailableError = TemporarilyUnavailableError;
/**
* Unsupported response type error - The authorization server does not support
* obtaining an authorization code using this method.
*/
class UnsupportedResponseTypeError extends OAuthError {
constructor(message, errorUri) {
super("unsupported_response_type", message, errorUri);
}
}
exports.UnsupportedResponseTypeError = UnsupportedResponseTypeError;
/**
* Unsupported token type error - The authorization server does not support
* the requested token type.
*/
class UnsupportedTokenTypeError extends OAuthError {
constructor(message, errorUri) {
super("unsupported_token_type", message, errorUri);
}
}
exports.UnsupportedTokenTypeError = UnsupportedTokenTypeError;
/**
* Invalid token error - The access token provided is expired, revoked, malformed,
* or invalid for other reasons.
*/
class InvalidTokenError extends OAuthError {
constructor(message, errorUri) {
super("invalid_token", message, errorUri);
}
}
exports.InvalidTokenError = InvalidTokenError;
/**
* Method not allowed error - The HTTP method used is not allowed for this endpoint.
* (Custom, non-standard error)
*/
class MethodNotAllowedError extends OAuthError {
constructor(message, errorUri) {
super("method_not_allowed", message, errorUri);
}
}
exports.MethodNotAllowedError = MethodNotAllowedError;
/**
* Too many requests error - Rate limit exceeded.
* (Custom, non-standard error based on RFC 6585)
*/
class TooManyRequestsError extends OAuthError {
constructor(message, errorUri) {
super("too_many_requests", message, errorUri);
}
}
exports.TooManyRequestsError = TooManyRequestsError;
/**
* Invalid client metadata error - The client metadata is invalid.
* (Custom error for dynamic client registration - RFC 7591)
*/
class InvalidClientMetadataError extends OAuthError {
constructor(message, errorUri) {
super("invalid_client_metadata", message, errorUri);
}
}
exports.InvalidClientMetadataError = InvalidClientMetadataError;
/**
* Insufficient scope error - The request requires higher privileges than provided by the access token.
*/
class InsufficientScopeError extends OAuthError {
constructor(message, errorUri) {
super("insufficient_scope", message, errorUri);
}
}
exports.InsufficientScopeError = InsufficientScopeError;
//# sourceMappingURL=errors.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../../src/server/auth/errors.ts"],"names":[],"mappings":";;;AAEA;;GAEG;AACH,MAAa,UAAW,SAAQ,KAAK;IACnC,YACkB,SAAiB,EACjC,OAAe,EACC,QAAiB;QAEjC,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,cAAS,GAAT,SAAS,CAAQ;QAEjB,aAAQ,GAAR,QAAQ,CAAS;QAGjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IACpC,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,MAAM,QAAQ,GAAuB;YACnC,KAAK,EAAE,IAAI,CAAC,SAAS;YACrB,iBAAiB,EAAE,IAAI,CAAC,OAAO;SAChC,CAAC;QAEF,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC;QACrC,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AAzBD,gCAyBC;AAED;;;;GAIG;AACH,MAAa,mBAAoB,SAAQ,UAAU;IACjD,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,iBAAiB,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC9C,CAAC;CACF;AAJD,kDAIC;AAED;;;GAGG;AACH,MAAa,kBAAmB,SAAQ,UAAU;IAChD,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,gBAAgB,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC;CACF;AAJD,gDAIC;AAED;;;;GAIG;AACH,MAAa,iBAAkB,SAAQ,UAAU;IAC/C,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,eAAe,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;CACF;AAJD,8CAIC;AAED;;;GAGG;AACH,MAAa,uBAAwB,SAAQ,UAAU;IACrD,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,qBAAqB,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAClD,CAAC;CACF;AAJD,0DAIC;AAED;;;GAGG;AACH,MAAa,yBAA0B,SAAQ,UAAU;IACvD,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,wBAAwB,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACrD,CAAC;CACF;AAJD,8DAIC;AAED;;;GAGG;AACH,MAAa,iBAAkB,SAAQ,UAAU;IAC/C,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,eAAe,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;CACF;AAJD,8CAIC;AAED;;GAEG;AACH,MAAa,iBAAkB,SAAQ,UAAU;IAC/C,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,eAAe,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;CACF;AAJD,8CAIC;AAED;;;GAGG;AACH,MAAa,WAAY,SAAQ,UAAU;IACzC,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,cAAc,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC3C,CAAC;CACF;AAJD,kCAIC;AAED;;;GAGG;AACH,MAAa,2BAA4B,SAAQ,UAAU;IACzD,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,yBAAyB,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACtD,CAAC;CACF;AAJD,kEAIC;AAED;;;GAGG;AACH,MAAa,4BAA6B,SAAQ,UAAU;IAC1D,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,2BAA2B,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACxD,CAAC;CACF;AAJD,oEAIC;AAED;;;GAGG;AACH,MAAa,yBAA0B,SAAQ,UAAU;IACvD,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,wBAAwB,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACrD,CAAC;CACF;AAJD,8DAIC;AAED;;;GAGG;AACH,MAAa,iBAAkB,SAAQ,UAAU;IAC/C,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,eAAe,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;CACF;AAJD,8CAIC;AAED;;;GAGG;AACH,MAAa,qBAAsB,SAAQ,UAAU;IACnD,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,oBAAoB,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACjD,CAAC;CACF;AAJD,sDAIC;AAED;;;GAGG;AACH,MAAa,oBAAqB,SAAQ,UAAU;IAClD,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,mBAAmB,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAChD,CAAC;CACF;AAJD,oDAIC;AAED;;;GAGG;AACH,MAAa,0BAA2B,SAAQ,UAAU;IACxD,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,yBAAyB,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACtD,CAAC;CACF;AAJD,gEAIC;AAED;;GAEG;AACH,MAAa,sBAAuB,SAAQ,UAAU;IACpD,YAAY,OAAe,EAAE,QAAiB;QAC5C,KAAK,CAAC,oBAAoB,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACjD,CAAC;CACF;AAJD,wDAIC"}

View File

@@ -0,0 +1,13 @@
import { RequestHandler } from "express";
import { OAuthServerProvider } from "../provider.js";
import { Options as RateLimitOptions } from "express-rate-limit";
export type AuthorizationHandlerOptions = {
provider: OAuthServerProvider;
/**
* Rate limiting configuration for the authorization endpoint.
* Set to false to disable rate limiting for this endpoint.
*/
rateLimit?: Partial<RateLimitOptions> | false;
};
export declare function authorizationHandler({ provider, rateLimit: rateLimitConfig }: AuthorizationHandlerOptions): RequestHandler;
//# sourceMappingURL=authorize.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../../../../src/server/auth/handlers/authorize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAGzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAa,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAW5E,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,KAAK,CAAC;CAC/C,CAAC;AAkBF,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE,EAAE,2BAA2B,GAAG,cAAc,CAkH1H"}

View File

@@ -0,0 +1,149 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.authorizationHandler = authorizationHandler;
const zod_1 = require("zod");
const express_1 = __importDefault(require("express"));
const express_rate_limit_1 = require("express-rate-limit");
const allowedMethods_js_1 = require("../middleware/allowedMethods.js");
const errors_js_1 = require("../errors.js");
// Parameters that must be validated in order to issue redirects.
const ClientAuthorizationParamsSchema = zod_1.z.object({
client_id: zod_1.z.string(),
redirect_uri: zod_1.z.string().optional().refine((value) => value === undefined || URL.canParse(value), { message: "redirect_uri must be a valid URL" }),
});
// Parameters that must be validated for a successful authorization request. Failure can be reported to the redirect URI.
const RequestAuthorizationParamsSchema = zod_1.z.object({
response_type: zod_1.z.literal("code"),
code_challenge: zod_1.z.string(),
code_challenge_method: zod_1.z.literal("S256"),
scope: zod_1.z.string().optional(),
state: zod_1.z.string().optional(),
resource: zod_1.z.string().url().optional(),
});
function authorizationHandler({ provider, rateLimit: rateLimitConfig }) {
// Create a router to apply middleware
const router = express_1.default.Router();
router.use((0, allowedMethods_js_1.allowedMethods)(["GET", "POST"]));
router.use(express_1.default.urlencoded({ extended: false }));
// Apply rate limiting unless explicitly disabled
if (rateLimitConfig !== false) {
router.use((0, express_rate_limit_1.rateLimit)({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per windowMs
standardHeaders: true,
legacyHeaders: false,
message: new errors_js_1.TooManyRequestsError('You have exceeded the rate limit for authorization requests').toResponseObject(),
...rateLimitConfig
}));
}
router.all("/", async (req, res) => {
var _a;
res.setHeader('Cache-Control', 'no-store');
// In the authorization flow, errors are split into two categories:
// 1. Pre-redirect errors (direct response with 400)
// 2. Post-redirect errors (redirect with error parameters)
// Phase 1: Validate client_id and redirect_uri. Any errors here must be direct responses.
let client_id, redirect_uri, client;
try {
const result = ClientAuthorizationParamsSchema.safeParse(req.method === 'POST' ? req.body : req.query);
if (!result.success) {
throw new errors_js_1.InvalidRequestError(result.error.message);
}
client_id = result.data.client_id;
redirect_uri = result.data.redirect_uri;
client = await provider.clientsStore.getClient(client_id);
if (!client) {
throw new errors_js_1.InvalidClientError("Invalid client_id");
}
if (redirect_uri !== undefined) {
if (!client.redirect_uris.includes(redirect_uri)) {
throw new errors_js_1.InvalidRequestError("Unregistered redirect_uri");
}
}
else if (client.redirect_uris.length === 1) {
redirect_uri = client.redirect_uris[0];
}
else {
throw new errors_js_1.InvalidRequestError("redirect_uri must be specified when client has multiple registered URIs");
}
}
catch (error) {
// Pre-redirect errors - return direct response
//
// These don't need to be JSON encoded, as they'll be displayed in a user
// agent, but OTOH they all represent exceptional situations (arguably,
// "programmer error"), so presenting a nice HTML page doesn't help the
// user anyway.
if (error instanceof errors_js_1.OAuthError) {
const status = error instanceof errors_js_1.ServerError ? 500 : 400;
res.status(status).json(error.toResponseObject());
}
else {
const serverError = new errors_js_1.ServerError("Internal Server Error");
res.status(500).json(serverError.toResponseObject());
}
return;
}
// Phase 2: Validate other parameters. Any errors here should go into redirect responses.
let state;
try {
// Parse and validate authorization parameters
const parseResult = RequestAuthorizationParamsSchema.safeParse(req.method === 'POST' ? req.body : req.query);
if (!parseResult.success) {
throw new errors_js_1.InvalidRequestError(parseResult.error.message);
}
const { scope, code_challenge, resource } = parseResult.data;
state = parseResult.data.state;
// Validate scopes
let requestedScopes = [];
if (scope !== undefined) {
requestedScopes = scope.split(" ");
const allowedScopes = new Set((_a = client.scope) === null || _a === void 0 ? void 0 : _a.split(" "));
// Check each requested scope against allowed scopes
for (const scope of requestedScopes) {
if (!allowedScopes.has(scope)) {
throw new errors_js_1.InvalidScopeError(`Client was not registered with scope ${scope}`);
}
}
}
// All validation passed, proceed with authorization
await provider.authorize(client, {
state,
scopes: requestedScopes,
redirectUri: redirect_uri,
codeChallenge: code_challenge,
resource: resource ? new URL(resource) : undefined,
}, res);
}
catch (error) {
// Post-redirect errors - redirect with error parameters
if (error instanceof errors_js_1.OAuthError) {
res.redirect(302, createErrorRedirect(redirect_uri, error, state));
}
else {
const serverError = new errors_js_1.ServerError("Internal Server Error");
res.redirect(302, createErrorRedirect(redirect_uri, serverError, state));
}
}
});
return router;
}
/**
* Helper function to create redirect URL with error parameters
*/
function createErrorRedirect(redirectUri, error, state) {
const errorUrl = new URL(redirectUri);
errorUrl.searchParams.set("error", error.errorCode);
errorUrl.searchParams.set("error_description", error.message);
if (error.errorUri) {
errorUrl.searchParams.set("error_uri", error.errorUri);
}
if (state) {
errorUrl.searchParams.set("state", state);
}
return errorUrl.href;
}
//# sourceMappingURL=authorize.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
import { RequestHandler } from "express";
import { OAuthMetadata, OAuthProtectedResourceMetadata } from "../../../shared/auth.js";
export declare function metadataHandler(metadata: OAuthMetadata | OAuthProtectedResourceMetadata): RequestHandler;
//# sourceMappingURL=metadata.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../../../../src/server/auth/handlers/metadata.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,8BAA8B,EAAE,MAAM,yBAAyB,CAAC;AAIxF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,aAAa,GAAG,8BAA8B,GAAG,cAAc,CAaxG"}

View File

@@ -0,0 +1,21 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.metadataHandler = metadataHandler;
const express_1 = __importDefault(require("express"));
const cors_1 = __importDefault(require("cors"));
const allowedMethods_js_1 = require("../middleware/allowedMethods.js");
function metadataHandler(metadata) {
// Nested router so we can configure middleware and restrict HTTP method
const router = express_1.default.Router();
// Configure CORS to allow any origin, to make accessible to web-based MCP clients
router.use((0, cors_1.default)());
router.use((0, allowedMethods_js_1.allowedMethods)(['GET']));
router.get("/", (req, res) => {
res.status(200).json(metadata);
});
return router;
}
//# sourceMappingURL=metadata.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../../../../src/server/auth/handlers/metadata.ts"],"names":[],"mappings":";;;;;AAKA,0CAaC;AAlBD,sDAAkD;AAElD,gDAAwB;AACxB,uEAAiE;AAEjE,SAAgB,eAAe,CAAC,QAAwD;IACtF,wEAAwE;IACxE,MAAM,MAAM,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAC;IAEhC,kFAAkF;IAClF,MAAM,CAAC,GAAG,CAAC,IAAA,cAAI,GAAE,CAAC,CAAC;IAEnB,MAAM,CAAC,GAAG,CAAC,IAAA,kCAAc,EAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACpC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC3B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}

View File

@@ -0,0 +1,23 @@
import { RequestHandler } from "express";
import { OAuthRegisteredClientsStore } from "../clients.js";
import { Options as RateLimitOptions } from "express-rate-limit";
export type ClientRegistrationHandlerOptions = {
/**
* A store used to save information about dynamically registered OAuth clients.
*/
clientsStore: OAuthRegisteredClientsStore;
/**
* The number of seconds after which to expire issued client secrets, or 0 to prevent expiration of client secrets (not recommended).
*
* If not set, defaults to 30 days.
*/
clientSecretExpirySeconds?: number;
/**
* Rate limiting configuration for the client registration endpoint.
* Set to false to disable rate limiting for this endpoint.
* Registration endpoints are particularly sensitive to abuse and should be rate limited.
*/
rateLimit?: Partial<RateLimitOptions> | false;
};
export declare function clientRegistrationHandler({ clientsStore, clientSecretExpirySeconds, rateLimit: rateLimitConfig }: ClientRegistrationHandlerOptions): RequestHandler;
//# sourceMappingURL=register.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../../../src/server/auth/handlers/register.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAIlD,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAa,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAS5E,MAAM,MAAM,gCAAgC,GAAG;IAC7C;;OAEG;IACH,YAAY,EAAE,2BAA2B,CAAC;IAE1C;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAEnC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,KAAK,CAAC;CAC/C,CAAC;AAIF,wBAAgB,yBAAyB,CAAC,EACxC,YAAY,EACZ,yBAAgE,EAChE,SAAS,EAAE,eAAe,EAC3B,EAAE,gCAAgC,GAAG,cAAc,CAwEnD"}

View File

@@ -0,0 +1,78 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.clientRegistrationHandler = clientRegistrationHandler;
const express_1 = __importDefault(require("express"));
const auth_js_1 = require("../../../shared/auth.js");
const node_crypto_1 = __importDefault(require("node:crypto"));
const cors_1 = __importDefault(require("cors"));
const express_rate_limit_1 = require("express-rate-limit");
const allowedMethods_js_1 = require("../middleware/allowedMethods.js");
const errors_js_1 = require("../errors.js");
const DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS = 30 * 24 * 60 * 60; // 30 days
function clientRegistrationHandler({ clientsStore, clientSecretExpirySeconds = DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS, rateLimit: rateLimitConfig }) {
if (!clientsStore.registerClient) {
throw new Error("Client registration store does not support registering clients");
}
// Nested router so we can configure middleware and restrict HTTP method
const router = express_1.default.Router();
// Configure CORS to allow any origin, to make accessible to web-based MCP clients
router.use((0, cors_1.default)());
router.use((0, allowedMethods_js_1.allowedMethods)(["POST"]));
router.use(express_1.default.json());
// Apply rate limiting unless explicitly disabled - stricter limits for registration
if (rateLimitConfig !== false) {
router.use((0, express_rate_limit_1.rateLimit)({
windowMs: 60 * 60 * 1000, // 1 hour
max: 20, // 20 requests per hour - stricter as registration is sensitive
standardHeaders: true,
legacyHeaders: false,
message: new errors_js_1.TooManyRequestsError('You have exceeded the rate limit for client registration requests').toResponseObject(),
...rateLimitConfig
}));
}
router.post("/", async (req, res) => {
res.setHeader('Cache-Control', 'no-store');
try {
const parseResult = auth_js_1.OAuthClientMetadataSchema.safeParse(req.body);
if (!parseResult.success) {
throw new errors_js_1.InvalidClientMetadataError(parseResult.error.message);
}
const clientMetadata = parseResult.data;
const isPublicClient = clientMetadata.token_endpoint_auth_method === 'none';
// Generate client credentials
const clientId = node_crypto_1.default.randomUUID();
const clientSecret = isPublicClient
? undefined
: node_crypto_1.default.randomBytes(32).toString('hex');
const clientIdIssuedAt = Math.floor(Date.now() / 1000);
// Calculate client secret expiry time
const clientsDoExpire = clientSecretExpirySeconds > 0;
const secretExpiryTime = clientsDoExpire ? clientIdIssuedAt + clientSecretExpirySeconds : 0;
const clientSecretExpiresAt = isPublicClient ? undefined : secretExpiryTime;
let clientInfo = {
...clientMetadata,
client_id: clientId,
client_secret: clientSecret,
client_id_issued_at: clientIdIssuedAt,
client_secret_expires_at: clientSecretExpiresAt,
};
clientInfo = await clientsStore.registerClient(clientInfo);
res.status(201).json(clientInfo);
}
catch (error) {
if (error instanceof errors_js_1.OAuthError) {
const status = error instanceof errors_js_1.ServerError ? 500 : 400;
res.status(status).json(error.toResponseObject());
}
else {
const serverError = new errors_js_1.ServerError("Internal Server Error");
res.status(500).json(serverError.toResponseObject());
}
}
});
return router;
}
//# sourceMappingURL=register.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../../../../src/server/auth/handlers/register.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,13 @@
import { OAuthServerProvider } from "../provider.js";
import { RequestHandler } from "express";
import { Options as RateLimitOptions } from "express-rate-limit";
export type RevocationHandlerOptions = {
provider: OAuthServerProvider;
/**
* Rate limiting configuration for the token revocation endpoint.
* Set to false to disable rate limiting for this endpoint.
*/
rateLimit?: Partial<RateLimitOptions> | false;
};
export declare function revocationHandler({ provider, rateLimit: rateLimitConfig, }: RevocationHandlerOptions): RequestHandler;
//# sourceMappingURL=revoke.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../../../../../src/server/auth/handlers/revoke.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAIlD,OAAO,EAAa,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAS5E,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,KAAK,CAAC;CAC/C,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,SAAS,EAAE,eAAe,GAC3B,EAAE,wBAAwB,GAAG,cAAc,CA8D3C"}

View File

@@ -0,0 +1,65 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.revocationHandler = revocationHandler;
const express_1 = __importDefault(require("express"));
const cors_1 = __importDefault(require("cors"));
const clientAuth_js_1 = require("../middleware/clientAuth.js");
const auth_js_1 = require("../../../shared/auth.js");
const express_rate_limit_1 = require("express-rate-limit");
const allowedMethods_js_1 = require("../middleware/allowedMethods.js");
const errors_js_1 = require("../errors.js");
function revocationHandler({ provider, rateLimit: rateLimitConfig, }) {
if (!provider.revokeToken) {
throw new Error("Auth provider does not support revoking tokens");
}
// Nested router so we can configure middleware and restrict HTTP method
const router = express_1.default.Router();
// Configure CORS to allow any origin, to make accessible to web-based MCP clients
router.use((0, cors_1.default)());
router.use((0, allowedMethods_js_1.allowedMethods)(["POST"]));
router.use(express_1.default.urlencoded({ extended: false }));
// Apply rate limiting unless explicitly disabled
if (rateLimitConfig !== false) {
router.use((0, express_rate_limit_1.rateLimit)({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 50, // 50 requests per windowMs
standardHeaders: true,
legacyHeaders: false,
message: new errors_js_1.TooManyRequestsError("You have exceeded the rate limit for token revocation requests").toResponseObject(),
...rateLimitConfig,
}));
}
// Authenticate and extract client details
router.use((0, clientAuth_js_1.authenticateClient)({ clientsStore: provider.clientsStore }));
router.post("/", async (req, res) => {
res.setHeader("Cache-Control", "no-store");
try {
const parseResult = auth_js_1.OAuthTokenRevocationRequestSchema.safeParse(req.body);
if (!parseResult.success) {
throw new errors_js_1.InvalidRequestError(parseResult.error.message);
}
const client = req.client;
if (!client) {
// This should never happen
throw new errors_js_1.ServerError("Internal Server Error");
}
await provider.revokeToken(client, parseResult.data);
res.status(200).json({});
}
catch (error) {
if (error instanceof errors_js_1.OAuthError) {
const status = error instanceof errors_js_1.ServerError ? 500 : 400;
res.status(status).json(error.toResponseObject());
}
else {
const serverError = new errors_js_1.ServerError("Internal Server Error");
res.status(500).json(serverError.toResponseObject());
}
}
});
return router;
}
//# sourceMappingURL=revoke.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"revoke.js","sourceRoot":"","sources":["../../../../../src/server/auth/handlers/revoke.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,13 @@
import { RequestHandler } from "express";
import { OAuthServerProvider } from "../provider.js";
import { Options as RateLimitOptions } from "express-rate-limit";
export type TokenHandlerOptions = {
provider: OAuthServerProvider;
/**
* Rate limiting configuration for the token endpoint.
* Set to false to disable rate limiting for this endpoint.
*/
rateLimit?: Partial<RateLimitOptions> | false;
};
export declare function tokenHandler({ provider, rateLimit: rateLimitConfig }: TokenHandlerOptions): RequestHandler;
//# sourceMappingURL=token.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../../../../src/server/auth/handlers/token.ts"],"names":[],"mappings":"AACA,OAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAIrD,OAAO,EAAa,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAW5E,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,KAAK,CAAC;CAC/C,CAAC;AAmBF,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE,EAAE,mBAAmB,GAAG,cAAc,CA4G1G"}

View File

@@ -0,0 +1,113 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.tokenHandler = tokenHandler;
const zod_1 = require("zod");
const express_1 = __importDefault(require("express"));
const cors_1 = __importDefault(require("cors"));
const pkce_challenge_1 = require("pkce-challenge");
const clientAuth_js_1 = require("../middleware/clientAuth.js");
const express_rate_limit_1 = require("express-rate-limit");
const allowedMethods_js_1 = require("../middleware/allowedMethods.js");
const errors_js_1 = require("../errors.js");
const TokenRequestSchema = zod_1.z.object({
grant_type: zod_1.z.string(),
});
const AuthorizationCodeGrantSchema = zod_1.z.object({
code: zod_1.z.string(),
code_verifier: zod_1.z.string(),
redirect_uri: zod_1.z.string().optional(),
resource: zod_1.z.string().url().optional(),
});
const RefreshTokenGrantSchema = zod_1.z.object({
refresh_token: zod_1.z.string(),
scope: zod_1.z.string().optional(),
resource: zod_1.z.string().url().optional(),
});
function tokenHandler({ provider, rateLimit: rateLimitConfig }) {
// Nested router so we can configure middleware and restrict HTTP method
const router = express_1.default.Router();
// Configure CORS to allow any origin, to make accessible to web-based MCP clients
router.use((0, cors_1.default)());
router.use((0, allowedMethods_js_1.allowedMethods)(["POST"]));
router.use(express_1.default.urlencoded({ extended: false }));
// Apply rate limiting unless explicitly disabled
if (rateLimitConfig !== false) {
router.use((0, express_rate_limit_1.rateLimit)({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 50, // 50 requests per windowMs
standardHeaders: true,
legacyHeaders: false,
message: new errors_js_1.TooManyRequestsError('You have exceeded the rate limit for token requests').toResponseObject(),
...rateLimitConfig
}));
}
// Authenticate and extract client details
router.use((0, clientAuth_js_1.authenticateClient)({ clientsStore: provider.clientsStore }));
router.post("/", async (req, res) => {
res.setHeader('Cache-Control', 'no-store');
try {
const parseResult = TokenRequestSchema.safeParse(req.body);
if (!parseResult.success) {
throw new errors_js_1.InvalidRequestError(parseResult.error.message);
}
const { grant_type } = parseResult.data;
const client = req.client;
if (!client) {
// This should never happen
throw new errors_js_1.ServerError("Internal Server Error");
}
switch (grant_type) {
case "authorization_code": {
const parseResult = AuthorizationCodeGrantSchema.safeParse(req.body);
if (!parseResult.success) {
throw new errors_js_1.InvalidRequestError(parseResult.error.message);
}
const { code, code_verifier, redirect_uri, resource } = parseResult.data;
const skipLocalPkceValidation = provider.skipLocalPkceValidation;
// Perform local PKCE validation unless explicitly skipped
// (e.g. to validate code_verifier in upstream server)
if (!skipLocalPkceValidation) {
const codeChallenge = await provider.challengeForAuthorizationCode(client, code);
if (!(await (0, pkce_challenge_1.verifyChallenge)(code_verifier, codeChallenge))) {
throw new errors_js_1.InvalidGrantError("code_verifier does not match the challenge");
}
}
// Passes the code_verifier to the provider if PKCE validation didn't occur locally
const tokens = await provider.exchangeAuthorizationCode(client, code, skipLocalPkceValidation ? code_verifier : undefined, redirect_uri, resource ? new URL(resource) : undefined);
res.status(200).json(tokens);
break;
}
case "refresh_token": {
const parseResult = RefreshTokenGrantSchema.safeParse(req.body);
if (!parseResult.success) {
throw new errors_js_1.InvalidRequestError(parseResult.error.message);
}
const { refresh_token, scope, resource } = parseResult.data;
const scopes = scope === null || scope === void 0 ? void 0 : scope.split(" ");
const tokens = await provider.exchangeRefreshToken(client, refresh_token, scopes, resource ? new URL(resource) : undefined);
res.status(200).json(tokens);
break;
}
// Not supported right now
//case "client_credentials":
default:
throw new errors_js_1.UnsupportedGrantTypeError("The grant type is not supported by this authorization server.");
}
}
catch (error) {
if (error instanceof errors_js_1.OAuthError) {
const status = error instanceof errors_js_1.ServerError ? 500 : 400;
res.status(status).json(error.toResponseObject());
}
else {
const serverError = new errors_js_1.ServerError("Internal Server Error");
res.status(500).json(serverError.toResponseObject());
}
}
});
return router;
}
//# sourceMappingURL=token.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../../../../src/server/auth/handlers/token.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,9 @@
import { RequestHandler } from "express";
/**
* Middleware to handle unsupported HTTP methods with a 405 Method Not Allowed response.
*
* @param allowedMethods Array of allowed HTTP methods for this endpoint (e.g., ['GET', 'POST'])
* @returns Express middleware that returns a 405 error if method not in allowed list
*/
export declare function allowedMethods(allowedMethods: string[]): RequestHandler;
//# sourceMappingURL=allowedMethods.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"allowedMethods.d.ts","sourceRoot":"","sources":["../../../../../src/server/auth/middleware/allowedMethods.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAGzC;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,cAAc,CAYvE"}

View File

@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.allowedMethods = allowedMethods;
const errors_js_1 = require("../errors.js");
/**
* Middleware to handle unsupported HTTP methods with a 405 Method Not Allowed response.
*
* @param allowedMethods Array of allowed HTTP methods for this endpoint (e.g., ['GET', 'POST'])
* @returns Express middleware that returns a 405 error if method not in allowed list
*/
function allowedMethods(allowedMethods) {
return (req, res, next) => {
if (allowedMethods.includes(req.method)) {
next();
return;
}
const error = new errors_js_1.MethodNotAllowedError(`The method ${req.method} is not allowed for this endpoint`);
res.status(405)
.set('Allow', allowedMethods.join(', '))
.json(error.toResponseObject());
};
}
//# sourceMappingURL=allowedMethods.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"allowedMethods.js","sourceRoot":"","sources":["../../../../../src/server/auth/middleware/allowedMethods.ts"],"names":[],"mappings":";;AASA,wCAYC;AApBD,4CAAqD;AAErD;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,cAAwB;IACrD,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACxB,IAAI,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACxC,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,iCAAqB,CAAC,cAAc,GAAG,CAAC,MAAM,mCAAmC,CAAC,CAAC;QACrG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;aACZ,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aACvC,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC;AACJ,CAAC"}

View File

@@ -0,0 +1,35 @@
import { RequestHandler } from "express";
import { OAuthTokenVerifier } from "../provider.js";
import { AuthInfo } from "../types.js";
export type BearerAuthMiddlewareOptions = {
/**
* A provider used to verify tokens.
*/
verifier: OAuthTokenVerifier;
/**
* Optional scopes that the token must have.
*/
requiredScopes?: string[];
/**
* Optional resource metadata URL to include in WWW-Authenticate header.
*/
resourceMetadataUrl?: string;
};
declare module "express-serve-static-core" {
interface Request {
/**
* Information about the validated access token, if the `requireBearerAuth` middleware was used.
*/
auth?: AuthInfo;
}
}
/**
* Middleware that requires a valid Bearer token in the Authorization header.
*
* This will validate the token with the auth provider and add the resulting auth info to the request object.
*
* If resourceMetadataUrl is provided, it will be included in the WWW-Authenticate header
* for 401 responses as per the OAuth 2.0 Protected Resource Metadata spec.
*/
export declare function requireBearerAuth({ verifier, requiredScopes, resourceMetadataUrl }: BearerAuthMiddlewareOptions): RequestHandler;
//# sourceMappingURL=bearerAuth.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"bearerAuth.d.ts","sourceRoot":"","sources":["../../../../../src/server/auth/middleware/bearerAuth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,MAAM,MAAM,2BAA2B,GAAG;IACxC;;OAEG;IACH,QAAQ,EAAE,kBAAkB,CAAC;IAE7B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,OAAO,QAAQ,2BAA2B,CAAC;IACzC,UAAU,OAAO;QACf;;WAEG;QACH,IAAI,CAAC,EAAE,QAAQ,CAAC;KACjB;CACF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,cAAmB,EAAE,mBAAmB,EAAE,EAAE,2BAA2B,GAAG,cAAc,CAwDrI"}

View File

@@ -0,0 +1,67 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.requireBearerAuth = requireBearerAuth;
const errors_js_1 = require("../errors.js");
/**
* Middleware that requires a valid Bearer token in the Authorization header.
*
* This will validate the token with the auth provider and add the resulting auth info to the request object.
*
* If resourceMetadataUrl is provided, it will be included in the WWW-Authenticate header
* for 401 responses as per the OAuth 2.0 Protected Resource Metadata spec.
*/
function requireBearerAuth({ verifier, requiredScopes = [], resourceMetadataUrl }) {
return async (req, res, next) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader) {
throw new errors_js_1.InvalidTokenError("Missing Authorization header");
}
const [type, token] = authHeader.split(' ');
if (type.toLowerCase() !== 'bearer' || !token) {
throw new errors_js_1.InvalidTokenError("Invalid Authorization header format, expected 'Bearer TOKEN'");
}
const authInfo = await verifier.verifyAccessToken(token);
// Check if token has the required scopes (if any)
if (requiredScopes.length > 0) {
const hasAllScopes = requiredScopes.every(scope => authInfo.scopes.includes(scope));
if (!hasAllScopes) {
throw new errors_js_1.InsufficientScopeError("Insufficient scope");
}
}
// Check if the token is expired
if (!!authInfo.expiresAt && authInfo.expiresAt < Date.now() / 1000) {
throw new errors_js_1.InvalidTokenError("Token has expired");
}
req.auth = authInfo;
next();
}
catch (error) {
if (error instanceof errors_js_1.InvalidTokenError) {
const wwwAuthValue = resourceMetadataUrl
? `Bearer error="${error.errorCode}", error_description="${error.message}", resource_metadata="${resourceMetadataUrl}"`
: `Bearer error="${error.errorCode}", error_description="${error.message}"`;
res.set("WWW-Authenticate", wwwAuthValue);
res.status(401).json(error.toResponseObject());
}
else if (error instanceof errors_js_1.InsufficientScopeError) {
const wwwAuthValue = resourceMetadataUrl
? `Bearer error="${error.errorCode}", error_description="${error.message}", resource_metadata="${resourceMetadataUrl}"`
: `Bearer error="${error.errorCode}", error_description="${error.message}"`;
res.set("WWW-Authenticate", wwwAuthValue);
res.status(403).json(error.toResponseObject());
}
else if (error instanceof errors_js_1.ServerError) {
res.status(500).json(error.toResponseObject());
}
else if (error instanceof errors_js_1.OAuthError) {
res.status(400).json(error.toResponseObject());
}
else {
const serverError = new errors_js_1.ServerError("Internal Server Error");
res.status(500).json(serverError.toResponseObject());
}
}
};
}
//# sourceMappingURL=bearerAuth.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"bearerAuth.js","sourceRoot":"","sources":["../../../../../src/server/auth/middleware/bearerAuth.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,19 @@
import { RequestHandler } from "express";
import { OAuthRegisteredClientsStore } from "../clients.js";
import { OAuthClientInformationFull } from "../../../shared/auth.js";
export type ClientAuthenticationMiddlewareOptions = {
/**
* A store used to read information about registered OAuth clients.
*/
clientsStore: OAuthRegisteredClientsStore;
};
declare module "express-serve-static-core" {
interface Request {
/**
* The authenticated client for this request, if the `authenticateClient` middleware was used.
*/
client?: OAuthClientInformationFull;
}
}
export declare function authenticateClient({ clientsStore }: ClientAuthenticationMiddlewareOptions): RequestHandler;
//# sourceMappingURL=clientAuth.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"clientAuth.d.ts","sourceRoot":"","sources":["../../../../../src/server/auth/middleware/clientAuth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAGrE,MAAM,MAAM,qCAAqC,GAAG;IAClD;;OAEG;IACH,YAAY,EAAE,2BAA2B,CAAC;CAC3C,CAAA;AAOD,OAAO,QAAQ,2BAA2B,CAAC;IACzC,UAAU,OAAO;QACf;;WAEG;QACH,MAAM,CAAC,EAAE,0BAA0B,CAAC;KACrC;CACF;AAED,wBAAgB,kBAAkB,CAAC,EAAE,YAAY,EAAE,EAAE,qCAAqC,GAAG,cAAc,CA4C1G"}

View File

@@ -0,0 +1,52 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.authenticateClient = authenticateClient;
const zod_1 = require("zod");
const errors_js_1 = require("../errors.js");
const ClientAuthenticatedRequestSchema = zod_1.z.object({
client_id: zod_1.z.string(),
client_secret: zod_1.z.string().optional(),
});
function authenticateClient({ clientsStore }) {
return async (req, res, next) => {
try {
const result = ClientAuthenticatedRequestSchema.safeParse(req.body);
if (!result.success) {
throw new errors_js_1.InvalidRequestError(String(result.error));
}
const { client_id, client_secret } = result.data;
const client = await clientsStore.getClient(client_id);
if (!client) {
throw new errors_js_1.InvalidClientError("Invalid client_id");
}
// If client has a secret, validate it
if (client.client_secret) {
// Check if client_secret is required but not provided
if (!client_secret) {
throw new errors_js_1.InvalidClientError("Client secret is required");
}
// Check if client_secret matches
if (client.client_secret !== client_secret) {
throw new errors_js_1.InvalidClientError("Invalid client_secret");
}
// Check if client_secret has expired
if (client.client_secret_expires_at && client.client_secret_expires_at < Math.floor(Date.now() / 1000)) {
throw new errors_js_1.InvalidClientError("Client secret has expired");
}
}
req.client = client;
next();
}
catch (error) {
if (error instanceof errors_js_1.OAuthError) {
const status = error instanceof errors_js_1.ServerError ? 500 : 400;
res.status(status).json(error.toResponseObject());
}
else {
const serverError = new errors_js_1.ServerError("Internal Server Error");
res.status(500).json(serverError.toResponseObject());
}
}
};
}
//# sourceMappingURL=clientAuth.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"clientAuth.js","sourceRoot":"","sources":["../../../../../src/server/auth/middleware/clientAuth.ts"],"names":[],"mappings":";;AA2BA,gDA4CC;AAvED,6BAAwB;AAIxB,4CAAgG;AAShG,MAAM,gCAAgC,GAAG,OAAC,CAAC,MAAM,CAAC;IAChD,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;IACrB,aAAa,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACrC,CAAC,CAAC;AAWH,SAAgB,kBAAkB,CAAC,EAAE,YAAY,EAAyC;IACxF,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC9B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,gCAAgC,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACpE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,IAAI,+BAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACtD,CAAC;YAED,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;YACjD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YACvD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,8BAAkB,CAAC,mBAAmB,CAAC,CAAC;YACpD,CAAC;YAED,sCAAsC;YACtC,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;gBACzB,sDAAsD;gBACtD,IAAI,CAAC,aAAa,EAAE,CAAC;oBACnB,MAAM,IAAI,8BAAkB,CAAC,2BAA2B,CAAC,CAAC;gBAC5D,CAAC;gBAED,iCAAiC;gBACjC,IAAI,MAAM,CAAC,aAAa,KAAK,aAAa,EAAE,CAAC;oBAC3C,MAAM,IAAI,8BAAkB,CAAC,uBAAuB,CAAC,CAAC;gBACxD,CAAC;gBAED,qCAAqC;gBACrC,IAAI,MAAM,CAAC,wBAAwB,IAAI,MAAM,CAAC,wBAAwB,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;oBACvG,MAAM,IAAI,8BAAkB,CAAC,2BAA2B,CAAC,CAAC;gBAC5D,CAAC;YACH,CAAC;YAED,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;YACpB,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,sBAAU,EAAE,CAAC;gBAChC,MAAM,MAAM,GAAG,KAAK,YAAY,uBAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;gBACxD,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACpD,CAAC;iBAAM,CAAC;gBACN,MAAM,WAAW,GAAG,IAAI,uBAAW,CAAC,uBAAuB,CAAC,CAAC;gBAC7D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACvD,CAAC;QACH,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}

View File

@@ -0,0 +1,68 @@
import { Response } from "express";
import { OAuthRegisteredClientsStore } from "./clients.js";
import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from "../../shared/auth.js";
import { AuthInfo } from "./types.js";
export type AuthorizationParams = {
state?: string;
scopes?: string[];
codeChallenge: string;
redirectUri: string;
resource?: URL;
};
/**
* Implements an end-to-end OAuth server.
*/
export interface OAuthServerProvider {
/**
* A store used to read information about registered OAuth clients.
*/
get clientsStore(): OAuthRegisteredClientsStore;
/**
* Begins the authorization flow, which can either be implemented by this server itself or via redirection to a separate authorization server.
*
* This server must eventually issue a redirect with an authorization response or an error response to the given redirect URI. Per OAuth 2.1:
* - In the successful case, the redirect MUST include the `code` and `state` (if present) query parameters.
* - In the error case, the redirect MUST include the `error` query parameter, and MAY include an optional `error_description` query parameter.
*/
authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise<void>;
/**
* Returns the `codeChallenge` that was used when the indicated authorization began.
*/
challengeForAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string): Promise<string>;
/**
* Exchanges an authorization code for an access token.
*/
exchangeAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string, codeVerifier?: string, redirectUri?: string, resource?: URL): Promise<OAuthTokens>;
/**
* Exchanges a refresh token for an access token.
*/
exchangeRefreshToken(client: OAuthClientInformationFull, refreshToken: string, scopes?: string[], resource?: URL): Promise<OAuthTokens>;
/**
* Verifies an access token and returns information about it.
*/
verifyAccessToken(token: string): Promise<AuthInfo>;
/**
* Revokes an access or refresh token. If unimplemented, token revocation is not supported (not recommended).
*
* If the given token is invalid or already revoked, this method should do nothing.
*/
revokeToken?(client: OAuthClientInformationFull, request: OAuthTokenRevocationRequest): Promise<void>;
/**
* Whether to skip local PKCE validation.
*
* If true, the server will not perform PKCE validation locally and will pass the code_verifier to the upstream server.
*
* NOTE: This should only be true if the upstream server is performing the actual PKCE validation.
*/
skipLocalPkceValidation?: boolean;
}
/**
* Slim implementation useful for token verification
*/
export interface OAuthTokenVerifier {
/**
* Verifies an access token and returns information about it.
*/
verifyAccessToken(token: string): Promise<AuthInfo>;
}
//# sourceMappingURL=provider.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../../src/server/auth/provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC5G,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,GAAG,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,IAAI,YAAY,IAAI,2BAA2B,CAAC;IAEhD;;;;;;OAMG;IACH,SAAS,CAAC,MAAM,EAAE,0BAA0B,EAAE,MAAM,EAAE,mBAAmB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzG;;OAEG;IACH,6BAA6B,CAAC,MAAM,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9G;;OAEG;IACH,yBAAyB,CACvB,MAAM,EAAE,0BAA0B,EAClC,iBAAiB,EAAE,MAAM,EACzB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,GAAG,GACb,OAAO,CAAC,WAAW,CAAC,CAAC;IAExB;;OAEG;IACH,oBAAoB,CAAC,MAAM,EAAE,0BAA0B,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAExI;;OAEG;IACH,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEpD;;;;OAIG;IACH,WAAW,CAAC,CAAC,MAAM,EAAE,0BAA0B,EAAE,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtG;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAGD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACrD"}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=provider.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../../src/server/auth/provider.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,43 @@
import { Response } from "express";
import { OAuthRegisteredClientsStore } from "../clients.js";
import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from "../../../shared/auth.js";
import { AuthInfo } from "../types.js";
import { AuthorizationParams, OAuthServerProvider } from "../provider.js";
export type ProxyEndpoints = {
authorizationUrl: string;
tokenUrl: string;
revocationUrl?: string;
registrationUrl?: string;
};
export type ProxyOptions = {
/**
* Individual endpoint URLs for proxying specific OAuth operations
*/
endpoints: ProxyEndpoints;
/**
* Function to verify access tokens and return auth info
*/
verifyAccessToken: (token: string) => Promise<AuthInfo>;
/**
* Function to fetch client information from the upstream server
*/
getClient: (clientId: string) => Promise<OAuthClientInformationFull | undefined>;
};
/**
* Implements an OAuth server that proxies requests to another OAuth server.
*/
export declare class ProxyOAuthServerProvider implements OAuthServerProvider {
protected readonly _endpoints: ProxyEndpoints;
protected readonly _verifyAccessToken: (token: string) => Promise<AuthInfo>;
protected readonly _getClient: (clientId: string) => Promise<OAuthClientInformationFull | undefined>;
skipLocalPkceValidation: boolean;
revokeToken?: (client: OAuthClientInformationFull, request: OAuthTokenRevocationRequest) => Promise<void>;
constructor(options: ProxyOptions);
get clientsStore(): OAuthRegisteredClientsStore;
authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise<void>;
challengeForAuthorizationCode(_client: OAuthClientInformationFull, _authorizationCode: string): Promise<string>;
exchangeAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string, codeVerifier?: string, redirectUri?: string, resource?: URL): Promise<OAuthTokens>;
exchangeRefreshToken(client: OAuthClientInformationFull, refreshToken: string, scopes?: string[], resource?: URL): Promise<OAuthTokens>;
verifyAccessToken(token: string): Promise<AuthInfo>;
}
//# sourceMappingURL=proxyProvider.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"proxyProvider.d.ts","sourceRoot":"","sources":["../../../../../src/server/auth/providers/proxyProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EACL,0BAA0B,EAE1B,2BAA2B,EAC3B,WAAW,EAEZ,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG1E,MAAM,MAAM,cAAc,GAAG;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB;;OAEG;IACH,SAAS,EAAE,cAAc,CAAC;IAE1B;;MAEE;IACF,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAExD;;MAEE;IACF,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC,CAAC;CAElF,CAAC;AAEF;;GAEG;AACH,qBAAa,wBAAyB,YAAW,mBAAmB;IAClE,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IAC9C,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5E,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC,CAAC;IAErG,uBAAuB,UAAQ;IAE/B,WAAW,CAAC,EAAE,CACZ,MAAM,EAAE,0BAA0B,EAClC,OAAO,EAAE,2BAA2B,KACjC,OAAO,CAAC,IAAI,CAAC,CAAC;gBAEP,OAAO,EAAE,YAAY;IAwCjC,IAAI,YAAY,IAAI,2BAA2B,CAuB9C;IAEK,SAAS,CACb,MAAM,EAAE,0BAA0B,EAClC,MAAM,EAAE,mBAAmB,EAC3B,GAAG,EAAE,QAAQ,GACZ,OAAO,CAAC,IAAI,CAAC;IAoBV,6BAA6B,CACjC,OAAO,EAAE,0BAA0B,EACnC,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAAC,MAAM,CAAC;IAMZ,yBAAyB,CAC7B,MAAM,EAAE,0BAA0B,EAClC,iBAAiB,EAAE,MAAM,EACzB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,GAAG,GACb,OAAO,CAAC,WAAW,CAAC;IAwCjB,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;IAoCjB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAG1D"}

View File

@@ -0,0 +1,156 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProxyOAuthServerProvider = void 0;
const auth_js_1 = require("../../../shared/auth.js");
const errors_js_1 = require("../errors.js");
/**
* Implements an OAuth server that proxies requests to another OAuth server.
*/
class ProxyOAuthServerProvider {
constructor(options) {
var _a;
this.skipLocalPkceValidation = true;
this._endpoints = options.endpoints;
this._verifyAccessToken = options.verifyAccessToken;
this._getClient = options.getClient;
if ((_a = options.endpoints) === null || _a === void 0 ? void 0 : _a.revocationUrl) {
this.revokeToken = async (client, request) => {
const revocationUrl = this._endpoints.revocationUrl;
if (!revocationUrl) {
throw new Error("No revocation endpoint configured");
}
const params = new URLSearchParams();
params.set("token", request.token);
params.set("client_id", client.client_id);
if (client.client_secret) {
params.set("client_secret", client.client_secret);
}
if (request.token_type_hint) {
params.set("token_type_hint", request.token_type_hint);
}
const response = await fetch(revocationUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: params.toString(),
});
if (!response.ok) {
throw new errors_js_1.ServerError(`Token revocation failed: ${response.status}`);
}
};
}
}
get clientsStore() {
const registrationUrl = this._endpoints.registrationUrl;
return {
getClient: this._getClient,
...(registrationUrl && {
registerClient: async (client) => {
const response = await fetch(registrationUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(client),
});
if (!response.ok) {
throw new errors_js_1.ServerError(`Client registration failed: ${response.status}`);
}
const data = await response.json();
return auth_js_1.OAuthClientInformationFullSchema.parse(data);
}
})
};
}
async authorize(client, params, res) {
var _a;
// Start with required OAuth parameters
const targetUrl = new URL(this._endpoints.authorizationUrl);
const searchParams = new URLSearchParams({
client_id: client.client_id,
response_type: "code",
redirect_uri: params.redirectUri,
code_challenge: params.codeChallenge,
code_challenge_method: "S256"
});
// Add optional standard OAuth parameters
if (params.state)
searchParams.set("state", params.state);
if ((_a = params.scopes) === null || _a === void 0 ? void 0 : _a.length)
searchParams.set("scope", params.scopes.join(" "));
if (params.resource)
searchParams.set("resource", params.resource.href);
targetUrl.search = searchParams.toString();
res.redirect(targetUrl.toString());
}
async challengeForAuthorizationCode(_client, _authorizationCode) {
// In a proxy setup, we don't store the code challenge ourselves
// Instead, we proxy the token request and let the upstream server validate it
return "";
}
async exchangeAuthorizationCode(client, authorizationCode, codeVerifier, redirectUri, resource) {
const params = new URLSearchParams({
grant_type: "authorization_code",
client_id: client.client_id,
code: authorizationCode,
});
if (client.client_secret) {
params.append("client_secret", client.client_secret);
}
if (codeVerifier) {
params.append("code_verifier", codeVerifier);
}
if (redirectUri) {
params.append("redirect_uri", redirectUri);
}
if (resource) {
params.append("resource", resource.href);
}
const response = await fetch(this._endpoints.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: params.toString(),
});
if (!response.ok) {
throw new errors_js_1.ServerError(`Token exchange failed: ${response.status}`);
}
const data = await response.json();
return auth_js_1.OAuthTokensSchema.parse(data);
}
async exchangeRefreshToken(client, refreshToken, scopes, resource) {
const params = new URLSearchParams({
grant_type: "refresh_token",
client_id: client.client_id,
refresh_token: refreshToken,
});
if (client.client_secret) {
params.set("client_secret", client.client_secret);
}
if (scopes === null || scopes === void 0 ? void 0 : scopes.length) {
params.set("scope", scopes.join(" "));
}
if (resource) {
params.set("resource", resource.href);
}
const response = await fetch(this._endpoints.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: params.toString(),
});
if (!response.ok) {
throw new errors_js_1.ServerError(`Token refresh failed: ${response.status}`);
}
const data = await response.json();
return auth_js_1.OAuthTokensSchema.parse(data);
}
async verifyAccessToken(token) {
return this._verifyAccessToken(token);
}
}
exports.ProxyOAuthServerProvider = ProxyOAuthServerProvider;
//# sourceMappingURL=proxyProvider.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"proxyProvider.js","sourceRoot":"","sources":["../../../../../src/server/auth/providers/proxyProvider.ts"],"names":[],"mappings":";;;AAEA,qDAMiC;AAGjC,4CAA2C;AA2B3C;;GAEG;AACH,MAAa,wBAAwB;IAYnC,YAAY,OAAqB;;QAPjC,4BAAuB,GAAG,IAAI,CAAC;QAQ7B,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;QACpC,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;QACpD,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;QACpC,IAAI,MAAA,OAAO,CAAC,SAAS,0CAAE,aAAa,EAAE,CAAC;YACrC,IAAI,CAAC,WAAW,GAAG,KAAK,EACtB,MAAkC,EAClC,OAAoC,EACpC,EAAE;gBACF,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;gBAEpD,IAAI,CAAC,aAAa,EAAE,CAAC;oBACnB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;gBACvD,CAAC;gBAED,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;gBACrC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;gBACnC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;gBAC1C,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;oBACzB,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;gBACpD,CAAC;gBACD,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;oBAC5B,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;gBACzD,CAAC;gBAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,aAAa,EAAE;oBAC1C,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE;wBACP,cAAc,EAAE,mCAAmC;qBACpD;oBACD,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE;iBACxB,CAAC,CAAC;gBAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBACjB,MAAM,IAAI,uBAAW,CAAC,4BAA4B,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;gBACvE,CAAC;YACH,CAAC,CAAA;QACH,CAAC;IACH,CAAC;IAED,IAAI,YAAY;QACd,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC;QACxD,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,UAAU;YAC1B,GAAG,CAAC,eAAe,IAAI;gBACrB,cAAc,EAAE,KAAK,EAAE,MAAkC,EAAE,EAAE;oBAC3D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,eAAe,EAAE;wBAC5C,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE;4BACP,cAAc,EAAE,kBAAkB;yBACnC;wBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;qBAC7B,CAAC,CAAC;oBAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;wBACjB,MAAM,IAAI,uBAAW,CAAC,+BAA+B,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;oBAC1E,CAAC;oBAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;oBACnC,OAAO,0CAAgC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACtD,CAAC;aACF,CAAC;SACH,CAAA;IACH,CAAC;IAED,KAAK,CAAC,SAAS,CACb,MAAkC,EAClC,MAA2B,EAC3B,GAAa;;QAEb,uCAAuC;QACvC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;QAC5D,MAAM,YAAY,GAAG,IAAI,eAAe,CAAC;YACvC,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,aAAa,EAAE,MAAM;YACrB,YAAY,EAAE,MAAM,CAAC,WAAW;YAChC,cAAc,EAAE,MAAM,CAAC,aAAa;YACpC,qBAAqB,EAAE,MAAM;SAC9B,CAAC,CAAC;QAEH,yCAAyC;QACzC,IAAI,MAAM,CAAC,KAAK;YAAE,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1D,IAAI,MAAA,MAAM,CAAC,MAAM,0CAAE,MAAM;YAAE,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9E,IAAI,MAAM,CAAC,QAAQ;YAAE,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAExE,SAAS,CAAC,MAAM,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC;QAC3C,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,6BAA6B,CACjC,OAAmC,EACnC,kBAA0B;QAE1B,gEAAgE;QAChE,8EAA8E;QAC9E,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,KAAK,CAAC,yBAAyB,CAC7B,MAAkC,EAClC,iBAAyB,EACzB,YAAqB,EACrB,WAAoB,EACpB,QAAc;QAEd,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YACjC,UAAU,EAAE,oBAAoB;YAChC,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,IAAI,EAAE,iBAAiB;SACxB,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YACzB,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;QACvD,CAAC;QAED,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC;QAC/C,CAAC;QAED,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;QAC7C,CAAC;QAED,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC3C,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE;YACrD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,mCAAmC;aACpD;YACD,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE;SACxB,CAAC,CAAC;QAGH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,uBAAW,CAAC,0BAA0B,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACrE,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,2BAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,oBAAoB,CACxB,MAAkC,EAClC,YAAoB,EACpB,MAAiB,EACjB,QAAc;QAGd,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YACjC,UAAU,EAAE,eAAe;YAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,aAAa,EAAE,YAAY;SAC5B,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,MAAM,EAAE,CAAC;YACnB,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACxC,CAAC;QAED,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;QACxC,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE;YACrD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,mCAAmC;aACpD;YACD,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE;SACxB,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,uBAAW,CAAC,yBAAyB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,2BAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,KAAa;QACnC,OAAO,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;CACF;AAxMD,4DAwMC"}

View File

@@ -0,0 +1,96 @@
import { RequestHandler } from "express";
import { ClientRegistrationHandlerOptions } from "./handlers/register.js";
import { TokenHandlerOptions } from "./handlers/token.js";
import { AuthorizationHandlerOptions } from "./handlers/authorize.js";
import { RevocationHandlerOptions } from "./handlers/revoke.js";
import { OAuthServerProvider } from "./provider.js";
import { OAuthMetadata } from "../../shared/auth.js";
export type AuthRouterOptions = {
/**
* A provider implementing the actual authorization logic for this router.
*/
provider: OAuthServerProvider;
/**
* The authorization server's issuer identifier, which is a URL that uses the "https" scheme and has no query or fragment components.
*/
issuerUrl: URL;
/**
* The base URL of the authorization server to use for the metadata endpoints.
*
* If not provided, the issuer URL will be used as the base URL.
*/
baseUrl?: URL;
/**
* An optional URL of a page containing human-readable information that developers might want or need to know when using the authorization server.
*/
serviceDocumentationUrl?: URL;
/**
* An optional list of scopes supported by this authorization server
*/
scopesSupported?: string[];
/**
* The resource name to be displayed in protected resource metadata
*/
resourceName?: string;
authorizationOptions?: Omit<AuthorizationHandlerOptions, "provider">;
clientRegistrationOptions?: Omit<ClientRegistrationHandlerOptions, "clientsStore">;
revocationOptions?: Omit<RevocationHandlerOptions, "provider">;
tokenOptions?: Omit<TokenHandlerOptions, "provider">;
};
export declare const createOAuthMetadata: (options: {
provider: OAuthServerProvider;
issuerUrl: URL;
baseUrl?: URL;
serviceDocumentationUrl?: URL;
scopesSupported?: string[];
}) => OAuthMetadata;
/**
* Installs standard MCP authorization server endpoints, including dynamic client registration and token revocation (if supported).
* Also advertises standard authorization server metadata, for easier discovery of supported configurations by clients.
* Note: if your MCP server is only a resource server and not an authorization server, use mcpAuthMetadataRouter instead.
*
* By default, rate limiting is applied to all endpoints to prevent abuse.
*
* This router MUST be installed at the application root, like so:
*
* const app = express();
* app.use(mcpAuthRouter(...));
*/
export declare function mcpAuthRouter(options: AuthRouterOptions): RequestHandler;
export type AuthMetadataOptions = {
/**
* OAuth Metadata as would be returned from the authorization server
* this MCP server relies on
*/
oauthMetadata: OAuthMetadata;
/**
* The url of the MCP server, for use in protected resource metadata
*/
resourceServerUrl: URL;
/**
* The url for documentation for the MCP server
*/
serviceDocumentationUrl?: URL;
/**
* An optional list of scopes supported by this MCP server
*/
scopesSupported?: string[];
/**
* An optional resource name to display in resource metadata
*/
resourceName?: string;
};
export declare function mcpAuthMetadataRouter(options: AuthMetadataOptions): import("express-serve-static-core").Router;
/**
* Helper function to construct the OAuth 2.0 Protected Resource Metadata URL
* from a given server URL. This replaces the path with the standard metadata endpoint.
*
* @param serverUrl - The base URL of the protected resource server
* @returns The URL for the OAuth protected resource metadata endpoint
*
* @example
* getOAuthProtectedResourceMetadataUrl(new URL('https://api.example.com/mcp'))
* // Returns: 'https://api.example.com/.well-known/oauth-protected-resource'
*/
export declare function getOAuthProtectedResourceMetadataUrl(serverUrl: URL): string;
//# sourceMappingURL=router.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../../src/server/auth/router.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAA6B,gCAAgC,EAAE,MAAM,wBAAwB,CAAC;AACrG,OAAO,EAAgB,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAwB,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAC5F,OAAO,EAAqB,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAEnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,aAAa,EAAkC,MAAM,sBAAsB,CAAC;AAErF,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;OAEG;IACH,QAAQ,EAAE,mBAAmB,CAAC;IAE9B;;OAEG;IACH,SAAS,EAAE,GAAG,CAAC;IAEf;;;;OAIG;IACH,OAAO,CAAC,EAAE,GAAG,CAAC;IAEd;;OAEG;IACH,uBAAuB,CAAC,EAAE,GAAG,CAAC;IAE9B;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAG3B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,oBAAoB,CAAC,EAAE,IAAI,CAAC,2BAA2B,EAAE,UAAU,CAAC,CAAC;IACrE,yBAAyB,CAAC,EAAE,IAAI,CAAC,gCAAgC,EAAE,cAAc,CAAC,CAAC;IACnF,iBAAiB,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,UAAU,CAAC,CAAC;IAC/D,YAAY,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,UAAU,CAAC,CAAC;CACtD,CAAC;AAeF,eAAO,MAAM,mBAAmB,YAAa;IAC3C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,SAAS,EAAE,GAAG,CAAC;IACf,OAAO,CAAC,EAAE,GAAG,CAAA;IACb,uBAAuB,CAAC,EAAE,GAAG,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B,KAAG,aAgCH,CAAA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CA0CxE;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;OAGG;IACH,aAAa,EAAE,aAAa,CAAC;IAE7B;;OAEG;IACH,iBAAiB,EAAE,GAAG,CAAC;IAEvB;;OAEG;IACH,uBAAuB,CAAC,EAAE,GAAG,CAAC;IAE9B;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAE3B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAA;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,mBAAmB,8CAuBjE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oCAAoC,CAAC,SAAS,EAAE,GAAG,GAAG,MAAM,CAE3E"}

View File

@@ -0,0 +1,122 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createOAuthMetadata = void 0;
exports.mcpAuthRouter = mcpAuthRouter;
exports.mcpAuthMetadataRouter = mcpAuthMetadataRouter;
exports.getOAuthProtectedResourceMetadataUrl = getOAuthProtectedResourceMetadataUrl;
const express_1 = __importDefault(require("express"));
const register_js_1 = require("./handlers/register.js");
const token_js_1 = require("./handlers/token.js");
const authorize_js_1 = require("./handlers/authorize.js");
const revoke_js_1 = require("./handlers/revoke.js");
const metadata_js_1 = require("./handlers/metadata.js");
const checkIssuerUrl = (issuer) => {
// Technically RFC 8414 does not permit a localhost HTTPS exemption, but this will be necessary for ease of testing
if (issuer.protocol !== "https:" && issuer.hostname !== "localhost" && issuer.hostname !== "127.0.0.1") {
throw new Error("Issuer URL must be HTTPS");
}
if (issuer.hash) {
throw new Error(`Issuer URL must not have a fragment: ${issuer}`);
}
if (issuer.search) {
throw new Error(`Issuer URL must not have a query string: ${issuer}`);
}
};
const createOAuthMetadata = (options) => {
var _a;
const issuer = options.issuerUrl;
const baseUrl = options.baseUrl;
checkIssuerUrl(issuer);
const authorization_endpoint = "/authorize";
const token_endpoint = "/token";
const registration_endpoint = options.provider.clientsStore.registerClient ? "/register" : undefined;
const revocation_endpoint = options.provider.revokeToken ? "/revoke" : undefined;
const metadata = {
issuer: issuer.href,
service_documentation: (_a = options.serviceDocumentationUrl) === null || _a === void 0 ? void 0 : _a.href,
authorization_endpoint: new URL(authorization_endpoint, baseUrl || issuer).href,
response_types_supported: ["code"],
code_challenge_methods_supported: ["S256"],
token_endpoint: new URL(token_endpoint, baseUrl || issuer).href,
token_endpoint_auth_methods_supported: ["client_secret_post"],
grant_types_supported: ["authorization_code", "refresh_token"],
scopes_supported: options.scopesSupported,
revocation_endpoint: revocation_endpoint ? new URL(revocation_endpoint, baseUrl || issuer).href : undefined,
revocation_endpoint_auth_methods_supported: revocation_endpoint ? ["client_secret_post"] : undefined,
registration_endpoint: registration_endpoint ? new URL(registration_endpoint, baseUrl || issuer).href : undefined,
};
return metadata;
};
exports.createOAuthMetadata = createOAuthMetadata;
/**
* Installs standard MCP authorization server endpoints, including dynamic client registration and token revocation (if supported).
* Also advertises standard authorization server metadata, for easier discovery of supported configurations by clients.
* Note: if your MCP server is only a resource server and not an authorization server, use mcpAuthMetadataRouter instead.
*
* By default, rate limiting is applied to all endpoints to prevent abuse.
*
* This router MUST be installed at the application root, like so:
*
* const app = express();
* app.use(mcpAuthRouter(...));
*/
function mcpAuthRouter(options) {
const oauthMetadata = (0, exports.createOAuthMetadata)(options);
const router = express_1.default.Router();
router.use(new URL(oauthMetadata.authorization_endpoint).pathname, (0, authorize_js_1.authorizationHandler)({ provider: options.provider, ...options.authorizationOptions }));
router.use(new URL(oauthMetadata.token_endpoint).pathname, (0, token_js_1.tokenHandler)({ provider: options.provider, ...options.tokenOptions }));
router.use(mcpAuthMetadataRouter({
oauthMetadata,
// This router is used for AS+RS combo's, so the issuer is also the resource server
resourceServerUrl: new URL(oauthMetadata.issuer),
serviceDocumentationUrl: options.serviceDocumentationUrl,
scopesSupported: options.scopesSupported,
resourceName: options.resourceName
}));
if (oauthMetadata.registration_endpoint) {
router.use(new URL(oauthMetadata.registration_endpoint).pathname, (0, register_js_1.clientRegistrationHandler)({
clientsStore: options.provider.clientsStore,
...options,
}));
}
if (oauthMetadata.revocation_endpoint) {
router.use(new URL(oauthMetadata.revocation_endpoint).pathname, (0, revoke_js_1.revocationHandler)({ provider: options.provider, ...options.revocationOptions }));
}
return router;
}
function mcpAuthMetadataRouter(options) {
var _a;
checkIssuerUrl(new URL(options.oauthMetadata.issuer));
const router = express_1.default.Router();
const protectedResourceMetadata = {
resource: options.resourceServerUrl.href,
authorization_servers: [
options.oauthMetadata.issuer
],
scopes_supported: options.scopesSupported,
resource_name: options.resourceName,
resource_documentation: (_a = options.serviceDocumentationUrl) === null || _a === void 0 ? void 0 : _a.href,
};
router.use("/.well-known/oauth-protected-resource", (0, metadata_js_1.metadataHandler)(protectedResourceMetadata));
// Always add this for backwards compatibility
router.use("/.well-known/oauth-authorization-server", (0, metadata_js_1.metadataHandler)(options.oauthMetadata));
return router;
}
/**
* Helper function to construct the OAuth 2.0 Protected Resource Metadata URL
* from a given server URL. This replaces the path with the standard metadata endpoint.
*
* @param serverUrl - The base URL of the protected resource server
* @returns The URL for the OAuth protected resource metadata endpoint
*
* @example
* getOAuthProtectedResourceMetadataUrl(new URL('https://api.example.com/mcp'))
* // Returns: 'https://api.example.com/.well-known/oauth-protected-resource'
*/
function getOAuthProtectedResourceMetadataUrl(serverUrl) {
return new URL('/.well-known/oauth-protected-resource', serverUrl).href;
}
//# sourceMappingURL=router.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../../../src/server/auth/router.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,32 @@
/**
* Information about a validated access token, provided to request handlers.
*/
export interface AuthInfo {
/**
* The access token.
*/
token: string;
/**
* The client ID associated with this token.
*/
clientId: string;
/**
* Scopes associated with this token.
*/
scopes: string[];
/**
* When the token expires (in seconds since epoch).
*/
expiresAt?: number;
/**
* The RFC 8707 resource server identifier for which this token is valid.
* If set, this MUST match the MCP server's resource identifier (minus hash fragment).
*/
resource?: URL;
/**
* Additional data associated with the token.
* This field should be used for any additional data that needs to be attached to the auth info.
*/
extra?: Record<string, unknown>;
}
//# sourceMappingURL=types.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/server/auth/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IAEjB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,GAAG,CAAC;IAEf;;;MAGE;IACF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC"}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/server/auth/types.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,24 @@
import { ZodTypeAny, ZodTypeDef, ZodType, ParseInput, ParseReturnType, RawCreateParams } from "zod";
export declare enum McpZodTypeKind {
Completable = "McpCompletable"
}
export type CompleteCallback<T extends ZodTypeAny = ZodTypeAny> = (value: T["_input"], context?: {
arguments?: Record<string, string>;
}) => T["_input"][] | Promise<T["_input"][]>;
export interface CompletableDef<T extends ZodTypeAny = ZodTypeAny> extends ZodTypeDef {
type: T;
complete: CompleteCallback<T>;
typeName: McpZodTypeKind.Completable;
}
export declare class Completable<T extends ZodTypeAny> extends ZodType<T["_output"], CompletableDef<T>, T["_input"]> {
_parse(input: ParseInput): ParseReturnType<this["_output"]>;
unwrap(): T;
static create: <T_1 extends ZodTypeAny>(type: T_1, params: RawCreateParams & {
complete: CompleteCallback<T_1>;
}) => Completable<T_1>;
}
/**
* Wraps a Zod type to provide autocompletion capabilities. Useful for, e.g., prompt arguments in MCP.
*/
export declare function completable<T extends ZodTypeAny>(schema: T, complete: CompleteCallback<T>): Completable<T>;
//# sourceMappingURL=completable.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"completable.d.ts","sourceRoot":"","sources":["../../../src/server/completable.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,UAAU,EACV,OAAO,EACP,UAAU,EACV,eAAe,EACf,eAAe,EAGhB,MAAM,KAAK,CAAC;AAEb,oBAAY,cAAc;IACxB,WAAW,mBAAmB;CAC/B;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI,CAChE,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,EAClB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC,KACE,CAAC,CAAC,QAAQ,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAE5C,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,CAC/D,SAAQ,UAAU;IAClB,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC9B,QAAQ,EAAE,cAAc,CAAC,WAAW,CAAC;CACtC;AAED,qBAAa,WAAW,CAAC,CAAC,SAAS,UAAU,CAAE,SAAQ,OAAO,CAC5D,CAAC,CAAC,SAAS,CAAC,EACZ,cAAc,CAAC,CAAC,CAAC,EACjB,CAAC,CAAC,QAAQ,CAAC,CACZ;IACC,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAU3D,MAAM;IAIN,MAAM,CAAC,MAAM,eAAc,UAAU,QAC7B,GAAC,UACC,eAAe,GAAG;QACxB,QAAQ,EAAE,gBAAgB,CAAC,GAAC,CAAC,CAAC;KAC/B,KACA,WAAW,CAAC,GAAC,CAAC,CAOf;CACH;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,UAAU,EAC9C,MAAM,EAAE,CAAC,EACT,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAC5B,WAAW,CAAC,CAAC,CAAC,CAEhB"}

View File

@@ -0,0 +1,65 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Completable = exports.McpZodTypeKind = void 0;
exports.completable = completable;
const zod_1 = require("zod");
var McpZodTypeKind;
(function (McpZodTypeKind) {
McpZodTypeKind["Completable"] = "McpCompletable";
})(McpZodTypeKind || (exports.McpZodTypeKind = McpZodTypeKind = {}));
class Completable extends zod_1.ZodType {
_parse(input) {
const { ctx } = this._processInputParams(input);
const data = ctx.data;
return this._def.type._parse({
data,
path: ctx.path,
parent: ctx,
});
}
unwrap() {
return this._def.type;
}
}
exports.Completable = Completable;
Completable.create = (type, params) => {
return new Completable({
type,
typeName: McpZodTypeKind.Completable,
complete: params.complete,
...processCreateParams(params),
});
};
/**
* Wraps a Zod type to provide autocompletion capabilities. Useful for, e.g., prompt arguments in MCP.
*/
function completable(schema, complete) {
return Completable.create(schema, { ...schema._def, complete });
}
// Not sure why this isn't exported from Zod:
// https://github.com/colinhacks/zod/blob/f7ad26147ba291cb3fb257545972a8e00e767470/src/types.ts#L130
function processCreateParams(params) {
if (!params)
return {};
const { errorMap, invalid_type_error, required_error, description } = params;
if (errorMap && (invalid_type_error || required_error)) {
throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`);
}
if (errorMap)
return { errorMap: errorMap, description };
const customMap = (iss, ctx) => {
var _a, _b;
const { message } = params;
if (iss.code === "invalid_enum_value") {
return { message: message !== null && message !== void 0 ? message : ctx.defaultError };
}
if (typeof ctx.data === "undefined") {
return { message: (_a = message !== null && message !== void 0 ? message : required_error) !== null && _a !== void 0 ? _a : ctx.defaultError };
}
if (iss.code !== "invalid_type")
return { message: ctx.defaultError };
return { message: (_b = message !== null && message !== void 0 ? message : invalid_type_error) !== null && _b !== void 0 ? _b : ctx.defaultError };
};
return { errorMap: customMap, description };
}
//# sourceMappingURL=completable.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"completable.js","sourceRoot":"","sources":["../../../src/server/completable.ts"],"names":[],"mappings":";;;AAkEA,kCAKC;AAvED,6BASa;AAEb,IAAY,cAEX;AAFD,WAAY,cAAc;IACxB,gDAA8B,CAAA;AAChC,CAAC,EAFW,cAAc,8BAAd,cAAc,QAEzB;AAgBD,MAAa,WAAkC,SAAQ,aAItD;IACC,MAAM,CAAC,KAAiB;QACtB,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACtB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YAC3B,IAAI;YACJ,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,MAAM,EAAE,GAAG;SACZ,CAAC,CAAC;IACL,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IACxB,CAAC;;AAjBH,kCAgCC;AAbQ,kBAAM,GAAG,CACd,IAAO,EACP,MAEC,EACe,EAAE;IAClB,OAAO,IAAI,WAAW,CAAC;QACrB,IAAI;QACJ,QAAQ,EAAE,cAAc,CAAC,WAAW;QACpC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,mBAAmB,CAAC,MAAM,CAAC;KAC/B,CAAC,CAAC;AACL,CAAC,CAAC;AAGJ;;GAEG;AACH,SAAgB,WAAW,CACzB,MAAS,EACT,QAA6B;IAE7B,OAAO,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;AAClE,CAAC;AAED,6CAA6C;AAC7C,oGAAoG;AACpG,SAAS,mBAAmB,CAAC,MAAuB;IAClD,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE,cAAc,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAC7E,IAAI,QAAQ,IAAI,CAAC,kBAAkB,IAAI,cAAc,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CACb,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ;QAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;IACzD,MAAM,SAAS,GAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;;QAC1C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;QAE3B,IAAI,GAAG,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;YACtC,OAAO,EAAE,OAAO,EAAE,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,GAAG,CAAC,YAAY,EAAE,CAAC;QAClD,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACpC,OAAO,EAAE,OAAO,EAAE,MAAA,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,cAAc,mCAAI,GAAG,CAAC,YAAY,EAAE,CAAC;QACpE,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc;YAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;QACtE,OAAO,EAAE,OAAO,EAAE,MAAA,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,kBAAkB,mCAAI,GAAG,CAAC,YAAY,EAAE,CAAC;IACxE,CAAC,CAAC;IACF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AAC9C,CAAC"}

View File

@@ -0,0 +1,148 @@
import { Protocol, ProtocolOptions, RequestOptions } from "../shared/protocol.js";
import { ClientCapabilities, CreateMessageRequest, ElicitRequest, ElicitResult, Implementation, ListRootsRequest, LoggingMessageNotification, Notification, Request, ResourceUpdatedNotification, Result, ServerCapabilities, ServerNotification, ServerRequest, ServerResult } from "../types.js";
export type ServerOptions = ProtocolOptions & {
/**
* Capabilities to advertise as being supported by this server.
*/
capabilities?: ServerCapabilities;
/**
* Optional instructions describing how to use the server and its features.
*/
instructions?: string;
};
/**
* An MCP server on top of a pluggable transport.
*
* This server will automatically respond to the initialization flow as initiated from the client.
*
* To use with custom types, extend the base Request/Notification/Result types and pass them as type parameters:
*
* ```typescript
* // Custom schemas
* const CustomRequestSchema = RequestSchema.extend({...})
* const CustomNotificationSchema = NotificationSchema.extend({...})
* const CustomResultSchema = ResultSchema.extend({...})
*
* // Type aliases
* type CustomRequest = z.infer<typeof CustomRequestSchema>
* type CustomNotification = z.infer<typeof CustomNotificationSchema>
* type CustomResult = z.infer<typeof CustomResultSchema>
*
* // Create typed server
* const server = new Server<CustomRequest, CustomNotification, CustomResult>({
* name: "CustomServer",
* version: "1.0.0"
* })
* ```
*/
export declare class Server<RequestT extends Request = Request, NotificationT extends Notification = Notification, ResultT extends Result = Result> extends Protocol<ServerRequest | RequestT, ServerNotification | NotificationT, ServerResult | ResultT> {
private _serverInfo;
private _clientCapabilities?;
private _clientVersion?;
private _capabilities;
private _instructions?;
/**
* Callback for when initialization has fully completed (i.e., the client has sent an `initialized` notification).
*/
oninitialized?: () => void;
/**
* Initializes this server with the given name and version information.
*/
constructor(_serverInfo: Implementation, options?: ServerOptions);
/**
* Registers new capabilities. This can only be called before connecting to a transport.
*
* The new capabilities will be merged with any existing capabilities previously given (e.g., at initialization).
*/
registerCapabilities(capabilities: ServerCapabilities): void;
protected assertCapabilityForMethod(method: RequestT["method"]): void;
protected assertNotificationCapability(method: (ServerNotification | NotificationT)["method"]): void;
protected assertRequestHandlerCapability(method: string): void;
private _oninitialize;
/**
* After initialization has completed, this will be populated with the client's reported capabilities.
*/
getClientCapabilities(): ClientCapabilities | undefined;
/**
* After initialization has completed, this will be populated with information about the client's name and version.
*/
getClientVersion(): Implementation | undefined;
private getCapabilities;
ping(): Promise<{
_meta?: import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough"> | undefined;
}>;
createMessage(params: CreateMessageRequest["params"], options?: RequestOptions): Promise<import("zod").objectOutputType<import("zod").objectUtil.extendShape<{
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, {
model: import("zod").ZodString;
stopReason: import("zod").ZodOptional<import("zod").ZodUnion<[import("zod").ZodEnum<["endTurn", "stopSequence", "maxTokens"]>, import("zod").ZodString]>>;
role: import("zod").ZodEnum<["user", "assistant"]>;
content: import("zod").ZodDiscriminatedUnion<"type", [import("zod").ZodObject<{
type: import("zod").ZodLiteral<"text">;
text: import("zod").ZodString;
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
type: import("zod").ZodLiteral<"text">;
text: import("zod").ZodString;
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
type: import("zod").ZodLiteral<"text">;
text: import("zod").ZodString;
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, import("zod").ZodTypeAny, "passthrough">>, import("zod").ZodObject<{
type: import("zod").ZodLiteral<"image">;
data: import("zod").ZodString;
mimeType: import("zod").ZodString;
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
type: import("zod").ZodLiteral<"image">;
data: import("zod").ZodString;
mimeType: import("zod").ZodString;
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
type: import("zod").ZodLiteral<"image">;
data: import("zod").ZodString;
mimeType: import("zod").ZodString;
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, import("zod").ZodTypeAny, "passthrough">>, import("zod").ZodObject<{
type: import("zod").ZodLiteral<"audio">;
data: import("zod").ZodString;
mimeType: import("zod").ZodString;
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
type: import("zod").ZodLiteral<"audio">;
data: import("zod").ZodString;
mimeType: import("zod").ZodString;
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
type: import("zod").ZodLiteral<"audio">;
data: import("zod").ZodString;
mimeType: import("zod").ZodString;
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, import("zod").ZodTypeAny, "passthrough">>]>;
}>, import("zod").ZodTypeAny, "passthrough">>;
elicitInput(params: ElicitRequest["params"], options?: RequestOptions): Promise<ElicitResult>;
listRoots(params?: ListRootsRequest["params"], options?: RequestOptions): Promise<import("zod").objectOutputType<import("zod").objectUtil.extendShape<{
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, {
roots: import("zod").ZodArray<import("zod").ZodObject<{
uri: import("zod").ZodString;
name: import("zod").ZodOptional<import("zod").ZodString>;
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
uri: import("zod").ZodString;
name: import("zod").ZodOptional<import("zod").ZodString>;
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
uri: import("zod").ZodString;
name: import("zod").ZodOptional<import("zod").ZodString>;
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
}, import("zod").ZodTypeAny, "passthrough">>, "many">;
}>, import("zod").ZodTypeAny, "passthrough">>;
sendLoggingMessage(params: LoggingMessageNotification["params"]): Promise<void>;
sendResourceUpdated(params: ResourceUpdatedNotification["params"]): Promise<void>;
sendResourceListChanged(): Promise<void>;
sendToolListChanged(): Promise<void>;
sendPromptListChanged(): Promise<void>;
}
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EACR,eAAe,EACf,cAAc,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EAEpB,aAAa,EACb,YAAY,EAGZ,cAAc,EAMd,gBAAgB,EAEhB,0BAA0B,EAG1B,YAAY,EACZ,OAAO,EACP,2BAA2B,EAC3B,MAAM,EACN,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,YAAY,EAEb,MAAM,aAAa,CAAC;AAGrB,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG;IAC5C;;OAEG;IACH,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAElC;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,MAAM,CACjB,QAAQ,SAAS,OAAO,GAAG,OAAO,EAClC,aAAa,SAAS,YAAY,GAAG,YAAY,EACjD,OAAO,SAAS,MAAM,GAAG,MAAM,CAC/B,SAAQ,QAAQ,CAChB,aAAa,GAAG,QAAQ,EACxB,kBAAkB,GAAG,aAAa,EAClC,YAAY,GAAG,OAAO,CACvB;IAeG,OAAO,CAAC,WAAW;IAdrB,OAAO,CAAC,mBAAmB,CAAC,CAAqB;IACjD,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,aAAa,CAAC,CAAS;IAE/B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAE3B;;OAEG;gBAEO,WAAW,EAAE,cAAc,EACnC,OAAO,CAAC,EAAE,aAAa;IAczB;;;;OAIG;IACI,oBAAoB,CAAC,YAAY,EAAE,kBAAkB,GAAG,IAAI;IAUnE,SAAS,CAAC,yBAAyB,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,IAAI;IAgCrE,SAAS,CAAC,4BAA4B,CACpC,MAAM,EAAE,CAAC,kBAAkB,GAAG,aAAa,CAAC,CAAC,QAAQ,CAAC,GACrD,IAAI;IA6CP,SAAS,CAAC,8BAA8B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;YAqDhD,aAAa;IAoB3B;;OAEG;IACH,qBAAqB,IAAI,kBAAkB,GAAG,SAAS;IAIvD;;OAEG;IACH,gBAAgB,IAAI,cAAc,GAAG,SAAS;IAI9C,OAAO,CAAC,eAAe;IAIjB,IAAI;;;IAIJ,aAAa,CACjB,MAAM,EAAE,oBAAoB,CAAC,QAAQ,CAAC,EACtC,OAAO,CAAC,EAAE,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IASpB,WAAW,CACf,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,EAC/B,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,YAAY,CAAC;IAmClB,SAAS,CACb,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EACnC,OAAO,CAAC,EAAE,cAAc;;;;;;;;;;;;;;;;;IASpB,kBAAkB,CAAC,MAAM,EAAE,0BAA0B,CAAC,QAAQ,CAAC;IAI/D,mBAAmB,CAAC,MAAM,EAAE,2BAA2B,CAAC,QAAQ,CAAC;IAOjE,uBAAuB;IAMvB,mBAAmB;IAInB,qBAAqB;CAG5B"}

View File

@@ -0,0 +1,231 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Server = void 0;
const protocol_js_1 = require("../shared/protocol.js");
const types_js_1 = require("../types.js");
const ajv_1 = __importDefault(require("ajv"));
/**
* An MCP server on top of a pluggable transport.
*
* This server will automatically respond to the initialization flow as initiated from the client.
*
* To use with custom types, extend the base Request/Notification/Result types and pass them as type parameters:
*
* ```typescript
* // Custom schemas
* const CustomRequestSchema = RequestSchema.extend({...})
* const CustomNotificationSchema = NotificationSchema.extend({...})
* const CustomResultSchema = ResultSchema.extend({...})
*
* // Type aliases
* type CustomRequest = z.infer<typeof CustomRequestSchema>
* type CustomNotification = z.infer<typeof CustomNotificationSchema>
* type CustomResult = z.infer<typeof CustomResultSchema>
*
* // Create typed server
* const server = new Server<CustomRequest, CustomNotification, CustomResult>({
* name: "CustomServer",
* version: "1.0.0"
* })
* ```
*/
class Server extends protocol_js_1.Protocol {
/**
* Initializes this server with the given name and version information.
*/
constructor(_serverInfo, options) {
var _a;
super(options);
this._serverInfo = _serverInfo;
this._capabilities = (_a = options === null || options === void 0 ? void 0 : options.capabilities) !== null && _a !== void 0 ? _a : {};
this._instructions = options === null || options === void 0 ? void 0 : options.instructions;
this.setRequestHandler(types_js_1.InitializeRequestSchema, (request) => this._oninitialize(request));
this.setNotificationHandler(types_js_1.InitializedNotificationSchema, () => { var _a; return (_a = this.oninitialized) === null || _a === void 0 ? void 0 : _a.call(this); });
}
/**
* Registers new capabilities. This can only be called before connecting to a transport.
*
* The new capabilities will be merged with any existing capabilities previously given (e.g., at initialization).
*/
registerCapabilities(capabilities) {
if (this.transport) {
throw new Error("Cannot register capabilities after connecting to transport");
}
this._capabilities = (0, protocol_js_1.mergeCapabilities)(this._capabilities, capabilities);
}
assertCapabilityForMethod(method) {
var _a, _b, _c;
switch (method) {
case "sampling/createMessage":
if (!((_a = this._clientCapabilities) === null || _a === void 0 ? void 0 : _a.sampling)) {
throw new Error(`Client does not support sampling (required for ${method})`);
}
break;
case "elicitation/create":
if (!((_b = this._clientCapabilities) === null || _b === void 0 ? void 0 : _b.elicitation)) {
throw new Error(`Client does not support elicitation (required for ${method})`);
}
break;
case "roots/list":
if (!((_c = this._clientCapabilities) === null || _c === void 0 ? void 0 : _c.roots)) {
throw new Error(`Client does not support listing roots (required for ${method})`);
}
break;
case "ping":
// No specific capability required for ping
break;
}
}
assertNotificationCapability(method) {
switch (method) {
case "notifications/message":
if (!this._capabilities.logging) {
throw new Error(`Server does not support logging (required for ${method})`);
}
break;
case "notifications/resources/updated":
case "notifications/resources/list_changed":
if (!this._capabilities.resources) {
throw new Error(`Server does not support notifying about resources (required for ${method})`);
}
break;
case "notifications/tools/list_changed":
if (!this._capabilities.tools) {
throw new Error(`Server does not support notifying of tool list changes (required for ${method})`);
}
break;
case "notifications/prompts/list_changed":
if (!this._capabilities.prompts) {
throw new Error(`Server does not support notifying of prompt list changes (required for ${method})`);
}
break;
case "notifications/cancelled":
// Cancellation notifications are always allowed
break;
case "notifications/progress":
// Progress notifications are always allowed
break;
}
}
assertRequestHandlerCapability(method) {
switch (method) {
case "sampling/createMessage":
if (!this._capabilities.sampling) {
throw new Error(`Server does not support sampling (required for ${method})`);
}
break;
case "logging/setLevel":
if (!this._capabilities.logging) {
throw new Error(`Server does not support logging (required for ${method})`);
}
break;
case "prompts/get":
case "prompts/list":
if (!this._capabilities.prompts) {
throw new Error(`Server does not support prompts (required for ${method})`);
}
break;
case "resources/list":
case "resources/templates/list":
case "resources/read":
if (!this._capabilities.resources) {
throw new Error(`Server does not support resources (required for ${method})`);
}
break;
case "tools/call":
case "tools/list":
if (!this._capabilities.tools) {
throw new Error(`Server does not support tools (required for ${method})`);
}
break;
case "ping":
case "initialize":
// No specific capability required for these methods
break;
}
}
async _oninitialize(request) {
const requestedVersion = request.params.protocolVersion;
this._clientCapabilities = request.params.capabilities;
this._clientVersion = request.params.clientInfo;
const protocolVersion = types_js_1.SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion)
? requestedVersion
: types_js_1.LATEST_PROTOCOL_VERSION;
return {
protocolVersion,
capabilities: this.getCapabilities(),
serverInfo: this._serverInfo,
...(this._instructions && { instructions: this._instructions }),
};
}
/**
* After initialization has completed, this will be populated with the client's reported capabilities.
*/
getClientCapabilities() {
return this._clientCapabilities;
}
/**
* After initialization has completed, this will be populated with information about the client's name and version.
*/
getClientVersion() {
return this._clientVersion;
}
getCapabilities() {
return this._capabilities;
}
async ping() {
return this.request({ method: "ping" }, types_js_1.EmptyResultSchema);
}
async createMessage(params, options) {
return this.request({ method: "sampling/createMessage", params }, types_js_1.CreateMessageResultSchema, options);
}
async elicitInput(params, options) {
const result = await this.request({ method: "elicitation/create", params }, types_js_1.ElicitResultSchema, options);
// Validate the response content against the requested schema if action is "accept"
if (result.action === "accept" && result.content) {
try {
const ajv = new ajv_1.default();
const validate = ajv.compile(params.requestedSchema);
const isValid = validate(result.content);
if (!isValid) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Elicitation response content does not match requested schema: ${ajv.errorsText(validate.errors)}`);
}
}
catch (error) {
if (error instanceof types_js_1.McpError) {
throw error;
}
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Error validating elicitation response: ${error}`);
}
}
return result;
}
async listRoots(params, options) {
return this.request({ method: "roots/list", params }, types_js_1.ListRootsResultSchema, options);
}
async sendLoggingMessage(params) {
return this.notification({ method: "notifications/message", params });
}
async sendResourceUpdated(params) {
return this.notification({
method: "notifications/resources/updated",
params,
});
}
async sendResourceListChanged() {
return this.notification({
method: "notifications/resources/list_changed",
});
}
async sendToolListChanged() {
return this.notification({ method: "notifications/tools/list_changed" });
}
async sendPromptListChanged() {
return this.notification({ method: "notifications/prompts/list_changed" });
}
}
exports.Server = Server;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,299 @@
import { Server, ServerOptions } from "./index.js";
import { z, ZodRawShape, ZodObject, AnyZodObject, ZodTypeAny, ZodType, ZodTypeDef, ZodOptional } from "zod";
import { Implementation, CallToolResult, Resource, ListResourcesResult, GetPromptResult, ReadResourceResult, ServerRequest, ServerNotification, ToolAnnotations } from "../types.js";
import { UriTemplate, Variables } from "../shared/uriTemplate.js";
import { RequestHandlerExtra } from "../shared/protocol.js";
import { Transport } from "../shared/transport.js";
/**
* High-level MCP server that provides a simpler API for working with resources, tools, and prompts.
* For advanced usage (like sending notifications or setting custom request handlers), use the underlying
* Server instance available via the `server` property.
*/
export declare class McpServer {
/**
* The underlying Server instance, useful for advanced operations like sending notifications.
*/
readonly server: Server;
private _registeredResources;
private _registeredResourceTemplates;
private _registeredTools;
private _registeredPrompts;
constructor(serverInfo: Implementation, options?: ServerOptions);
/**
* Attaches to the given transport, starts it, and starts listening for messages.
*
* The `server` object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
*/
connect(transport: Transport): Promise<void>;
/**
* Closes the connection.
*/
close(): Promise<void>;
private _toolHandlersInitialized;
private setToolRequestHandlers;
private _completionHandlerInitialized;
private setCompletionRequestHandler;
private handlePromptCompletion;
private handleResourceCompletion;
private _resourceHandlersInitialized;
private setResourceRequestHandlers;
private _promptHandlersInitialized;
private setPromptRequestHandlers;
/**
* Registers a resource `name` at a fixed URI, which will use the given callback to respond to read requests.
*/
resource(name: string, uri: string, readCallback: ReadResourceCallback): RegisteredResource;
/**
* Registers a resource `name` at a fixed URI with metadata, which will use the given callback to respond to read requests.
*/
resource(name: string, uri: string, metadata: ResourceMetadata, readCallback: ReadResourceCallback): RegisteredResource;
/**
* Registers a resource `name` with a template pattern, which will use the given callback to respond to read requests.
*/
resource(name: string, template: ResourceTemplate, readCallback: ReadResourceTemplateCallback): RegisteredResourceTemplate;
/**
* Registers a resource `name` with a template pattern and metadata, which will use the given callback to respond to read requests.
*/
resource(name: string, template: ResourceTemplate, metadata: ResourceMetadata, readCallback: ReadResourceTemplateCallback): RegisteredResourceTemplate;
/**
* Registers a resource with a config object and callback.
* For static resources, use a URI string. For dynamic resources, use a ResourceTemplate.
*/
registerResource(name: string, uriOrTemplate: string, config: ResourceMetadata, readCallback: ReadResourceCallback): RegisteredResource;
registerResource(name: string, uriOrTemplate: ResourceTemplate, config: ResourceMetadata, readCallback: ReadResourceTemplateCallback): RegisteredResourceTemplate;
private _createRegisteredResource;
private _createRegisteredResourceTemplate;
private _createRegisteredPrompt;
private _createRegisteredTool;
/**
* Registers a zero-argument tool `name`, which will run the given function when the client calls it.
*/
tool(name: string, cb: ToolCallback): RegisteredTool;
/**
* Registers a zero-argument tool `name` (with a description) which will run the given function when the client calls it.
*/
tool(name: string, description: string, cb: ToolCallback): RegisteredTool;
/**
* Registers a tool taking either a parameter schema for validation or annotations for additional metadata.
* This unified overload handles both `tool(name, paramsSchema, cb)` and `tool(name, annotations, cb)` cases.
*
* Note: We use a union type for the second parameter because TypeScript cannot reliably disambiguate
* between ToolAnnotations and ZodRawShape during overload resolution, as both are plain object types.
*/
tool<Args extends ZodRawShape>(name: string, paramsSchemaOrAnnotations: Args | ToolAnnotations, cb: ToolCallback<Args>): RegisteredTool;
/**
* Registers a tool `name` (with a description) taking either parameter schema or annotations.
* This unified overload handles both `tool(name, description, paramsSchema, cb)` and
* `tool(name, description, annotations, cb)` cases.
*
* Note: We use a union type for the third parameter because TypeScript cannot reliably disambiguate
* between ToolAnnotations and ZodRawShape during overload resolution, as both are plain object types.
*/
tool<Args extends ZodRawShape>(name: string, description: string, paramsSchemaOrAnnotations: Args | ToolAnnotations, cb: ToolCallback<Args>): RegisteredTool;
/**
* Registers a tool with both parameter schema and annotations.
*/
tool<Args extends ZodRawShape>(name: string, paramsSchema: Args, annotations: ToolAnnotations, cb: ToolCallback<Args>): RegisteredTool;
/**
* Registers a tool with description, parameter schema, and annotations.
*/
tool<Args extends ZodRawShape>(name: string, description: string, paramsSchema: Args, annotations: ToolAnnotations, cb: ToolCallback<Args>): RegisteredTool;
/**
* Registers a tool with a config object and callback.
*/
registerTool<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape>(name: string, config: {
title?: string;
description?: string;
inputSchema?: InputArgs;
outputSchema?: OutputArgs;
annotations?: ToolAnnotations;
}, cb: ToolCallback<InputArgs>): RegisteredTool;
/**
* Registers a zero-argument prompt `name`, which will run the given function when the client calls it.
*/
prompt(name: string, cb: PromptCallback): RegisteredPrompt;
/**
* Registers a zero-argument prompt `name` (with a description) which will run the given function when the client calls it.
*/
prompt(name: string, description: string, cb: PromptCallback): RegisteredPrompt;
/**
* Registers a prompt `name` accepting the given arguments, which must be an object containing named properties associated with Zod schemas. When the client calls it, the function will be run with the parsed and validated arguments.
*/
prompt<Args extends PromptArgsRawShape>(name: string, argsSchema: Args, cb: PromptCallback<Args>): RegisteredPrompt;
/**
* Registers a prompt `name` (with a description) accepting the given arguments, which must be an object containing named properties associated with Zod schemas. When the client calls it, the function will be run with the parsed and validated arguments.
*/
prompt<Args extends PromptArgsRawShape>(name: string, description: string, argsSchema: Args, cb: PromptCallback<Args>): RegisteredPrompt;
/**
* Registers a prompt with a config object and callback.
*/
registerPrompt<Args extends PromptArgsRawShape>(name: string, config: {
title?: string;
description?: string;
argsSchema?: Args;
}, cb: PromptCallback<Args>): RegisteredPrompt;
/**
* Checks if the server is connected to a transport.
* @returns True if the server is connected
*/
isConnected(): boolean;
/**
* Sends a resource list changed event to the client, if connected.
*/
sendResourceListChanged(): void;
/**
* Sends a tool list changed event to the client, if connected.
*/
sendToolListChanged(): void;
/**
* Sends a prompt list changed event to the client, if connected.
*/
sendPromptListChanged(): void;
}
/**
* A callback to complete one variable within a resource template's URI template.
*/
export type CompleteResourceTemplateCallback = (value: string, context?: {
arguments?: Record<string, string>;
}) => string[] | Promise<string[]>;
/**
* A resource template combines a URI pattern with optional functionality to enumerate
* all resources matching that pattern.
*/
export declare class ResourceTemplate {
private _callbacks;
private _uriTemplate;
constructor(uriTemplate: string | UriTemplate, _callbacks: {
/**
* A callback to list all resources matching this template. This is required to specified, even if `undefined`, to avoid accidentally forgetting resource listing.
*/
list: ListResourcesCallback | undefined;
/**
* An optional callback to autocomplete variables within the URI template. Useful for clients and users to discover possible values.
*/
complete?: {
[variable: string]: CompleteResourceTemplateCallback;
};
});
/**
* Gets the URI template pattern.
*/
get uriTemplate(): UriTemplate;
/**
* Gets the list callback, if one was provided.
*/
get listCallback(): ListResourcesCallback | undefined;
/**
* Gets the callback for completing a specific URI template variable, if one was provided.
*/
completeCallback(variable: string): CompleteResourceTemplateCallback | undefined;
}
/**
* Callback for a tool handler registered with Server.tool().
*
* Parameters will include tool arguments, if applicable, as well as other request handler context.
*
* The callback should return:
* - `structuredContent` if the tool has an outputSchema defined
* - `content` if the tool does not have an outputSchema
* - Both fields are optional but typically one should be provided
*/
export type ToolCallback<Args extends undefined | ZodRawShape = undefined> = Args extends ZodRawShape ? (args: z.objectOutputType<Args, ZodTypeAny>, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => CallToolResult | Promise<CallToolResult> : (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => CallToolResult | Promise<CallToolResult>;
export type RegisteredTool = {
title?: string;
description?: string;
inputSchema?: AnyZodObject;
outputSchema?: AnyZodObject;
annotations?: ToolAnnotations;
callback: ToolCallback<undefined | ZodRawShape>;
enabled: boolean;
enable(): void;
disable(): void;
update<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape>(updates: {
name?: string | null;
title?: string;
description?: string;
paramsSchema?: InputArgs;
outputSchema?: OutputArgs;
annotations?: ToolAnnotations;
callback?: ToolCallback<InputArgs>;
enabled?: boolean;
}): void;
remove(): void;
};
/**
* Additional, optional information for annotating a resource.
*/
export type ResourceMetadata = Omit<Resource, "uri" | "name">;
/**
* Callback to list all resources matching a given template.
*/
export type ListResourcesCallback = (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => ListResourcesResult | Promise<ListResourcesResult>;
/**
* Callback to read a resource at a given URI.
*/
export type ReadResourceCallback = (uri: URL, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => ReadResourceResult | Promise<ReadResourceResult>;
export type RegisteredResource = {
name: string;
title?: string;
metadata?: ResourceMetadata;
readCallback: ReadResourceCallback;
enabled: boolean;
enable(): void;
disable(): void;
update(updates: {
name?: string;
title?: string;
uri?: string | null;
metadata?: ResourceMetadata;
callback?: ReadResourceCallback;
enabled?: boolean;
}): void;
remove(): void;
};
/**
* Callback to read a resource at a given URI, following a filled-in URI template.
*/
export type ReadResourceTemplateCallback = (uri: URL, variables: Variables, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => ReadResourceResult | Promise<ReadResourceResult>;
export type RegisteredResourceTemplate = {
resourceTemplate: ResourceTemplate;
title?: string;
metadata?: ResourceMetadata;
readCallback: ReadResourceTemplateCallback;
enabled: boolean;
enable(): void;
disable(): void;
update(updates: {
name?: string | null;
title?: string;
template?: ResourceTemplate;
metadata?: ResourceMetadata;
callback?: ReadResourceTemplateCallback;
enabled?: boolean;
}): void;
remove(): void;
};
type PromptArgsRawShape = {
[k: string]: ZodType<string, ZodTypeDef, string> | ZodOptional<ZodType<string, ZodTypeDef, string>>;
};
export type PromptCallback<Args extends undefined | PromptArgsRawShape = undefined> = Args extends PromptArgsRawShape ? (args: z.objectOutputType<Args, ZodTypeAny>, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => GetPromptResult | Promise<GetPromptResult> : (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => GetPromptResult | Promise<GetPromptResult>;
export type RegisteredPrompt = {
title?: string;
description?: string;
argsSchema?: ZodObject<PromptArgsRawShape>;
callback: PromptCallback<undefined | PromptArgsRawShape>;
enabled: boolean;
enable(): void;
disable(): void;
update<Args extends PromptArgsRawShape>(updates: {
name?: string | null;
title?: string;
description?: string;
argsSchema?: Args;
callback?: PromptCallback<Args>;
enabled?: boolean;
}): void;
remove(): void;
};
export {};
//# sourceMappingURL=mcp.d.ts.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,668 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResourceTemplate = exports.McpServer = void 0;
const index_js_1 = require("./index.js");
const zod_to_json_schema_1 = require("zod-to-json-schema");
const zod_1 = require("zod");
const types_js_1 = require("../types.js");
const completable_js_1 = require("./completable.js");
const uriTemplate_js_1 = require("../shared/uriTemplate.js");
/**
* High-level MCP server that provides a simpler API for working with resources, tools, and prompts.
* For advanced usage (like sending notifications or setting custom request handlers), use the underlying
* Server instance available via the `server` property.
*/
class McpServer {
constructor(serverInfo, options) {
this._registeredResources = {};
this._registeredResourceTemplates = {};
this._registeredTools = {};
this._registeredPrompts = {};
this._toolHandlersInitialized = false;
this._completionHandlerInitialized = false;
this._resourceHandlersInitialized = false;
this._promptHandlersInitialized = false;
this.server = new index_js_1.Server(serverInfo, options);
}
/**
* Attaches to the given transport, starts it, and starts listening for messages.
*
* The `server` object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
*/
async connect(transport) {
return await this.server.connect(transport);
}
/**
* Closes the connection.
*/
async close() {
await this.server.close();
}
setToolRequestHandlers() {
if (this._toolHandlersInitialized) {
return;
}
this.server.assertCanSetRequestHandler(types_js_1.ListToolsRequestSchema.shape.method.value);
this.server.assertCanSetRequestHandler(types_js_1.CallToolRequestSchema.shape.method.value);
this.server.registerCapabilities({
tools: {
listChanged: true
}
});
this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, () => ({
tools: Object.entries(this._registeredTools).filter(([, tool]) => tool.enabled).map(([name, tool]) => {
const toolDefinition = {
name,
title: tool.title,
description: tool.description,
inputSchema: tool.inputSchema
? (0, zod_to_json_schema_1.zodToJsonSchema)(tool.inputSchema, {
strictUnions: true,
})
: EMPTY_OBJECT_JSON_SCHEMA,
annotations: tool.annotations,
};
if (tool.outputSchema) {
toolDefinition.outputSchema = (0, zod_to_json_schema_1.zodToJsonSchema)(tool.outputSchema, { strictUnions: true });
}
return toolDefinition;
}),
}));
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request, extra) => {
const tool = this._registeredTools[request.params.name];
if (!tool) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
}
if (!tool.enabled) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Tool ${request.params.name} disabled`);
}
let result;
if (tool.inputSchema) {
const parseResult = await tool.inputSchema.safeParseAsync(request.params.arguments);
if (!parseResult.success) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid arguments for tool ${request.params.name}: ${parseResult.error.message}`);
}
const args = parseResult.data;
const cb = tool.callback;
try {
result = await Promise.resolve(cb(args, extra));
}
catch (error) {
result = {
content: [
{
type: "text",
text: error instanceof Error ? error.message : String(error),
},
],
isError: true,
};
}
}
else {
const cb = tool.callback;
try {
result = await Promise.resolve(cb(extra));
}
catch (error) {
result = {
content: [
{
type: "text",
text: error instanceof Error ? error.message : String(error),
},
],
isError: true,
};
}
}
if (tool.outputSchema && !result.isError) {
if (!result.structuredContent) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Tool ${request.params.name} has an output schema but no structured content was provided`);
}
// if the tool has an output schema, validate structured content
const parseResult = await tool.outputSchema.safeParseAsync(result.structuredContent);
if (!parseResult.success) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid structured content for tool ${request.params.name}: ${parseResult.error.message}`);
}
}
return result;
});
this._toolHandlersInitialized = true;
}
setCompletionRequestHandler() {
if (this._completionHandlerInitialized) {
return;
}
this.server.assertCanSetRequestHandler(types_js_1.CompleteRequestSchema.shape.method.value);
this.server.registerCapabilities({
completions: {},
});
this.server.setRequestHandler(types_js_1.CompleteRequestSchema, async (request) => {
switch (request.params.ref.type) {
case "ref/prompt":
return this.handlePromptCompletion(request, request.params.ref);
case "ref/resource":
return this.handleResourceCompletion(request, request.params.ref);
default:
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid completion reference: ${request.params.ref}`);
}
});
this._completionHandlerInitialized = true;
}
async handlePromptCompletion(request, ref) {
const prompt = this._registeredPrompts[ref.name];
if (!prompt) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Prompt ${ref.name} not found`);
}
if (!prompt.enabled) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Prompt ${ref.name} disabled`);
}
if (!prompt.argsSchema) {
return EMPTY_COMPLETION_RESULT;
}
const field = prompt.argsSchema.shape[request.params.argument.name];
if (!(field instanceof completable_js_1.Completable)) {
return EMPTY_COMPLETION_RESULT;
}
const def = field._def;
const suggestions = await def.complete(request.params.argument.value, request.params.context);
return createCompletionResult(suggestions);
}
async handleResourceCompletion(request, ref) {
const template = Object.values(this._registeredResourceTemplates).find((t) => t.resourceTemplate.uriTemplate.toString() === ref.uri);
if (!template) {
if (this._registeredResources[ref.uri]) {
// Attempting to autocomplete a fixed resource URI is not an error in the spec (but probably should be).
return EMPTY_COMPLETION_RESULT;
}
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Resource template ${request.params.ref.uri} not found`);
}
const completer = template.resourceTemplate.completeCallback(request.params.argument.name);
if (!completer) {
return EMPTY_COMPLETION_RESULT;
}
const suggestions = await completer(request.params.argument.value, request.params.context);
return createCompletionResult(suggestions);
}
setResourceRequestHandlers() {
if (this._resourceHandlersInitialized) {
return;
}
this.server.assertCanSetRequestHandler(types_js_1.ListResourcesRequestSchema.shape.method.value);
this.server.assertCanSetRequestHandler(types_js_1.ListResourceTemplatesRequestSchema.shape.method.value);
this.server.assertCanSetRequestHandler(types_js_1.ReadResourceRequestSchema.shape.method.value);
this.server.registerCapabilities({
resources: {
listChanged: true
}
});
this.server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async (request, extra) => {
const resources = Object.entries(this._registeredResources).filter(([_, resource]) => resource.enabled).map(([uri, resource]) => ({
uri,
name: resource.name,
...resource.metadata,
}));
const templateResources = [];
for (const template of Object.values(this._registeredResourceTemplates)) {
if (!template.resourceTemplate.listCallback) {
continue;
}
const result = await template.resourceTemplate.listCallback(extra);
for (const resource of result.resources) {
templateResources.push({
...template.metadata,
// the defined resource metadata should override the template metadata if present
...resource,
});
}
}
return { resources: [...resources, ...templateResources] };
});
this.server.setRequestHandler(types_js_1.ListResourceTemplatesRequestSchema, async () => {
const resourceTemplates = Object.entries(this._registeredResourceTemplates).map(([name, template]) => ({
name,
uriTemplate: template.resourceTemplate.uriTemplate.toString(),
...template.metadata,
}));
return { resourceTemplates };
});
this.server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request, extra) => {
const uri = new URL(request.params.uri);
// First check for exact resource match
const resource = this._registeredResources[uri.toString()];
if (resource) {
if (!resource.enabled) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Resource ${uri} disabled`);
}
return resource.readCallback(uri, extra);
}
// Then check templates
for (const template of Object.values(this._registeredResourceTemplates)) {
const variables = template.resourceTemplate.uriTemplate.match(uri.toString());
if (variables) {
return template.readCallback(uri, variables, extra);
}
}
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Resource ${uri} not found`);
});
this.setCompletionRequestHandler();
this._resourceHandlersInitialized = true;
}
setPromptRequestHandlers() {
if (this._promptHandlersInitialized) {
return;
}
this.server.assertCanSetRequestHandler(types_js_1.ListPromptsRequestSchema.shape.method.value);
this.server.assertCanSetRequestHandler(types_js_1.GetPromptRequestSchema.shape.method.value);
this.server.registerCapabilities({
prompts: {
listChanged: true
}
});
this.server.setRequestHandler(types_js_1.ListPromptsRequestSchema, () => ({
prompts: Object.entries(this._registeredPrompts).filter(([, prompt]) => prompt.enabled).map(([name, prompt]) => {
return {
name,
title: prompt.title,
description: prompt.description,
arguments: prompt.argsSchema
? promptArgumentsFromSchema(prompt.argsSchema)
: undefined,
};
}),
}));
this.server.setRequestHandler(types_js_1.GetPromptRequestSchema, async (request, extra) => {
const prompt = this._registeredPrompts[request.params.name];
if (!prompt) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Prompt ${request.params.name} not found`);
}
if (!prompt.enabled) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Prompt ${request.params.name} disabled`);
}
if (prompt.argsSchema) {
const parseResult = await prompt.argsSchema.safeParseAsync(request.params.arguments);
if (!parseResult.success) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${parseResult.error.message}`);
}
const args = parseResult.data;
const cb = prompt.callback;
return await Promise.resolve(cb(args, extra));
}
else {
const cb = prompt.callback;
return await Promise.resolve(cb(extra));
}
});
this.setCompletionRequestHandler();
this._promptHandlersInitialized = true;
}
resource(name, uriOrTemplate, ...rest) {
let metadata;
if (typeof rest[0] === "object") {
metadata = rest.shift();
}
const readCallback = rest[0];
if (typeof uriOrTemplate === "string") {
if (this._registeredResources[uriOrTemplate]) {
throw new Error(`Resource ${uriOrTemplate} is already registered`);
}
const registeredResource = this._createRegisteredResource(name, undefined, uriOrTemplate, metadata, readCallback);
this.setResourceRequestHandlers();
this.sendResourceListChanged();
return registeredResource;
}
else {
if (this._registeredResourceTemplates[name]) {
throw new Error(`Resource template ${name} is already registered`);
}
const registeredResourceTemplate = this._createRegisteredResourceTemplate(name, undefined, uriOrTemplate, metadata, readCallback);
this.setResourceRequestHandlers();
this.sendResourceListChanged();
return registeredResourceTemplate;
}
}
registerResource(name, uriOrTemplate, config, readCallback) {
if (typeof uriOrTemplate === "string") {
if (this._registeredResources[uriOrTemplate]) {
throw new Error(`Resource ${uriOrTemplate} is already registered`);
}
const registeredResource = this._createRegisteredResource(name, config.title, uriOrTemplate, config, readCallback);
this.setResourceRequestHandlers();
this.sendResourceListChanged();
return registeredResource;
}
else {
if (this._registeredResourceTemplates[name]) {
throw new Error(`Resource template ${name} is already registered`);
}
const registeredResourceTemplate = this._createRegisteredResourceTemplate(name, config.title, uriOrTemplate, config, readCallback);
this.setResourceRequestHandlers();
this.sendResourceListChanged();
return registeredResourceTemplate;
}
}
_createRegisteredResource(name, title, uri, metadata, readCallback) {
const registeredResource = {
name,
title,
metadata,
readCallback,
enabled: true,
disable: () => registeredResource.update({ enabled: false }),
enable: () => registeredResource.update({ enabled: true }),
remove: () => registeredResource.update({ uri: null }),
update: (updates) => {
if (typeof updates.uri !== "undefined" && updates.uri !== uri) {
delete this._registeredResources[uri];
if (updates.uri)
this._registeredResources[updates.uri] = registeredResource;
}
if (typeof updates.name !== "undefined")
registeredResource.name = updates.name;
if (typeof updates.title !== "undefined")
registeredResource.title = updates.title;
if (typeof updates.metadata !== "undefined")
registeredResource.metadata = updates.metadata;
if (typeof updates.callback !== "undefined")
registeredResource.readCallback = updates.callback;
if (typeof updates.enabled !== "undefined")
registeredResource.enabled = updates.enabled;
this.sendResourceListChanged();
},
};
this._registeredResources[uri] = registeredResource;
return registeredResource;
}
_createRegisteredResourceTemplate(name, title, template, metadata, readCallback) {
const registeredResourceTemplate = {
resourceTemplate: template,
title,
metadata,
readCallback,
enabled: true,
disable: () => registeredResourceTemplate.update({ enabled: false }),
enable: () => registeredResourceTemplate.update({ enabled: true }),
remove: () => registeredResourceTemplate.update({ name: null }),
update: (updates) => {
if (typeof updates.name !== "undefined" && updates.name !== name) {
delete this._registeredResourceTemplates[name];
if (updates.name)
this._registeredResourceTemplates[updates.name] = registeredResourceTemplate;
}
if (typeof updates.title !== "undefined")
registeredResourceTemplate.title = updates.title;
if (typeof updates.template !== "undefined")
registeredResourceTemplate.resourceTemplate = updates.template;
if (typeof updates.metadata !== "undefined")
registeredResourceTemplate.metadata = updates.metadata;
if (typeof updates.callback !== "undefined")
registeredResourceTemplate.readCallback = updates.callback;
if (typeof updates.enabled !== "undefined")
registeredResourceTemplate.enabled = updates.enabled;
this.sendResourceListChanged();
},
};
this._registeredResourceTemplates[name] = registeredResourceTemplate;
return registeredResourceTemplate;
}
_createRegisteredPrompt(name, title, description, argsSchema, callback) {
const registeredPrompt = {
title,
description,
argsSchema: argsSchema === undefined ? undefined : zod_1.z.object(argsSchema),
callback,
enabled: true,
disable: () => registeredPrompt.update({ enabled: false }),
enable: () => registeredPrompt.update({ enabled: true }),
remove: () => registeredPrompt.update({ name: null }),
update: (updates) => {
if (typeof updates.name !== "undefined" && updates.name !== name) {
delete this._registeredPrompts[name];
if (updates.name)
this._registeredPrompts[updates.name] = registeredPrompt;
}
if (typeof updates.title !== "undefined")
registeredPrompt.title = updates.title;
if (typeof updates.description !== "undefined")
registeredPrompt.description = updates.description;
if (typeof updates.argsSchema !== "undefined")
registeredPrompt.argsSchema = zod_1.z.object(updates.argsSchema);
if (typeof updates.callback !== "undefined")
registeredPrompt.callback = updates.callback;
if (typeof updates.enabled !== "undefined")
registeredPrompt.enabled = updates.enabled;
this.sendPromptListChanged();
},
};
this._registeredPrompts[name] = registeredPrompt;
return registeredPrompt;
}
_createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, callback) {
const registeredTool = {
title,
description,
inputSchema: inputSchema === undefined ? undefined : zod_1.z.object(inputSchema),
outputSchema: outputSchema === undefined ? undefined : zod_1.z.object(outputSchema),
annotations,
callback,
enabled: true,
disable: () => registeredTool.update({ enabled: false }),
enable: () => registeredTool.update({ enabled: true }),
remove: () => registeredTool.update({ name: null }),
update: (updates) => {
if (typeof updates.name !== "undefined" && updates.name !== name) {
delete this._registeredTools[name];
if (updates.name)
this._registeredTools[updates.name] = registeredTool;
}
if (typeof updates.title !== "undefined")
registeredTool.title = updates.title;
if (typeof updates.description !== "undefined")
registeredTool.description = updates.description;
if (typeof updates.paramsSchema !== "undefined")
registeredTool.inputSchema = zod_1.z.object(updates.paramsSchema);
if (typeof updates.callback !== "undefined")
registeredTool.callback = updates.callback;
if (typeof updates.annotations !== "undefined")
registeredTool.annotations = updates.annotations;
if (typeof updates.enabled !== "undefined")
registeredTool.enabled = updates.enabled;
this.sendToolListChanged();
},
};
this._registeredTools[name] = registeredTool;
this.setToolRequestHandlers();
this.sendToolListChanged();
return registeredTool;
}
/**
* tool() implementation. Parses arguments passed to overrides defined above.
*/
tool(name, ...rest) {
if (this._registeredTools[name]) {
throw new Error(`Tool ${name} is already registered`);
}
let description;
let inputSchema;
let outputSchema;
let annotations;
// Tool properties are passed as separate arguments, with omissions allowed.
// Support for this style is frozen as of protocol version 2025-03-26. Future additions
// to tool definition should *NOT* be added.
if (typeof rest[0] === "string") {
description = rest.shift();
}
// Handle the different overload combinations
if (rest.length > 1) {
// We have at least one more arg before the callback
const firstArg = rest[0];
if (isZodRawShape(firstArg)) {
// We have a params schema as the first arg
inputSchema = rest.shift();
// Check if the next arg is potentially annotations
if (rest.length > 1 && typeof rest[0] === "object" && rest[0] !== null && !(isZodRawShape(rest[0]))) {
// Case: tool(name, paramsSchema, annotations, cb)
// Or: tool(name, description, paramsSchema, annotations, cb)
annotations = rest.shift();
}
}
else if (typeof firstArg === "object" && firstArg !== null) {
// Not a ZodRawShape, so must be annotations in this position
// Case: tool(name, annotations, cb)
// Or: tool(name, description, annotations, cb)
annotations = rest.shift();
}
}
const callback = rest[0];
return this._createRegisteredTool(name, undefined, description, inputSchema, outputSchema, annotations, callback);
}
/**
* Registers a tool with a config object and callback.
*/
registerTool(name, config, cb) {
if (this._registeredTools[name]) {
throw new Error(`Tool ${name} is already registered`);
}
const { title, description, inputSchema, outputSchema, annotations } = config;
return this._createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, cb);
}
prompt(name, ...rest) {
if (this._registeredPrompts[name]) {
throw new Error(`Prompt ${name} is already registered`);
}
let description;
if (typeof rest[0] === "string") {
description = rest.shift();
}
let argsSchema;
if (rest.length > 1) {
argsSchema = rest.shift();
}
const cb = rest[0];
const registeredPrompt = this._createRegisteredPrompt(name, undefined, description, argsSchema, cb);
this.setPromptRequestHandlers();
this.sendPromptListChanged();
return registeredPrompt;
}
/**
* Registers a prompt with a config object and callback.
*/
registerPrompt(name, config, cb) {
if (this._registeredPrompts[name]) {
throw new Error(`Prompt ${name} is already registered`);
}
const { title, description, argsSchema } = config;
const registeredPrompt = this._createRegisteredPrompt(name, title, description, argsSchema, cb);
this.setPromptRequestHandlers();
this.sendPromptListChanged();
return registeredPrompt;
}
/**
* Checks if the server is connected to a transport.
* @returns True if the server is connected
*/
isConnected() {
return this.server.transport !== undefined;
}
/**
* Sends a resource list changed event to the client, if connected.
*/
sendResourceListChanged() {
if (this.isConnected()) {
this.server.sendResourceListChanged();
}
}
/**
* Sends a tool list changed event to the client, if connected.
*/
sendToolListChanged() {
if (this.isConnected()) {
this.server.sendToolListChanged();
}
}
/**
* Sends a prompt list changed event to the client, if connected.
*/
sendPromptListChanged() {
if (this.isConnected()) {
this.server.sendPromptListChanged();
}
}
}
exports.McpServer = McpServer;
/**
* A resource template combines a URI pattern with optional functionality to enumerate
* all resources matching that pattern.
*/
class ResourceTemplate {
constructor(uriTemplate, _callbacks) {
this._callbacks = _callbacks;
this._uriTemplate =
typeof uriTemplate === "string"
? new uriTemplate_js_1.UriTemplate(uriTemplate)
: uriTemplate;
}
/**
* Gets the URI template pattern.
*/
get uriTemplate() {
return this._uriTemplate;
}
/**
* Gets the list callback, if one was provided.
*/
get listCallback() {
return this._callbacks.list;
}
/**
* Gets the callback for completing a specific URI template variable, if one was provided.
*/
completeCallback(variable) {
var _a;
return (_a = this._callbacks.complete) === null || _a === void 0 ? void 0 : _a[variable];
}
}
exports.ResourceTemplate = ResourceTemplate;
const EMPTY_OBJECT_JSON_SCHEMA = {
type: "object",
properties: {},
};
// Helper to check if an object is a Zod schema (ZodRawShape)
function isZodRawShape(obj) {
if (typeof obj !== "object" || obj === null)
return false;
const isEmptyObject = Object.keys(obj).length === 0;
// Check if object is empty or at least one property is a ZodType instance
// Note: use heuristic check to avoid instanceof failure across different Zod versions
return isEmptyObject || Object.values(obj).some(isZodTypeLike);
}
function isZodTypeLike(value) {
return value !== null &&
typeof value === 'object' &&
'parse' in value && typeof value.parse === 'function' &&
'safeParse' in value && typeof value.safeParse === 'function';
}
function promptArgumentsFromSchema(schema) {
return Object.entries(schema.shape).map(([name, field]) => ({
name,
description: field.description,
required: !field.isOptional(),
}));
}
function createCompletionResult(suggestions) {
return {
completion: {
values: suggestions.slice(0, 100),
total: suggestions.length,
hasMore: suggestions.length > 100,
},
};
}
const EMPTY_COMPLETION_RESULT = {
completion: {
values: [],
hasMore: false,
},
};
//# sourceMappingURL=mcp.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,75 @@
import { IncomingMessage, ServerResponse } from "node:http";
import { Transport } from "../shared/transport.js";
import { JSONRPCMessage, MessageExtraInfo } from "../types.js";
import { AuthInfo } from "./auth/types.js";
/**
* Configuration options for SSEServerTransport.
*/
export interface SSEServerTransportOptions {
/**
* List of allowed host header values for DNS rebinding protection.
* If not specified, host validation is disabled.
*/
allowedHosts?: string[];
/**
* List of allowed origin header values for DNS rebinding protection.
* If not specified, origin validation is disabled.
*/
allowedOrigins?: string[];
/**
* Enable DNS rebinding protection (requires allowedHosts and/or allowedOrigins to be configured).
* Default is false for backwards compatibility.
*/
enableDnsRebindingProtection?: boolean;
}
/**
* Server transport for SSE: this will send messages over an SSE connection and receive messages from HTTP POST requests.
*
* This transport is only available in Node.js environments.
*/
export declare class SSEServerTransport implements Transport {
private _endpoint;
private res;
private _sseResponse?;
private _sessionId;
private _options;
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void;
/**
* Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL identified by `_endpoint`.
*/
constructor(_endpoint: string, res: ServerResponse, options?: SSEServerTransportOptions);
/**
* Validates request headers for DNS rebinding protection.
* @returns Error message if validation fails, undefined if validation passes.
*/
private validateRequestHeaders;
/**
* Handles the initial SSE connection request.
*
* This should be called when a GET request is made to establish the SSE stream.
*/
start(): Promise<void>;
/**
* Handles incoming POST messages.
*
* This should be called when a POST request is made to send a message to the server.
*/
handlePostMessage(req: IncomingMessage & {
auth?: AuthInfo;
}, res: ServerResponse, parsedBody?: unknown): Promise<void>;
/**
* Handle a client message, regardless of how it arrived. This can be used to inform the server of messages that arrive via a means different than HTTP POST.
*/
handleMessage(message: unknown, extra?: MessageExtraInfo): Promise<void>;
close(): Promise<void>;
send(message: JSONRPCMessage): Promise<void>;
/**
* Returns the session ID for this transport.
*
* This can be used to route incoming POST requests.
*/
get sessionId(): string;
}
//# sourceMappingURL=sse.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../src/server/sse.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAwB,gBAAgB,EAAe,MAAM,aAAa,CAAC;AAGlG,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAK3C;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B;;;OAGG;IACH,4BAA4B,CAAC,EAAE,OAAO,CAAC;CACxC;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,SAAS;IAYhD,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,GAAG;IAZb,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAExE;;OAEG;gBAEO,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,cAAc,EAC3B,OAAO,CAAC,EAAE,yBAAyB;IAMrC;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAyB9B;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkC5B;;;;OAIG;IACG,iBAAiB,CACrB,GAAG,EAAE,eAAe,GAAG;QAAE,IAAI,CAAC,EAAE,QAAQ,CAAA;KAAE,EAC1C,GAAG,EAAE,cAAc,EACnB,UAAU,CAAC,EAAE,OAAO,GACnB,OAAO,CAAC,IAAI,CAAC;IA6ChB;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlD;;;;OAIG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;CACF"}

View File

@@ -0,0 +1,166 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SSEServerTransport = void 0;
const node_crypto_1 = require("node:crypto");
const types_js_1 = require("../types.js");
const raw_body_1 = __importDefault(require("raw-body"));
const content_type_1 = __importDefault(require("content-type"));
const url_1 = require("url");
const MAXIMUM_MESSAGE_SIZE = "4mb";
/**
* Server transport for SSE: this will send messages over an SSE connection and receive messages from HTTP POST requests.
*
* This transport is only available in Node.js environments.
*/
class SSEServerTransport {
/**
* Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL identified by `_endpoint`.
*/
constructor(_endpoint, res, options) {
this._endpoint = _endpoint;
this.res = res;
this._sessionId = (0, node_crypto_1.randomUUID)();
this._options = options || { enableDnsRebindingProtection: false };
}
/**
* Validates request headers for DNS rebinding protection.
* @returns Error message if validation fails, undefined if validation passes.
*/
validateRequestHeaders(req) {
// Skip validation if protection is not enabled
if (!this._options.enableDnsRebindingProtection) {
return undefined;
}
// Validate Host header if allowedHosts is configured
if (this._options.allowedHosts && this._options.allowedHosts.length > 0) {
const hostHeader = req.headers.host;
if (!hostHeader || !this._options.allowedHosts.includes(hostHeader)) {
return `Invalid Host header: ${hostHeader}`;
}
}
// Validate Origin header if allowedOrigins is configured
if (this._options.allowedOrigins && this._options.allowedOrigins.length > 0) {
const originHeader = req.headers.origin;
if (!originHeader || !this._options.allowedOrigins.includes(originHeader)) {
return `Invalid Origin header: ${originHeader}`;
}
}
return undefined;
}
/**
* Handles the initial SSE connection request.
*
* This should be called when a GET request is made to establish the SSE stream.
*/
async start() {
if (this._sseResponse) {
throw new Error("SSEServerTransport already started! If using Server class, note that connect() calls start() automatically.");
}
this.res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
});
// Send the endpoint event
// Use a dummy base URL because this._endpoint is relative.
// This allows using URL/URLSearchParams for robust parameter handling.
const dummyBase = 'http://localhost'; // Any valid base works
const endpointUrl = new url_1.URL(this._endpoint, dummyBase);
endpointUrl.searchParams.set('sessionId', this._sessionId);
// Reconstruct the relative URL string (pathname + search + hash)
const relativeUrlWithSession = endpointUrl.pathname + endpointUrl.search + endpointUrl.hash;
this.res.write(`event: endpoint\ndata: ${relativeUrlWithSession}\n\n`);
this._sseResponse = this.res;
this.res.on("close", () => {
var _a;
this._sseResponse = undefined;
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
});
}
/**
* Handles incoming POST messages.
*
* This should be called when a POST request is made to send a message to the server.
*/
async handlePostMessage(req, res, parsedBody) {
var _a, _b, _c, _d;
if (!this._sseResponse) {
const message = "SSE connection not established";
res.writeHead(500).end(message);
throw new Error(message);
}
// Validate request headers for DNS rebinding protection
const validationError = this.validateRequestHeaders(req);
if (validationError) {
res.writeHead(403).end(validationError);
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(validationError));
return;
}
const authInfo = req.auth;
const requestInfo = { headers: req.headers };
let body;
try {
const ct = content_type_1.default.parse((_b = req.headers["content-type"]) !== null && _b !== void 0 ? _b : "");
if (ct.type !== "application/json") {
throw new Error(`Unsupported content-type: ${ct.type}`);
}
body = parsedBody !== null && parsedBody !== void 0 ? parsedBody : await (0, raw_body_1.default)(req, {
limit: MAXIMUM_MESSAGE_SIZE,
encoding: (_c = ct.parameters.charset) !== null && _c !== void 0 ? _c : "utf-8",
});
}
catch (error) {
res.writeHead(400).end(String(error));
(_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, error);
return;
}
try {
await this.handleMessage(typeof body === 'string' ? JSON.parse(body) : body, { requestInfo, authInfo });
}
catch (_e) {
res.writeHead(400).end(`Invalid message: ${body}`);
return;
}
res.writeHead(202).end("Accepted");
}
/**
* Handle a client message, regardless of how it arrived. This can be used to inform the server of messages that arrive via a means different than HTTP POST.
*/
async handleMessage(message, extra) {
var _a, _b;
let parsedMessage;
try {
parsedMessage = types_js_1.JSONRPCMessageSchema.parse(message);
}
catch (error) {
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
throw error;
}
(_b = this.onmessage) === null || _b === void 0 ? void 0 : _b.call(this, parsedMessage, extra);
}
async close() {
var _a, _b;
(_a = this._sseResponse) === null || _a === void 0 ? void 0 : _a.end();
this._sseResponse = undefined;
(_b = this.onclose) === null || _b === void 0 ? void 0 : _b.call(this);
}
async send(message) {
if (!this._sseResponse) {
throw new Error("Not connected");
}
this._sseResponse.write(`event: message\ndata: ${JSON.stringify(message)}\n\n`);
}
/**
* Returns the session ID for this transport.
*
* This can be used to route incoming POST requests.
*/
get sessionId() {
return this._sessionId;
}
}
exports.SSEServerTransport = SSEServerTransport;
//# sourceMappingURL=sse.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../../src/server/sse.ts"],"names":[],"mappings":";;;;;;AAAA,6CAAyC;AAGzC,0CAAkG;AAClG,wDAAkC;AAClC,gEAAuC;AAEvC,6BAA0B;AAE1B,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAyBnC;;;;GAIG;AACH,MAAa,kBAAkB;IAQ7B;;OAEG;IACH,YACU,SAAiB,EACjB,GAAmB,EAC3B,OAAmC;QAF3B,cAAS,GAAT,SAAS,CAAQ;QACjB,QAAG,GAAH,GAAG,CAAgB;QAG3B,IAAI,CAAC,UAAU,GAAG,IAAA,wBAAU,GAAE,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,OAAO,IAAI,EAAC,4BAA4B,EAAE,KAAK,EAAC,CAAC;IACnE,CAAC;IAED;;;OAGG;IACK,sBAAsB,CAAC,GAAoB;QACjD,+CAA+C;QAC/C,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,4BAA4B,EAAE,CAAC;YAChD,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,qDAAqD;QACrD,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxE,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC;YACpC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBACpE,OAAO,wBAAwB,UAAU,EAAE,CAAC;YAC9C,CAAC;QACH,CAAC;QAED,yDAAyD;QACzD,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5E,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;YACxC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC1E,OAAO,0BAA0B,YAAY,EAAE,CAAC;YAClD,CAAC;QACH,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CACb,6GAA6G,CAC9G,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;YACtB,cAAc,EAAE,mBAAmB;YACnC,eAAe,EAAE,wBAAwB;YACzC,UAAU,EAAE,YAAY;SACzB,CAAC,CAAC;QAEH,0BAA0B;QAC1B,2DAA2D;QAC3D,uEAAuE;QACvE,MAAM,SAAS,GAAG,kBAAkB,CAAC,CAAC,uBAAuB;QAC7D,MAAM,WAAW,GAAG,IAAI,SAAG,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACvD,WAAW,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAE3D,iEAAiE;QACjE,MAAM,sBAAsB,GAAG,WAAW,CAAC,QAAQ,GAAG,WAAW,CAAC,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC;QAE5F,IAAI,CAAC,GAAG,CAAC,KAAK,CACZ,0BAA0B,sBAAsB,MAAM,CACvD,CAAC;QAEF,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;;YACxB,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;YAC9B,MAAA,IAAI,CAAC,OAAO,oDAAI,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,iBAAiB,CACrB,GAA0C,EAC1C,GAAmB,EACnB,UAAoB;;QAEpB,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,gCAAgC,CAAC;YACjD,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;QAED,wDAAwD;QACxD,MAAM,eAAe,GAAG,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC;QACzD,IAAI,eAAe,EAAE,CAAC;YACpB,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YACxC,MAAA,IAAI,CAAC,OAAO,qDAAG,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAyB,GAAG,CAAC,IAAI,CAAC;QAChD,MAAM,WAAW,GAAgB,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;QAE1D,IAAI,IAAsB,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,sBAAW,CAAC,KAAK,CAAC,MAAA,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,mCAAI,EAAE,CAAC,CAAC;YAChE,IAAI,EAAE,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;gBACnC,MAAM,IAAI,KAAK,CAAC,6BAA6B,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1D,CAAC;YAED,IAAI,GAAG,UAAU,aAAV,UAAU,cAAV,UAAU,GAAI,MAAM,IAAA,kBAAU,EAAC,GAAG,EAAE;gBACzC,KAAK,EAAE,oBAAoB;gBAC3B,QAAQ,EAAE,MAAA,EAAE,CAAC,UAAU,CAAC,OAAO,mCAAI,OAAO;aAC3C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACtC,MAAA,IAAI,CAAC,OAAO,qDAAG,KAAc,CAAC,CAAC;YAC/B,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC1G,CAAC;QAAC,WAAM,CAAC;YACP,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QAED,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACrC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa,CAAC,OAAgB,EAAE,KAAwB;;QAC5D,IAAI,aAA6B,CAAC;QAClC,IAAI,CAAC;YACH,aAAa,GAAG,+BAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAA,IAAI,CAAC,OAAO,qDAAG,KAAc,CAAC,CAAC;YAC/B,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAA,IAAI,CAAC,SAAS,qDAAG,aAAa,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,KAAK;;QACT,MAAA,IAAI,CAAC,YAAY,0CAAE,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;QAC9B,MAAA,IAAI,CAAC,OAAO,oDAAI,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAuB;QAChC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;QACnC,CAAC;QAED,IAAI,CAAC,YAAY,CAAC,KAAK,CACrB,yBAAyB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CACvD,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;CACF;AArLD,gDAqLC"}

View File

@@ -0,0 +1,28 @@
import { Readable, Writable } from "node:stream";
import { JSONRPCMessage } from "../types.js";
import { Transport } from "../shared/transport.js";
/**
* Server transport for stdio: this communicates with a MCP client by reading from the current process' stdin and writing to stdout.
*
* This transport is only available in Node.js environments.
*/
export declare class StdioServerTransport implements Transport {
private _stdin;
private _stdout;
private _readBuffer;
private _started;
constructor(_stdin?: Readable, _stdout?: Writable);
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage) => void;
_ondata: (chunk: Buffer) => void;
_onerror: (error: Error) => void;
/**
* Starts listening for messages on stdin.
*/
start(): Promise<void>;
private processReadBuffer;
close(): Promise<void>;
send(message: JSONRPCMessage): Promise<void>;
}
//# sourceMappingURL=stdio.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../../src/server/stdio.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,SAAS;IAKlD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,OAAO;IALjB,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,QAAQ,CAAS;gBAGf,MAAM,GAAE,QAAwB,EAChC,OAAO,GAAE,QAAyB;IAG5C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAG9C,OAAO,UAAW,MAAM,UAGtB;IACF,QAAQ,UAAW,KAAK,UAEtB;IAEF;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,OAAO,CAAC,iBAAiB;IAenB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CAU7C"}

View File

@@ -0,0 +1,85 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StdioServerTransport = void 0;
const node_process_1 = __importDefault(require("node:process"));
const stdio_js_1 = require("../shared/stdio.js");
/**
* Server transport for stdio: this communicates with a MCP client by reading from the current process' stdin and writing to stdout.
*
* This transport is only available in Node.js environments.
*/
class StdioServerTransport {
constructor(_stdin = node_process_1.default.stdin, _stdout = node_process_1.default.stdout) {
this._stdin = _stdin;
this._stdout = _stdout;
this._readBuffer = new stdio_js_1.ReadBuffer();
this._started = false;
// Arrow functions to bind `this` properly, while maintaining function identity.
this._ondata = (chunk) => {
this._readBuffer.append(chunk);
this.processReadBuffer();
};
this._onerror = (error) => {
var _a;
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
};
}
/**
* Starts listening for messages on stdin.
*/
async start() {
if (this._started) {
throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
}
this._started = true;
this._stdin.on("data", this._ondata);
this._stdin.on("error", this._onerror);
}
processReadBuffer() {
var _a, _b;
while (true) {
try {
const message = this._readBuffer.readMessage();
if (message === null) {
break;
}
(_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, message);
}
catch (error) {
(_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
}
}
}
async close() {
var _a;
// Remove our event listeners first
this._stdin.off("data", this._ondata);
this._stdin.off("error", this._onerror);
// Check if we were the only data listener
const remainingDataListeners = this._stdin.listenerCount('data');
if (remainingDataListeners === 0) {
// Only pause stdin if we were the only listener
// This prevents interfering with other parts of the application that might be using stdin
this._stdin.pause();
}
// Clear the buffer and notify closure
this._readBuffer.clear();
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
}
send(message) {
return new Promise((resolve) => {
const json = (0, stdio_js_1.serializeMessage)(message);
if (this._stdout.write(json)) {
resolve();
}
else {
this._stdout.once("drain", resolve);
}
});
}
}
exports.StdioServerTransport = StdioServerTransport;
//# sourceMappingURL=stdio.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../../src/server/stdio.ts"],"names":[],"mappings":";;;;;;AAAA,gEAAmC;AAEnC,iDAAkE;AAIlE;;;;GAIG;AACH,MAAa,oBAAoB;IAI/B,YACU,SAAmB,sBAAO,CAAC,KAAK,EAChC,UAAoB,sBAAO,CAAC,MAAM;QADlC,WAAM,GAAN,MAAM,CAA0B;QAChC,YAAO,GAAP,OAAO,CAA2B;QALpC,gBAAW,GAAe,IAAI,qBAAU,EAAE,CAAC;QAC3C,aAAQ,GAAG,KAAK,CAAC;QAWzB,gFAAgF;QAChF,YAAO,GAAG,CAAC,KAAa,EAAE,EAAE;YAC1B,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC/B,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC,CAAC;QACF,aAAQ,GAAG,CAAC,KAAY,EAAE,EAAE;;YAC1B,MAAA,IAAI,CAAC,OAAO,qDAAG,KAAK,CAAC,CAAC;QACxB,CAAC,CAAC;IAbC,CAAC;IAeJ;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACb,+GAA+G,CAChH,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAEO,iBAAiB;;QACvB,OAAO,IAAI,EAAE,CAAC;YACZ,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC;gBAC/C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;oBACrB,MAAM;gBACR,CAAC;gBAED,MAAA,IAAI,CAAC,SAAS,qDAAG,OAAO,CAAC,CAAC;YAC5B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAA,IAAI,CAAC,OAAO,qDAAG,KAAc,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;;QACT,mCAAmC;QACnC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAExC,0CAA0C;QAC1C,MAAM,sBAAsB,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QACjE,IAAI,sBAAsB,KAAK,CAAC,EAAE,CAAC;YACjC,gDAAgD;YAChD,0FAA0F;YAC1F,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;QAED,sCAAsC;QACtC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QACzB,MAAA,IAAI,CAAC,OAAO,oDAAI,CAAC;IACnB,CAAC;IAED,IAAI,CAAC,OAAuB;QAC1B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,MAAM,IAAI,GAAG,IAAA,2BAAgB,EAAC,OAAO,CAAC,CAAC;YACvC,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7B,OAAO,EAAE,CAAC;YACZ,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACtC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAhFD,oDAgFC"}

View File

@@ -0,0 +1,173 @@
import { IncomingMessage, ServerResponse } from "node:http";
import { Transport } from "../shared/transport.js";
import { MessageExtraInfo, JSONRPCMessage, RequestId } from "../types.js";
import { AuthInfo } from "./auth/types.js";
export type StreamId = string;
export type EventId = string;
/**
* Interface for resumability support via event storage
*/
export interface EventStore {
/**
* Stores an event for later retrieval
* @param streamId ID of the stream the event belongs to
* @param message The JSON-RPC message to store
* @returns The generated event ID for the stored event
*/
storeEvent(streamId: StreamId, message: JSONRPCMessage): Promise<EventId>;
replayEventsAfter(lastEventId: EventId, { send }: {
send: (eventId: EventId, message: JSONRPCMessage) => Promise<void>;
}): Promise<StreamId>;
}
/**
* Configuration options for StreamableHTTPServerTransport
*/
export interface StreamableHTTPServerTransportOptions {
/**
* Function that generates a session ID for the transport.
* The session ID SHOULD be globally unique and cryptographically secure (e.g., a securely generated UUID, a JWT, or a cryptographic hash)
*
* Return undefined to disable session management.
*/
sessionIdGenerator: (() => string) | undefined;
/**
* A callback for session initialization events
* This is called when the server initializes a new session.
* Useful in cases when you need to register multiple mcp sessions
* and need to keep track of them.
* @param sessionId The generated session ID
*/
onsessioninitialized?: (sessionId: string) => void;
/**
* If true, the server will return JSON responses instead of starting an SSE stream.
* This can be useful for simple request/response scenarios without streaming.
* Default is false (SSE streams are preferred).
*/
enableJsonResponse?: boolean;
/**
* Event store for resumability support
* If provided, resumability will be enabled, allowing clients to reconnect and resume messages
*/
eventStore?: EventStore;
/**
* List of allowed host header values for DNS rebinding protection.
* If not specified, host validation is disabled.
*/
allowedHosts?: string[];
/**
* List of allowed origin header values for DNS rebinding protection.
* If not specified, origin validation is disabled.
*/
allowedOrigins?: string[];
/**
* Enable DNS rebinding protection (requires allowedHosts and/or allowedOrigins to be configured).
* Default is false for backwards compatibility.
*/
enableDnsRebindingProtection?: boolean;
}
/**
* Server transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
* It supports both SSE streaming and direct HTTP responses.
*
* Usage example:
*
* ```typescript
* // Stateful mode - server sets the session ID
* const statefulTransport = new StreamableHTTPServerTransport({
* sessionIdGenerator: () => randomUUID(),
* });
*
* // Stateless mode - explicitly set session ID to undefined
* const statelessTransport = new StreamableHTTPServerTransport({
* sessionIdGenerator: undefined,
* });
*
* // Using with pre-parsed request body
* app.post('/mcp', (req, res) => {
* transport.handleRequest(req, res, req.body);
* });
* ```
*
* In stateful mode:
* - Session ID is generated and included in response headers
* - Session ID is always included in initialization responses
* - Requests with invalid session IDs are rejected with 404 Not Found
* - Non-initialization requests without a session ID are rejected with 400 Bad Request
* - State is maintained in-memory (connections, message history)
*
* In stateless mode:
* - No Session ID is included in any responses
* - No session validation is performed
*/
export declare class StreamableHTTPServerTransport implements Transport {
private sessionIdGenerator;
private _started;
private _streamMapping;
private _requestToStreamMapping;
private _requestResponseMap;
private _initialized;
private _enableJsonResponse;
private _standaloneSseStreamId;
private _eventStore?;
private _onsessioninitialized?;
private _allowedHosts?;
private _allowedOrigins?;
private _enableDnsRebindingProtection;
sessionId?: string;
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void;
constructor(options: StreamableHTTPServerTransportOptions);
/**
* Starts the transport. This is required by the Transport interface but is a no-op
* for the Streamable HTTP transport as connections are managed per-request.
*/
start(): Promise<void>;
/**
* Validates request headers for DNS rebinding protection.
* @returns Error message if validation fails, undefined if validation passes.
*/
private validateRequestHeaders;
/**
* Handles an incoming HTTP request, whether GET or POST
*/
handleRequest(req: IncomingMessage & {
auth?: AuthInfo;
}, res: ServerResponse, parsedBody?: unknown): Promise<void>;
/**
* Handles GET requests for SSE stream
*/
private handleGetRequest;
/**
* Replays events that would have been sent after the specified event ID
* Only used when resumability is enabled
*/
private replayEvents;
/**
* Writes an event to the SSE stream with proper formatting
*/
private writeSSEEvent;
/**
* Handles unsupported requests (PUT, PATCH, etc.)
*/
private handleUnsupportedRequest;
/**
* Handles POST requests containing JSON-RPC messages
*/
private handlePostRequest;
/**
* Handles DELETE requests to terminate sessions
*/
private handleDeleteRequest;
/**
* Validates session ID for non-initialization requests
* Returns true if the session is valid, false otherwise
*/
private validateSession;
private validateProtocolVersion;
close(): Promise<void>;
send(message: JSONRPCMessage, options?: {
relatedRequestId?: RequestId;
}): Promise<void>;
}
//# sourceMappingURL=streamableHttp.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"streamableHttp.d.ts","sourceRoot":"","sources":["../../../src/server/streamableHttp.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,614 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StreamableHTTPServerTransport = void 0;
const types_js_1 = require("../types.js");
const raw_body_1 = __importDefault(require("raw-body"));
const content_type_1 = __importDefault(require("content-type"));
const node_crypto_1 = require("node:crypto");
const MAXIMUM_MESSAGE_SIZE = "4mb";
/**
* Server transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
* It supports both SSE streaming and direct HTTP responses.
*
* Usage example:
*
* ```typescript
* // Stateful mode - server sets the session ID
* const statefulTransport = new StreamableHTTPServerTransport({
* sessionIdGenerator: () => randomUUID(),
* });
*
* // Stateless mode - explicitly set session ID to undefined
* const statelessTransport = new StreamableHTTPServerTransport({
* sessionIdGenerator: undefined,
* });
*
* // Using with pre-parsed request body
* app.post('/mcp', (req, res) => {
* transport.handleRequest(req, res, req.body);
* });
* ```
*
* In stateful mode:
* - Session ID is generated and included in response headers
* - Session ID is always included in initialization responses
* - Requests with invalid session IDs are rejected with 404 Not Found
* - Non-initialization requests without a session ID are rejected with 400 Bad Request
* - State is maintained in-memory (connections, message history)
*
* In stateless mode:
* - No Session ID is included in any responses
* - No session validation is performed
*/
class StreamableHTTPServerTransport {
constructor(options) {
var _a, _b;
this._started = false;
this._streamMapping = new Map();
this._requestToStreamMapping = new Map();
this._requestResponseMap = new Map();
this._initialized = false;
this._enableJsonResponse = false;
this._standaloneSseStreamId = '_GET_stream';
this.sessionIdGenerator = options.sessionIdGenerator;
this._enableJsonResponse = (_a = options.enableJsonResponse) !== null && _a !== void 0 ? _a : false;
this._eventStore = options.eventStore;
this._onsessioninitialized = options.onsessioninitialized;
this._allowedHosts = options.allowedHosts;
this._allowedOrigins = options.allowedOrigins;
this._enableDnsRebindingProtection = (_b = options.enableDnsRebindingProtection) !== null && _b !== void 0 ? _b : false;
}
/**
* Starts the transport. This is required by the Transport interface but is a no-op
* for the Streamable HTTP transport as connections are managed per-request.
*/
async start() {
if (this._started) {
throw new Error("Transport already started");
}
this._started = true;
}
/**
* Validates request headers for DNS rebinding protection.
* @returns Error message if validation fails, undefined if validation passes.
*/
validateRequestHeaders(req) {
// Skip validation if protection is not enabled
if (!this._enableDnsRebindingProtection) {
return undefined;
}
// Validate Host header if allowedHosts is configured
if (this._allowedHosts && this._allowedHosts.length > 0) {
const hostHeader = req.headers.host;
if (!hostHeader || !this._allowedHosts.includes(hostHeader)) {
return `Invalid Host header: ${hostHeader}`;
}
}
// Validate Origin header if allowedOrigins is configured
if (this._allowedOrigins && this._allowedOrigins.length > 0) {
const originHeader = req.headers.origin;
if (!originHeader || !this._allowedOrigins.includes(originHeader)) {
return `Invalid Origin header: ${originHeader}`;
}
}
return undefined;
}
/**
* Handles an incoming HTTP request, whether GET or POST
*/
async handleRequest(req, res, parsedBody) {
var _a;
// Validate request headers for DNS rebinding protection
const validationError = this.validateRequestHeaders(req);
if (validationError) {
res.writeHead(403).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: validationError
},
id: null
}));
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(validationError));
return;
}
if (req.method === "POST") {
await this.handlePostRequest(req, res, parsedBody);
}
else if (req.method === "GET") {
await this.handleGetRequest(req, res);
}
else if (req.method === "DELETE") {
await this.handleDeleteRequest(req, res);
}
else {
await this.handleUnsupportedRequest(res);
}
}
/**
* Handles GET requests for SSE stream
*/
async handleGetRequest(req, res) {
// The client MUST include an Accept header, listing text/event-stream as a supported content type.
const acceptHeader = req.headers.accept;
if (!(acceptHeader === null || acceptHeader === void 0 ? void 0 : acceptHeader.includes("text/event-stream"))) {
res.writeHead(406).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Not Acceptable: Client must accept text/event-stream"
},
id: null
}));
return;
}
// If an Mcp-Session-Id is returned by the server during initialization,
// clients using the Streamable HTTP transport MUST include it
// in the Mcp-Session-Id header on all of their subsequent HTTP requests.
if (!this.validateSession(req, res)) {
return;
}
if (!this.validateProtocolVersion(req, res)) {
return;
}
// Handle resumability: check for Last-Event-ID header
if (this._eventStore) {
const lastEventId = req.headers['last-event-id'];
if (lastEventId) {
await this.replayEvents(lastEventId, res);
return;
}
}
// The server MUST either return Content-Type: text/event-stream in response to this HTTP GET,
// or else return HTTP 405 Method Not Allowed
const headers = {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
};
// After initialization, always include the session ID if we have one
if (this.sessionId !== undefined) {
headers["mcp-session-id"] = this.sessionId;
}
// Check if there's already an active standalone SSE stream for this session
if (this._streamMapping.get(this._standaloneSseStreamId) !== undefined) {
// Only one GET SSE stream is allowed per session
res.writeHead(409).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Conflict: Only one SSE stream is allowed per session"
},
id: null
}));
return;
}
// We need to send headers immediately as messages will arrive much later,
// otherwise the client will just wait for the first message
res.writeHead(200, headers).flushHeaders();
// Assign the response to the standalone SSE stream
this._streamMapping.set(this._standaloneSseStreamId, res);
// Set up close handler for client disconnects
res.on("close", () => {
this._streamMapping.delete(this._standaloneSseStreamId);
});
}
/**
* Replays events that would have been sent after the specified event ID
* Only used when resumability is enabled
*/
async replayEvents(lastEventId, res) {
var _a, _b;
if (!this._eventStore) {
return;
}
try {
const headers = {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
};
if (this.sessionId !== undefined) {
headers["mcp-session-id"] = this.sessionId;
}
res.writeHead(200, headers).flushHeaders();
const streamId = await ((_a = this._eventStore) === null || _a === void 0 ? void 0 : _a.replayEventsAfter(lastEventId, {
send: async (eventId, message) => {
var _a;
if (!this.writeSSEEvent(res, message, eventId)) {
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error("Failed replay events"));
res.end();
}
}
}));
this._streamMapping.set(streamId, res);
}
catch (error) {
(_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
}
}
/**
* Writes an event to the SSE stream with proper formatting
*/
writeSSEEvent(res, message, eventId) {
let eventData = `event: message\n`;
// Include event ID if provided - this is important for resumability
if (eventId) {
eventData += `id: ${eventId}\n`;
}
eventData += `data: ${JSON.stringify(message)}\n\n`;
return res.write(eventData);
}
/**
* Handles unsupported requests (PUT, PATCH, etc.)
*/
async handleUnsupportedRequest(res) {
res.writeHead(405, {
"Allow": "GET, POST, DELETE"
}).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Method not allowed."
},
id: null
}));
}
/**
* Handles POST requests containing JSON-RPC messages
*/
async handlePostRequest(req, res, parsedBody) {
var _a, _b, _c, _d, _e;
try {
// Validate the Accept header
const acceptHeader = req.headers.accept;
// The client MUST include an Accept header, listing both application/json and text/event-stream as supported content types.
if (!(acceptHeader === null || acceptHeader === void 0 ? void 0 : acceptHeader.includes("application/json")) || !acceptHeader.includes("text/event-stream")) {
res.writeHead(406).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Not Acceptable: Client must accept both application/json and text/event-stream"
},
id: null
}));
return;
}
const ct = req.headers["content-type"];
if (!ct || !ct.includes("application/json")) {
res.writeHead(415).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Unsupported Media Type: Content-Type must be application/json"
},
id: null
}));
return;
}
const authInfo = req.auth;
const requestInfo = { headers: req.headers };
let rawMessage;
if (parsedBody !== undefined) {
rawMessage = parsedBody;
}
else {
const parsedCt = content_type_1.default.parse(ct);
const body = await (0, raw_body_1.default)(req, {
limit: MAXIMUM_MESSAGE_SIZE,
encoding: (_a = parsedCt.parameters.charset) !== null && _a !== void 0 ? _a : "utf-8",
});
rawMessage = JSON.parse(body.toString());
}
let messages;
// handle batch and single messages
if (Array.isArray(rawMessage)) {
messages = rawMessage.map(msg => types_js_1.JSONRPCMessageSchema.parse(msg));
}
else {
messages = [types_js_1.JSONRPCMessageSchema.parse(rawMessage)];
}
// Check if this is an initialization request
// https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle/
const isInitializationRequest = messages.some(types_js_1.isInitializeRequest);
if (isInitializationRequest) {
// If it's a server with session management and the session ID is already set we should reject the request
// to avoid re-initialization.
if (this._initialized && this.sessionId !== undefined) {
res.writeHead(400).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32600,
message: "Invalid Request: Server already initialized"
},
id: null
}));
return;
}
if (messages.length > 1) {
res.writeHead(400).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32600,
message: "Invalid Request: Only one initialization request is allowed"
},
id: null
}));
return;
}
this.sessionId = (_b = this.sessionIdGenerator) === null || _b === void 0 ? void 0 : _b.call(this);
this._initialized = true;
// If we have a session ID and an onsessioninitialized handler, call it immediately
// This is needed in cases where the server needs to keep track of multiple sessions
if (this.sessionId && this._onsessioninitialized) {
this._onsessioninitialized(this.sessionId);
}
}
if (!isInitializationRequest) {
// If an Mcp-Session-Id is returned by the server during initialization,
// clients using the Streamable HTTP transport MUST include it
// in the Mcp-Session-Id header on all of their subsequent HTTP requests.
if (!this.validateSession(req, res)) {
return;
}
// Mcp-Protocol-Version header is required for all requests after initialization.
if (!this.validateProtocolVersion(req, res)) {
return;
}
}
// check if it contains requests
const hasRequests = messages.some(types_js_1.isJSONRPCRequest);
if (!hasRequests) {
// if it only contains notifications or responses, return 202
res.writeHead(202).end();
// handle each message
for (const message of messages) {
(_c = this.onmessage) === null || _c === void 0 ? void 0 : _c.call(this, message, { authInfo, requestInfo });
}
}
else if (hasRequests) {
// The default behavior is to use SSE streaming
// but in some cases server will return JSON responses
const streamId = (0, node_crypto_1.randomUUID)();
if (!this._enableJsonResponse) {
const headers = {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
};
// After initialization, always include the session ID if we have one
if (this.sessionId !== undefined) {
headers["mcp-session-id"] = this.sessionId;
}
res.writeHead(200, headers);
}
// Store the response for this request to send messages back through this connection
// We need to track by request ID to maintain the connection
for (const message of messages) {
if ((0, types_js_1.isJSONRPCRequest)(message)) {
this._streamMapping.set(streamId, res);
this._requestToStreamMapping.set(message.id, streamId);
}
}
// Set up close handler for client disconnects
res.on("close", () => {
this._streamMapping.delete(streamId);
});
// handle each message
for (const message of messages) {
(_d = this.onmessage) === null || _d === void 0 ? void 0 : _d.call(this, message, { authInfo, requestInfo });
}
// The server SHOULD NOT close the SSE stream before sending all JSON-RPC responses
// This will be handled by the send() method when responses are ready
}
}
catch (error) {
// return JSON-RPC formatted error
res.writeHead(400).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32700,
message: "Parse error",
data: String(error)
},
id: null
}));
(_e = this.onerror) === null || _e === void 0 ? void 0 : _e.call(this, error);
}
}
/**
* Handles DELETE requests to terminate sessions
*/
async handleDeleteRequest(req, res) {
if (!this.validateSession(req, res)) {
return;
}
if (!this.validateProtocolVersion(req, res)) {
return;
}
await this.close();
res.writeHead(200).end();
}
/**
* Validates session ID for non-initialization requests
* Returns true if the session is valid, false otherwise
*/
validateSession(req, res) {
if (this.sessionIdGenerator === undefined) {
// If the sessionIdGenerator ID is not set, the session management is disabled
// and we don't need to validate the session ID
return true;
}
if (!this._initialized) {
// If the server has not been initialized yet, reject all requests
res.writeHead(400).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Bad Request: Server not initialized"
},
id: null
}));
return false;
}
const sessionId = req.headers["mcp-session-id"];
if (!sessionId) {
// Non-initialization requests without a session ID should return 400 Bad Request
res.writeHead(400).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Bad Request: Mcp-Session-Id header is required"
},
id: null
}));
return false;
}
else if (Array.isArray(sessionId)) {
res.writeHead(400).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Bad Request: Mcp-Session-Id header must be a single value"
},
id: null
}));
return false;
}
else if (sessionId !== this.sessionId) {
// Reject requests with invalid session ID with 404 Not Found
res.writeHead(404).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32001,
message: "Session not found"
},
id: null
}));
return false;
}
return true;
}
validateProtocolVersion(req, res) {
var _a;
let protocolVersion = (_a = req.headers["mcp-protocol-version"]) !== null && _a !== void 0 ? _a : types_js_1.DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
if (Array.isArray(protocolVersion)) {
protocolVersion = protocolVersion[protocolVersion.length - 1];
}
if (!types_js_1.SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
res.writeHead(400).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: `Bad Request: Unsupported protocol version (supported versions: ${types_js_1.SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`
},
id: null
}));
return false;
}
return true;
}
async close() {
var _a;
// Close all SSE connections
this._streamMapping.forEach((response) => {
response.end();
});
this._streamMapping.clear();
// Clear any pending responses
this._requestResponseMap.clear();
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
}
async send(message, options) {
let requestId = options === null || options === void 0 ? void 0 : options.relatedRequestId;
if ((0, types_js_1.isJSONRPCResponse)(message) || (0, types_js_1.isJSONRPCError)(message)) {
// If the message is a response, use the request ID from the message
requestId = message.id;
}
// Check if this message should be sent on the standalone SSE stream (no request ID)
// Ignore notifications from tools (which have relatedRequestId set)
// Those will be sent via dedicated response SSE streams
if (requestId === undefined) {
// For standalone SSE streams, we can only send requests and notifications
if ((0, types_js_1.isJSONRPCResponse)(message) || (0, types_js_1.isJSONRPCError)(message)) {
throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");
}
const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
if (standaloneSse === undefined) {
// The spec says the server MAY send messages on the stream, so it's ok to discard if no stream
return;
}
// Generate and store event ID if event store is provided
let eventId;
if (this._eventStore) {
// Stores the event and gets the generated event ID
eventId = await this._eventStore.storeEvent(this._standaloneSseStreamId, message);
}
// Send the message to the standalone SSE stream
this.writeSSEEvent(standaloneSse, message, eventId);
return;
}
// Get the response for this request
const streamId = this._requestToStreamMapping.get(requestId);
const response = this._streamMapping.get(streamId);
if (!streamId) {
throw new Error(`No connection established for request ID: ${String(requestId)}`);
}
if (!this._enableJsonResponse) {
// For SSE responses, generate event ID if event store is provided
let eventId;
if (this._eventStore) {
eventId = await this._eventStore.storeEvent(streamId, message);
}
if (response) {
// Write the event to the response stream
this.writeSSEEvent(response, message, eventId);
}
}
if ((0, types_js_1.isJSONRPCResponse)(message) || (0, types_js_1.isJSONRPCError)(message)) {
this._requestResponseMap.set(requestId, message);
const relatedIds = Array.from(this._requestToStreamMapping.entries())
.filter(([_, streamId]) => this._streamMapping.get(streamId) === response)
.map(([id]) => id);
// Check if we have responses for all requests using this connection
const allResponsesReady = relatedIds.every(id => this._requestResponseMap.has(id));
if (allResponsesReady) {
if (!response) {
throw new Error(`No connection established for request ID: ${String(requestId)}`);
}
if (this._enableJsonResponse) {
// All responses ready, send as JSON
const headers = {
'Content-Type': 'application/json',
};
if (this.sessionId !== undefined) {
headers['mcp-session-id'] = this.sessionId;
}
const responses = relatedIds
.map(id => this._requestResponseMap.get(id));
response.writeHead(200, headers);
if (responses.length === 1) {
response.end(JSON.stringify(responses[0]));
}
else {
response.end(JSON.stringify(responses));
}
}
else {
// End the SSE stream
response.end();
}
// Clean up
for (const id of relatedIds) {
this._requestResponseMap.delete(id);
this._requestToStreamMapping.delete(id);
}
}
}
}
}
exports.StreamableHTTPServerTransport = StreamableHTTPServerTransport;
//# sourceMappingURL=streamableHttp.js.map

File diff suppressed because one or more lines are too long