Skip to content

Creating MCP Servers

Build MCP servers that provide tools, resources, and prompts to AI assistants.

Source Code Structure

MCP servers are Node.js 20.x applications using @modelcontextprotocol/sdk. Your code must export a createMcpServer() function:

javascript
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
const { z } = require("zod");

function createMcpServer() {
  const server = new McpServer({
    name: "my-server",
    version: "1.0.0"
  });

  // Register tools, resources, and prompts here

  return server;
}

module.exports = { createMcpServer };

Registering Tools

Tools are functions that AI assistants can call:

javascript
server.registerTool("greet", {
  title: "Greet",
  description: "Returns a greeting for the given name",
  inputSchema: {
    name: z.string().describe("Name to greet")
  }
}, async ({ name }) => ({
  content: [{ type: "text", text: JSON.stringify({ greeting: `Hello, ${name}!` }) }]
}));

Tool with Multiple Parameters

javascript
server.registerTool("search", {
  title: "Search",
  description: "Search the web for information",
  inputSchema: {
    query: z.string().describe("Search query"),
    limit: z.number().optional().describe("Max results (default: 10)")
  }
}, async ({ query, limit = 10 }) => {
  // Your search logic here
  const results = await performSearch(query, limit);
  return {
    content: [{ type: "text", text: JSON.stringify(results) }]
  };
});

Registering Resources

Resources provide data that AI assistants can read:

javascript
server.resource(
  "info",
  "my-server://info",
  {
    description: "Server information",
    mimeType: "application/json"
  },
  async (uri) => ({
    contents: [{
      uri: uri.href,
      mimeType: "application/json",
      text: JSON.stringify({
        name: "my-server",
        version: "1.0.0",
        tools: ["greet", "search"]
      })
    }]
  })
);

Registering Prompts

Prompts are pre-built templates for common tasks:

javascript
server.prompt(
  "analyze-data",
  "Prompt to analyze a dataset",
  {
    topic: z.string().describe("What to analyze")
  },
  async ({ topic }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please analyze the following topic thoroughly: ${topic}`
      }
    }]
  })
);

Accessing API Keys

Third-party API keys are automatically injected as environment variables. Keys are flattened to uppercase with _KEY suffix:

javascript
function createMcpServer() {
  const server = new McpServer({ name: "my-server", version: "1.0.0" });

  server.registerTool("search", {
    title: "Search",
    description: "Search the web",
    inputSchema: { query: z.string() }
  }, async ({ query }) => {
    // Keys are available as environment variables
    const apiKey = process.env.SERPAPI_KEY;

    if (!apiKey) {
      return {
        content: [{ type: "text", text: "Error: SerpAPI key not configured" }]
      };
    }

    const response = await fetch(
      `https://serpapi.com/search?q=${encodeURIComponent(query)}&api_key=${apiKey}`
    );
    const data = await response.json();

    return {
      content: [{ type: "text", text: JSON.stringify(data) }]
    };
  });

  return server;
}

Key Naming Convention

Service NameEnvironment Variable
serpapiprocess.env.SERPAPI_KEY
openaiprocess.env.OPENAI_KEY
githubprocess.env.GITHUB_KEY
awsprocess.env.AWS_KEY

Author Keys (Role Tokens)

If you want to provide your own API keys so users don't need to:

  1. Create a role token with your keys
  2. Set authorRoleToken on your MCP server
  3. Your keys are injected automatically — author keys override user keys

Available Imports

javascript
// MCP SDK
const { McpServer, ResourceTemplate } = require("@modelcontextprotocol/sdk/server/mcp.js");
const { z } = require("zod");

// Node.js built-ins
const https = require("https");
const http = require("http");
const crypto = require("crypto");
const url = require("url");
const querystring = require("querystring");
const buffer = require("buffer");
const stream = require("stream");
const util = require("util");
const path = require("path");
const os = require("os");

WARNING

fetch is available globally (Node.js 20.x). File system (fs) and child process (child_process) are not available for security.

Deploying

Via API

bash
curl -s -X POST https://api.universalapi.co/mcp-admin/create \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "serverName": "my-server",
    "description": "A server that does useful things",
    "sourceCode": "const { McpServer } = require(\"@modelcontextprotocol/sdk/server/mcp.js\");\nconst { z } = require(\"zod\");\n\nfunction createMcpServer() {\n  const server = new McpServer({ name: \"my-server\", version: \"1.0.0\" });\n  server.registerTool(\"hello\", {\n    title: \"Hello\",\n    description: \"Say hello\",\n    inputSchema: { name: z.string() }\n  }, async ({ name }) => ({\n    content: [{ type: \"text\", text: `Hello, ${name}!` }]\n  }));\n  return server;\n}\nmodule.exports = { createMcpServer };",
    "visibility": "public"
  }' | jq

Via Python Script

python
import requests

TOKEN = "uapi_ut_xxxx_your_token"

source_code = '''
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
const { z } = require("zod");

function createMcpServer() {
  const server = new McpServer({ name: "my-server", version: "1.0.0" });

  server.registerTool("hello", {
    title: "Hello",
    description: "Say hello to someone",
    inputSchema: { name: z.string().describe("Name to greet") }
  }, async ({ name }) => ({
    content: [{ type: "text", text: JSON.stringify({ greeting: `Hello, ${name}!` }) }]
  }));

  return server;
}
module.exports = { createMcpServer };
'''

response = requests.post(
    "https://api.universalapi.co/mcp-admin/create",
    headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
    json={
        "serverName": "my-server",
        "description": "A greeting server",
        "sourceCode": source_code,
        "visibility": "public"
    }
)
print(response.json())

Updating

bash
curl -s -X PUT https://api.universalapi.co/mcp-admin/update \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "serverId": "mcp-xxx",
    "sourceCode": "...",
    "description": "Updated description"
  }' | jq

Best Practices

  1. Descriptive tool names and descriptions — The AI uses these to decide when to call your tool
  2. Use Zod schemas — Validate inputs with .describe() for each parameter
  3. Return JSON strings — Wrap results in JSON.stringify() for structured data
  4. Handle errors gracefully — Return error messages in the content, don't throw
  5. Check for required keys — Verify API keys exist before making external calls
  6. Keep tools focused — One tool per task, not one tool that does everything

Universal API - The agentic entry point to the universe of APIs