SystemPrompt MCP Server
by Ejb503
Verified
- src
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ErrorCode,
ListResourcesRequestSchema,
ListResourceTemplatesRequestSchema,
ListToolsRequestSchema,
McpError,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { ConfluenceClient } from "./client/confluence-client.js";
import { handleGetSpace, handleListSpaces } from "./handlers/space-handlers.js";
import {
handleCreatePage,
handleGetPage,
handleListPages,
handleUpdatePage,
} from "./handlers/page-handlers.js";
import {
handleAddLabel,
handleGetLabels,
handleRemoveLabel,
handleSearchContent,
} from "./handlers/search-label-handlers.js";
import { toolSchemas } from "./schemas/tool-schemas.js";
// Required environment variables
const requiredEnvVars = [
"CONFLUENCE_DOMAIN",
"CONFLUENCE_EMAIL",
"CONFLUENCE_API_TOKEN",
] as const;
// Validate environment variables
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
throw new Error(`Missing required environment variable: ${envVar}`);
}
}
class ConfluenceServer {
private server: Server;
private confluenceClient: ConfluenceClient;
constructor() {
console.error("Loading tool schemas...");
console.error("Available schemas:", Object.keys(toolSchemas));
// Convert tool schemas to the format expected by the MCP SDK
const tools = Object.entries(toolSchemas).map(([key, schema]) => {
console.error(`Registering tool: ${key}`);
const inputSchema = {
type: "object",
properties: schema.inputSchema.properties,
} as const;
// Only add required field if it exists in the schema
if ("required" in schema.inputSchema) {
Object.assign(inputSchema, { required: schema.inputSchema.required });
}
return {
name: key,
description: schema.description,
inputSchema,
};
});
console.error("Initializing server with tools:", JSON.stringify(tools, null, 2));
this.server = new Server(
{
name: "confluence-cloud",
version: "0.1.0",
},
{
capabilities: {
tools: {
schemas: tools,
},
resources: {
schemas: [], // Explicitly define empty resources
},
},
}
);
this.confluenceClient = new ConfluenceClient({
domain: process.env.CONFLUENCE_DOMAIN!,
email: process.env.CONFLUENCE_EMAIL!,
apiToken: process.env.CONFLUENCE_API_TOKEN!,
});
this.setupHandlers();
this.server.onerror = (error) => console.error("[MCP Error]", error);
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
}
private setupHandlers() {
// Set up required MCP protocol handlers
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: Object.entries(toolSchemas).map(([key, schema]) => ({
name: key,
description: schema.description,
inputSchema: {
type: "object",
properties: schema.inputSchema.properties,
...("required" in schema.inputSchema
? { required: schema.inputSchema.required }
: {}),
},
})),
}));
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [], // No resources provided by this server
}));
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
resourceTemplates: [], // No resource templates provided by this server
}));
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
throw new McpError(
ErrorCode.InvalidRequest,
`No resources available: ${request.params.uri}`
);
});
// Set up tool handlers
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
console.error("Received request:", JSON.stringify(request, null, 2));
const { name, arguments: args } = request.params;
console.error(`Handling tool request: ${name}`);
try {
switch (name) {
// Space operations
case "list_spaces": {
const { limit, start } = (args || {}) as { limit?: number; start?: number };
return await handleListSpaces(this.confluenceClient, { limit, start });
}
case "get_space": {
const { spaceId } = (args || {}) as { spaceId: string };
if (!spaceId) throw new McpError(ErrorCode.InvalidParams, "spaceId is required");
return await handleGetSpace(this.confluenceClient, { spaceId });
}
// Page operations
case "list_pages": {
const { spaceId, limit, start } = (args || {}) as { spaceId: string; limit?: number; start?: number };
if (!spaceId) throw new McpError(ErrorCode.InvalidParams, "spaceId is required");
return await handleListPages(this.confluenceClient, { spaceId, limit, start });
}
case "get_page": {
const { pageId } = (args || {}) as { pageId: string };
if (!pageId) throw new McpError(ErrorCode.InvalidParams, "pageId is required");
return await handleGetPage(this.confluenceClient, { pageId });
}
case "create_page": {
const { spaceId, title, content, parentId } = (args || {}) as {
spaceId: string;
title: string;
content: string;
parentId?: string
};
if (!spaceId || !title || !content) {
throw new McpError(ErrorCode.InvalidParams, "spaceId, title, and content are required");
}
return await handleCreatePage(this.confluenceClient, { spaceId, title, content, parentId });
}
case "update_page": {
const { pageId, title, content, version } = (args || {}) as {
pageId: string;
title: string;
content: string;
version: number;
};
if (!pageId || !title || !content || version === undefined) {
throw new McpError(ErrorCode.InvalidParams, "pageId, title, content, and version are required");
}
return await handleUpdatePage(this.confluenceClient, { pageId, title, content, version });
}
// Search operation
case "search_content": {
const { query, limit, start } = (args || {}) as {
query: string;
limit?: number;
start?: number
};
if (!query) throw new McpError(ErrorCode.InvalidParams, "query is required");
return await handleSearchContent(this.confluenceClient, { query, limit, start });
}
// Label operations
case "get_labels": {
const { pageId } = (args || {}) as { pageId: string };
if (!pageId) throw new McpError(ErrorCode.InvalidParams, "pageId is required");
return await handleGetLabels(this.confluenceClient, { pageId });
}
case "add_label": {
const { pageId, label } = (args || {}) as { pageId: string; label: string };
if (!pageId || !label) throw new McpError(ErrorCode.InvalidParams, "pageId and label are required");
return await handleAddLabel(this.confluenceClient, { pageId, label });
}
case "remove_label": {
const { pageId, label } = (args || {}) as { pageId: string; label: string };
if (!pageId || !label) throw new McpError(ErrorCode.InvalidParams, "pageId and label are required");
return await handleRemoveLabel(this.confluenceClient, { pageId, label });
}
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
} catch (error) {
console.error("Error handling request:", error);
if (error instanceof McpError) {
throw error;
}
throw new McpError(
ErrorCode.InternalError,
`Internal server error: ${error instanceof Error ? error.message : String(error)}`
);
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("Confluence Cloud MCP server running on stdio");
}
}
const server = new ConfluenceServer();
server.run().catch(console.error);