UNPKG

@artinet/sdk

Version:
252 lines 9.04 kB
import cors from "cors"; import express from "express"; import { ServiceManager } from "../../services/manager.js"; import { errorHandler } from "../../utils/common/errors.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { Protocol } from "../../types/services/protocol.js"; import { v4 as uuidv4 } from "uuid"; import util from "util"; import { logError, logInfo } from "../../utils/logging/log.js"; import { INVALID_REQUEST } from "../../utils/common/errors.js"; import { A2AService } from "../../services/a2a/service.js"; /** * @deprecated Use ExpressServer instead. * @description Creates an Express server for the A2A protocol. * Handles task creation, streaming, cancellation and more. * Uses Jayson for JSON-RPC handling. */ export function createExpressServer(params) { const { card, corsOptions, basePath, rpcServer, fallbackPath, errorHandler, onTaskSendSubscribe, onTaskResubscribe, } = params; const app = express(); app.use(cors(corsOptions)); app.use(express.json()); app.get("/.well-known/agent.json", (_, res) => { res.json(card); }); app.get(fallbackPath, (_, res) => { res.json(card); }); //SSE Paths app.post(basePath, async (req, res, next) => { try { const body = req.body; if (body && body.method) { if (body.method === "message/stream") { return await onTaskSendSubscribe(body, res); } else if (body.method === "tasks/resubscribe") { return await onTaskResubscribe(body, res); } } next(); } catch (error) { next(error); } }); //RPC server app.post(basePath, rpcServer.middleware()); // Fallback error handler app.use(errorHandler); return { app }; } /** * @description The express server class. */ export class ExpressServer extends ServiceManager { basePath; fallbackPath; _serverInstance; port; app; register; corsOptions; initialized = false; /** * @description Gets the server instance. * @returns {http.Server | undefined} The server instance. */ get serverInstance() { return this._serverInstance; } /** * @description The constructor. * @param {ExpressServerOptions} params The express server options. */ constructor(params) { const atBasePath = Object.keys(params.services ?? {}).length > 1 ? false : true; super({ ...params, services: params.services ?? { [Protocol.A2A]: new A2AService({ engine: params.engine, taskStore: params.storage, card: params.card, }), }, }); this.corsOptions = params.corsOptions ?? { origin: "*", methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowedHeaders: ["Content-Type", "Authorization"], }; let basePath = params.basePath ?? "/"; if (basePath !== "/") { basePath = `/${basePath.replace(/^\/|\/$/g, "")}/`; } this.basePath = basePath; this.fallbackPath = params.fallbackPath ?? "/agent-card"; this.port = params.port ?? 41241; this.app = params.app ?? express(); this.app.use(cors(this.corsOptions)); this.app.use(express.json()); this.register = params.register ?? false; this.registerRoutes(atBasePath); } /** * @description Registers the routes. * @param {StreamableHTTPServerTransport} transport The mcp transport. */ registerRoutes(atBasePath = true, transport) { this.app.get(`/.well-known/agent.json`, (_, res) => { res.json(this.getCard()); }); this.app.get(this.fallbackPath, (_, res) => { res.json(this.getCard()); }); for (const service of Object.values(this.services)) { const path = atBasePath ? `${this.basePath}` : this.basePath === "/" ? `/${service.protocol}` : `${this.basePath}${service.protocol}`; this.app.get(path, async (req, res, next) => { try { const { id, method, params, jsonrpc } = req.body; if (jsonrpc !== "2.0") { res.status(400).json({ error: "Invalid JSON-RPC version" }); return; } const context = this.createRequestContext({ id, protocol: service.protocol, method, params: { ...params, protocol: service.protocol, }, request: req, response: res, transport: service.protocol === Protocol.MCP ? transport ? transport : new StreamableHTTPServerTransport({ sessionIdGenerator: () => uuidv4(), }) : undefined, }); return await this.onRequest(context).catch((error) => { res.status(500).json({ id, error: error.message }); }); } catch (error) { logError("ExpressServer", "Error in GET", error); next(error); } }); this.app.post(path, async (req, res, next) => { try { const { id, method, params, jsonrpc } = req.body; if (jsonrpc !== "2.0") { res.status(200).send({ jsonrpc: "2.0", id: id, error: INVALID_REQUEST({ jsonrpc: jsonrpc, data: req.body, }), }); return; } const context = this.createRequestContext({ id, protocol: service.protocol, method, params: { ...params, protocol: service.protocol, }, request: req, response: res, transport: service.protocol === Protocol.MCP ? transport ? transport : new StreamableHTTPServerTransport({ sessionIdGenerator: () => uuidv4(), }) : undefined, }); return await this.onRequest(context).catch((error) => { res.status(500).json({ error: error.message }); }); } catch (error) { console.error("Error in POST", error); next(error); } }); } this.app.use(errorHandler); this.initialized = true; } /** * @description Gets the app. * @returns {express.Express} The app. */ getApp() { if (!this.initialized) { this.registerRoutes(); } return this.app; } /** * @description Starts the server. * @returns {Promise<http.Server>} The server. */ async start() { if (!this.initialized) { this.registerRoutes(); } if (this._serverInstance) { return this._serverInstance; } this._serverInstance = this.app.listen(this.port, () => { logInfo("ExpressServer", `Express Server started and listening`, { port: this.port, path: this.basePath, }); }); return this._serverInstance; } /** * @description Stops the server. */ async stop() { await super.destroy(); if (!this._serverInstance) { return; } try { const closeServer = util .promisify(this._serverInstance.close) .bind(this._serverInstance); await closeServer(); this._serverInstance = undefined; } catch (error) { this._serverInstance = undefined; logError("ExpressServer", "Error stopping server:", error); } } } //# sourceMappingURL=express-server.js.map