├── .cursor └── rules │ ├── mcp.mdc │ └── typescript-mcp.mdc ├── .eslintrc.js ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── README.md └── stdio.ts ├── package-lock.json ├── package.json ├── src ├── config.ts ├── index.ts ├── prompts │ └── index.ts ├── resources │ └── index.ts ├── server.ts └── tools │ ├── candidateTools.ts │ ├── index.ts │ ├── interviewTools.ts │ └── types.ts └── tsconfig.json /.cursor/rules/mcp.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # Model Context Protocol (MCP) 7 | 8 | MCP is a standard protocol to exchange contextual data between clients, servers, and LLMs. This rule provides an at-a-glance reference for key concepts, spec versions, tooling, and best practices. 9 | 10 | ## Core Concepts 11 | 12 | - **Lifecycle**: Define session start/end, request and response phases. 13 | - **Messages**: JSON-encoded context, prompts, and completions. 14 | - **Transports**: HTTP, WebSocket, gRPC—pluggable transports for message delivery. 15 | - **Cancellation**: Send cancellation messages to abort in-progress requests. 16 | - **Ping (Heartbeat)**: Keep connections alive; detect timeouts. 17 | - **Progress**: Stream partial results and progress updates. 18 | - **Roots**: External content references (e.g. code, docs) managed by ID. 19 | - **Sampling**: Control streaming vs. non-streaming completions; fine-tune sampling parameters. 20 | - **Tools**: Plugin external actions and APIs into LLM workflows. 21 | 22 | ## Specification Versions 23 | 24 | - **Basic 2024-11-05**: Overview, lifecycle, messages, transports, utilities (cancellation, ping, progress). 25 | - **Server/Client 2024-11-05**: Prompts, resources, roots, sampling, tools, logging, pagination. 26 | - **Extended 2025-03-26**: Authorization, architecture refinements, changelog. 27 | - **Drafts**: Early designs; consult changelog for breaking changes. 28 | 29 | ## SDKs & Examples 30 | 31 | - **TypeScript SDK**: https://github.com/modelcontextprotocol/typescript-sdk 32 | - **Python SDK**: https://github.com/modelcontextprotocol/python-sdk 33 | - **Java/Kotlin/C# SDKs**: Refer to https://modelcontextprotocol.io 34 | - **Example Clients & Servers**: https://modelcontextprotocol.io/examples.md 35 | 36 | ## Best Practices 37 | 38 | - Always annotate messages with spec version and timestamp. 39 | - Use **roots** to manage large contexts; avoid embedding bulky data inline. 40 | - Gracefully handle **cancellation** and **errors**; propagate clear error codes. 41 | - Emit **progress** events for long-running streams to improve UX. 42 | - Maintain a **ping** schedule to detect and recover dropped connections. 43 | - Leverage **tools** to encapsulate external API calls and side effects. 44 | - Follow spec utilities: **logging**, **pagination**, and **completion** conventions. 45 | - Review the official spec and changelog regularly to stay up to date. 46 | 47 | --- 48 | _For full details, consult the online documentation at https://modelcontextprotocol.io_ -------------------------------------------------------------------------------- /.cursor/rules/typescript-mcp.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: *.ts 4 | alwaysApply: false 5 | --- 6 | # MCP TypeScript SDK 7 | 8 | MCP TypeScript SDK implements the full Model Context Protocol specification, allowing you to build MCP servers and clients using TypeScript. 9 | 10 | ## Installation 11 | 12 | ```bash 13 | npm install @modelcontextprotocol/sdk --save 14 | # or 15 | yarn add @modelcontextprotocol/sdk 16 | ``` 17 | 18 | ## Quickstart: Create an MCP Server 19 | 20 | ```typescript 21 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 22 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 23 | import { z } from "zod"; 24 | 25 | const server = new McpServer({ name: "DemoServer", version: "1.0.0" }); 26 | 27 | // Define a simple addition tool 28 | type AddParams = { a: number; b: number }; 29 | server.tool( 30 | "add", 31 | { a: z.number(), b: z.number() }, 32 | async ({ a, b }: AddParams) => ({ content: [{ type: "text", text: `${a + b}` }] }) 33 | ); 34 | 35 | // Start listening on stdin/stdout transport 36 | await server.connect(new StdioServerTransport()); 37 | ``` 38 | 39 | ## Core Concepts 40 | 41 | - **McpServer**: entry point for protocol compliance, message routing, and lifecycle management. 42 | - **Resources**: read-only data endpoints via `server.resource(name, template, handler)` and `ResourceTemplate`. 43 | - **Tools**: action endpoints via `server.tool(name, schema, executor)`. 44 | - **Prompts**: reusable message templates via `server.prompt(name, schema, builder)`. 45 | - **Transports**: connect servers/clients over stdio, HTTP, SSE, or Streamable HTTP (`StdioServerTransport`, `StreamableHttpServerTransport`, etc.). 46 | 47 | ## Preferred Transport: Streamable HTTP 48 | 49 | Streamable HTTP is the recommended transport for production environments, offering full-duplex streaming, session management, and backward compatibility over older SSE-based transports. 50 | 51 | ### Streamable HTTP Server Example 52 | 53 | ```typescript 54 | import express from "express"; 55 | import { randomUUID } from "crypto"; 56 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 57 | import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; 58 | import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; 59 | 60 | const app = express(); 61 | app.use(express.json()); 62 | 63 | const server = new McpServer({ name: "StreamableExample", version: "1.0.0" }); 64 | const transports: Record = {}; 65 | 66 | app.post('/mcp', async (req, res) => { 67 | const sid = req.headers['mcp-session-id'] as string | undefined; 68 | let transport: StreamableHTTPServerTransport; 69 | 70 | if (sid && transports[sid]) { 71 | transport = transports[sid]; 72 | } else if (!sid && isInitializeRequest(req.body)) { 73 | transport = new StreamableHTTPServerTransport({ 74 | sessionIdGenerator: () => randomUUID(), 75 | onsessioninitialized: (sessionId) => (transports[sessionId] = transport) 76 | }); 77 | transport.onclose = () => delete transports[transport.sessionId!]; 78 | } else { 79 | res.status(400).send("Invalid request"); 80 | return; 81 | } 82 | 83 | await server.connect(transport); 84 | transport.handleRequest(req, res); 85 | }); 86 | 87 | const PORT = process.env.PORT ?? 3000; 88 | app.listen(PORT, () => console.log(`MCP Streamable HTTP server listening on ${PORT}`)); 89 | ``` 90 | 91 | ## Examples 92 | 93 | ## Examples 94 | 95 | ### Echo Server 96 | 97 | ```typescript 98 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; 99 | import { z } from "zod"; 100 | 101 | const server = new McpServer({ name: "Echo", version: "1.0.0" }); 102 | 103 | server.resource( 104 | "echo", 105 | new ResourceTemplate("echo://{message}", { list: undefined }), 106 | async (uri, { message }) => ({ contents: [{ uri: uri.href, text: `Resource echo: ${message}` }] }) 107 | ); 108 | 109 | server.tool( 110 | "echo", 111 | { message: z.string() }, 112 | async ({ message }) => ({ content: [{ type: "text", text: `Tool echo: ${message}` }] }) 113 | ); 114 | 115 | server.prompt( 116 | "echo", 117 | { message: z.string() }, 118 | ({ message }) => ({ messages: [{ role: "user", content: { type: "text", text: `Please process: ${message}` } }] }) 119 | ); 120 | ``` 121 | 122 | ### SQLite Explorer 123 | 124 | ```typescript 125 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 126 | import sqlite3 from "sqlite3"; 127 | import { promisify } from "util"; 128 | import { z } from "zod"; 129 | 130 | const server = new McpServer({ name: "SQLiteExplorer", version: "1.0.0" }); 131 | const db = new sqlite3.Database("data.db"); 132 | const allAsync = promisify(db.all.bind(db)); 133 | 134 | server.resource( 135 | "tables", 136 | "sqlite://tables", 137 | async () => { 138 | const rows = await allAsync(`SELECT name FROM sqlite_master WHERE type='table'`); 139 | return { contents: rows.map(r => ({ uri: `sqlite://table/${r.name}`, text: r.name })) }; 140 | } 141 | ); 142 | ``` 143 | 144 | ## Quickstart: Create an MCP Client 145 | 146 | ```typescript 147 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; 148 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; 149 | 150 | const client = new Client({ name: "DemoClient", version: "1.0.0" }); 151 | await client.connect(new StdioClientTransport()); 152 | 153 | // List available tools and call one 154 | const tools = await client.listTools(); 155 | const result = await client.callTool({ toolName: tools[0].name, args: { message: "Hi" } }); 156 | console.log(result); 157 | ``` 158 | 159 | ## Testing & Debugging 160 | 161 | - Use the [MCP Inspector](mdc:https:/github.com/modelcontextprotocol/inspector) for interactive protocol testing. 162 | - Add logging or use `server.on('message', ...)` and `client.on('message', ...)` for low-level tracing. 163 | 164 | ## Best Practices 165 | 166 | - Always specify `name` and `version` in server/client metadata. 167 | - Validate inputs with **zod** schemas for safety. 168 | - Emit **progress** or **cancellation** updates for long-running tasks. 169 | - Choose appropriate transport: stdio for CLI, Streamable HTTP for web services. 170 | - Keep resource handlers idempotent and side-effect-free. 171 | - Review official docs: https://modelcontextprotocol.io/sdk/typescript 172 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended' 6 | ], 7 | parserOptions: { 8 | ecmaVersion: 2020, 9 | sourceType: 'module', 10 | }, 11 | env: { 12 | node: true, 13 | jest: true, 14 | es6: true, 15 | }, 16 | rules: { 17 | '@typescript-eslint/no-explicit-any': 'warn', 18 | '@typescript-eslint/explicit-module-boundary-types': 'off', 19 | '@typescript-eslint/no-unused-vars': ['error', { 20 | argsIgnorePattern: '^_', 21 | varsIgnorePattern: '^_', 22 | }], 23 | }, 24 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # TypeScript build output 8 | dist 9 | *.tsbuildinfo 10 | 11 | # Coverage directory 12 | coverage 13 | 14 | # Environment variables 15 | .env 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | # Editor directories and files 22 | .idea 23 | .vscode/* 24 | !.vscode/extensions.json 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | *.suo 29 | *.ntvs* 30 | *.njsproj 31 | *.sln 32 | *.sw? 33 | 34 | # OS generated files 35 | .DS_Store 36 | .DS_Store? 37 | ._* 38 | .Spotlight-V100 39 | .Trashes 40 | ehthumbs.db 41 | Thumbs.db -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.3.3 2 | 3 | - Add constructors for the config objects 4 | 5 | # 1.3.2 6 | 7 | - Expose getServerCapabilities 8 | 9 | # 1.3.1 10 | 11 | - Replace the mailgun transport with a more modern package 12 | 13 | # 1.3.0 14 | 15 | - Add the ability to bindto a specific mcp server object rather than creating a new server 16 | 17 | # 1.2.1 18 | 19 | - add some experimental new toolds 20 | 21 | # 1.2.0 22 | 23 | - Add the ability to contact the candidate via email 24 | 25 | # 1.1.2 26 | 27 | - Put the candidates name in the tool descriptions 28 | 29 | # 1.1.1 30 | 31 | - this build came out without any changes 32 | 33 | # 1.1.0 34 | 35 | - Add a set of tools that replicate the resources 36 | 37 | # 1.0.4 38 | 39 | - Don't explicity state capabilities 40 | 41 | # 1.0.3 42 | 43 | - Fix import strategy 44 | - Move Example out of src 45 | 46 | # 1.0.2 47 | 48 | - explicity state capabilities 49 | 50 | # 1.0.1 51 | 52 | - Readme update 53 | 54 | # 1.0.0 55 | 56 | - Init -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Jake Gaylor 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Candidate MCP Server Library 2 | 3 | A Model Context Protocol (MCP) server that gives LLMs access to information about a candidate. 4 | 5 | ## Overview 6 | 7 | > **Important**: This server is intended to be used as a library to be integrated into other applications, not as a standalone service. The provided startup methods are for demonstration and testing purposes only. 8 | 9 | ### Resources 10 | 11 | This MCP server provides the following resources: 12 | 13 | - `candidate-info://resume-text`: Resume content as text 14 | - `candidate-info://resume-url`: URL to the resume 15 | - `candidate-info://linkedin-url`: LinkedIn profile URL 16 | - `candidate-info://github-url`: GitHub profile URL 17 | - `candidate-info://website-url`: Personal website URL 18 | - `candidate-info://website-text`: Content from the personal website 19 | 20 | ### Tools 21 | 22 | This MCP server also provides tools that return the same candidate information: 23 | 24 | - `get_resume_text`: Returns the candidate's resume content as text 25 | - `get_resume_url`: Returns the URL to the candidate's resume 26 | - `get_linkedin_url`: Returns the candidate's LinkedIn profile URL 27 | - `get_github_url`: Returns the candidate's GitHub profile URL 28 | - `get_website_url`: Returns the candidate's personal website URL 29 | - `get_website_text`: Returns the content from the candidate's personal website 30 | - `contact_candidate`: Sends an email to the candidate (requires Mailgun configuration) 31 | 32 | ## Usage 33 | 34 | `npm install @jhgaylor/candidate-mcp-server` 35 | 36 | ### Library Usage 37 | 38 | This package is designed to be imported and used within your own applications. 39 | 40 | #### Stdio 41 | 42 | Starting the process is a breeze with stdio. The interesting part is providing the candidate configuration. 43 | 44 | Where you source the candidate configuration is entirely up to you. Maybe you hard code it. Maybe you take a JSONResume url when you start the process. It's up to you! 45 | 46 | ```javascript 47 | import { createServer } from '@jhgaylor/candidate-mcp-server'; 48 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 49 | 50 | // Configure your server 51 | const serverConfig = { 52 | name: "MyCandidateServer", 53 | version: "1.0.0", 54 | mailgunApiKey: process.env.MAILGUN_API_KEY, 55 | mailgunDomain: process.env.MAILGUN_DOMAIN 56 | }; 57 | const candidateConfig = { 58 | name: "John Doe", 59 | email: "john.doe@example.com", // Required for the contact_candidate tool 60 | resumeUrl: "https://example.com/resume.pdf", 61 | // other candidate properties 62 | }; 63 | 64 | // Create server instance 65 | const server = createServer(serverConfig, candidateConfig); 66 | 67 | // Connect with your preferred transport 68 | await server.connect(new StdioServerTransport()); 69 | // or integrate with your existing HTTP server 70 | ``` 71 | 72 | #### StreamableHttp 73 | 74 | Using the example code provided by the typescript sdk we can bind this mcp server to an express server. 75 | 76 | ```javascript 77 | import express from 'express'; 78 | import { Request, Response } from 'express'; 79 | import { createServer } from '@jhgaylor/candidate-mcp-server'; 80 | import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamablehttp.js"; 81 | 82 | // Configure your server 83 | const serverConfig = { 84 | name: "MyCandidateServer", 85 | version: "1.0.0", 86 | mailgunApiKey: process.env.MAILGUN_API_KEY, 87 | mailgunDomain: process.env.MAILGUN_DOMAIN, 88 | contactEmail: "john.doe@example.com", 89 | }; 90 | const candidateConfig = { 91 | name: "John Doe", 92 | resumeUrl: "https://example.com/resume.pdf", 93 | // other candidate properties 94 | }; 95 | 96 | // Factory function to create a new server instance for each request 97 | const getServer = () => createServer(serverConfig, candidateConfig); 98 | 99 | const app = express(); 100 | app.use(express.json()); 101 | 102 | app.post('/mcp', async (req: Request, res: Response) => { 103 | // In stateless mode, create a new instance of transport and server for each request 104 | // to ensure complete isolation. A single instance would cause request ID collisions 105 | // when multiple clients connect concurrently. 106 | 107 | try { 108 | const server = getServer(); 109 | const transport = new StreamableHTTPServerTransport({ 110 | sessionIdGenerator: undefined, 111 | }); 112 | res.on('close', () => { 113 | console.log('Request closed'); 114 | transport.close(); 115 | server.close(); 116 | }); 117 | await server.connect(transport); 118 | await transport.handleRequest(req, res, req.body); 119 | } catch (error) { 120 | console.error('Error handling MCP request:', error); 121 | if (!res.headersSent) { 122 | res.status(500).json({ 123 | jsonrpc: '2.0', 124 | error: { 125 | code: -32603, 126 | message: 'Internal server error', 127 | }, 128 | id: null, 129 | }); 130 | } 131 | } 132 | }); 133 | 134 | app.get('/mcp', async (req: Request, res: Response) => { 135 | console.log('Received GET MCP request'); 136 | res.writeHead(405).end(JSON.stringify({ 137 | jsonrpc: "2.0", 138 | error: { 139 | code: -32000, 140 | message: "Method not allowed." 141 | }, 142 | id: null 143 | })); 144 | }); 145 | 146 | app.delete('/mcp', async (req: Request, res: Response) => { 147 | console.log('Received DELETE MCP request'); 148 | res.writeHead(405).end(JSON.stringify({ 149 | jsonrpc: "2.0", 150 | error: { 151 | code: -32000, 152 | message: "Method not allowed." 153 | }, 154 | id: null 155 | })); 156 | }); 157 | 158 | // Start the server 159 | const PORT = process.env.PORT || 3000; 160 | app.listen(PORT, () => { 161 | console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`); 162 | }); 163 | ``` 164 | 165 | #### Express 166 | 167 | Instead of writing the binding between express and the mcp transport yourself, you can use `express-mcp-handler` to do it for you. 168 | 169 | `npm install express-mcp-handler` 170 | 171 | ```javascript 172 | import express from 'express'; 173 | import { statelessHandler } from 'express-mcp-handler'; 174 | import { createServer } from './server'; 175 | 176 | // You can configure the server factory to include Mailgun settings 177 | const createServerWithConfig = () => { 178 | const serverConfig = { 179 | name: "MyCandidateServer", 180 | version: "1.0.0", 181 | mailgunApiKey: process.env.MAILGUN_API_KEY, 182 | mailgunDomain: process.env.MAILGUN_DOMAIN, 183 | contactEmail: "john.doe@example.com", 184 | }; 185 | const candidateConfig = { 186 | name: "John Doe", 187 | resumeUrl: "https://example.com/resume.pdf", 188 | // other candidate properties 189 | }; 190 | 191 | return createServer(serverConfig, candidateConfig); 192 | }; 193 | 194 | // Configure the stateless handler 195 | const handler = statelessHandler(createServerWithConfig); 196 | 197 | // Create Express app 198 | const app = express(); 199 | app.use(express.json()); 200 | 201 | // Mount the handler (stateless only needs POST) 202 | app.post('/mcp', handler); 203 | 204 | // Start the server 205 | const PORT = process.env.PORT || 3002; 206 | app.listen(PORT, () => { 207 | console.log(`Stateless MCP server running on port ${PORT}`); 208 | }); 209 | ``` 210 | 211 | ## Development 212 | 213 | ```bash 214 | # Install dependencies 215 | npm install 216 | 217 | # Build the project 218 | npm run build 219 | 220 | # Run in development mode with auto-restart 221 | npm run dev 222 | ``` 223 | 224 | ### Demo / Debug Startup via stdio 225 | 226 | ```bash 227 | # Start with STDIO (demo only) 228 | npm start 229 | ``` 230 | 231 | When running with STDIO, you can interact with the server by sending MCP messages as single-line JSON objects: 232 | 233 | ```bash 234 | # Example of sending an initialize message via STDIO 235 | echo '{"jsonrpc": "2.0","id": 1,"method": "initialize","params": {"protocolVersion": "2024-11-05","capabilities": {"roots": {"listChanged": true},"sampling": {}},"clientInfo": {"name": "ExampleClient","version": "1.0.0"}}}' | node dist/index.js --stdio 236 | 237 | # List resources 238 | echo '{"jsonrpc": "2.0","id": 2,"method": "resources/list","params": {}}' | node dist/index.js --stdio 239 | 240 | # Access a resource 241 | echo '{"jsonrpc": "2.0","id": 3,"method": "resources/read","params": {"uri": "candidate-info://resume-text"}}' | node dist/index.js --stdio 242 | 243 | # List Tools 244 | echo '{"jsonrpc": "2.0","id": 2,"method": "tools/list","params": {}}' | node dist/index.js --stdio 245 | 246 | # Call a tool 247 | echo '{"jsonrpc": "2.0","id": 4,"method": "tools/call","params": {"name": "get_resume_text", "args": {}}}' | node dist/index.js --stdio 248 | 249 | # Send an email to the candidate 250 | echo '{"jsonrpc": "2.0","id": 5,"method": "tools/call","params": {"name": "contact_candidate", "args": {"subject": "Hello from AI!", "message": "This is a test email sent via the MCP server.", "reply_address": "recruiter@company.com"}}}' | node dist/index.js --stdio 251 | ``` 252 | 253 | Each message must be on a single line with no line breaks within the JSON object. 254 | 255 | ## Features 256 | 257 | - Library-first design for integration into other applications 258 | - Modular resource system for extending with custom candidate information 259 | - TypeScript for type safety and better developer experience 260 | - Implements the full Model Context Protocol specification 261 | - Supports multiple transport types (STDIO, HTTP, Streamable HTTP) 262 | - Minimal dependencies 263 | 264 | ## Server Structure 265 | 266 | ``` 267 | src/ 268 | ├── index.ts # Main package entry point 269 | ├── server.ts # MCP server factory with configuration 270 | ├── config.ts # Configuration type definitions 271 | └── resources/ # Modular resource definitions 272 | └── index.ts # Resource factory and implementation 273 | ``` 274 | 275 | ## MCP Protocol 276 | 277 | This library implements the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP), a standardized way for LLMs to interact with external data and functionality. When integrated into your application, it exposes a stateless API that responds to JSON-RPC requests. 278 | 279 | ### API Usage 280 | 281 | Once integrated into your application, clients can interact with the MCP server by sending JSON-RPC requests. Here are examples of requests that your application would handle after integrating this library: 282 | 283 | #### Initialize 284 | 285 | ```bash 286 | curl -X POST http://your-application-url/mcp \ 287 | -H "Content-Type: application/json" \ 288 | -H "Accept: application/json" \ 289 | -H "Accept: text/event-stream" \ 290 | -d '{ 291 | "jsonrpc": "2.0", 292 | "id": 1, 293 | "method": "initialize", 294 | "params": { 295 | "protocolVersion": "2024-11-05", 296 | "capabilities": { 297 | "roots": { 298 | "listChanged": true 299 | }, 300 | "sampling": {} 301 | }, 302 | "clientInfo": { 303 | "name": "ExampleClient", 304 | "version": "1.0.0" 305 | } 306 | } 307 | }' 308 | ``` 309 | 310 | #### Access Candidate Resources 311 | 312 | ```bash 313 | curl -X POST http://your-application-url/mcp \ 314 | -H "Content-Type: application/json" \ 315 | -H "Accept: application/json" \ 316 | -H "Accept: text/event-stream" \ 317 | -d '{ 318 | "jsonrpc": "2.0", 319 | "method": "resources/read", 320 | "params": { 321 | "uri": "candidate-info://resume-text" 322 | }, 323 | "id": 2 324 | }' 325 | ``` 326 | 327 | ## Extending the Library 328 | 329 | This library is designed to be extended with custom resources, tools, and prompts. Here's how to add your own resources: 330 | 331 | ```javascript 332 | import { McpServer, Resource } from '@jhgaylor/candidate-mcp-server'; 333 | 334 | // Create your custom resource class 335 | class CustomCandidateResource extends Resource { 336 | constructor(candidateConfig) { 337 | super( 338 | `${candidateConfig.name} Custom Data`, 339 | "candidate-info://custom-data", 340 | async () => { 341 | return { 342 | contents: [ 343 | { 344 | uri: "candidate-info://custom-data", 345 | mimeType: "text/plain", 346 | text: "Your custom candidate data here" 347 | } 348 | ] 349 | }; 350 | } 351 | ); 352 | } 353 | } 354 | 355 | // Create server with standard configuration 356 | const server = createServer(serverConfig, candidateConfig); 357 | 358 | // Add your custom resource 359 | const customResource = new CustomCandidateResource(candidateConfig); 360 | customResource.bind(server); 361 | 362 | // Connect with preferred transport 363 | // ... 364 | ``` 365 | 366 | ### Adding Custom Tools 367 | 368 | You can also extend the library with custom tools: 369 | 370 | ```javascript 371 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 372 | import { z } from 'zod'; 373 | import { createServer } from '@jhgaylor/candidate-mcp-server'; 374 | 375 | // Create server with standard configuration 376 | const server = createServer(serverConfig, candidateConfig); 377 | 378 | // Add a custom tool 379 | server.tool( 380 | 'get_candidate_skills', 381 | 'Returns a list of the candidate skills', 382 | {}, 383 | async (_args, _extra) => { 384 | return { 385 | content: [ 386 | { 387 | type: "text", 388 | text: "JavaScript, TypeScript, React, Node.js, MCP Protocol" 389 | } 390 | ] 391 | }; 392 | } 393 | ); 394 | 395 | // Connect with preferred transport 396 | // ... 397 | ``` 398 | 399 | ## Requirements 400 | 401 | - Node.js 20+ 402 | - npm or yarn 403 | 404 | ## License 405 | 406 | [MIT](LICENSE) 407 | 408 | ## Publishing to npm 409 | 410 | Log in to npm if you haven't already: 411 | ```bash 412 | npm login 413 | ``` 414 | 415 | Publish the package to npm (will run your prepublishOnly build): 416 | ```bash 417 | npm publish 418 | ``` 419 | 420 | To bump, tag, and push a new version: 421 | ```bash 422 | npm version patch # or minor, major 423 | git push origin main --tags 424 | ``` 425 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | An example of using this library with an stdio server. 2 | 3 | Usage: 4 | 5 | ``` 6 | # from the root of the repo 7 | npm run build 8 | node examples/start.ts 9 | ``` -------------------------------------------------------------------------------- /examples/stdio.ts: -------------------------------------------------------------------------------- 1 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 2 | import { createServer, CandidateConfig, ServerConfig } from "../dist"; 3 | 4 | async function startServer() { 5 | try { 6 | const candidateConfig = new CandidateConfig(); 7 | candidateConfig.name = "Jane Doe"; 8 | candidateConfig.resumeUrl = "https://example.com/jane_doe_resume.pdf"; 9 | candidateConfig.websiteUrl = "https://janedoe.com"; 10 | candidateConfig.linkedinUrl = "https://linkedin.com/in/janedoe"; 11 | candidateConfig.githubUrl = "https://github.com/janedoe"; 12 | candidateConfig.resumeText = `Jane Doe 13 | Embedded Systems Engineer 14 | 15 | Experience: 16 | - IoT Solutions Inc. (2019-Present) 17 | Senior Embedded Engineer 18 | 19 | Skills: 20 | - C, C++, Assembly 21 | - RTOS, Embedded Linux 22 | - PCB Design, Hardware Integration 23 | - Microcontrollers (ARM, AVR)`; 24 | 25 | const serverConfig = new ServerConfig(); 26 | const server = createServer(serverConfig, candidateConfig); 27 | await server.connect(new StdioServerTransport()); 28 | } catch (error) { 29 | console.error("Failed to start server:", error); 30 | process.exit(1); 31 | } 32 | } 33 | 34 | // Execute the function 35 | startServer(); -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jhgaylor/candidate-mcp-server", 3 | "version": "1.3.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@jhgaylor/candidate-mcp-server", 9 | "version": "1.3.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@modelcontextprotocol/sdk": "^1.0.0", 13 | "@types/nodemailer": "^6.4.17", 14 | "mailgun-nodemailer-transport": "^3.0.2", 15 | "nodemailer": "^6.10.1" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20.11.24", 19 | "nodemon": "^3.0.3", 20 | "typescript": "^5.8.3" 21 | } 22 | }, 23 | "node_modules/@modelcontextprotocol/sdk": { 24 | "version": "1.10.2", 25 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.10.2.tgz", 26 | "integrity": "sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==", 27 | "license": "MIT", 28 | "dependencies": { 29 | "content-type": "^1.0.5", 30 | "cors": "^2.8.5", 31 | "cross-spawn": "^7.0.3", 32 | "eventsource": "^3.0.2", 33 | "express": "^5.0.1", 34 | "express-rate-limit": "^7.5.0", 35 | "pkce-challenge": "^5.0.0", 36 | "raw-body": "^3.0.0", 37 | "zod": "^3.23.8", 38 | "zod-to-json-schema": "^3.24.1" 39 | }, 40 | "engines": { 41 | "node": ">=18" 42 | } 43 | }, 44 | "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { 45 | "version": "2.0.0", 46 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", 47 | "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 48 | "license": "MIT", 49 | "dependencies": { 50 | "mime-types": "^3.0.0", 51 | "negotiator": "^1.0.0" 52 | }, 53 | "engines": { 54 | "node": ">= 0.6" 55 | } 56 | }, 57 | "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { 58 | "version": "2.2.0", 59 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", 60 | "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", 61 | "license": "MIT", 62 | "dependencies": { 63 | "bytes": "^3.1.2", 64 | "content-type": "^1.0.5", 65 | "debug": "^4.4.0", 66 | "http-errors": "^2.0.0", 67 | "iconv-lite": "^0.6.3", 68 | "on-finished": "^2.4.1", 69 | "qs": "^6.14.0", 70 | "raw-body": "^3.0.0", 71 | "type-is": "^2.0.0" 72 | }, 73 | "engines": { 74 | "node": ">=18" 75 | } 76 | }, 77 | "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { 78 | "version": "1.0.0", 79 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", 80 | "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", 81 | "license": "MIT", 82 | "dependencies": { 83 | "safe-buffer": "5.2.1" 84 | }, 85 | "engines": { 86 | "node": ">= 0.6" 87 | } 88 | }, 89 | "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { 90 | "version": "1.2.2", 91 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", 92 | "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", 93 | "license": "MIT", 94 | "engines": { 95 | "node": ">=6.6.0" 96 | } 97 | }, 98 | "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { 99 | "version": "4.4.0", 100 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 101 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 102 | "license": "MIT", 103 | "dependencies": { 104 | "ms": "^2.1.3" 105 | }, 106 | "engines": { 107 | "node": ">=6.0" 108 | }, 109 | "peerDependenciesMeta": { 110 | "supports-color": { 111 | "optional": true 112 | } 113 | } 114 | }, 115 | "node_modules/@modelcontextprotocol/sdk/node_modules/express": { 116 | "version": "5.1.0", 117 | "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", 118 | "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", 119 | "license": "MIT", 120 | "dependencies": { 121 | "accepts": "^2.0.0", 122 | "body-parser": "^2.2.0", 123 | "content-disposition": "^1.0.0", 124 | "content-type": "^1.0.5", 125 | "cookie": "^0.7.1", 126 | "cookie-signature": "^1.2.1", 127 | "debug": "^4.4.0", 128 | "encodeurl": "^2.0.0", 129 | "escape-html": "^1.0.3", 130 | "etag": "^1.8.1", 131 | "finalhandler": "^2.1.0", 132 | "fresh": "^2.0.0", 133 | "http-errors": "^2.0.0", 134 | "merge-descriptors": "^2.0.0", 135 | "mime-types": "^3.0.0", 136 | "on-finished": "^2.4.1", 137 | "once": "^1.4.0", 138 | "parseurl": "^1.3.3", 139 | "proxy-addr": "^2.0.7", 140 | "qs": "^6.14.0", 141 | "range-parser": "^1.2.1", 142 | "router": "^2.2.0", 143 | "send": "^1.1.0", 144 | "serve-static": "^2.2.0", 145 | "statuses": "^2.0.1", 146 | "type-is": "^2.0.1", 147 | "vary": "^1.1.2" 148 | }, 149 | "engines": { 150 | "node": ">= 18" 151 | }, 152 | "funding": { 153 | "type": "opencollective", 154 | "url": "https://opencollective.com/express" 155 | } 156 | }, 157 | "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { 158 | "version": "2.1.0", 159 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", 160 | "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", 161 | "license": "MIT", 162 | "dependencies": { 163 | "debug": "^4.4.0", 164 | "encodeurl": "^2.0.0", 165 | "escape-html": "^1.0.3", 166 | "on-finished": "^2.4.1", 167 | "parseurl": "^1.3.3", 168 | "statuses": "^2.0.1" 169 | }, 170 | "engines": { 171 | "node": ">= 0.8" 172 | } 173 | }, 174 | "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { 175 | "version": "2.0.0", 176 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", 177 | "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", 178 | "license": "MIT", 179 | "engines": { 180 | "node": ">= 0.8" 181 | } 182 | }, 183 | "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { 184 | "version": "1.1.0", 185 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", 186 | "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", 187 | "license": "MIT", 188 | "engines": { 189 | "node": ">= 0.8" 190 | } 191 | }, 192 | "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { 193 | "version": "2.0.0", 194 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", 195 | "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", 196 | "license": "MIT", 197 | "engines": { 198 | "node": ">=18" 199 | }, 200 | "funding": { 201 | "url": "https://github.com/sponsors/sindresorhus" 202 | } 203 | }, 204 | "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { 205 | "version": "1.54.0", 206 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 207 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 208 | "license": "MIT", 209 | "engines": { 210 | "node": ">= 0.6" 211 | } 212 | }, 213 | "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { 214 | "version": "3.0.1", 215 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", 216 | "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", 217 | "license": "MIT", 218 | "dependencies": { 219 | "mime-db": "^1.54.0" 220 | }, 221 | "engines": { 222 | "node": ">= 0.6" 223 | } 224 | }, 225 | "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { 226 | "version": "2.1.3", 227 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 228 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 229 | "license": "MIT" 230 | }, 231 | "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { 232 | "version": "1.0.0", 233 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 234 | "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 235 | "license": "MIT", 236 | "engines": { 237 | "node": ">= 0.6" 238 | } 239 | }, 240 | "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { 241 | "version": "6.14.0", 242 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", 243 | "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", 244 | "license": "BSD-3-Clause", 245 | "dependencies": { 246 | "side-channel": "^1.1.0" 247 | }, 248 | "engines": { 249 | "node": ">=0.6" 250 | }, 251 | "funding": { 252 | "url": "https://github.com/sponsors/ljharb" 253 | } 254 | }, 255 | "node_modules/@modelcontextprotocol/sdk/node_modules/send": { 256 | "version": "1.2.0", 257 | "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", 258 | "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", 259 | "license": "MIT", 260 | "dependencies": { 261 | "debug": "^4.3.5", 262 | "encodeurl": "^2.0.0", 263 | "escape-html": "^1.0.3", 264 | "etag": "^1.8.1", 265 | "fresh": "^2.0.0", 266 | "http-errors": "^2.0.0", 267 | "mime-types": "^3.0.1", 268 | "ms": "^2.1.3", 269 | "on-finished": "^2.4.1", 270 | "range-parser": "^1.2.1", 271 | "statuses": "^2.0.1" 272 | }, 273 | "engines": { 274 | "node": ">= 18" 275 | } 276 | }, 277 | "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { 278 | "version": "2.2.0", 279 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", 280 | "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", 281 | "license": "MIT", 282 | "dependencies": { 283 | "encodeurl": "^2.0.0", 284 | "escape-html": "^1.0.3", 285 | "parseurl": "^1.3.3", 286 | "send": "^1.2.0" 287 | }, 288 | "engines": { 289 | "node": ">= 18" 290 | } 291 | }, 292 | "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { 293 | "version": "2.0.1", 294 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", 295 | "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", 296 | "license": "MIT", 297 | "dependencies": { 298 | "content-type": "^1.0.5", 299 | "media-typer": "^1.1.0", 300 | "mime-types": "^3.0.0" 301 | }, 302 | "engines": { 303 | "node": ">= 0.6" 304 | } 305 | }, 306 | "node_modules/@types/node": { 307 | "version": "20.17.32", 308 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.32.tgz", 309 | "integrity": "sha512-zeMXFn8zQ+UkjK4ws0RiOC9EWByyW1CcVmLe+2rQocXRsGEDxUCwPEIVgpsGcLHS/P8JkT0oa3839BRABS0oPw==", 310 | "license": "MIT", 311 | "dependencies": { 312 | "undici-types": "~6.19.2" 313 | } 314 | }, 315 | "node_modules/@types/nodemailer": { 316 | "version": "6.4.17", 317 | "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", 318 | "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", 319 | "license": "MIT", 320 | "dependencies": { 321 | "@types/node": "*" 322 | } 323 | }, 324 | "node_modules/accepts": { 325 | "version": "1.3.8", 326 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 327 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 328 | "license": "MIT", 329 | "peer": true, 330 | "dependencies": { 331 | "mime-types": "~2.1.34", 332 | "negotiator": "0.6.3" 333 | }, 334 | "engines": { 335 | "node": ">= 0.6" 336 | } 337 | }, 338 | "node_modules/anymatch": { 339 | "version": "3.1.3", 340 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 341 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 342 | "dev": true, 343 | "license": "ISC", 344 | "dependencies": { 345 | "normalize-path": "^3.0.0", 346 | "picomatch": "^2.0.4" 347 | }, 348 | "engines": { 349 | "node": ">= 8" 350 | } 351 | }, 352 | "node_modules/array-flatten": { 353 | "version": "1.1.1", 354 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 355 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", 356 | "license": "MIT", 357 | "peer": true 358 | }, 359 | "node_modules/asynckit": { 360 | "version": "0.4.0", 361 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 362 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 363 | "license": "MIT" 364 | }, 365 | "node_modules/balanced-match": { 366 | "version": "1.0.2", 367 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 368 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 369 | "dev": true, 370 | "license": "MIT" 371 | }, 372 | "node_modules/binary-extensions": { 373 | "version": "2.3.0", 374 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 375 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 376 | "dev": true, 377 | "license": "MIT", 378 | "engines": { 379 | "node": ">=8" 380 | }, 381 | "funding": { 382 | "url": "https://github.com/sponsors/sindresorhus" 383 | } 384 | }, 385 | "node_modules/body-parser": { 386 | "version": "1.20.3", 387 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", 388 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", 389 | "license": "MIT", 390 | "peer": true, 391 | "dependencies": { 392 | "bytes": "3.1.2", 393 | "content-type": "~1.0.5", 394 | "debug": "2.6.9", 395 | "depd": "2.0.0", 396 | "destroy": "1.2.0", 397 | "http-errors": "2.0.0", 398 | "iconv-lite": "0.4.24", 399 | "on-finished": "2.4.1", 400 | "qs": "6.13.0", 401 | "raw-body": "2.5.2", 402 | "type-is": "~1.6.18", 403 | "unpipe": "1.0.0" 404 | }, 405 | "engines": { 406 | "node": ">= 0.8", 407 | "npm": "1.2.8000 || >= 1.4.16" 408 | } 409 | }, 410 | "node_modules/body-parser/node_modules/iconv-lite": { 411 | "version": "0.4.24", 412 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 413 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 414 | "license": "MIT", 415 | "peer": true, 416 | "dependencies": { 417 | "safer-buffer": ">= 2.1.2 < 3" 418 | }, 419 | "engines": { 420 | "node": ">=0.10.0" 421 | } 422 | }, 423 | "node_modules/body-parser/node_modules/raw-body": { 424 | "version": "2.5.2", 425 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 426 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 427 | "license": "MIT", 428 | "peer": true, 429 | "dependencies": { 430 | "bytes": "3.1.2", 431 | "http-errors": "2.0.0", 432 | "iconv-lite": "0.4.24", 433 | "unpipe": "1.0.0" 434 | }, 435 | "engines": { 436 | "node": ">= 0.8" 437 | } 438 | }, 439 | "node_modules/brace-expansion": { 440 | "version": "1.1.11", 441 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 442 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 443 | "dev": true, 444 | "license": "MIT", 445 | "dependencies": { 446 | "balanced-match": "^1.0.0", 447 | "concat-map": "0.0.1" 448 | } 449 | }, 450 | "node_modules/braces": { 451 | "version": "3.0.3", 452 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 453 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 454 | "dev": true, 455 | "license": "MIT", 456 | "dependencies": { 457 | "fill-range": "^7.1.1" 458 | }, 459 | "engines": { 460 | "node": ">=8" 461 | } 462 | }, 463 | "node_modules/bytes": { 464 | "version": "3.1.2", 465 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 466 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 467 | "license": "MIT", 468 | "engines": { 469 | "node": ">= 0.8" 470 | } 471 | }, 472 | "node_modules/call-bind-apply-helpers": { 473 | "version": "1.0.2", 474 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 475 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 476 | "license": "MIT", 477 | "dependencies": { 478 | "es-errors": "^1.3.0", 479 | "function-bind": "^1.1.2" 480 | }, 481 | "engines": { 482 | "node": ">= 0.4" 483 | } 484 | }, 485 | "node_modules/call-bound": { 486 | "version": "1.0.4", 487 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 488 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 489 | "license": "MIT", 490 | "dependencies": { 491 | "call-bind-apply-helpers": "^1.0.2", 492 | "get-intrinsic": "^1.3.0" 493 | }, 494 | "engines": { 495 | "node": ">= 0.4" 496 | }, 497 | "funding": { 498 | "url": "https://github.com/sponsors/ljharb" 499 | } 500 | }, 501 | "node_modules/chokidar": { 502 | "version": "3.6.0", 503 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 504 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 505 | "dev": true, 506 | "license": "MIT", 507 | "dependencies": { 508 | "anymatch": "~3.1.2", 509 | "braces": "~3.0.2", 510 | "glob-parent": "~5.1.2", 511 | "is-binary-path": "~2.1.0", 512 | "is-glob": "~4.0.1", 513 | "normalize-path": "~3.0.0", 514 | "readdirp": "~3.6.0" 515 | }, 516 | "engines": { 517 | "node": ">= 8.10.0" 518 | }, 519 | "funding": { 520 | "url": "https://paulmillr.com/funding/" 521 | }, 522 | "optionalDependencies": { 523 | "fsevents": "~2.3.2" 524 | } 525 | }, 526 | "node_modules/combined-stream": { 527 | "version": "1.0.8", 528 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 529 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 530 | "license": "MIT", 531 | "dependencies": { 532 | "delayed-stream": "~1.0.0" 533 | }, 534 | "engines": { 535 | "node": ">= 0.8" 536 | } 537 | }, 538 | "node_modules/concat-map": { 539 | "version": "0.0.1", 540 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 541 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 542 | "dev": true, 543 | "license": "MIT" 544 | }, 545 | "node_modules/content-disposition": { 546 | "version": "0.5.4", 547 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 548 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 549 | "license": "MIT", 550 | "peer": true, 551 | "dependencies": { 552 | "safe-buffer": "5.2.1" 553 | }, 554 | "engines": { 555 | "node": ">= 0.6" 556 | } 557 | }, 558 | "node_modules/content-type": { 559 | "version": "1.0.5", 560 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 561 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 562 | "license": "MIT", 563 | "engines": { 564 | "node": ">= 0.6" 565 | } 566 | }, 567 | "node_modules/cookie": { 568 | "version": "0.7.1", 569 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 570 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 571 | "license": "MIT", 572 | "engines": { 573 | "node": ">= 0.6" 574 | } 575 | }, 576 | "node_modules/cookie-signature": { 577 | "version": "1.0.6", 578 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 579 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", 580 | "license": "MIT", 581 | "peer": true 582 | }, 583 | "node_modules/cors": { 584 | "version": "2.8.5", 585 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 586 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 587 | "license": "MIT", 588 | "dependencies": { 589 | "object-assign": "^4", 590 | "vary": "^1" 591 | }, 592 | "engines": { 593 | "node": ">= 0.10" 594 | } 595 | }, 596 | "node_modules/cross-spawn": { 597 | "version": "7.0.6", 598 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 599 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 600 | "license": "MIT", 601 | "dependencies": { 602 | "path-key": "^3.1.0", 603 | "shebang-command": "^2.0.0", 604 | "which": "^2.0.1" 605 | }, 606 | "engines": { 607 | "node": ">= 8" 608 | } 609 | }, 610 | "node_modules/debug": { 611 | "version": "2.6.9", 612 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 613 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 614 | "license": "MIT", 615 | "peer": true, 616 | "dependencies": { 617 | "ms": "2.0.0" 618 | } 619 | }, 620 | "node_modules/delayed-stream": { 621 | "version": "1.0.0", 622 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 623 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 624 | "license": "MIT", 625 | "engines": { 626 | "node": ">=0.4.0" 627 | } 628 | }, 629 | "node_modules/depd": { 630 | "version": "2.0.0", 631 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 632 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 633 | "license": "MIT", 634 | "engines": { 635 | "node": ">= 0.8" 636 | } 637 | }, 638 | "node_modules/destroy": { 639 | "version": "1.2.0", 640 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 641 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 642 | "license": "MIT", 643 | "peer": true, 644 | "engines": { 645 | "node": ">= 0.8", 646 | "npm": "1.2.8000 || >= 1.4.16" 647 | } 648 | }, 649 | "node_modules/dunder-proto": { 650 | "version": "1.0.1", 651 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 652 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 653 | "license": "MIT", 654 | "dependencies": { 655 | "call-bind-apply-helpers": "^1.0.1", 656 | "es-errors": "^1.3.0", 657 | "gopd": "^1.2.0" 658 | }, 659 | "engines": { 660 | "node": ">= 0.4" 661 | } 662 | }, 663 | "node_modules/ee-first": { 664 | "version": "1.1.1", 665 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 666 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 667 | "license": "MIT" 668 | }, 669 | "node_modules/encodeurl": { 670 | "version": "2.0.0", 671 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 672 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 673 | "license": "MIT", 674 | "engines": { 675 | "node": ">= 0.8" 676 | } 677 | }, 678 | "node_modules/es-define-property": { 679 | "version": "1.0.1", 680 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 681 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 682 | "license": "MIT", 683 | "engines": { 684 | "node": ">= 0.4" 685 | } 686 | }, 687 | "node_modules/es-errors": { 688 | "version": "1.3.0", 689 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 690 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 691 | "license": "MIT", 692 | "engines": { 693 | "node": ">= 0.4" 694 | } 695 | }, 696 | "node_modules/es-object-atoms": { 697 | "version": "1.1.1", 698 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 699 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 700 | "license": "MIT", 701 | "dependencies": { 702 | "es-errors": "^1.3.0" 703 | }, 704 | "engines": { 705 | "node": ">= 0.4" 706 | } 707 | }, 708 | "node_modules/es-set-tostringtag": { 709 | "version": "2.1.0", 710 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", 711 | "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", 712 | "license": "MIT", 713 | "dependencies": { 714 | "es-errors": "^1.3.0", 715 | "get-intrinsic": "^1.2.6", 716 | "has-tostringtag": "^1.0.2", 717 | "hasown": "^2.0.2" 718 | }, 719 | "engines": { 720 | "node": ">= 0.4" 721 | } 722 | }, 723 | "node_modules/escape-html": { 724 | "version": "1.0.3", 725 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 726 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 727 | "license": "MIT" 728 | }, 729 | "node_modules/etag": { 730 | "version": "1.8.1", 731 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 732 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 733 | "license": "MIT", 734 | "engines": { 735 | "node": ">= 0.6" 736 | } 737 | }, 738 | "node_modules/eventsource": { 739 | "version": "3.0.6", 740 | "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", 741 | "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==", 742 | "license": "MIT", 743 | "dependencies": { 744 | "eventsource-parser": "^3.0.1" 745 | }, 746 | "engines": { 747 | "node": ">=18.0.0" 748 | } 749 | }, 750 | "node_modules/eventsource-parser": { 751 | "version": "3.0.1", 752 | "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", 753 | "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", 754 | "license": "MIT", 755 | "engines": { 756 | "node": ">=18.0.0" 757 | } 758 | }, 759 | "node_modules/express": { 760 | "version": "4.21.2", 761 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", 762 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 763 | "license": "MIT", 764 | "peer": true, 765 | "dependencies": { 766 | "accepts": "~1.3.8", 767 | "array-flatten": "1.1.1", 768 | "body-parser": "1.20.3", 769 | "content-disposition": "0.5.4", 770 | "content-type": "~1.0.4", 771 | "cookie": "0.7.1", 772 | "cookie-signature": "1.0.6", 773 | "debug": "2.6.9", 774 | "depd": "2.0.0", 775 | "encodeurl": "~2.0.0", 776 | "escape-html": "~1.0.3", 777 | "etag": "~1.8.1", 778 | "finalhandler": "1.3.1", 779 | "fresh": "0.5.2", 780 | "http-errors": "2.0.0", 781 | "merge-descriptors": "1.0.3", 782 | "methods": "~1.1.2", 783 | "on-finished": "2.4.1", 784 | "parseurl": "~1.3.3", 785 | "path-to-regexp": "0.1.12", 786 | "proxy-addr": "~2.0.7", 787 | "qs": "6.13.0", 788 | "range-parser": "~1.2.1", 789 | "safe-buffer": "5.2.1", 790 | "send": "0.19.0", 791 | "serve-static": "1.16.2", 792 | "setprototypeof": "1.2.0", 793 | "statuses": "2.0.1", 794 | "type-is": "~1.6.18", 795 | "utils-merge": "1.0.1", 796 | "vary": "~1.1.2" 797 | }, 798 | "engines": { 799 | "node": ">= 0.10.0" 800 | }, 801 | "funding": { 802 | "type": "opencollective", 803 | "url": "https://opencollective.com/express" 804 | } 805 | }, 806 | "node_modules/express-rate-limit": { 807 | "version": "7.5.0", 808 | "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", 809 | "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", 810 | "license": "MIT", 811 | "engines": { 812 | "node": ">= 16" 813 | }, 814 | "funding": { 815 | "url": "https://github.com/sponsors/express-rate-limit" 816 | }, 817 | "peerDependencies": { 818 | "express": "^4.11 || 5 || ^5.0.0-beta.1" 819 | } 820 | }, 821 | "node_modules/fill-range": { 822 | "version": "7.1.1", 823 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 824 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 825 | "dev": true, 826 | "license": "MIT", 827 | "dependencies": { 828 | "to-regex-range": "^5.0.1" 829 | }, 830 | "engines": { 831 | "node": ">=8" 832 | } 833 | }, 834 | "node_modules/finalhandler": { 835 | "version": "1.3.1", 836 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", 837 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", 838 | "license": "MIT", 839 | "peer": true, 840 | "dependencies": { 841 | "debug": "2.6.9", 842 | "encodeurl": "~2.0.0", 843 | "escape-html": "~1.0.3", 844 | "on-finished": "2.4.1", 845 | "parseurl": "~1.3.3", 846 | "statuses": "2.0.1", 847 | "unpipe": "~1.0.0" 848 | }, 849 | "engines": { 850 | "node": ">= 0.8" 851 | } 852 | }, 853 | "node_modules/form-data": { 854 | "version": "4.0.2", 855 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", 856 | "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", 857 | "license": "MIT", 858 | "dependencies": { 859 | "asynckit": "^0.4.0", 860 | "combined-stream": "^1.0.8", 861 | "es-set-tostringtag": "^2.1.0", 862 | "mime-types": "^2.1.12" 863 | }, 864 | "engines": { 865 | "node": ">= 6" 866 | } 867 | }, 868 | "node_modules/forwarded": { 869 | "version": "0.2.0", 870 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 871 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 872 | "license": "MIT", 873 | "engines": { 874 | "node": ">= 0.6" 875 | } 876 | }, 877 | "node_modules/fresh": { 878 | "version": "0.5.2", 879 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 880 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 881 | "license": "MIT", 882 | "peer": true, 883 | "engines": { 884 | "node": ">= 0.6" 885 | } 886 | }, 887 | "node_modules/fsevents": { 888 | "version": "2.3.3", 889 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 890 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 891 | "dev": true, 892 | "hasInstallScript": true, 893 | "license": "MIT", 894 | "optional": true, 895 | "os": [ 896 | "darwin" 897 | ], 898 | "engines": { 899 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 900 | } 901 | }, 902 | "node_modules/function-bind": { 903 | "version": "1.1.2", 904 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 905 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 906 | "license": "MIT", 907 | "funding": { 908 | "url": "https://github.com/sponsors/ljharb" 909 | } 910 | }, 911 | "node_modules/get-intrinsic": { 912 | "version": "1.3.0", 913 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 914 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 915 | "license": "MIT", 916 | "dependencies": { 917 | "call-bind-apply-helpers": "^1.0.2", 918 | "es-define-property": "^1.0.1", 919 | "es-errors": "^1.3.0", 920 | "es-object-atoms": "^1.1.1", 921 | "function-bind": "^1.1.2", 922 | "get-proto": "^1.0.1", 923 | "gopd": "^1.2.0", 924 | "has-symbols": "^1.1.0", 925 | "hasown": "^2.0.2", 926 | "math-intrinsics": "^1.1.0" 927 | }, 928 | "engines": { 929 | "node": ">= 0.4" 930 | }, 931 | "funding": { 932 | "url": "https://github.com/sponsors/ljharb" 933 | } 934 | }, 935 | "node_modules/get-proto": { 936 | "version": "1.0.1", 937 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 938 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 939 | "license": "MIT", 940 | "dependencies": { 941 | "dunder-proto": "^1.0.1", 942 | "es-object-atoms": "^1.0.0" 943 | }, 944 | "engines": { 945 | "node": ">= 0.4" 946 | } 947 | }, 948 | "node_modules/glob-parent": { 949 | "version": "5.1.2", 950 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 951 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 952 | "dev": true, 953 | "license": "ISC", 954 | "dependencies": { 955 | "is-glob": "^4.0.1" 956 | }, 957 | "engines": { 958 | "node": ">= 6" 959 | } 960 | }, 961 | "node_modules/gopd": { 962 | "version": "1.2.0", 963 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 964 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 965 | "license": "MIT", 966 | "engines": { 967 | "node": ">= 0.4" 968 | }, 969 | "funding": { 970 | "url": "https://github.com/sponsors/ljharb" 971 | } 972 | }, 973 | "node_modules/has-flag": { 974 | "version": "3.0.0", 975 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 976 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 977 | "dev": true, 978 | "license": "MIT", 979 | "engines": { 980 | "node": ">=4" 981 | } 982 | }, 983 | "node_modules/has-symbols": { 984 | "version": "1.1.0", 985 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 986 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 987 | "license": "MIT", 988 | "engines": { 989 | "node": ">= 0.4" 990 | }, 991 | "funding": { 992 | "url": "https://github.com/sponsors/ljharb" 993 | } 994 | }, 995 | "node_modules/has-tostringtag": { 996 | "version": "1.0.2", 997 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", 998 | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", 999 | "license": "MIT", 1000 | "dependencies": { 1001 | "has-symbols": "^1.0.3" 1002 | }, 1003 | "engines": { 1004 | "node": ">= 0.4" 1005 | }, 1006 | "funding": { 1007 | "url": "https://github.com/sponsors/ljharb" 1008 | } 1009 | }, 1010 | "node_modules/hasown": { 1011 | "version": "2.0.2", 1012 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 1013 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 1014 | "license": "MIT", 1015 | "dependencies": { 1016 | "function-bind": "^1.1.2" 1017 | }, 1018 | "engines": { 1019 | "node": ">= 0.4" 1020 | } 1021 | }, 1022 | "node_modules/http-errors": { 1023 | "version": "2.0.0", 1024 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 1025 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1026 | "license": "MIT", 1027 | "dependencies": { 1028 | "depd": "2.0.0", 1029 | "inherits": "2.0.4", 1030 | "setprototypeof": "1.2.0", 1031 | "statuses": "2.0.1", 1032 | "toidentifier": "1.0.1" 1033 | }, 1034 | "engines": { 1035 | "node": ">= 0.8" 1036 | } 1037 | }, 1038 | "node_modules/iconv-lite": { 1039 | "version": "0.6.3", 1040 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 1041 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 1042 | "license": "MIT", 1043 | "dependencies": { 1044 | "safer-buffer": ">= 2.1.2 < 3.0.0" 1045 | }, 1046 | "engines": { 1047 | "node": ">=0.10.0" 1048 | } 1049 | }, 1050 | "node_modules/ignore-by-default": { 1051 | "version": "1.0.1", 1052 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 1053 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", 1054 | "dev": true, 1055 | "license": "ISC" 1056 | }, 1057 | "node_modules/inherits": { 1058 | "version": "2.0.4", 1059 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1060 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1061 | "license": "ISC" 1062 | }, 1063 | "node_modules/ipaddr.js": { 1064 | "version": "1.9.1", 1065 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1066 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 1067 | "license": "MIT", 1068 | "engines": { 1069 | "node": ">= 0.10" 1070 | } 1071 | }, 1072 | "node_modules/is-binary-path": { 1073 | "version": "2.1.0", 1074 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 1075 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 1076 | "dev": true, 1077 | "license": "MIT", 1078 | "dependencies": { 1079 | "binary-extensions": "^2.0.0" 1080 | }, 1081 | "engines": { 1082 | "node": ">=8" 1083 | } 1084 | }, 1085 | "node_modules/is-extglob": { 1086 | "version": "2.1.1", 1087 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1088 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1089 | "dev": true, 1090 | "license": "MIT", 1091 | "engines": { 1092 | "node": ">=0.10.0" 1093 | } 1094 | }, 1095 | "node_modules/is-glob": { 1096 | "version": "4.0.3", 1097 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1098 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1099 | "dev": true, 1100 | "license": "MIT", 1101 | "dependencies": { 1102 | "is-extglob": "^2.1.1" 1103 | }, 1104 | "engines": { 1105 | "node": ">=0.10.0" 1106 | } 1107 | }, 1108 | "node_modules/is-number": { 1109 | "version": "7.0.0", 1110 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1111 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1112 | "dev": true, 1113 | "license": "MIT", 1114 | "engines": { 1115 | "node": ">=0.12.0" 1116 | } 1117 | }, 1118 | "node_modules/is-promise": { 1119 | "version": "4.0.0", 1120 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 1121 | "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", 1122 | "license": "MIT" 1123 | }, 1124 | "node_modules/isexe": { 1125 | "version": "2.0.0", 1126 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1127 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1128 | "license": "ISC" 1129 | }, 1130 | "node_modules/mailgun-nodemailer-transport": { 1131 | "version": "3.0.2", 1132 | "resolved": "https://registry.npmjs.org/mailgun-nodemailer-transport/-/mailgun-nodemailer-transport-3.0.2.tgz", 1133 | "integrity": "sha512-jyalJCt/2qJIEqvQkdeQvEhC2RDIvnax2bvxLCBOKreM/jtA7MNTleApVVMlCnrc7RQgcZUP0VKvWkzN9Sd+cg==", 1134 | "license": "MIT", 1135 | "dependencies": { 1136 | "form-data": "^4.0.0" 1137 | }, 1138 | "engines": { 1139 | "node": ">=12.0.0" 1140 | }, 1141 | "peerDependencies": { 1142 | "nodemailer": ">=6.0.0" 1143 | } 1144 | }, 1145 | "node_modules/math-intrinsics": { 1146 | "version": "1.1.0", 1147 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 1148 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 1149 | "license": "MIT", 1150 | "engines": { 1151 | "node": ">= 0.4" 1152 | } 1153 | }, 1154 | "node_modules/media-typer": { 1155 | "version": "0.3.0", 1156 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1157 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 1158 | "license": "MIT", 1159 | "peer": true, 1160 | "engines": { 1161 | "node": ">= 0.6" 1162 | } 1163 | }, 1164 | "node_modules/merge-descriptors": { 1165 | "version": "1.0.3", 1166 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", 1167 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", 1168 | "license": "MIT", 1169 | "peer": true, 1170 | "funding": { 1171 | "url": "https://github.com/sponsors/sindresorhus" 1172 | } 1173 | }, 1174 | "node_modules/methods": { 1175 | "version": "1.1.2", 1176 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1177 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 1178 | "license": "MIT", 1179 | "peer": true, 1180 | "engines": { 1181 | "node": ">= 0.6" 1182 | } 1183 | }, 1184 | "node_modules/mime": { 1185 | "version": "1.6.0", 1186 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1187 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1188 | "license": "MIT", 1189 | "peer": true, 1190 | "bin": { 1191 | "mime": "cli.js" 1192 | }, 1193 | "engines": { 1194 | "node": ">=4" 1195 | } 1196 | }, 1197 | "node_modules/mime-db": { 1198 | "version": "1.52.0", 1199 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1200 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1201 | "license": "MIT", 1202 | "engines": { 1203 | "node": ">= 0.6" 1204 | } 1205 | }, 1206 | "node_modules/mime-types": { 1207 | "version": "2.1.35", 1208 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1209 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1210 | "license": "MIT", 1211 | "dependencies": { 1212 | "mime-db": "1.52.0" 1213 | }, 1214 | "engines": { 1215 | "node": ">= 0.6" 1216 | } 1217 | }, 1218 | "node_modules/minimatch": { 1219 | "version": "3.1.2", 1220 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1221 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1222 | "dev": true, 1223 | "license": "ISC", 1224 | "dependencies": { 1225 | "brace-expansion": "^1.1.7" 1226 | }, 1227 | "engines": { 1228 | "node": "*" 1229 | } 1230 | }, 1231 | "node_modules/ms": { 1232 | "version": "2.0.0", 1233 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1234 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 1235 | "license": "MIT", 1236 | "peer": true 1237 | }, 1238 | "node_modules/negotiator": { 1239 | "version": "0.6.3", 1240 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1241 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 1242 | "license": "MIT", 1243 | "peer": true, 1244 | "engines": { 1245 | "node": ">= 0.6" 1246 | } 1247 | }, 1248 | "node_modules/nodemailer": { 1249 | "version": "6.10.1", 1250 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", 1251 | "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", 1252 | "license": "MIT-0", 1253 | "engines": { 1254 | "node": ">=6.0.0" 1255 | } 1256 | }, 1257 | "node_modules/nodemon": { 1258 | "version": "3.1.10", 1259 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", 1260 | "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", 1261 | "dev": true, 1262 | "license": "MIT", 1263 | "dependencies": { 1264 | "chokidar": "^3.5.2", 1265 | "debug": "^4", 1266 | "ignore-by-default": "^1.0.1", 1267 | "minimatch": "^3.1.2", 1268 | "pstree.remy": "^1.1.8", 1269 | "semver": "^7.5.3", 1270 | "simple-update-notifier": "^2.0.0", 1271 | "supports-color": "^5.5.0", 1272 | "touch": "^3.1.0", 1273 | "undefsafe": "^2.0.5" 1274 | }, 1275 | "bin": { 1276 | "nodemon": "bin/nodemon.js" 1277 | }, 1278 | "engines": { 1279 | "node": ">=10" 1280 | }, 1281 | "funding": { 1282 | "type": "opencollective", 1283 | "url": "https://opencollective.com/nodemon" 1284 | } 1285 | }, 1286 | "node_modules/nodemon/node_modules/debug": { 1287 | "version": "4.4.0", 1288 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 1289 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 1290 | "dev": true, 1291 | "license": "MIT", 1292 | "dependencies": { 1293 | "ms": "^2.1.3" 1294 | }, 1295 | "engines": { 1296 | "node": ">=6.0" 1297 | }, 1298 | "peerDependenciesMeta": { 1299 | "supports-color": { 1300 | "optional": true 1301 | } 1302 | } 1303 | }, 1304 | "node_modules/nodemon/node_modules/ms": { 1305 | "version": "2.1.3", 1306 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1307 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1308 | "dev": true, 1309 | "license": "MIT" 1310 | }, 1311 | "node_modules/normalize-path": { 1312 | "version": "3.0.0", 1313 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1314 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1315 | "dev": true, 1316 | "license": "MIT", 1317 | "engines": { 1318 | "node": ">=0.10.0" 1319 | } 1320 | }, 1321 | "node_modules/object-assign": { 1322 | "version": "4.1.1", 1323 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1324 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 1325 | "license": "MIT", 1326 | "engines": { 1327 | "node": ">=0.10.0" 1328 | } 1329 | }, 1330 | "node_modules/object-inspect": { 1331 | "version": "1.13.4", 1332 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 1333 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 1334 | "license": "MIT", 1335 | "engines": { 1336 | "node": ">= 0.4" 1337 | }, 1338 | "funding": { 1339 | "url": "https://github.com/sponsors/ljharb" 1340 | } 1341 | }, 1342 | "node_modules/on-finished": { 1343 | "version": "2.4.1", 1344 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1345 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1346 | "license": "MIT", 1347 | "dependencies": { 1348 | "ee-first": "1.1.1" 1349 | }, 1350 | "engines": { 1351 | "node": ">= 0.8" 1352 | } 1353 | }, 1354 | "node_modules/once": { 1355 | "version": "1.4.0", 1356 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1357 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1358 | "license": "ISC", 1359 | "dependencies": { 1360 | "wrappy": "1" 1361 | } 1362 | }, 1363 | "node_modules/parseurl": { 1364 | "version": "1.3.3", 1365 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1366 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1367 | "license": "MIT", 1368 | "engines": { 1369 | "node": ">= 0.8" 1370 | } 1371 | }, 1372 | "node_modules/path-key": { 1373 | "version": "3.1.1", 1374 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1375 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1376 | "license": "MIT", 1377 | "engines": { 1378 | "node": ">=8" 1379 | } 1380 | }, 1381 | "node_modules/path-to-regexp": { 1382 | "version": "0.1.12", 1383 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", 1384 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", 1385 | "license": "MIT", 1386 | "peer": true 1387 | }, 1388 | "node_modules/picomatch": { 1389 | "version": "2.3.1", 1390 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1391 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1392 | "dev": true, 1393 | "license": "MIT", 1394 | "engines": { 1395 | "node": ">=8.6" 1396 | }, 1397 | "funding": { 1398 | "url": "https://github.com/sponsors/jonschlinkert" 1399 | } 1400 | }, 1401 | "node_modules/pkce-challenge": { 1402 | "version": "5.0.0", 1403 | "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", 1404 | "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", 1405 | "license": "MIT", 1406 | "engines": { 1407 | "node": ">=16.20.0" 1408 | } 1409 | }, 1410 | "node_modules/proxy-addr": { 1411 | "version": "2.0.7", 1412 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1413 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1414 | "license": "MIT", 1415 | "dependencies": { 1416 | "forwarded": "0.2.0", 1417 | "ipaddr.js": "1.9.1" 1418 | }, 1419 | "engines": { 1420 | "node": ">= 0.10" 1421 | } 1422 | }, 1423 | "node_modules/pstree.remy": { 1424 | "version": "1.1.8", 1425 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 1426 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", 1427 | "dev": true, 1428 | "license": "MIT" 1429 | }, 1430 | "node_modules/qs": { 1431 | "version": "6.13.0", 1432 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 1433 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 1434 | "license": "BSD-3-Clause", 1435 | "peer": true, 1436 | "dependencies": { 1437 | "side-channel": "^1.0.6" 1438 | }, 1439 | "engines": { 1440 | "node": ">=0.6" 1441 | }, 1442 | "funding": { 1443 | "url": "https://github.com/sponsors/ljharb" 1444 | } 1445 | }, 1446 | "node_modules/range-parser": { 1447 | "version": "1.2.1", 1448 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1449 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1450 | "license": "MIT", 1451 | "engines": { 1452 | "node": ">= 0.6" 1453 | } 1454 | }, 1455 | "node_modules/raw-body": { 1456 | "version": "3.0.0", 1457 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 1458 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 1459 | "license": "MIT", 1460 | "dependencies": { 1461 | "bytes": "3.1.2", 1462 | "http-errors": "2.0.0", 1463 | "iconv-lite": "0.6.3", 1464 | "unpipe": "1.0.0" 1465 | }, 1466 | "engines": { 1467 | "node": ">= 0.8" 1468 | } 1469 | }, 1470 | "node_modules/readdirp": { 1471 | "version": "3.6.0", 1472 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1473 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1474 | "dev": true, 1475 | "license": "MIT", 1476 | "dependencies": { 1477 | "picomatch": "^2.2.1" 1478 | }, 1479 | "engines": { 1480 | "node": ">=8.10.0" 1481 | } 1482 | }, 1483 | "node_modules/router": { 1484 | "version": "2.2.0", 1485 | "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", 1486 | "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", 1487 | "license": "MIT", 1488 | "dependencies": { 1489 | "debug": "^4.4.0", 1490 | "depd": "^2.0.0", 1491 | "is-promise": "^4.0.0", 1492 | "parseurl": "^1.3.3", 1493 | "path-to-regexp": "^8.0.0" 1494 | }, 1495 | "engines": { 1496 | "node": ">= 18" 1497 | } 1498 | }, 1499 | "node_modules/router/node_modules/debug": { 1500 | "version": "4.4.0", 1501 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 1502 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 1503 | "license": "MIT", 1504 | "dependencies": { 1505 | "ms": "^2.1.3" 1506 | }, 1507 | "engines": { 1508 | "node": ">=6.0" 1509 | }, 1510 | "peerDependenciesMeta": { 1511 | "supports-color": { 1512 | "optional": true 1513 | } 1514 | } 1515 | }, 1516 | "node_modules/router/node_modules/ms": { 1517 | "version": "2.1.3", 1518 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1519 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1520 | "license": "MIT" 1521 | }, 1522 | "node_modules/router/node_modules/path-to-regexp": { 1523 | "version": "8.2.0", 1524 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", 1525 | "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", 1526 | "license": "MIT", 1527 | "engines": { 1528 | "node": ">=16" 1529 | } 1530 | }, 1531 | "node_modules/safe-buffer": { 1532 | "version": "5.2.1", 1533 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1534 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1535 | "funding": [ 1536 | { 1537 | "type": "github", 1538 | "url": "https://github.com/sponsors/feross" 1539 | }, 1540 | { 1541 | "type": "patreon", 1542 | "url": "https://www.patreon.com/feross" 1543 | }, 1544 | { 1545 | "type": "consulting", 1546 | "url": "https://feross.org/support" 1547 | } 1548 | ], 1549 | "license": "MIT" 1550 | }, 1551 | "node_modules/safer-buffer": { 1552 | "version": "2.1.2", 1553 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1554 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1555 | "license": "MIT" 1556 | }, 1557 | "node_modules/semver": { 1558 | "version": "7.7.1", 1559 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", 1560 | "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", 1561 | "dev": true, 1562 | "license": "ISC", 1563 | "bin": { 1564 | "semver": "bin/semver.js" 1565 | }, 1566 | "engines": { 1567 | "node": ">=10" 1568 | } 1569 | }, 1570 | "node_modules/send": { 1571 | "version": "0.19.0", 1572 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", 1573 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 1574 | "license": "MIT", 1575 | "peer": true, 1576 | "dependencies": { 1577 | "debug": "2.6.9", 1578 | "depd": "2.0.0", 1579 | "destroy": "1.2.0", 1580 | "encodeurl": "~1.0.2", 1581 | "escape-html": "~1.0.3", 1582 | "etag": "~1.8.1", 1583 | "fresh": "0.5.2", 1584 | "http-errors": "2.0.0", 1585 | "mime": "1.6.0", 1586 | "ms": "2.1.3", 1587 | "on-finished": "2.4.1", 1588 | "range-parser": "~1.2.1", 1589 | "statuses": "2.0.1" 1590 | }, 1591 | "engines": { 1592 | "node": ">= 0.8.0" 1593 | } 1594 | }, 1595 | "node_modules/send/node_modules/encodeurl": { 1596 | "version": "1.0.2", 1597 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1598 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 1599 | "license": "MIT", 1600 | "peer": true, 1601 | "engines": { 1602 | "node": ">= 0.8" 1603 | } 1604 | }, 1605 | "node_modules/send/node_modules/ms": { 1606 | "version": "2.1.3", 1607 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1608 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1609 | "license": "MIT", 1610 | "peer": true 1611 | }, 1612 | "node_modules/serve-static": { 1613 | "version": "1.16.2", 1614 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", 1615 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", 1616 | "license": "MIT", 1617 | "peer": true, 1618 | "dependencies": { 1619 | "encodeurl": "~2.0.0", 1620 | "escape-html": "~1.0.3", 1621 | "parseurl": "~1.3.3", 1622 | "send": "0.19.0" 1623 | }, 1624 | "engines": { 1625 | "node": ">= 0.8.0" 1626 | } 1627 | }, 1628 | "node_modules/setprototypeof": { 1629 | "version": "1.2.0", 1630 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1631 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 1632 | "license": "ISC" 1633 | }, 1634 | "node_modules/shebang-command": { 1635 | "version": "2.0.0", 1636 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1637 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1638 | "license": "MIT", 1639 | "dependencies": { 1640 | "shebang-regex": "^3.0.0" 1641 | }, 1642 | "engines": { 1643 | "node": ">=8" 1644 | } 1645 | }, 1646 | "node_modules/shebang-regex": { 1647 | "version": "3.0.0", 1648 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1649 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1650 | "license": "MIT", 1651 | "engines": { 1652 | "node": ">=8" 1653 | } 1654 | }, 1655 | "node_modules/side-channel": { 1656 | "version": "1.1.0", 1657 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 1658 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1659 | "license": "MIT", 1660 | "dependencies": { 1661 | "es-errors": "^1.3.0", 1662 | "object-inspect": "^1.13.3", 1663 | "side-channel-list": "^1.0.0", 1664 | "side-channel-map": "^1.0.1", 1665 | "side-channel-weakmap": "^1.0.2" 1666 | }, 1667 | "engines": { 1668 | "node": ">= 0.4" 1669 | }, 1670 | "funding": { 1671 | "url": "https://github.com/sponsors/ljharb" 1672 | } 1673 | }, 1674 | "node_modules/side-channel-list": { 1675 | "version": "1.0.0", 1676 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 1677 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1678 | "license": "MIT", 1679 | "dependencies": { 1680 | "es-errors": "^1.3.0", 1681 | "object-inspect": "^1.13.3" 1682 | }, 1683 | "engines": { 1684 | "node": ">= 0.4" 1685 | }, 1686 | "funding": { 1687 | "url": "https://github.com/sponsors/ljharb" 1688 | } 1689 | }, 1690 | "node_modules/side-channel-map": { 1691 | "version": "1.0.1", 1692 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 1693 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1694 | "license": "MIT", 1695 | "dependencies": { 1696 | "call-bound": "^1.0.2", 1697 | "es-errors": "^1.3.0", 1698 | "get-intrinsic": "^1.2.5", 1699 | "object-inspect": "^1.13.3" 1700 | }, 1701 | "engines": { 1702 | "node": ">= 0.4" 1703 | }, 1704 | "funding": { 1705 | "url": "https://github.com/sponsors/ljharb" 1706 | } 1707 | }, 1708 | "node_modules/side-channel-weakmap": { 1709 | "version": "1.0.2", 1710 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1711 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1712 | "license": "MIT", 1713 | "dependencies": { 1714 | "call-bound": "^1.0.2", 1715 | "es-errors": "^1.3.0", 1716 | "get-intrinsic": "^1.2.5", 1717 | "object-inspect": "^1.13.3", 1718 | "side-channel-map": "^1.0.1" 1719 | }, 1720 | "engines": { 1721 | "node": ">= 0.4" 1722 | }, 1723 | "funding": { 1724 | "url": "https://github.com/sponsors/ljharb" 1725 | } 1726 | }, 1727 | "node_modules/simple-update-notifier": { 1728 | "version": "2.0.0", 1729 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", 1730 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", 1731 | "dev": true, 1732 | "license": "MIT", 1733 | "dependencies": { 1734 | "semver": "^7.5.3" 1735 | }, 1736 | "engines": { 1737 | "node": ">=10" 1738 | } 1739 | }, 1740 | "node_modules/statuses": { 1741 | "version": "2.0.1", 1742 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1743 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1744 | "license": "MIT", 1745 | "engines": { 1746 | "node": ">= 0.8" 1747 | } 1748 | }, 1749 | "node_modules/supports-color": { 1750 | "version": "5.5.0", 1751 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1752 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1753 | "dev": true, 1754 | "license": "MIT", 1755 | "dependencies": { 1756 | "has-flag": "^3.0.0" 1757 | }, 1758 | "engines": { 1759 | "node": ">=4" 1760 | } 1761 | }, 1762 | "node_modules/to-regex-range": { 1763 | "version": "5.0.1", 1764 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1765 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1766 | "dev": true, 1767 | "license": "MIT", 1768 | "dependencies": { 1769 | "is-number": "^7.0.0" 1770 | }, 1771 | "engines": { 1772 | "node": ">=8.0" 1773 | } 1774 | }, 1775 | "node_modules/toidentifier": { 1776 | "version": "1.0.1", 1777 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1778 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1779 | "license": "MIT", 1780 | "engines": { 1781 | "node": ">=0.6" 1782 | } 1783 | }, 1784 | "node_modules/touch": { 1785 | "version": "3.1.1", 1786 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", 1787 | "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", 1788 | "dev": true, 1789 | "license": "ISC", 1790 | "bin": { 1791 | "nodetouch": "bin/nodetouch.js" 1792 | } 1793 | }, 1794 | "node_modules/type-is": { 1795 | "version": "1.6.18", 1796 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1797 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1798 | "license": "MIT", 1799 | "peer": true, 1800 | "dependencies": { 1801 | "media-typer": "0.3.0", 1802 | "mime-types": "~2.1.24" 1803 | }, 1804 | "engines": { 1805 | "node": ">= 0.6" 1806 | } 1807 | }, 1808 | "node_modules/typescript": { 1809 | "version": "5.8.3", 1810 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", 1811 | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", 1812 | "dev": true, 1813 | "license": "Apache-2.0", 1814 | "bin": { 1815 | "tsc": "bin/tsc", 1816 | "tsserver": "bin/tsserver" 1817 | }, 1818 | "engines": { 1819 | "node": ">=14.17" 1820 | } 1821 | }, 1822 | "node_modules/undefsafe": { 1823 | "version": "2.0.5", 1824 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 1825 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", 1826 | "dev": true, 1827 | "license": "MIT" 1828 | }, 1829 | "node_modules/undici-types": { 1830 | "version": "6.19.8", 1831 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 1832 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 1833 | "license": "MIT" 1834 | }, 1835 | "node_modules/unpipe": { 1836 | "version": "1.0.0", 1837 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1838 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1839 | "license": "MIT", 1840 | "engines": { 1841 | "node": ">= 0.8" 1842 | } 1843 | }, 1844 | "node_modules/utils-merge": { 1845 | "version": "1.0.1", 1846 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1847 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1848 | "license": "MIT", 1849 | "peer": true, 1850 | "engines": { 1851 | "node": ">= 0.4.0" 1852 | } 1853 | }, 1854 | "node_modules/vary": { 1855 | "version": "1.1.2", 1856 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1857 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1858 | "license": "MIT", 1859 | "engines": { 1860 | "node": ">= 0.8" 1861 | } 1862 | }, 1863 | "node_modules/which": { 1864 | "version": "2.0.2", 1865 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1866 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1867 | "license": "ISC", 1868 | "dependencies": { 1869 | "isexe": "^2.0.0" 1870 | }, 1871 | "bin": { 1872 | "node-which": "bin/node-which" 1873 | }, 1874 | "engines": { 1875 | "node": ">= 8" 1876 | } 1877 | }, 1878 | "node_modules/wrappy": { 1879 | "version": "1.0.2", 1880 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1881 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1882 | "license": "ISC" 1883 | }, 1884 | "node_modules/zod": { 1885 | "version": "3.24.3", 1886 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", 1887 | "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", 1888 | "license": "MIT", 1889 | "funding": { 1890 | "url": "https://github.com/sponsors/colinhacks" 1891 | } 1892 | }, 1893 | "node_modules/zod-to-json-schema": { 1894 | "version": "3.24.5", 1895 | "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", 1896 | "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", 1897 | "license": "ISC", 1898 | "peerDependencies": { 1899 | "zod": "^3.24.1" 1900 | } 1901 | } 1902 | } 1903 | } 1904 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jhgaylor/candidate-mcp-server", 3 | "version": "1.3.3", 4 | "main": "dist/index.js", 5 | "scripts": { 6 | "build": "tsc", 7 | "start": "node dist/stdio.js", 8 | "dev": "nodemon --watch src --ext ts --exec 'npm run build && npm start'", 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "prepublishOnly": "npm run build" 11 | }, 12 | "author": "Jake Gaylor ", 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/jhgaylor/candidate-mcp-server.git" 17 | }, 18 | "homepage": "https://github.com/jhgaylor/candidate-mcp-server", 19 | "bugs": { 20 | "url": "https://github.com/jhgaylor/candidate-mcp-server/issues" 21 | }, 22 | "description": "A stateless Model Context Protocol (MCP) server built with Express and TypeScript that provides information about a candidate.", 23 | "dependencies": { 24 | "@modelcontextprotocol/sdk": "^1.0.0", 25 | "@types/nodemailer": "^6.4.17", 26 | "mailgun-nodemailer-transport": "^3.0.2", 27 | "nodemailer": "^6.10.1" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "^20.11.24", 31 | "nodemon": "^3.0.3", 32 | "typescript": "^5.8.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | class ServerConfig { 2 | name: string; 3 | version: string; 4 | mailgunApiKey?: string; 5 | mailgunDomain?: string; 6 | contactEmail?: string; 7 | 8 | constructor(name = "Candidate MCP Server", version = "1.0.0", options: { 9 | mailgunApiKey?: string; 10 | mailgunDomain?: string; 11 | contactEmail?: string; 12 | } = {}) { 13 | this.name = name; 14 | this.version = version; 15 | this.mailgunApiKey = options.mailgunApiKey; 16 | this.mailgunDomain = options.mailgunDomain; 17 | this.contactEmail = options.contactEmail; 18 | } 19 | } 20 | 21 | class CandidateConfig { 22 | name: string = "Candidate"; 23 | resumeText?: string; 24 | resumeUrl?: string; 25 | linkedinUrl?: string; 26 | githubUrl?: string; 27 | websiteUrl?: string; 28 | websiteText?: string; 29 | 30 | constructor(name: string, options: { 31 | resumeText?: string; 32 | resumeUrl?: string; 33 | linkedinUrl?: string; 34 | githubUrl?: string; 35 | websiteUrl?: string; 36 | websiteText?: string; 37 | } = {}) { 38 | this.name = name; 39 | this.resumeText = options?.resumeText; 40 | this.resumeUrl = options?.resumeUrl; 41 | this.linkedinUrl = options?.linkedinUrl; 42 | this.githubUrl = options?.githubUrl; 43 | this.websiteUrl = options?.websiteUrl; 44 | this.websiteText = options?.websiteText; 45 | } 46 | } 47 | 48 | export { CandidateConfig, ServerConfig }; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { createServer, bindToServer, getServerCapabilities } from "./server"; 2 | import { CandidateConfig, ServerConfig } from "./config"; 3 | import { candidatePrompts } from "./prompts"; 4 | import { interviewTools } from "./tools/interviewTools"; 5 | 6 | export { 7 | createServer, 8 | bindToServer, 9 | CandidateConfig, 10 | ServerConfig, 11 | candidatePrompts, 12 | interviewTools, 13 | getServerCapabilities 14 | }; 15 | -------------------------------------------------------------------------------- /src/prompts/index.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { z } from "zod"; 3 | import { CandidateConfig } from "../config"; 4 | 5 | // Define a type for the prompts collection 6 | interface PromptCollection { 7 | GetCandidateBackground: GetCandidateBackground; 8 | AssessTechProficiency: AssessTechProficiency; 9 | GeneratePhoneScreen: GeneratePhoneScreen; 10 | SummarizeCareerHighlights: SummarizeCareerHighlights; 11 | EvaluateJobFit: EvaluateJobFit; 12 | AssessProductCollaboration: AssessProductCollaboration; 13 | AssessStartupFit: AssessStartupFit; 14 | } 15 | 16 | function candidatePrompts(candidateConfig: CandidateConfig): PromptCollection { 17 | return { 18 | GetCandidateBackground: new GetCandidateBackground(candidateConfig), 19 | AssessTechProficiency: new AssessTechProficiency(candidateConfig), 20 | GeneratePhoneScreen: new GeneratePhoneScreen(candidateConfig), 21 | SummarizeCareerHighlights: new SummarizeCareerHighlights(candidateConfig), 22 | EvaluateJobFit: new EvaluateJobFit(candidateConfig), 23 | AssessProductCollaboration: new AssessProductCollaboration(candidateConfig), 24 | AssessStartupFit: new AssessStartupFit(candidateConfig), 25 | }; 26 | } 27 | 28 | class Prompt { 29 | name: string; 30 | description: string; 31 | schema: Record; 32 | builder: (args: Record) => { messages: Array<{ role: "user" | "assistant"; content: { type: "text"; text: string } }> }; 33 | 34 | constructor( 35 | name: string, 36 | description: string, 37 | schema: Record, 38 | builder: (args: Record) => { messages: Array<{ role: "user" | "assistant"; content: { type: "text"; text: string } }> } 39 | ) { 40 | this.name = name; 41 | this.description = description; 42 | this.schema = schema; 43 | this.builder = builder; 44 | } 45 | 46 | bind(server: McpServer) { 47 | return server.prompt( 48 | this.name, 49 | this.description, 50 | this.schema, 51 | this.builder 52 | ); 53 | } 54 | } 55 | 56 | class GetCandidateBackground extends Prompt { 57 | constructor(candidateConfig: CandidateConfig) { 58 | super( 59 | "get_candidate_background", 60 | `Get information about ${candidateConfig.name}'s experience, skills, and background`, 61 | { 62 | specific_area: z.string().optional().describe("Optional specific area of experience or background to focus on") 63 | }, 64 | (args) => ({ 65 | messages: [ 66 | { 67 | role: "user" as const, 68 | content: { 69 | type: "text" as const, 70 | text: `Please provide information about ${candidateConfig.name}'s professional background${args.specific_area ? ` with focus on ${args.specific_area}` : ''}. 71 | Include details about their experience, skills, and relevant qualifications.` 72 | } 73 | } 74 | ] 75 | }) 76 | ); 77 | } 78 | } 79 | 80 | class AssessTechProficiency extends Prompt { 81 | constructor(candidateConfig: CandidateConfig) { 82 | super( 83 | "assess_tech_proficiency", 84 | `Assess ${candidateConfig.name}'s proficiency in specific technologies`, 85 | { 86 | technologies: z.string().describe("Comma-separated list of technologies to assess") 87 | }, 88 | (args) => ({ 89 | messages: [ 90 | { 91 | role: "user" as const, 92 | content: { 93 | type: "text" as const, 94 | text: `What is ${candidateConfig.name}'s proficiency level with ${args.technologies}? 95 | Please provide specific examples from their experience and projects where available.` 96 | } 97 | } 98 | ] 99 | }) 100 | ); 101 | } 102 | } 103 | 104 | class GeneratePhoneScreen extends Prompt { 105 | constructor(candidateConfig: CandidateConfig) { 106 | super( 107 | "generate_phone_screen", 108 | `Generate interview questions for ${candidateConfig.name} based on specific technical areas`, 109 | { 110 | focus_area: z.string().describe("e.g. 'API design and testing'") 111 | }, 112 | (args) => ({ 113 | messages: [ 114 | { 115 | role: "user" as const, 116 | content: { 117 | type: "text" as const, 118 | text: `Create a phone screen for ${candidateConfig.name} around ${args.focus_area}. 119 | Include 5-7 questions that would effectively assess their knowledge and experience in this area, 120 | with consideration for their background and the skill level required for the position.` 121 | } 122 | } 123 | ] 124 | }) 125 | ); 126 | } 127 | } 128 | 129 | class SummarizeCareerHighlights extends Prompt { 130 | constructor(candidateConfig: CandidateConfig) { 131 | super( 132 | "summarize_career_highlights", 133 | `Generate a summary of ${candidateConfig.name}'s career highlights`, 134 | {}, 135 | () => ({ 136 | messages: [ 137 | { 138 | role: "user" as const, 139 | content: { 140 | type: "text" as const, 141 | text: `Generate a comprehensive summary of ${candidateConfig.name}'s career highlights. 142 | Include key achievements, notable projects, technology expertise, and professional growth. 143 | Pull information from their resume, GitHub projects, and website content where available.` 144 | } 145 | } 146 | ] 147 | }) 148 | ); 149 | } 150 | } 151 | 152 | class EvaluateJobFit extends Prompt { 153 | constructor(candidateConfig: CandidateConfig) { 154 | super( 155 | "evaluate_job_fit", 156 | `Evaluate if ${candidateConfig.name} is a good fit for a specific role`, 157 | { 158 | job_description: z.string().describe("Full job description to evaluate fit against") 159 | }, 160 | (args) => ({ 161 | messages: [ 162 | { 163 | role: "user" as const, 164 | content: { 165 | type: "text" as const, 166 | text: `Is ${candidateConfig.name} a good fit for the following role? Please analyze their skills, experience, and background against the requirements. 167 | 168 | Job Description: 169 | ${args.job_description} 170 | 171 | Provide a detailed analysis of strengths, potential gaps, and overall fit. Include specific examples from their background that relate to key requirements.` 172 | } 173 | } 174 | ] 175 | }) 176 | ); 177 | } 178 | } 179 | 180 | class AssessProductCollaboration extends Prompt { 181 | constructor(candidateConfig: CandidateConfig) { 182 | super( 183 | "assess_product_collaboration", 184 | `Understand how ${candidateConfig.name} collaborates on product vision and feature prioritization`, 185 | { 186 | collaboration_aspect: z.string().optional().describe("e.g. 'feature roadmap development'") 187 | }, 188 | (args) => ({ 189 | messages: [ 190 | { 191 | role: "user" as const, 192 | content: { 193 | type: "text" as const, 194 | text: `How would ${candidateConfig.name} contribute to ${args.collaboration_aspect || 'product vision and feature prioritization'}? 195 | Detail their approach to product collaboration, experience with product teams, and methodology for prioritizing features and improvements. 196 | Include specific examples from their past work where available.` 197 | } 198 | } 199 | ] 200 | }) 201 | ); 202 | } 203 | } 204 | 205 | class AssessStartupFit extends Prompt { 206 | constructor(candidateConfig: CandidateConfig) { 207 | super( 208 | "assess_startup_fit", 209 | `Assess ${candidateConfig.name}'s fit for a startup or small team environment`, 210 | { 211 | role_type: z.string().optional().describe("e.g. 'full-stack generalist'") 212 | }, 213 | (args) => ({ 214 | messages: [ 215 | { 216 | role: "user" as const, 217 | content: { 218 | type: "text" as const, 219 | text: `Would ${candidateConfig.name} be a good fit for ${args.role_type ? `a ${args.role_type} role at` : ''} an early-stage startup? 220 | Evaluate their adaptability, breadth of skills, ability to work with limited resources, and experience in fast-paced environments. 221 | Consider both technical capabilities and soft skills relevant to startup environments.` 222 | } 223 | } 224 | ] 225 | }) 226 | ); 227 | } 228 | } 229 | 230 | export { candidatePrompts }; 231 | -------------------------------------------------------------------------------- /src/resources/index.ts: -------------------------------------------------------------------------------- 1 | import { McpServer, ReadResourceCallback } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { CandidateConfig } from "../config"; 3 | 4 | function candidateResources(candidateConfig: CandidateConfig) { 5 | return { 6 | ResumeText: new ResumeText(candidateConfig), 7 | ResumeUrl: new ResumeUrl(candidateConfig), 8 | LinkedinUrl: new LinkedinUrl(candidateConfig), 9 | GithubUrl: new GithubUrl(candidateConfig), 10 | WebsiteUrl: new WebsiteUrl(candidateConfig), 11 | WebsiteText: new WebsiteText(candidateConfig), 12 | }; 13 | } 14 | 15 | class Resource { 16 | name: string; 17 | uri: string; 18 | callback: ReadResourceCallback; 19 | 20 | constructor(name: string, uri: string, callback: ReadResourceCallback) { 21 | this.name = name; 22 | this.uri = uri; 23 | this.callback = callback; 24 | } 25 | 26 | bind(server: McpServer) { 27 | return server.resource( 28 | this.name, 29 | this.uri, 30 | this.callback, 31 | ); 32 | } 33 | } 34 | 35 | class ResumeText extends Resource { 36 | constructor(candidateConfig: CandidateConfig) { 37 | super(`${candidateConfig.name} Resume Text`, "candidate-info://resume-text", async () => { 38 | return { 39 | contents: [ 40 | { uri: "candidate-info://resume-text", mimeType: "text/plain", text: candidateConfig.resumeText! } 41 | ] 42 | }; 43 | }); 44 | } 45 | } 46 | 47 | class ResumeUrl extends Resource { 48 | constructor(candidateConfig: CandidateConfig) { 49 | super(`${candidateConfig.name} Resume URL`, "candidate-info://resume-url", async () => { 50 | return { 51 | contents: [ 52 | { uri: "candidate-info://resume-url", mimeType: "text/plain", text: candidateConfig.resumeUrl! } 53 | ] 54 | }; 55 | }); 56 | } 57 | } 58 | 59 | class LinkedinUrl extends Resource { 60 | constructor(candidateConfig: CandidateConfig) { 61 | super(`${candidateConfig.name} LinkedIn Profile URL`, "candidate-info://linkedin-url", async () => { 62 | return { 63 | contents: [ 64 | { uri: "candidate-info://linkedin-url", mimeType: "text/plain", text: candidateConfig.linkedinUrl! } 65 | ] 66 | }; 67 | }); 68 | } 69 | } 70 | 71 | class GithubUrl extends Resource { 72 | constructor(candidateConfig: CandidateConfig) { 73 | super(`${candidateConfig.name} GitHub Profile URL`, "candidate-info://github-url", async () => { 74 | return { 75 | contents: [ 76 | { uri: "candidate-info://github-url", mimeType: "text/plain", text: candidateConfig.githubUrl! } 77 | ] 78 | }; 79 | }); 80 | } 81 | } 82 | 83 | class WebsiteUrl extends Resource { 84 | constructor(candidateConfig: CandidateConfig) { 85 | super(`${candidateConfig.name} Website URL`, "candidate-info://website-url", async () => { 86 | return { 87 | contents: [ 88 | { uri: "candidate-info://website-url", mimeType: "text/plain", text: candidateConfig.websiteUrl! } 89 | ] 90 | }; 91 | }); 92 | } 93 | } 94 | 95 | class WebsiteText extends Resource { 96 | constructor(candidateConfig: CandidateConfig) { 97 | super(`${candidateConfig.name} Website Text`, "candidate-info://website-text", async () => { 98 | return { 99 | contents: [ 100 | { uri: "candidate-info://website-text", mimeType: "text/plain", text: candidateConfig.websiteText! } 101 | ] 102 | }; 103 | }); 104 | } 105 | } 106 | 107 | export { candidateResources }; 108 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { ServerCapabilities } from "@modelcontextprotocol/sdk/types.js"; 3 | import { ServerConfig, CandidateConfig } from "./config"; 4 | import { candidateResources } from "./resources"; 5 | import { candidateTools } from "./tools"; 6 | import { candidatePrompts } from "./prompts"; 7 | import { interviewTools } from "./tools/interviewTools"; 8 | 9 | // Return a new instance of an MCP server 10 | function createServer( 11 | serverConfig: ServerConfig, 12 | candidateConfig: CandidateConfig 13 | ): McpServer { 14 | 15 | const server = new McpServer({ 16 | name: serverConfig.name, 17 | capabilities: getServerCapabilities(), 18 | version: serverConfig.version, 19 | }); 20 | 21 | return bindToServer(server, serverConfig, candidateConfig); 22 | } 23 | 24 | function bindToServer(server: McpServer, serverConfig: ServerConfig, candidateConfig: CandidateConfig) { 25 | // Bind all available candidate tools + resources based on candidate configuration 26 | const resourceInstances = candidateResources(candidateConfig); 27 | const toolInstances = candidateTools(candidateConfig, serverConfig); 28 | const promptInstances = candidatePrompts(candidateConfig); 29 | const interviewToolInstances = interviewTools(candidateConfig); 30 | 31 | if (candidateConfig.resumeText) { 32 | resourceInstances.ResumeText.bind(server); 33 | toolInstances.GetResumeText.bind(server); 34 | } 35 | 36 | if (candidateConfig.resumeUrl) { 37 | resourceInstances.ResumeUrl.bind(server); 38 | toolInstances.GetResumeUrl.bind(server); 39 | } 40 | 41 | if (candidateConfig.linkedinUrl) { 42 | resourceInstances.LinkedinUrl.bind(server); 43 | toolInstances.GetLinkedinUrl.bind(server); 44 | } 45 | 46 | if (candidateConfig.githubUrl) { 47 | resourceInstances.GithubUrl.bind(server); 48 | toolInstances.GetGithubUrl.bind(server); 49 | } 50 | 51 | if (candidateConfig.websiteUrl) { 52 | resourceInstances.WebsiteUrl.bind(server); 53 | toolInstances.GetWebsiteUrl.bind(server); 54 | } 55 | 56 | if (candidateConfig.websiteText) { 57 | resourceInstances.WebsiteText.bind(server); 58 | toolInstances.GetWebsiteText.bind(server); 59 | } 60 | 61 | // Conditionally bind ContactCandidate tool if email and Mailgun config are available 62 | if (serverConfig.contactEmail && serverConfig.mailgunApiKey && serverConfig.mailgunDomain) { 63 | toolInstances.ContactCandidate?.bind(server); 64 | } 65 | 66 | // Bind all prompt templates 67 | promptInstances.GetCandidateBackground.bind(server); 68 | promptInstances.AssessTechProficiency.bind(server); 69 | promptInstances.GeneratePhoneScreen.bind(server); 70 | promptInstances.SummarizeCareerHighlights.bind(server); 71 | promptInstances.EvaluateJobFit.bind(server); 72 | promptInstances.AssessProductCollaboration.bind(server); 73 | promptInstances.AssessStartupFit.bind(server); 74 | 75 | // Bind interview tools 76 | interviewToolInstances.GenerateInterviewQuestions.bind(server); 77 | interviewToolInstances.AssessRoleFit.bind(server); 78 | 79 | return server; 80 | } 81 | 82 | function getServerCapabilities() { 83 | return { 84 | resources: {}, 85 | tools: {}, 86 | prompts: {}, 87 | }; 88 | } 89 | 90 | export { createServer, bindToServer, getServerCapabilities }; 91 | -------------------------------------------------------------------------------- /src/tools/candidateTools.ts: -------------------------------------------------------------------------------- 1 | import { CandidateConfig, ServerConfig } from "../config"; 2 | import { Tool } from "./types"; 3 | import * as nodemailer from 'nodemailer'; 4 | import { MailgunTransport } from 'mailgun-nodemailer-transport'; 5 | 6 | 7 | import { z } from "zod"; 8 | 9 | // Define a type for the tools collection 10 | interface CandidateToolCollection { 11 | GetResumeText: GetResumeText; 12 | GetResumeUrl: GetResumeUrl; 13 | GetLinkedinUrl: GetLinkedinUrl; 14 | GetGithubUrl: GetGithubUrl; 15 | GetWebsiteUrl: GetWebsiteUrl; 16 | GetWebsiteText: GetWebsiteText; 17 | ContactCandidate?: ContactCandidate; 18 | } 19 | 20 | function candidateTools(candidateConfig: CandidateConfig, serverConfig: ServerConfig): CandidateToolCollection { 21 | const tools: CandidateToolCollection = { 22 | GetResumeText: new GetResumeText(candidateConfig), 23 | GetResumeUrl: new GetResumeUrl(candidateConfig), 24 | GetLinkedinUrl: new GetLinkedinUrl(candidateConfig), 25 | GetGithubUrl: new GetGithubUrl(candidateConfig), 26 | GetWebsiteUrl: new GetWebsiteUrl(candidateConfig), 27 | GetWebsiteText: new GetWebsiteText(candidateConfig), 28 | }; 29 | 30 | tools.ContactCandidate = new ContactCandidate(candidateConfig, serverConfig); 31 | 32 | return tools; 33 | } 34 | 35 | 36 | class ContactCandidate extends Tool { 37 | constructor(candidateConfig: CandidateConfig, serverConfig: ServerConfig) { 38 | super( 39 | "contact_candidate", 40 | `Send an email to the candidate ${candidateConfig.name}`, 41 | { 42 | subject: z.string().describe("Email subject line"), 43 | message: z.string().describe("Email message body"), 44 | reply_address: z.string().describe("Email address where the candidate can reply") 45 | }, 46 | async (args, _extra) => { 47 | try { 48 | const transporter = nodemailer.createTransport(new MailgunTransport({ 49 | auth: { 50 | domain: serverConfig.mailgunDomain!, 51 | apiKey: serverConfig.mailgunApiKey! 52 | } 53 | })); 54 | 55 | const mailOptions = { 56 | from: `AI Assistant `, 57 | to: serverConfig.contactEmail!, 58 | subject: args.subject, 59 | text: args.message, 60 | replyTo: args.reply_address 61 | }; 62 | 63 | await transporter.sendMail(mailOptions); 64 | 65 | return { 66 | content: [ 67 | { type: "text", text: `Email successfully sent to ${candidateConfig.name} at ${serverConfig.contactEmail}` } 68 | ] 69 | }; 70 | } catch (error) { 71 | console.error("Failed to send email:", error); 72 | return { 73 | content: [ 74 | { type: "text", text: `Failed to send email: ${error instanceof Error ? error.message : String(error)}` } 75 | ] 76 | }; 77 | } 78 | } 79 | ); 80 | } 81 | } 82 | 83 | class GetResumeText extends Tool { 84 | constructor(candidateConfig: CandidateConfig) { 85 | super( 86 | `get_resume_text`, 87 | `Get the resume text of the candidate ${candidateConfig.name}`, 88 | {}, 89 | async (_args, _extra) => { 90 | return { 91 | content: [ 92 | { type: "text", text: candidateConfig.resumeText || "Resume text not available" } 93 | ] 94 | }; 95 | } 96 | ); 97 | } 98 | } 99 | 100 | class GetResumeUrl extends Tool { 101 | constructor(candidateConfig: CandidateConfig) { 102 | super( 103 | `get_resume_url`, 104 | `Get the resume URL of the candidate ${candidateConfig.name}`, 105 | {}, 106 | async (_args, _extra) => { 107 | return { 108 | content: [ 109 | { type: "text", text: candidateConfig.resumeUrl || "Resume URL not available" } 110 | ] 111 | }; 112 | } 113 | ); 114 | } 115 | } 116 | 117 | class GetLinkedinUrl extends Tool { 118 | constructor(candidateConfig: CandidateConfig) { 119 | super( 120 | `get_linkedin_url`, 121 | `Get the LinkedIn URL of the candidate ${candidateConfig.name}`, 122 | {}, 123 | async (_args, _extra) => { 124 | return { 125 | content: [ 126 | { type: "text", text: candidateConfig.linkedinUrl || "LinkedIn URL not available" } 127 | ] 128 | }; 129 | } 130 | ); 131 | } 132 | } 133 | 134 | class GetGithubUrl extends Tool { 135 | constructor(candidateConfig: CandidateConfig) { 136 | super( 137 | `get_github_url`, 138 | `Get the GitHub URL of the candidate ${candidateConfig.name}`, 139 | {}, 140 | async (_args, _extra) => { 141 | return { 142 | content: [ 143 | { type: "text", text: candidateConfig.githubUrl || "GitHub URL not available" } 144 | ] 145 | }; 146 | } 147 | ); 148 | } 149 | } 150 | 151 | class GetWebsiteUrl extends Tool { 152 | constructor(candidateConfig: CandidateConfig) { 153 | super( 154 | `get_website_url`, 155 | `Get the website URL of the candidate ${candidateConfig.name}`, 156 | {}, 157 | async (_args, _extra) => { 158 | return { 159 | content: [ 160 | { type: "text", text: candidateConfig.websiteUrl || "Website URL not available" } 161 | ] 162 | }; 163 | } 164 | ); 165 | } 166 | } 167 | 168 | class GetWebsiteText extends Tool { 169 | constructor(candidateConfig: CandidateConfig) { 170 | super( 171 | `get_website_text`, 172 | `Get the website text of the candidate ${candidateConfig.name}`, 173 | {}, 174 | async (_args, _extra) => { 175 | return { 176 | content: [ 177 | { type: "text", text: candidateConfig.websiteText || "Website text not available" } 178 | ] 179 | }; 180 | } 181 | ); 182 | } 183 | } 184 | 185 | export { candidateTools }; -------------------------------------------------------------------------------- /src/tools/index.ts: -------------------------------------------------------------------------------- 1 | import { Tool } from "./types"; 2 | import { candidateTools } from "./candidateTools"; 3 | import { interviewTools } from "./interviewTools"; 4 | 5 | export { Tool, candidateTools, interviewTools }; -------------------------------------------------------------------------------- /src/tools/interviewTools.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { z } from "zod"; 3 | import { CandidateConfig, ServerConfig } from "../config"; 4 | import { Tool } from "./types"; 5 | // Define a type for the interview tools collection 6 | interface InterviewToolCollection { 7 | GenerateInterviewQuestions: GenerateInterviewQuestions; 8 | AssessRoleFit: AssessRoleFit; 9 | } 10 | 11 | function interviewTools(candidateConfig: CandidateConfig): InterviewToolCollection { 12 | return { 13 | GenerateInterviewQuestions: new GenerateInterviewQuestions(candidateConfig), 14 | AssessRoleFit: new AssessRoleFit(candidateConfig), 15 | }; 16 | } 17 | 18 | class GenerateInterviewQuestions extends Tool { 19 | constructor(candidateConfig: CandidateConfig) { 20 | super( 21 | "generate_interview_questions", 22 | `Generate tailored interview questions for ${candidateConfig.name}`, 23 | { 24 | interview_type: z.enum(["phone_screen", "technical", "behavioral", "system_design", "culture_fit"]).describe("Type of interview to generate questions for"), 25 | focus_areas: z.string().describe("Comma-separated list of technical areas to focus questions on"), 26 | difficulty: z.enum(["entry", "mid", "senior", "staff"]).describe("Target difficulty level for questions") 27 | }, 28 | async (args, _extra) => { 29 | // This function would typically call an external service or generate content 30 | // For now we just return a template response acknowledging the request 31 | const questionTypes: Record = { 32 | phone_screen: [ 33 | `Tell me about your experience with ${args.focus_areas}.`, 34 | `What projects have you worked on that used ${args.focus_areas}?`, 35 | `How do you approach learning new technologies related to ${args.focus_areas}?`, 36 | `What challenges have you faced while working with ${args.focus_areas} and how did you overcome them?`, 37 | `Where do you see the future of ${args.focus_areas} heading?` 38 | ], 39 | technical: [ 40 | `Explain how you would implement a ${args.difficulty} level solution for [problem related to ${args.focus_areas}].`, 41 | `What are the performance considerations when working with ${args.focus_areas}?`, 42 | `How would you debug an issue in a system using ${args.focus_areas}?`, 43 | `Describe the architecture of a system you built using ${args.focus_areas}.`, 44 | `How would you test an application that uses ${args.focus_areas}?` 45 | ], 46 | behavioral: [ 47 | `Describe a situation where you had to use ${args.focus_areas} to solve a difficult problem.`, 48 | `Tell me about a time when you had to learn ${args.focus_areas} quickly to meet a deadline.`, 49 | `How do you handle disagreements with team members about technical approaches related to ${args.focus_areas}?`, 50 | `Describe a project where you were proud of your contribution involving ${args.focus_areas}.`, 51 | `How do you prioritize tasks when working on a project involving ${args.focus_areas}?` 52 | ], 53 | system_design: [ 54 | `Design a scalable system that uses ${args.focus_areas} for [specific application].`, 55 | `How would you handle database scaling for a system using ${args.focus_areas}?`, 56 | `Describe how you would approach security concerns in a system using ${args.focus_areas}.`, 57 | `How would you ensure reliability and fault tolerance in a ${args.focus_areas} application?`, 58 | `Explain how you would design an API for a service that uses ${args.focus_areas}.` 59 | ], 60 | culture_fit: [ 61 | `How do you stay up to date with developments in ${args.focus_areas}?`, 62 | `How do you approach knowledge sharing about ${args.focus_areas} within your team?`, 63 | `Describe your ideal work environment when working with ${args.focus_areas}.`, 64 | `How do you handle situations where project requirements related to ${args.focus_areas} change midway?`, 65 | `What do you think makes someone successful when working with ${args.focus_areas}?` 66 | ] 67 | }; 68 | 69 | const selectedQuestions = questionTypes[args.interview_type as string] || []; 70 | 71 | return { 72 | content: [ 73 | { 74 | type: "text", 75 | text: `Here are tailored ${args.interview_type} interview questions for ${candidateConfig.name} focusing on ${args.focus_areas} at ${args.difficulty} level:\n\n${selectedQuestions.join('\n\n')}` 76 | } 77 | ] 78 | }; 79 | } 80 | ); 81 | } 82 | } 83 | 84 | class AssessRoleFit extends Tool { 85 | constructor(candidateConfig: CandidateConfig) { 86 | super( 87 | "assess_role_fit", 88 | `Assess ${candidateConfig.name}'s fit for a specific role based on job description`, 89 | { 90 | job_title: z.string().describe("Title of the job position"), 91 | job_description: z.string().describe("Full job description text"), 92 | key_requirements: z.string().describe("Comma-separated list of key requirements for the role") 93 | }, 94 | async (args, _extra) => { 95 | // Basic template response that would normally be more sophisticated 96 | return { 97 | content: [ 98 | { 99 | type: "text", 100 | text: `Role Fit Assessment for ${candidateConfig.name} - ${args.job_title}\n\n` + 101 | `I've analyzed ${candidateConfig.name}'s background against the provided job description and requirements.\n\n` + 102 | `This assessment is based on reviewing the candidate's resume, website content, and GitHub profile (if available).\n\n` + 103 | `For a detailed assessment, please use the "evaluate_job_fit" prompt with the full job description.` 104 | } 105 | ] 106 | }; 107 | } 108 | ); 109 | } 110 | } 111 | 112 | export { interviewTools }; -------------------------------------------------------------------------------- /src/tools/types.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { z } from "zod"; 3 | 4 | class Tool { 5 | name: string; 6 | description: string; 7 | schema: Record; 8 | executor: (args: Record, extra: any) => Promise<{ content: Array<{ type: "text"; text: string }> }>; 9 | 10 | constructor( 11 | name: string, 12 | description: string, 13 | schema: Record, 14 | executor: (args: Record, extra: any) => Promise<{ content: Array<{ type: "text"; text: string }> }> 15 | ) { 16 | this.name = name; 17 | this.description = description; 18 | this.schema = schema; 19 | this.executor = executor; 20 | } 21 | 22 | bind(server: McpServer) { 23 | return server.tool( 24 | this.name, 25 | this.description, 26 | this.schema, 27 | this.executor, 28 | ); 29 | } 30 | } 31 | 32 | export { Tool }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "skipLibCheck": true, 12 | "sourceMap": true 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": ["node_modules", "**/*.test.ts"] 16 | } --------------------------------------------------------------------------------