auto-prompt-mcp-server
Version:
MCP server that automatically adds prompts to file submissions when no user prompt is provided
352 lines (305 loc) • 10.8 kB
text/typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
CallToolRequest,
ListToolsRequest,
Tool,
TextContent,
CallToolResult,
ListToolsResult,
} from '@modelcontextprotocol/sdk/types.js';
interface FileSubmission {
name: string;
content: string;
mimeType?: string;
size?: number;
}
interface ProcessFileSubmissionArgs {
files: FileSubmission[];
userPrompt?: string;
promptType?: PromptType;
}
interface ConfigurePromptsArgs {
defaultPrompt?: string;
filePrompt?: string;
imagePrompt?: string;
codePrompt?: string;
dataPrompt?: string;
}
type PromptType = 'default' | 'file' | 'image' | 'code' | 'data';
interface PromptConfig {
defaultPrompt: string;
filePrompt: string;
imagePrompt: string;
codePrompt: string;
dataPrompt: string;
}
class AutoPromptServer {
private server: Server;
private config: PromptConfig;
private readonly IMAGE_EXTENSIONS = new Set([
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'
]);
private readonly CODE_EXTENSIONS = new Set([
'js', 'ts', 'jsx', 'tsx', 'py', 'java', 'cpp', 'c', 'cs', 'php', 'rb', 'go',
'rs', 'swift', 'kt', 'scala', 'html', 'css', 'scss', 'sass', 'vue', 'svelte'
]);
private readonly DATA_EXTENSIONS = new Set([
'csv', 'json', 'xml', 'xlsx', 'xls', 'sql'
]);
constructor() {
this.server = new Server({
name: 'auto-prompt-server',
version: '1.0.0',
capabilities: {
tools: {},
},
});
// Default configuration - can be overridden via environment variables
this.config = {
defaultPrompt: process.env.AUTO_PROMPT_DEFAULT || 'Please analyze this file and provide insights.',
filePrompt: process.env.AUTO_PROMPT_FILE || 'What can you tell me about this file?',
imagePrompt: process.env.AUTO_PROMPT_IMAGE || 'Describe this image and its contents.',
codePrompt: process.env.AUTO_PROMPT_CODE || 'Review this code and explain what it does.',
dataPrompt: process.env.AUTO_PROMPT_DATA || 'Analyze this data and summarize key findings.',
};
this.setupHandlers();
}
private validateProcessFileSubmissionArgs(args: unknown): ProcessFileSubmissionArgs {
if (typeof args !== 'object' || args === null) {
throw new Error('Invalid arguments: expected object');
}
const obj = args as Record<string, unknown>;
if (!Array.isArray(obj.files)) {
throw new Error('Invalid arguments: files must be an array');
}
// Validate each file in the array
for (const file of obj.files) {
if (typeof file !== 'object' || file === null) {
throw new Error('Invalid file: each file must be an object');
}
const fileObj = file as Record<string, unknown>;
if (typeof fileObj.name !== 'string' || typeof fileObj.content !== 'string') {
throw new Error('Invalid file: name and content must be strings');
}
}
return {
files: obj.files as FileSubmission[],
userPrompt: typeof obj.userPrompt === 'string' ? obj.userPrompt : undefined,
promptType: typeof obj.promptType === 'string' ? obj.promptType as PromptType : undefined,
};
}
private validateConfigurePromptsArgs(args: unknown): ConfigurePromptsArgs {
if (typeof args !== 'object' || args === null) {
throw new Error('Invalid arguments: expected object');
}
return args as ConfigurePromptsArgs;
}
private setupHandlers(): void {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'process_file_submission',
description: 'Processes file submissions and adds appropriate prompts when none are provided',
inputSchema: {
type: 'object',
properties: {
files: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
content: { type: 'string' },
mimeType: { type: 'string' },
size: { type: 'number' }
},
required: ['name', 'content']
}
},
userPrompt: {
type: 'string',
description: 'Optional user prompt - if provided, no auto-prompt will be added'
},
promptType: {
type: 'string',
enum: ['default', 'file', 'image', 'code', 'data'],
description: 'Type of auto-prompt to use if no user prompt provided'
}
},
required: ['files']
}
},
{
name: 'configure_prompts',
description: 'Configure the default prompts used for different file types',
inputSchema: {
type: 'object',
properties: {
defaultPrompt: { type: 'string' },
filePrompt: { type: 'string' },
imagePrompt: { type: 'string' },
codePrompt: { type: 'string' },
dataPrompt: { type: 'string' }
}
}
},
{
name: 'get_configuration',
description: 'Get current prompt configuration',
inputSchema: {
type: 'object',
properties: {}
}
}
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'process_file_submission':
return await this.processFileSubmission(this.validateProcessFileSubmissionArgs(args));
case 'configure_prompts':
return await this.configurePrompts(this.validateConfigurePromptsArgs(args));
case 'get_configuration':
return await this.getConfiguration();
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return {
content: [
{
type: 'text',
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
});
}
private async processFileSubmission(args: ProcessFileSubmissionArgs) {
const { files, userPrompt, promptType = 'default' } = args;
if (!files || !Array.isArray(files) || files.length === 0) {
throw new Error('No files provided');
}
// If user provided a prompt, don't add auto-prompt
if (userPrompt && userPrompt.trim()) {
return {
content: [
{
type: 'text',
text: `User prompt detected: "${userPrompt}"\nNo auto-prompt added. Processing ${files.length} file(s) with user's prompt.`,
},
],
};
}
// No user prompt detected, add appropriate auto-prompt
const autoPrompt = this.selectAutoPrompt(files, promptType);
const filesList = files.map(file => `- ${file.name} (${file.mimeType || 'unknown type'})`).join('\n');
return {
content: [
{
type: 'text',
text: `No user prompt detected. Auto-prompt added: "${autoPrompt}"\n\nFiles submitted:\n${filesList}\n\nProcessing with auto-prompt...`,
},
],
};
}
private selectAutoPrompt(files: FileSubmission[], promptType: PromptType): string {
// If specific prompt type requested, use it
if (promptType !== 'default') {
const promptKey = `${promptType}Prompt` as keyof PromptConfig;
return this.config[promptKey] || this.config.defaultPrompt;
}
// Auto-detect based on file types
const fileExtensions = files.map(file => {
const ext = file.name.toLowerCase().split('.').pop();
return ext || '';
});
const mimeTypes = files.map(file => file.mimeType || '').filter(Boolean);
// Check for images
if (this.hasImageFiles(mimeTypes, fileExtensions)) {
return this.config.imagePrompt;
}
// Check for code files
if (this.hasCodeFiles(fileExtensions)) {
return this.config.codePrompt;
}
// Check for data files
if (this.hasDataFiles(fileExtensions, mimeTypes)) {
return this.config.dataPrompt;
}
// Default for other file types
return this.config.filePrompt;
}
private hasImageFiles(mimeTypes: string[], extensions: string[]): boolean {
return mimeTypes.some(type => type.startsWith('image/')) ||
extensions.some(ext => this.IMAGE_EXTENSIONS.has(ext));
}
private hasCodeFiles(extensions: string[]): boolean {
return extensions.some(ext => this.CODE_EXTENSIONS.has(ext));
}
private hasDataFiles(extensions: string[], mimeTypes: string[]): boolean {
return extensions.some(ext => this.DATA_EXTENSIONS.has(ext)) ||
mimeTypes.some(type =>
type.includes('spreadsheet') ||
type.includes('json') ||
type.includes('xml')
);
}
private async configurePrompts(args: ConfigurePromptsArgs) {
const validKeys: (keyof PromptConfig)[] = ['defaultPrompt', 'filePrompt', 'imagePrompt', 'codePrompt', 'dataPrompt'];
const updates: string[] = [];
for (const [key, value] of Object.entries(args)) {
if (validKeys.includes(key as keyof PromptConfig) && typeof value === 'string' && value.trim()) {
const configKey = key as keyof PromptConfig;
this.config[configKey] = value.trim();
updates.push(`${key}: "${value.trim()}"`);
}
}
if (updates.length === 0) {
throw new Error('No valid prompt configurations provided');
}
return {
content: [
{
type: 'text',
text: `Prompt configuration updated:\n${updates.join('\n')}`,
},
],
};
}
private async getConfiguration() {
const configText = Object.entries(this.config)
.map(([key, value]) => `${key}: "${value}"`)
.join('\n');
return {
content: [
{
type: 'text',
text: `Current prompt configuration:\n${configText}`,
},
],
};
}
public async run(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Auto-Prompt MCP server running on stdio');
}
}
// Run the server
const isMainModule = process.argv[1] && process.argv[1].endsWith('server.js');
if (isMainModule) {
const server = new AutoPromptServer();
server.run().catch(console.error);
}
export { AutoPromptServer };
export default AutoPromptServer;