{"id":28089497,"url":"https://github.com/jhgaylor/node-candidate-mcp-server","last_synced_at":"2025-05-13T12:59:00.822Z","repository":{"id":290458651,"uuid":"974523801","full_name":"jhgaylor/node-candidate-mcp-server","owner":"jhgaylor","description":"A Model Context Protocol (MCP) server library that gives LLMs access to information about a candidate.","archived":false,"fork":false,"pushed_at":"2025-05-08T03:41:32.000Z","size":82,"stargazers_count":62,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-08T04:33:10.371Z","etag":null,"topics":["ai","mcp","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jhgaylor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-28T23:07:03.000Z","updated_at":"2025-05-08T03:41:36.000Z","dependencies_parsed_at":"2025-04-29T00:26:35.913Z","dependency_job_id":"15e11590-1a61-462e-9282-bd9a7290f481","html_url":"https://github.com/jhgaylor/node-candidate-mcp-server","commit_stats":null,"previous_names":["jhgaylor/node-candidate-mcp-server"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhgaylor%2Fnode-candidate-mcp-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhgaylor%2Fnode-candidate-mcp-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhgaylor%2Fnode-candidate-mcp-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhgaylor%2Fnode-candidate-mcp-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jhgaylor","download_url":"https://codeload.github.com/jhgaylor/node-candidate-mcp-server/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253948393,"owners_count":21988953,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","mcp","typescript"],"created_at":"2025-05-13T12:59:00.277Z","updated_at":"2025-05-13T12:59:00.807Z","avatar_url":"https://github.com/jhgaylor.png","language":"TypeScript","funding_links":[],"categories":["📚 Projects (1974 total)","🤖 AI/ML"],"sub_categories":["MCP Servers"],"readme":"# Candidate MCP Server Library\n\nA Model Context Protocol (MCP) server that gives LLMs access to information about a candidate.\n\n## Overview\n\n\u003e **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.\n\n### Resources\n\nThis MCP server provides the following resources:\n\n- `candidate-info://resume-text`: Resume content as text\n- `candidate-info://resume-url`: URL to the resume\n- `candidate-info://linkedin-url`: LinkedIn profile URL\n- `candidate-info://github-url`: GitHub profile URL\n- `candidate-info://website-url`: Personal website URL\n- `candidate-info://website-text`: Content from the personal website\n\n### Tools\n\nThis MCP server also provides tools that return the same candidate information:\n\n- `get_resume_text`: Returns the candidate's resume content as text\n- `get_resume_url`: Returns the URL to the candidate's resume\n- `get_linkedin_url`: Returns the candidate's LinkedIn profile URL\n- `get_github_url`: Returns the candidate's GitHub profile URL\n- `get_website_url`: Returns the candidate's personal website URL\n- `get_website_text`: Returns the content from the candidate's personal website\n- `contact_candidate`: Sends an email to the candidate (requires Mailgun configuration)\n\n## Usage\n\n`npm install @jhgaylor/candidate-mcp-server`\n\n### Library Usage\n\nThis package is designed to be imported and used within your own applications.\n\n#### Stdio\n\nStarting the process is a breeze with stdio. The interesting part is providing the candidate configuration.\n\nWhere 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!\n\n```javascript\nimport { createServer } from '@jhgaylor/candidate-mcp-server';\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\n\n// Configure your server\nconst serverConfig = { \n  name: \"MyCandidateServer\", \n  version: \"1.0.0\",\n  mailgunApiKey: process.env.MAILGUN_API_KEY,\n  mailgunDomain: process.env.MAILGUN_DOMAIN\n};\nconst candidateConfig = { \n  name: \"John Doe\",\n  email: \"john.doe@example.com\", // Required for the contact_candidate tool\n  resumeUrl: \"https://example.com/resume.pdf\",\n  // other candidate properties\n};\n\n// Create server instance\nconst server = createServer(serverConfig, candidateConfig);\n\n// Connect with your preferred transport\nawait server.connect(new StdioServerTransport());\n// or integrate with your existing HTTP server\n```\n\n#### StreamableHttp\n\nUsing the example code provided by the typescript sdk we can bind this mcp server to an express server.\n\n```javascript\nimport express from 'express';\nimport { Request, Response } from 'express';\nimport { createServer } from '@jhgaylor/candidate-mcp-server';\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamablehttp.js\";\n\n// Configure your server\nconst serverConfig = { \n  name: \"MyCandidateServer\", \n  version: \"1.0.0\",\n  mailgunApiKey: process.env.MAILGUN_API_KEY,\n  mailgunDomain: process.env.MAILGUN_DOMAIN,\n  contactEmail: \"john.doe@example.com\",\n};\nconst candidateConfig = { \n  name: \"John Doe\",\n  resumeUrl: \"https://example.com/resume.pdf\",\n  // other candidate properties\n};\n\n// Factory function to create a new server instance for each request\nconst getServer = () =\u003e createServer(serverConfig, candidateConfig);\n\nconst app = express();\napp.use(express.json());\n\napp.post('/mcp', async (req: Request, res: Response) =\u003e {\n  // In stateless mode, create a new instance of transport and server for each request\n  // to ensure complete isolation. A single instance would cause request ID collisions\n  // when multiple clients connect concurrently.\n  \n  try {\n    const server = getServer(); \n    const transport = new StreamableHTTPServerTransport({\n      sessionIdGenerator: undefined,\n    });\n    res.on('close', () =\u003e {\n      console.log('Request closed');\n      transport.close();\n      server.close();\n    });\n    await server.connect(transport);\n    await transport.handleRequest(req, res, req.body);\n  } catch (error) {\n    console.error('Error handling MCP request:', error);\n    if (!res.headersSent) {\n      res.status(500).json({\n        jsonrpc: '2.0',\n        error: {\n          code: -32603,\n          message: 'Internal server error',\n        },\n        id: null,\n      });\n    }\n  }\n});\n\napp.get('/mcp', async (req: Request, res: Response) =\u003e {\n  console.log('Received GET MCP request');\n  res.writeHead(405).end(JSON.stringify({\n    jsonrpc: \"2.0\",\n    error: {\n      code: -32000,\n      message: \"Method not allowed.\"\n    },\n    id: null\n  }));\n});\n\napp.delete('/mcp', async (req: Request, res: Response) =\u003e {\n  console.log('Received DELETE MCP request');\n  res.writeHead(405).end(JSON.stringify({\n    jsonrpc: \"2.0\",\n    error: {\n      code: -32000,\n      message: \"Method not allowed.\"\n    },\n    id: null\n  }));\n});\n\n// Start the server\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () =\u003e {\n  console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`);\n});\n```\n\n#### Express\n\nInstead of writing the binding between express and the mcp transport yourself, you can use `express-mcp-handler` to do it for you.\n\n`npm install express-mcp-handler`\n\n```javascript\nimport express from 'express';\nimport { statelessHandler } from 'express-mcp-handler';\nimport { createServer } from './server';\n\n// You can configure the server factory to include Mailgun settings\nconst createServerWithConfig = () =\u003e {\n  const serverConfig = { \n    name: \"MyCandidateServer\", \n    version: \"1.0.0\",\n    mailgunApiKey: process.env.MAILGUN_API_KEY,\n    mailgunDomain: process.env.MAILGUN_DOMAIN,\n    contactEmail: \"john.doe@example.com\",\n  };\n  const candidateConfig = { \n    name: \"John Doe\",\n    resumeUrl: \"https://example.com/resume.pdf\",\n    // other candidate properties\n  };\n  \n  return createServer(serverConfig, candidateConfig);\n};\n\n// Configure the stateless handler\nconst handler = statelessHandler(createServerWithConfig);\n\n// Create Express app\nconst app = express();\napp.use(express.json());\n\n// Mount the handler (stateless only needs POST)\napp.post('/mcp', handler);\n\n// Start the server\nconst PORT = process.env.PORT || 3002;\napp.listen(PORT, () =\u003e {\n  console.log(`Stateless MCP server running on port ${PORT}`);\n});\n```\n\n## Development \n\n```bash\n# Install dependencies\nnpm install\n\n# Build the project\nnpm run build\n\n# Run in development mode with auto-restart\nnpm run dev\n```\n\n### Demo / Debug Startup via stdio\n\n```bash\n# Start with STDIO (demo only)\nnpm start\n```\n\nWhen running with STDIO, you can interact with the server by sending MCP messages as single-line JSON objects:\n\n```bash\n# Example of sending an initialize message via STDIO\necho '{\"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\n\n# List resources\necho '{\"jsonrpc\": \"2.0\",\"id\": 2,\"method\": \"resources/list\",\"params\": {}}' | node dist/index.js --stdio\n\n# Access a resource\necho '{\"jsonrpc\": \"2.0\",\"id\": 3,\"method\": \"resources/read\",\"params\": {\"uri\": \"candidate-info://resume-text\"}}' | node dist/index.js --stdio\n\n# List Tools\necho '{\"jsonrpc\": \"2.0\",\"id\": 2,\"method\": \"tools/list\",\"params\": {}}' | node dist/index.js --stdio\n\n# Call a tool\necho '{\"jsonrpc\": \"2.0\",\"id\": 4,\"method\": \"tools/call\",\"params\": {\"name\": \"get_resume_text\", \"args\": {}}}' | node dist/index.js --stdio\n\n# Send an email to the candidate\necho '{\"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\n```\n\nEach message must be on a single line with no line breaks within the JSON object.\n\n## Features\n\n- Library-first design for integration into other applications\n- Modular resource system for extending with custom candidate information\n- TypeScript for type safety and better developer experience\n- Implements the full Model Context Protocol specification\n- Supports multiple transport types (STDIO, HTTP, Streamable HTTP)\n- Minimal dependencies\n\n## Server Structure\n\n```\nsrc/\n  ├── index.ts                # Main package entry point\n  ├── server.ts               # MCP server factory with configuration\n  ├── config.ts               # Configuration type definitions\n  └── resources/              # Modular resource definitions\n      └── index.ts            # Resource factory and implementation\n```\n\n## MCP Protocol\n\nThis 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.\n\n### API Usage\n\nOnce 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:\n\n#### Initialize\n\n```bash\ncurl -X POST http://your-application-url/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json\" \\\n  -H \"Accept: text/event-stream\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"initialize\",\n    \"params\": {\n      \"protocolVersion\": \"2024-11-05\",\n      \"capabilities\": {\n        \"roots\": {\n          \"listChanged\": true\n        },\n        \"sampling\": {}\n      },\n      \"clientInfo\": {\n        \"name\": \"ExampleClient\",\n        \"version\": \"1.0.0\"\n      }\n    }\n  }'\n```\n\n#### Access Candidate Resources\n\n```bash\ncurl -X POST http://your-application-url/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json\" \\\n  -H \"Accept: text/event-stream\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"method\": \"resources/read\",\n    \"params\": {\n      \"uri\": \"candidate-info://resume-text\"\n    },\n    \"id\": 2\n  }'\n```\n\n## Extending the Library\n\nThis library is designed to be extended with custom resources, tools, and prompts. Here's how to add your own resources:\n\n```javascript\nimport { McpServer, Resource } from '@jhgaylor/candidate-mcp-server';\n\n// Create your custom resource class\nclass CustomCandidateResource extends Resource {\n  constructor(candidateConfig) {\n    super(\n      `${candidateConfig.name} Custom Data`, \n      \"candidate-info://custom-data\", \n      async () =\u003e {\n        return {\n          contents: [\n            { \n              uri: \"candidate-info://custom-data\", \n              mimeType: \"text/plain\", \n              text: \"Your custom candidate data here\"\n            }\n          ]\n        };\n      }\n    );\n  }\n}\n\n// Create server with standard configuration\nconst server = createServer(serverConfig, candidateConfig);\n\n// Add your custom resource\nconst customResource = new CustomCandidateResource(candidateConfig);\ncustomResource.bind(server);\n\n// Connect with preferred transport\n// ...\n```\n\n### Adding Custom Tools\n\nYou can also extend the library with custom tools:\n\n```javascript\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { createServer } from '@jhgaylor/candidate-mcp-server';\n\n// Create server with standard configuration\nconst server = createServer(serverConfig, candidateConfig);\n\n// Add a custom tool\nserver.tool(\n  'get_candidate_skills',\n  'Returns a list of the candidate skills',\n  {},\n  async (_args, _extra) =\u003e {\n    return {\n      content: [\n        { \n          type: \"text\", \n          text: \"JavaScript, TypeScript, React, Node.js, MCP Protocol\" \n        }\n      ]\n    };\n  }\n);\n\n// Connect with preferred transport\n// ...\n```\n\n## Requirements\n\n- Node.js 20+ \n- npm or yarn\n\n## License\n\n[MIT](LICENSE) \n\n## Publishing to npm\n\nLog in to npm if you haven't already:\n```bash\nnpm login\n```\n\nPublish the package to npm (will run your prepublishOnly build):\n```bash\nnpm publish\n```\n\nTo bump, tag, and push a new version:\n```bash\nnpm version patch    # or minor, major\ngit push origin main --tags\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjhgaylor%2Fnode-candidate-mcp-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjhgaylor%2Fnode-candidate-mcp-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjhgaylor%2Fnode-candidate-mcp-server/lists"}