Skip to main content

creating-mcp-servers

Published by @askskills

Build Model Context Protocol (MCP) servers that extend Claude and other AI assistants with custom tools

Third-Party Content

This skill is created by a third-party developer, not Asterism. While security-scanned, we cannot guarantee safety. By using any skill, you accept our Terms of Service and assume all risk.

Installation
aster install creating-mcp-servers

Universal installation

By installing, you accept the Terms of Service. Skills are third-party content; Asterism is not liable for their use.

README

# Creating MCP Servers

## Viral Potential: ⭐⭐⭐⭐⭐

Build MCP servers that give Claude and other AI assistants new capabilities.

## What This Skill Does

This skill generates complete MCP server implementations:
- Tool definitions with proper schemas
- Resource providers for data access
- Server configuration
- Client integration instructions
- Testing utilities

## MCP Architecture Overview

```
┌─────────────────┐     ┌─────────────────┐
│  Claude/Client  │────▶│   MCP Server    │
│                 │◀────│                 │
└─────────────────┘     └────────┬────────┘
                                 │
                    ┌────────────┼────────────┐
                    ▼            ▼            ▼
              ┌─────────┐  ┌─────────┐  ┌─────────┐
              │  Tool   │  │Resource │  │ Prompt  │
              │ Handler │  │Provider │  │Template │
              └─────────┘  └─────────┘  └─────────┘
```

## TypeScript MCP Server Template

### Project Structure

```
my-mcp-server/
├── src/
│   ├── index.ts        # Server entry point
│   ├── tools/          # Tool implementations
│   │   └── myTool.ts
│   ├── resources/      # Resource providers
│   │   └── myResource.ts
│   └── utils/          # Helper functions
├── package.json
├── tsconfig.json
└── README.md
```

### Basic Server Implementation

```typescript
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

// Create server instance
const server = new Server(
  {
    name: "my-mcp-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
      resources: {},
    },
  }
);

// Define available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "my_tool",
        description: "Description of what this tool does",
        inputSchema: {
          type: "object",
          properties: {
            param1: {
              type: "string",
              description: "Description of parameter",
            },
            param2: {
              type: "number",
              description: "Another parameter",
            },
          },
          required: ["param1"],
        },
      },
    ],
  };
});

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "my_tool") {
    try {
      const result = await myToolFunction(args);
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }

  throw new Error(`Unknown tool: ${name}`);
});

// Define available resources
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: "myserver://resource/path",
        name: "My Resource",
        description: "Description of the resource",
        mimeType: "application/json",
      },
    ],
  };
});

// Handle resource reads
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params;

  if (uri.startsWith("myserver://")) {
    const data = await fetchResourceData(uri);
    return {
      contents: [
        {
          uri,
          mimeType: "application/json",
          text: JSON.stringify(data),
        },
      ],
    };
  }

  throw new Error(`Unknown resource: ${uri}`);
});

// Tool implementation
async function myToolFunction(args: any) {
  // Your tool logic here
  return { success: true, data: args };
}

// Resource fetcher
async function fetchResourceData(uri: string) {
  // Your resource fetching logic here
  return { uri, fetched: new Date().toISOString() };
}

// Start server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP server running on stdio");
}

main().catch(console.error);
```

### package.json

```json
{
  "name": "my-mcp-server",
  "version": "1.0.0",
  "description": "MCP server for [purpose]",
  "type": "module",
  "bin": {
    "my-mcp-server": "./dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^0.6.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0",
    "tsx": "^4.0.0"
  }
}
```

## Common Tool Patterns

### API Integration Tool

```typescript
const apiTool = {
  name: "fetch_api_data",
  description: "Fetches data from external API",
  inputSchema: {
    type: "object",
    properties: {
      endpoint: {
        type: "string",
        description: "API endpoint to call",
      },
      method: {
        type: "string",
        enum: ["GET", "POST"],
        default: "GET",
      },
      body: {
        type: "object",
        description: "Request body for POST",
      },
    },
    required: ["endpoint"],
  },
};

async function handleApiTool(args: any) {
  const response = await fetch(args.endpoint, {
    method: args.method || "GET",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.API_KEY}`,
    },
    body: args.body ? JSON.stringify(args.body) : undefined,
  });
  return response.json();
}
```

### Database Query Tool

```typescript
const dbTool = {
  name: "query_database",
  description: "Runs a read-only SQL query",
  inputSchema: {
    type: "object",
    properties: {
      query: {
        type: "string",
        description: "SQL SELECT query",
      },
      params: {
        type: "array",
        description: "Query parameters",
      },
    },
    required: ["query"],
  },
};

async function handleDbTool(args: any) {
  // Validate query is SELECT only
  if (!args.query.trim().toUpperCase().startsWith("SELECT")) {
    throw new Error("Only SELECT queries allowed");
  }
  const result = await db.query(args.query, args.params);
  return result.rows;
}
```

### File System Tool

```typescript
const fsTool = {
  name: "read_file",
  description: "Reads a file from allowed directories",
  inputSchema: {
    type: "object",
    properties: {
      path: {
        type: "string",
        description: "File path (relative to allowed root)",
      },
    },
    required: ["path"],
  },
};

async function handleFsTool(args: any) {
  const allowedRoot = process.env.ALLOWED_PATH || "./data";
  const fullPath = path.join(allowedRoot, args.path);

  // Security: Prevent directory traversal
  if (!fullPath.startsWith(path.resolve(allowedRoot))) {
    throw new Error("Path outside allowed directory");
  }

  return fs.readFileSync(fullPath, "utf-8");
}
```

## Client Configuration

### Claude Desktop (claude_desktop_config.json)

```json
{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["/path/to/my-mcp-server/dist/index.js"],
      "env": {
        "API_KEY": "your-api-key"
      }
    }
  }
}
```

### For npm-published servers

```json
{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "my-mcp-server"],
      "env": {
        "API_KEY": "your-api-key"
      }
    }
  }
}
```

## Testing Your MCP Server

### Manual Testing

```bash
# Build the server
npm run build

# Test with echo
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | node dist/index.js

# Test a tool call
echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"my_tool","arguments":{"param1":"test"}},"id":2}' | node dist/index.js
```

### Integration Test

```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

async function test() {
  const transport = new StdioClientTransport({
    command: "node",
    args: ["./dist/index.js"],
  });

  const client = new Client({ name: "test-client", version: "1.0.0" }, {});
  await client.connect(transport);

  // List tools
  const tools = await client.listTools();
  console.log("Available tools:", tools);

  // Call a tool
  const result = await client.callTool("my_tool", { param1: "test" });
  console.log("Tool result:", result);

  await client.close();
}
```

## Security Best Practices

1. **Validate all inputs** - Never trust tool arguments
2. **Use allowlists** - Restrict file paths, URLs, queries
3. **Sanitize outputs** - Don't leak sensitive data
4. **Rate limit** - Prevent abuse
5. **Log access** - Audit tool usage
6. **Use environment variables** - Never hardcode secrets

## Output Format

When creating MCP servers, provide:
1. **Complete server code** ready to use
2. **package.json** with dependencies
3. **Client configuration** for Claude/other clients
4. **Test commands** for validation
5. **Security considerations** specific to the integration
Comments

Sign in to leave a comment