@runbook-docs/mcp-server
Version:
Runbook Model Context Protocol Server
158 lines (157 loc) • 5.68 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildServer = buildServer;
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const resources_1 = require("./resources/resources");
const tools_1 = require("./tools/tools");
const prompts_1 = require("./prompts/prompts");
const client_1 = require("@runbook-docs/client");
function kebabToPascal(kebab) {
if (!kebab)
return '';
return kebab
.split('-')
.filter(Boolean)
.map((s) => s[0].toUpperCase() + s.slice(1).toLowerCase())
.join('');
}
function getErrorResponse(e) {
let text;
if (e instanceof client_1.RequestError) {
text = JSON.stringify(e.attributes);
}
else {
text = e instanceof Error ? e.message : String(e);
}
return {
content: [
{
type: 'text',
text
}
],
isError: true
};
}
async function buildServer(state) {
const server = new index_js_1.Server({
name: kebabToPascal(state.name),
version: '1.7.0'
}, {
capabilities: {
resources: {},
tools: {},
prompts: {}
},
instructions: `
This MCP server provides access to Runbook for document management and business workflow automation.
Always use Runbook MCP tools (e.g. runbook-create-article, runbook-update-article, runbook-list-articles) to interact with Runbook. Never attempt alternative approaches such as direct HTTP requests or curl commands instead of using these tools.
This MCP server connects to the Runbook tenant: ${state.baseUrl}.
When multiple Runbook MCP servers are configured, each connects to a different tenant. Always confirm which tenant the user intends to operate on before creating or updating content. If unclear, ask the user to specify the target tenant.
`
});
const resourceHandlersInstance = (0, resources_1.resourceHandlers)(state);
const toolHandlersInstance = (0, tools_1.toolHandlers)(state);
const promptHandlersInstance = (0, prompts_1.promptHandlers)(state);
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
try {
return { resources: await resourceHandlersInstance.listResources() };
}
catch (e) {
return getErrorResponse(e);
}
});
server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
try {
const content = await resourceHandlersInstance.readResource(uri);
return {
contents: [
{
uri,
text: content,
mimeType: 'application/json'
}
]
};
}
catch (e) {
return getErrorResponse(e);
}
});
server.setRequestHandler(types_js_1.ListResourceTemplatesRequestSchema, () => {
try {
return resourceHandlersInstance.listResourceTemplates();
}
catch (e) {
return getErrorResponse(e);
}
});
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
return {
tools: Object.entries(toolHandlersInstance).map(([name, handler]) => ({
name,
description: handler.description,
inputSchema: handler.inputSchema,
annotations: handler.annotations
}))
};
});
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const handler = toolHandlersInstance[name];
if (!handler) {
throw new Error(`Unknown tool: ${name}`);
}
try {
return await handler.handler(args);
}
catch (e) {
return getErrorResponse(e);
}
});
server.setRequestHandler(types_js_1.ListPromptsRequestSchema, async () => {
return {
prompts: Object.entries(promptHandlersInstance).map(([, handler]) => ({
name: handler.name,
title: handler.title || handler.name,
description: handler.description,
arguments: handler.arguments
}))
};
});
server.setRequestHandler(types_js_1.GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const handler = promptHandlersInstance[name];
if (!handler) {
throw new Error(`Unknown prompt: ${name}`);
}
// Simple template replacement
let prompt = handler.prompt;
if (args) {
for (const [key, value] of Object.entries(args)) {
const regex = new RegExp(`{{${key}}}`, 'g');
prompt = prompt.replace(regex, String(value));
}
// Handle conditional blocks like {{#if runStateUid}}
prompt = prompt.replace(/{{#if (\w+)}}([^}]*){{\/if}}/g, (_, condition, content) => {
return args[condition] ? content : '';
});
}
return {
description: handler.description,
messages: [
{
role: 'user',
content: {
type: 'text',
text: prompt
}
}
]
};
});
return server;
}