@smartsheet/smar-mcp
Version:
A Model Context Protocol (MCP) server for interacting with the Smartsheet API. This server provides tools for searching, retrieving, and updating Smartsheet sheets through the MCP protocol.
206 lines (205 loc) • 6.82 kB
JavaScript
import { z } from "zod";
/**
* Prompt parameter schema for validation
*/
export const PromptParameterSchema = z.object({
name: z.string(),
description: z.string().optional(),
required: z.boolean().default(false),
type: z.enum(['string', 'number', 'boolean', 'object', 'array']).default('string')
});
/**
* Prompt definition schema
*/
export const PromptDefinitionSchema = z.object({
name: z.string(),
description: z.string(),
arguments: z.array(PromptParameterSchema).optional(),
template: z.string(),
category: z.string().default('general'),
version: z.string().default('1.0.0')
});
/**
* Prompt registry to store all available prompts
*/
class PromptRegistry {
prompts = new Map();
/**
* Register a new prompt
*/
register(prompt) {
// Validate prompt definition
PromptDefinitionSchema.parse(prompt);
this.prompts.set(prompt.name, prompt);
}
/**
* Get all registered prompts
*/
getAll() {
return Array.from(this.prompts.values());
}
/**
* Get a specific prompt by name
*/
get(name) {
return this.prompts.get(name);
}
/**
* Check if a prompt exists
*/
has(name) {
return this.prompts.has(name);
}
/**
* Get prompts by category
*/
getByCategory(category) {
return Array.from(this.prompts.values()).filter(p => p.category === category);
}
}
// Global prompt registry instance
export const promptRegistry = new PromptRegistry();
/**
* Template variable substitution utility
*/
export class TemplateEngine {
/**
* Replace template variables with actual values
* Supports {variable_name} syntax
*/
static substitute(template, variables) {
return template.replace(/\{([^}]+)\}/g, (match, variableName) => {
const value = variables[variableName.trim()];
if (value === undefined || value === null) {
return match; // Keep original placeholder if variable not found
}
return String(value);
});
}
/**
* Extract variable names from template
*/
static extractVariables(template) {
const matches = template.match(/\{([^}]+)\}/g);
if (!matches)
return [];
return matches.map(match => match.slice(1, -1).trim());
}
/**
* Validate that all required variables are provided
*/
static validateVariables(template, variables, requiredParams = []) {
const templateVars = this.extractVariables(template);
const requiredVars = requiredParams.filter(p => p.required).map(p => p.name);
const allRequiredVars = [...new Set([...templateVars, ...requiredVars])];
const missingVariables = allRequiredVars.filter(varName => variables[varName] === undefined || variables[varName] === null);
return {
isValid: missingVariables.length === 0,
missingVariables
};
}
}
/**
* Auto-completion provider for prompt parameters
*/
export class PromptAutoCompletion {
/**
* Get auto-completion suggestions for sheet names
* In a real implementation, this would query the Smartsheet API
*/
static async getSheetNameSuggestions() {
// Placeholder - would integrate with SmartsheetAPI
return ['Project Dashboard', 'Sprint Backlog', 'Team Resources', 'Bug Tracker'];
}
/**
* Get auto-completion suggestions for column names
*/
static async getColumnNameSuggestions(_sheetId) {
// Placeholder - would integrate with SmartsheetAPI
return ['Task Name', 'Assigned To', 'Status', 'Due Date', 'Priority', 'Story Points'];
}
/**
* Get auto-completion suggestions for user names
*/
static async getUserNameSuggestions() {
// Placeholder - would integrate with SmartsheetAPI
return ['John Doe', 'Jane Smith', 'Mike Johnson', 'Sarah Wilson'];
}
/**
* Get contextual suggestions based on parameter type
*/
static async getSuggestions(parameterName, _parameterType) {
const normalizedName = parameterName.toLowerCase();
if (normalizedName.includes('sheet')) {
return this.getSheetNameSuggestions();
}
else if (normalizedName.includes('column')) {
return this.getColumnNameSuggestions();
}
else if (normalizedName.includes('user') || normalizedName.includes('assignee') || normalizedName.includes('owner')) {
return this.getUserNameSuggestions();
}
else if (normalizedName.includes('status')) {
return ['Not Started', 'In Progress', 'Complete', 'On Hold', 'Blocked'];
}
else if (normalizedName.includes('priority')) {
return ['High', 'Medium', 'Low', 'Critical'];
}
return [];
}
}
/**
* Register individual prompt with the MCP server using a simpler approach
*/
export function registerPrompt(server, promptDef) {
server.prompt(promptDef.name, promptDef.description, async (args) => {
console.info(`Executing prompt: ${promptDef.name}`);
// Validate provided arguments
const validation = TemplateEngine.validateVariables(promptDef.template, args, promptDef.arguments);
if (!validation.isValid) {
throw new Error(`Missing required parameters: ${validation.missingVariables.join(', ')}`);
}
// Substitute variables in template
const processedTemplate = TemplateEngine.substitute(promptDef.template, args);
// Create prompt messages
const messages = [
{
role: "user",
content: {
type: "text",
text: processedTemplate
}
}
];
return {
description: promptDef.description,
messages
};
});
}
/**
* Register all prompts from the registry with the MCP server
*/
export function registerPromptHandlers(server) {
const prompts = promptRegistry.getAll();
for (const prompt of prompts) {
registerPrompt(server, prompt);
}
console.info(`✅ Registered ${prompts.length} prompts with MCP server`);
}
/**
* Utility function to create and register a prompt
*/
export function createPrompt(definition) {
promptRegistry.register(definition);
}
/**
* Utility function to get prompt completion suggestions
*/
export async function getPromptCompletionSuggestions(promptName, parameterName) {
const prompt = promptRegistry.get(promptName);
if (!prompt)
return [];
const parameter = prompt.arguments?.find(p => p.name === parameterName);
return PromptAutoCompletion.getSuggestions(parameterName, parameter?.type);
}