@dav3/mcp-dynamics365-server
Version:
Generic MCP server for Dynamics 365 CRM operations (AI-generated side project with limited maintenance)
185 lines (184 loc) • 8.01 kB
JavaScript
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import cors from "cors";
import express from "express";
import { randomUUID } from "node:crypto";
import { validateAccessToken } from '../auth/token-validator.js';
export async function startHttpTransport(server, port = 3300, host = 'localhost') {
console.error(`Starting HTTP server on ${host}:${port}...`);
const app = express();
// Enable CORS for browser compatibility
app.use(cors({
origin: '*',
exposedHeaders: ['Mcp-Session-Id'],
allowedHeaders: ['Content-Type', 'mcp-session-id'],
}));
app.use(express.json());
// Map to store transports by session ID
const transports = {};
// Determine if OAuth is enabled
const oauthEnabled = Boolean(process.env.OAUTH_MCP_RESOURCE &&
process.env.OAUTH_AUTH_URL &&
process.env.OAUTH_BASE_URL &&
process.env.OAUTH_JWKS_URL &&
process.env.OAUTH_TOKEN_URL);
if (oauthEnabled) {
console.log('MCP HTTP server: OAuth authentication ENABLED');
}
else {
console.log('MCP HTTP server: OAuth authentication DISABLED');
}
// Conditionally add MCP OAuth authentication middleware
if (oauthEnabled) {
app.use('/mcp', async (req, res, next) => {
if (!['POST', 'GET', 'DELETE'].includes(req.method))
return next();
const authHeader = req.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Bearer ')) {
res.setHeader('WWW-Authenticate', `Bearer realm="MCP", resource_metadata="/mcp/.well-known/oauth-protected-resource"`);
return res.status(401).json({
jsonrpc: '2.0',
error: {
code: -32001,
message: 'Unauthorized: Missing or invalid access token',
},
id: null,
});
}
const token = authHeader.slice('Bearer '.length);
const flow = process.env.OAUTH_FLOW || 'jwt';
let validationOptions = {};
if (flow === 'jwt') {
validationOptions.jwksUri = process.env.OAUTH_JWKS_URL;
validationOptions.expectedAudience = process.env.OAUTH_MCP_RESOURCE;
}
else if (flow === 'opaque') {
validationOptions.opaqueUserApi = process.env.OAUTH_OPAQUE_USER_API || 'https://api.github.com/user';
}
try {
const result = await validateAccessToken(token, flow, validationOptions);
if (!result.valid)
throw new Error(result.reason || 'Invalid token');
// Optionally attach user info to request
req.user = result.payload;
next();
}
catch (err) {
res.setHeader('WWW-Authenticate', `Bearer realm="MCP", resource_metadata="/mcp/.well-known/oauth-protected-resource"`);
return res.status(401).json({
jsonrpc: '2.0',
error: {
code: -32001,
message: `Unauthorized: ${err instanceof Error ? err.message : 'Invalid access token'}`,
},
id: null,
});
}
});
}
else {
console.warn('MCP OAuth authentication is DISABLED (missing OAUTH_MCP_RESOURCE or OAuth envs)');
}
// MCP protected resource metadata endpoint
app.get('/mcp/.well-known/oauth-protected-resource', (req, res) => {
if (!oauthEnabled) {
return res.status(404).json({ error: 'OAuth metadata not available: server is running unauthenticated.' });
}
// Per RFC9728, return JSON with authorization_servers field
res.json({
resource: process.env.OAUTH_MCP_RESOURCE,
authorization_servers: [process.env.OAUTH_AUTH_URL],
issuer: process.env.OAUTH_BASE_URL,
jwks_uri: process.env.OAUTH_JWKS_URL,
token_endpoint: process.env.OAUTH_TOKEN_URL,
response_types_supported: ['code', 'token'],
grant_types_supported: ['authorization_code', 'client_credentials'],
});
});
// Handle POST requests for client-to-server communication
app.post('/mcp', async (req, res) => {
try {
// Check for existing session ID
const sessionId = req.headers['mcp-session-id'];
let transport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
}
else if (!sessionId && req.body?.method === 'initialize') {
// New initialization request
const isLocalDevelopment = host === 'localhost' || host === '127.0.0.1';
const isWildcardBinding = host === '0.0.0.0';
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sessionId) => {
// Store the transport by session ID
transports[sessionId] = transport;
},
// Disable DNS rebinding protection for localhost and wildcard binding
// Enable only for specific remote hosts
enableDnsRebindingProtection: !isLocalDevelopment && !isWildcardBinding,
allowedHosts: (!isLocalDevelopment && !isWildcardBinding) ? [host, `${host}:${port}`] : undefined,
});
// Clean up transport when closed
transport.onclose = () => {
if (transport.sessionId) {
delete transports[transport.sessionId];
}
};
// Connect the MCP server to this transport
await server.connect(transport);
}
else {
// Invalid request
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided',
},
id: null,
});
return;
}
// Handle the request
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,
});
}
}
});
// Handle GET requests for server-to-client notifications via SSE
app.get('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
const transport = transports[sessionId];
await transport.handleRequest(req, res);
});
// Handle DELETE requests for session termination
app.delete('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
const transport = transports[sessionId];
await transport.handleRequest(req, res);
});
// Start the HTTP server
app.listen(port, host, () => {
console.error(`HTTP server running on http://${host}:${port}`);
});
}