Appearance
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 Name | Environment Variable |
|---|---|
serpapi | process.env.SERPAPI_KEY |
openai | process.env.OPENAI_KEY |
github | process.env.GITHUB_KEY |
aws | process.env.AWS_KEY |
Author Keys (Role Tokens)
If you want to provide your own API keys so users don't need to:
- Create a role token with your keys
- Set
authorRoleTokenon your MCP server - 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"
}' | jqVia 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"
}' | jqBest Practices
- Descriptive tool names and descriptions — The AI uses these to decide when to call your tool
- Use Zod schemas — Validate inputs with
.describe()for each parameter - Return JSON strings — Wrap results in
JSON.stringify()for structured data - Handle errors gracefully — Return error messages in the content, don't throw
- Check for required keys — Verify API keys exist before making external calls
- Keep tools focused — One tool per task, not one tool that does everything