diff --git a/package-lock.json b/package-lock.json index c0ebf6f..821ae14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@toolprint/hypertool-mcp", - "version": "0.0.44", + "version": "0.0.46", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@toolprint/hypertool-mcp", - "version": "0.0.44", + "version": "0.0.46", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.15.1", diff --git a/package.json b/package.json index d004311..93b08e9 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,9 @@ "test:toolset": "vitest run src/toolset", "test:stdio": "vitest run src/integration/stdio-transport.test.ts --reporter=verbose", "test:stdio:debug": "DEBUG=* vitest run src/integration/stdio-transport.test.ts --reporter=verbose --testTimeout=60000", - "build": "rm -rf dist && tsc && npm run copy-examples && chmod +x dist/bin.js", + "build": "rm -rf dist && tsc && npm run copy-examples && npm run copy-prompts && chmod +x dist/bin.js", "copy-examples": "mkdir -p dist/examples && cp -r examples/mcp dist/examples/", + "copy-prompts": "mkdir -p dist/server/prompts && cp src/server/prompts/*.md dist/server/prompts/", "pack:hello-dxt": "cd examples/hello-dxt && npm install && cd ../.. && dxt pack examples/hello-dxt examples/hello-dxt.dxt", "build:watch": "tsc --watch", "lint": "eslint src --ext .ts", diff --git a/src/server/base.ts b/src/server/base.ts index 362c173..0357774 100644 --- a/src/server/base.ts +++ b/src/server/base.ts @@ -11,6 +11,8 @@ const logger = createChildLogger({ module: "server/base" }); import { ListToolsRequestSchema, CallToolRequestSchema, + ListPromptsRequestSchema, + GetPromptRequestSchema, Tool, Notification, } from "@modelcontextprotocol/sdk/types.js"; @@ -22,6 +24,7 @@ import { } from "./types.js"; import { EventEmitter } from "events"; import { McpHttpServer } from "./http-server.js"; +import { PromptRegistry } from "./prompts/index.js"; /** * Base Hypertool MCP server class @@ -35,11 +38,15 @@ export class MetaMCPServer extends EventEmitter { private state: ServerState = ServerState.STOPPED; private startTime?: Date; private connectedClients: number = 0; + protected promptRegistry: PromptRegistry; constructor(config: MetaMCPServerConfig) { super(); this.config = config; + // Initialize prompt registry + this.promptRegistry = new PromptRegistry(); + // Initialize MCP server this.server = new Server( { @@ -50,6 +57,7 @@ export class MetaMCPServer extends EventEmitter { { capabilities: { tools: {}, + prompts: {}, }, } ); @@ -75,6 +83,21 @@ export class MetaMCPServer extends EventEmitter { request.params.arguments ); }); + + // Handle list_prompts requests + this.server.setRequestHandler(ListPromptsRequestSchema, async () => { + return { + prompts: this.promptRegistry.getPrompts(), + }; + }); + + // Handle get_prompt requests + this.server.setRequestHandler(GetPromptRequestSchema, async (request) => { + return await this.promptRegistry.getPrompt( + request.params.name, + request.params.arguments + ); + }); } /** diff --git a/src/server/prompts/index.ts b/src/server/prompts/index.ts new file mode 100644 index 0000000..1bd6f92 --- /dev/null +++ b/src/server/prompts/index.ts @@ -0,0 +1,7 @@ +/** + * Prompts module exports + */ + +export * from "./types.js"; +export * from "./registry.js"; +export * from "./new-toolset.js"; diff --git a/src/server/prompts/new-toolset.md b/src/server/prompts/new-toolset.md new file mode 100644 index 0000000..6997999 --- /dev/null +++ b/src/server/prompts/new-toolset.md @@ -0,0 +1,239 @@ +You are helping the user create a new toolset for HyperTool MCP. Follow this comprehensive workflow to ensure they create an effective, well-organized toolset. + +# Creating a New Toolset - Guided Workflow + +## Step 1: List Available Tools + +First, discover what tools are available from the connected MCP servers: + +**Action**: Use the `list-available-tools` tool to see all discovered tools. + +**What to look for**: +- Tool names (these will be namespaced, e.g., "git__status", "docker__ps") +- Tool descriptions and capabilities +- Which MCP server each tool comes from +- Current availability status + +**Important**: Make note of the exact tool names as you'll need them for the toolset. + +--- + +## Step 2: Understand User Needs + +Before suggesting tools, ask the user clarifying questions to understand their workflow: + +**Questions to ask**: +- "What kind of work are you doing? (e.g., web development, data analysis, DevOps)" +- "What tasks do you perform most frequently?" +- "Are there specific technologies or platforms you work with?" +- "Do you have any existing toolsets or preferences?" + +**Goal**: Understand the user's context to suggest the most relevant tools. + +--- + +## Step 3: Check Existing Toolsets + +Before creating a new toolset, check if something similar already exists: + +**Action**: Use the `list-saved-toolsets` tool to see existing toolsets. + +**Evaluate**: +- Does an existing toolset already serve this purpose? +- Could an existing toolset be modified instead of creating a new one? +- Are there overlapping toolsets that could be consolidated? + +**Best Practice**: Reusing or modifying existing toolsets is often better than creating duplicates. + +--- + +## Step 4: Suggest Tools Based on Needs + +Based on the user's workflow and available tools, suggest a curated list: + +**Tool Selection Guidelines**: + +1. **Start Small**: Suggest 5-10 tools initially +2. **Focus on Essentials**: Include only tools they'll actually use +3. **Group Related Tools**: Keep tools that work together (e.g., git tools, docker tools) +4. **Consider Frequency**: Prioritize frequently-used tools +5. **Explain Choices**: Tell them why you're suggesting each tool + +**Example**: +"For web development, I recommend: +- git__status, git__commit, git__push (version control) +- npm__install, npm__run (package management) +- code__format (code quality)" + +--- + +## Step 5: Warn About Toolset Size + +**CRITICAL**: Monitor the number of tools being added. + +**Size Guidelines**: +- **Optimal**: 5-10 tools +- **Good**: 11-15 tools +- **Warning**: 16-20 tools (warn user about context overhead) +- **Too Large**: 21+ tools (strongly discourage, suggest splitting) + +**If exceeding 15 tools**, say: +⚠️ "Warning: This toolset would have [N] tools, which may cause: +- Increased context window usage +- Slower tool selection +- Higher API costs +- Potential confusion about which tool to use + +Consider: +- Splitting into multiple focused toolsets (e.g., 'dev-git' and 'dev-docker') +- Removing rarely-used tools +- Creating a smaller 'essential' version" + +--- + +## Step 6: Validate Tool Availability + +Before creating the toolset, verify all tools exist: + +**Action**: Cross-reference suggested tools with the `list-available-tools` output. + +**Check for**: +- Exact name matches (tools are case-sensitive and use specific namespacing) +- Tools that may have been disconnected or unavailable +- Typos in tool names + +**If a tool is missing**: Suggest alternatives or ask user to add the MCP server providing that tool. + +--- + +## Step 7: Suggest Toolset Name and Description + +Help the user choose a good name and description: + +**Naming Best Practices**: +- Use lowercase with hyphens (e.g., "web-dev-essentials") +- Keep it short but descriptive (2-4 words) +- Indicate the purpose or context +- Avoid generic names like "tools" or "my-toolset" + +**Good Examples**: +- "full-stack-web" +- "data-analysis" +- "devops-kubernetes" +- "content-writing" + +**Description Best Practices**: +- One sentence explaining when to use this toolset +- Mention key capabilities +- Note any specific project types + +**Example**: "Essential tools for full-stack web development including git, npm, and docker management" + +--- + +## Step 8: Build the Toolset + +Only after completing all validation steps above, proceed to create: + +**Action**: Use the `build-toolset` tool with: +- **toolsetName**: The validated name +- **toolList**: Array of exact tool names (from list-available-tools) +- **description**: The crafted description + +**Example**: +```json +{ + "toolsetName": "web-dev-essentials", + "toolList": [ + "git__status", + "git__commit", + "git__push", + "npm__install", + "npm__run", + "docker__ps", + "docker__logs" + ], + "description": "Essential tools for full-stack web development with git, npm, and docker" +} +``` + +--- + +## Step 9: Confirm and Equip + +After successful creation: + +1. **Confirm**: Tell the user the toolset was created successfully +2. **Show Contents**: List the tools that were included +3. **Suggest Next Step**: Ask if they want to equip it now using `equip-toolset` + +--- + +## Common Patterns and Templates + +Here are some common toolset patterns to suggest: + +### Web Development +``` +Tools: git, npm, docker, code-formatting, linting +Size: 8-12 tools +Purpose: Full-stack web development workflow +``` + +### Data Science +``` +Tools: python-repl, jupyter, pandas, matplotlib, database +Size: 6-10 tools +Purpose: Data analysis and visualization +``` + +### DevOps +``` +Tools: kubernetes, docker, terraform, aws-cli, monitoring +Size: 8-15 tools +Purpose: Infrastructure and deployment management +``` + +### Content Creation +``` +Tools: markdown, grammar-check, image-processing, file-management +Size: 5-8 tools +Purpose: Writing and content management +``` + +--- + +## Important Reminders + +- ✅ **Always check existing toolsets first** +- ✅ **Validate all tool names before creating** +- ✅ **Warn when toolsets exceed 15 tools** +- ✅ **Suggest descriptive names and clear descriptions** +- ✅ **Explain your tool selection choices** +- ✅ **Offer to equip the toolset after creation** +- ❌ **Never create duplicate toolsets** +- ❌ **Don't include tools the user won't actually use** +- ❌ **Don't use generic or unclear names** + +--- + +## Handling Edge Cases + +**If no tools are available**: +"I don't see any tools available from connected MCP servers. You may need to configure MCP servers first using the `list-available-tools` tool to verify connectivity." + +**If user requests too many tools**: +"I notice you're requesting [N] tools. This is quite large. Let's split this into 2-3 focused toolsets: +1. [Primary toolset name] - [Core tools] +2. [Secondary toolset name] - [Secondary tools] +This will be more efficient and easier to manage." + +**If user is unsure what they need**: +"Let's start with a small essential toolset (5-8 tools) for your most common tasks. You can always: +- Create additional specialized toolsets later +- Add more tools to this one +- Build on this foundation as you discover your needs" + +--- + +**Remember**: The goal is to help users create effective, focused toolsets that enhance their workflow without overwhelming them with too many options. Quality over quantity! diff --git a/src/server/prompts/new-toolset.ts b/src/server/prompts/new-toolset.ts new file mode 100644 index 0000000..75c2d42 --- /dev/null +++ b/src/server/prompts/new-toolset.ts @@ -0,0 +1,54 @@ +/** + * Prompt template for creating a new toolset with comprehensive workflow guidance + */ + +import { GetPromptResult } from "@modelcontextprotocol/sdk/types.js"; +import { PromptTemplate } from "./types.js"; +import { promises as fs } from "fs"; +import { join, dirname } from "path"; +import { fileURLToPath } from "url"; + +// Get the directory of the current module +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Cache for the prompt content to avoid repeated file reads +let cachedPromptContent: string | null = null; + +/** + * Load the prompt content from the markdown file + */ +async function loadPromptContent(): Promise { + if (cachedPromptContent) { + return cachedPromptContent; + } + + const promptPath = join(__dirname, "new-toolset.md"); + cachedPromptContent = await fs.readFile(promptPath, "utf-8"); + return cachedPromptContent; +} + +export const newToolsetPrompt: PromptTemplate = { + name: "new-toolset", + title: "Create New Toolset", + description: + "Interactive workflow for creating a new toolset with guided best practices", + arguments: [], + handler: async (): Promise => { + const promptText = await loadPromptContent(); + + return { + description: + "Comprehensive guide for creating a new toolset with best practices and validation", + messages: [ + { + role: "user", + content: { + type: "text", + text: promptText, + }, + }, + ], + }; + }, +}; diff --git a/src/server/prompts/registry.ts b/src/server/prompts/registry.ts new file mode 100644 index 0000000..2f23f37 --- /dev/null +++ b/src/server/prompts/registry.ts @@ -0,0 +1,60 @@ +/** + * Prompt registry for managing and serving MCP prompts + */ + +import { Prompt, GetPromptResult } from "@modelcontextprotocol/sdk/types.js"; +import { PromptTemplate, IPromptRegistry } from "./types.js"; +import { newToolsetPrompt } from "./new-toolset.js"; + +/** + * Registry implementation for managing server prompts + */ +export class PromptRegistry implements IPromptRegistry { + private prompts: Map = new Map(); + + constructor() { + // Register all available prompts + this.registerPrompt(newToolsetPrompt); + } + + /** + * Register a new prompt template + */ + private registerPrompt(template: PromptTemplate): void { + this.prompts.set(template.name, template); + } + + /** + * Get list of all available prompts + */ + getPrompts(): Prompt[] { + return Array.from(this.prompts.values()).map((template) => ({ + name: template.name, + title: template.title, + description: template.description, + arguments: template.arguments, + })); + } + + /** + * Get a specific prompt by name with optional arguments + */ + async getPrompt( + name: string, + args?: Record + ): Promise { + const template = this.prompts.get(name); + if (!template) { + throw new Error(`Prompt "${name}" not found`); + } + + return await template.handler(args); + } + + /** + * Check if a prompt exists + */ + hasPrompt(name: string): boolean { + return this.prompts.has(name); + } +} diff --git a/src/server/prompts/types.ts b/src/server/prompts/types.ts new file mode 100644 index 0000000..a536bfa --- /dev/null +++ b/src/server/prompts/types.ts @@ -0,0 +1,38 @@ +/** + * Types for MCP prompt definitions + */ + +import { Prompt, GetPromptResult } from "@modelcontextprotocol/sdk/types.js"; + +/** + * Prompt template interface for defining server prompts + */ +export interface PromptTemplate { + /** Programmatic name for the prompt */ + name: string; + /** Human-readable title for UI display */ + title?: string; + /** Description of what this prompt provides */ + description?: string; + /** Optional arguments for templating */ + arguments?: Array<{ + name: string; + description?: string; + required?: boolean; + }>; + /** Handler function that generates the prompt content */ + handler: (args?: Record) => Promise; +} + +/** + * Prompt registry interface for managing available prompts + */ +export interface IPromptRegistry { + /** Get all available prompts */ + getPrompts(): Prompt[]; + /** Get a specific prompt by name */ + getPrompt( + name: string, + args?: Record + ): Promise; +}