@sassoftware/mcp-serverjs
Version:
A mcp server for SAS Viya
237 lines (200 loc) • 5.68 kB
JavaScript
/*
* Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import express from 'express';
import { randomUUID } from "node:crypto";
import createMcpServer from './createMcpServer.js';
import https from 'https';
import cors from 'cors';
import debug from 'debug';
import fs from 'fs';
import selfsigned from 'selfsigned';
import { it } from 'node:test';
// setup express server
async function core() {
// setup for change to persistence session
const log = debug('main');
const appEnv = {
HTTPS: (process.env.HTTPS != null && process.env.HTTPS.toUpperCase() === 'TRUE') ? true : false,
tls: null,
transports: {},
mcpServer: null,
store: null,
casServer: null,
casSessionId: null,
computeSessionId: null
};
const app = express();
app.use(express.json());
app.use(cors({
origin: "*",
exposedHeaders: ['mcp-session-id'],
allowedHeaders: ["Accept", "Authorization", "Content-Type", "If-None-Match", "Accept-language", "mcp-session-id"],
}));
// setup routes
app.get('/health', (req, res) => {
log('Received request for health endpoint');
res.json({
name: '@sassoftware/mcp-server',
version: '1.0.0',
description: 'SAS Viya Sample MCP Server',
endpoints: {
mcp: '/mcp',
health: '/health'
},
usage: 'Use with MCP Inspector or compatible MCP clients like vscode or your own MCP client'
});
});
// Root endpoint info
app.get('/', (req, res) => {
res.json({
name: 'SAS Viya Sample MCP Server',
version: '1.0.0',
description: 'SAS Viya Sample MCP Server',
endpoints: {
mcp: '/mcp',
health: '/health'
},
usage: 'Use with MCP Inspector or compatible MCP clients'
});
});
// mcp endpoint - the key entrypoint for the MCP server
const handleRequest = async (req, res) => {
try {
let transport = await createMcpServer('http', appEnv);
await transport.handleRequest(req, res,req.body);
} catch (error) {
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: JSON.stringify(error),
},
id: null,
});
}
}
}
const handleRequest2 = async (req, res) => {
try {
log(req.headers);
// new server and transport on each invocation
// let mcpServer handle the request
log('Request body:', req.body);
res.json({ x: 1 })
} catch (error) {
log('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: JSON.stringify(error),
},
id: null,
});
}
}
}
app.post('/mcp', handleRequest);
app.post('/mcp2', handleRequest2);
// Start the server
const PORT = process.env.PORT || 8080;
// get user specified TLS options
if (process.env.TLS_CRT != null && fs.existsSync(process.env.TLS_CRT) === true) {
let options = {};
options.key = fs.readFileSync(process.env.TLS_KEY, { encoding: 'utf8' });
options.cert = fs.readFileSync(process.env.TLS_CRT, { encoding: 'utf8' });
options.ca = fs.readFileSync(process.env.TLS_CA, { encoding: 'utf8' });
appEnv.tls = options;
}
// place holder - https server has issues as mcp server
if (appEnv.HTTPS === true) {
if (appEnv.tls === null) {
appEnv.tls = await getTls();
appEnv.tls.requestCert = false;
appEnv.tls.rejectUnauthorized = false;
}
console.error(`[Note} MCP Server listening on port ${PORT}`);
console.error('[Note] Visit https://localhost:8080/health for health check');
console.error('[Note] Configure your mcp host to use https://localhost:8080/mcp to interact with the MCP server');
console.error('[Note] Press Ctrl+C to stop the server');
let server = https.createServer(appEnv.tls);
server.listen(PORT, () => {
});
} else {
console.error(`[Note] MCP Server listening on port ${PORT}`);
console.error('[Note] Visit http://localhost:8080/health for health check');
console.error('[Note] Configure your mcp host to use http://localhost:8080/mcp to interact with the MCP server');
console.error('[Note] Press Ctrl+C to stop the server');
let appServer = app.listen(PORT, () => {
});
process.on('SIGTERM', () => {
console.error('Server closed');
appServer.close(() => {
});
process.exit(0);
});
process.on('SIGINT', () => {
console.error('Server closed');
appServer.close(() => {
});
process.exit(0);
});
}
async function getTls() {
let options = {
keySize: 2048,
days: 360,
algorithm: "sha256",
clientCertificate: true,
extensions: {},
};
let tlscreate = (process.env.TLS_CREATE == null)
? 'TLS_CREATE=C:US,ST:NC,L:Cary,O:SAS Institute,OU:STO,CN:localhost,ALT:na.sas.com'
: process.env.TLS_CREATE;
let subjt = tlscreate.replaceAll('"', '').trim();
let subj = subjt.split(',');
let d = {};
subj.map(c => {
let r = c.split(':');
d[r[0]] = r[1];
return { value: r[1] };
});
let attr = [
{
name: 'commonName',
value: d.CN /*process.env.APPHOST*/,
},
{
name: 'countryName',
value: d.C
}, {
shortName: 'ST',
value: d.ST
}, {
name: 'localityName',
value: d.L,
}, {
name: 'organizationName',
value: d.O
},
{
shortName: 'OU',
value: d.OU
}
];
let pems = selfsigned.generate(attr);
// selfsigned generates a new keypair
let tls = {
cert: pems.cert,
key: pems.private
};
console.error('Generated self-signed TLS certificate');
return tls;
}
}
export default core;