node-candidate-mcp-server
If you are the rightful owner of node-candidate-mcp-server and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to henry@mcphub.com.
A Model Context Protocol (MCP) server that provides LLMs access to candidate information.
Candidate MCP Server Library
A Model Context Protocol (MCP) server that gives LLMs access to information about a candidate.
Overview
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.
Resources
This MCP server provides the following resources:
candidate-info://resume-text
: Resume content as textcandidate-info://resume-url
: URL to the resumecandidate-info://linkedin-url
: LinkedIn profile URLcandidate-info://github-url
: GitHub profile URLcandidate-info://website-url
: Personal website URLcandidate-info://website-text
: Content from the personal website
Tools
This MCP server also provides tools that return the same candidate information:
get_resume_text
: Returns the candidate's resume content as textget_resume_url
: Returns the URL to the candidate's resumeget_linkedin_url
: Returns the candidate's LinkedIn profile URLget_github_url
: Returns the candidate's GitHub profile URLget_website_url
: Returns the candidate's personal website URLget_website_text
: Returns the content from the candidate's personal websitecontact_candidate
: Sends an email to the candidate (requires Mailgun configuration)
Usage
npm install @jhgaylor/candidate-mcp-server
Library Usage
This package is designed to be imported and used within your own applications.
Stdio
Starting the process is a breeze with stdio. The interesting part is providing the candidate configuration.
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!
import { createServer } from '@jhgaylor/candidate-mcp-server';
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
// Configure your server
const serverConfig = {
name: "MyCandidateServer",
version: "1.0.0",
mailgunApiKey: process.env.MAILGUN_API_KEY,
mailgunDomain: process.env.MAILGUN_DOMAIN
};
const candidateConfig = {
name: "John Doe",
email: "john.doe@example.com", // Required for the contact_candidate tool
resumeUrl: "https://example.com/resume.pdf",
// other candidate properties
};
// Create server instance
const server = createServer(serverConfig, candidateConfig);
// Connect with your preferred transport
await server.connect(new StdioServerTransport());
// or integrate with your existing HTTP server
StreamableHttp
Using the example code provided by the typescript sdk we can bind this mcp server to an express server.
import express from 'express';
import { Request, Response } from 'express';
import { createServer } from '@jhgaylor/candidate-mcp-server';
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamablehttp.js";
// Configure your server
const serverConfig = {
name: "MyCandidateServer",
version: "1.0.0",
mailgunApiKey: process.env.MAILGUN_API_KEY,
mailgunDomain: process.env.MAILGUN_DOMAIN,
contactEmail: "john.doe@example.com",
};
const candidateConfig = {
name: "John Doe",
resumeUrl: "https://example.com/resume.pdf",
// other candidate properties
};
// Factory function to create a new server instance for each request
const getServer = () => createServer(serverConfig, candidateConfig);
const app = express();
app.use(express.json());
app.post('/mcp', async (req: Request, res: Response) => {
// In stateless mode, create a new instance of transport and server for each request
// to ensure complete isolation. A single instance would cause request ID collisions
// when multiple clients connect concurrently.
try {
const server = getServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
res.on('close', () => {
console.log('Request closed');
transport.close();
server.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error',
},
id: null,
});
}
}
});
app.get('/mcp', async (req: Request, res: Response) => {
console.log('Received GET MCP request');
res.writeHead(405).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Method not allowed."
},
id: null
}));
});
app.delete('/mcp', async (req: Request, res: Response) => {
console.log('Received DELETE MCP request');
res.writeHead(405).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Method not allowed."
},
id: null
}));
});
// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`);
});
Express
Instead of writing the binding between express and the mcp transport yourself, you can use express-mcp-handler
to do it for you.
npm install express-mcp-handler
import express from 'express';
import { statelessHandler } from 'express-mcp-handler';
import { createServer } from './server';
// You can configure the server factory to include Mailgun settings
const createServerWithConfig = () => {
const serverConfig = {
name: "MyCandidateServer",
version: "1.0.0",
mailgunApiKey: process.env.MAILGUN_API_KEY,
mailgunDomain: process.env.MAILGUN_DOMAIN,
contactEmail: "john.doe@example.com",
};
const candidateConfig = {
name: "John Doe",
resumeUrl: "https://example.com/resume.pdf",
// other candidate properties
};
return createServer(serverConfig, candidateConfig);
};
// Configure the stateless handler
const handler = statelessHandler(createServerWithConfig);
// Create Express app
const app = express();
app.use(express.json());
// Mount the handler (stateless only needs POST)
app.post('/mcp', handler);
// Start the server
const PORT = process.env.PORT || 3002;
app.listen(PORT, () => {
console.log(`Stateless MCP server running on port ${PORT}`);
});
Development
# Install dependencies
npm install
# Build the project
npm run build
# Run in development mode with auto-restart
npm run dev
Demo / Debug Startup via stdio
# Start with STDIO (demo only)
npm start
When running with STDIO, you can interact with the server by sending MCP messages as single-line JSON objects:
# Example of sending an initialize message via STDIO
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
# List resources
echo '{"jsonrpc": "2.0","id": 2,"method": "resources/list","params": {}}' | node dist/index.js --stdio
# Access a resource
echo '{"jsonrpc": "2.0","id": 3,"method": "resources/read","params": {"uri": "candidate-info://resume-text"}}' | node dist/index.js --stdio
# List Tools
echo '{"jsonrpc": "2.0","id": 2,"method": "tools/list","params": {}}' | node dist/index.js --stdio
# Call a tool
echo '{"jsonrpc": "2.0","id": 4,"method": "tools/call","params": {"name": "get_resume_text", "args": {}}}' | node dist/index.js --stdio
# Send an email to the candidate
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
Each message must be on a single line with no line breaks within the JSON object.
Features
- Library-first design for integration into other applications
- Modular resource system for extending with custom candidate information
- TypeScript for type safety and better developer experience
- Implements the full Model Context Protocol specification
- Supports multiple transport types (STDIO, HTTP, Streamable HTTP)
- Minimal dependencies
Server Structure
src/
├── index.ts # Main package entry point
├── server.ts # MCP server factory with configuration
├── config.ts # Configuration type definitions
└── resources/ # Modular resource definitions
└── index.ts # Resource factory and implementation
MCP Protocol
This library implements the Model Context Protocol (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.
API Usage
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:
Initialize
curl -X POST http://your-application-url/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Accept: text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {}
},
"clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
}'
Access Candidate Resources
curl -X POST http://your-application-url/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Accept: text/event-stream" \
-d '{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {
"uri": "candidate-info://resume-text"
},
"id": 2
}'
Extending the Library
This library is designed to be extended with custom resources, tools, and prompts. Here's how to add your own resources:
import { McpServer, Resource } from '@jhgaylor/candidate-mcp-server';
// Create your custom resource class
class CustomCandidateResource extends Resource {
constructor(candidateConfig) {
super(
`${candidateConfig.name} Custom Data`,
"candidate-info://custom-data",
async () => {
return {
contents: [
{
uri: "candidate-info://custom-data",
mimeType: "text/plain",
text: "Your custom candidate data here"
}
]
};
}
);
}
}
// Create server with standard configuration
const server = createServer(serverConfig, candidateConfig);
// Add your custom resource
const customResource = new CustomCandidateResource(candidateConfig);
customResource.bind(server);
// Connect with preferred transport
// ...
Adding Custom Tools
You can also extend the library with custom tools:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { createServer } from '@jhgaylor/candidate-mcp-server';
// Create server with standard configuration
const server = createServer(serverConfig, candidateConfig);
// Add a custom tool
server.tool(
'get_candidate_skills',
'Returns a list of the candidate skills',
{},
async (_args, _extra) => {
return {
content: [
{
type: "text",
text: "JavaScript, TypeScript, React, Node.js, MCP Protocol"
}
]
};
}
);
// Connect with preferred transport
// ...
Requirements
- Node.js 20+
- npm or yarn
License
Publishing to npm
Log in to npm if you haven't already:
npm login
Publish the package to npm (will run your prepublishOnly build):
npm publish
To bump, tag, and push a new version:
npm version patch # or minor, major
git push origin main --tags