import { config } from 'dotenv';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import MediumAuth from './auth';
import MediumClient from './client';
// Load environment variables
config();
class MediumMcpServer {
private server: McpServer;
private mediumClient: MediumClient;
private auth: MediumAuth;
constructor() {
// Initialize authentication
this.auth = new MediumAuth();
// Initialize Medium client
this.mediumClient = new MediumClient(this.auth);
// Create MCP server instance
this.server = new McpServer({
name: "medium-mcp-server",
version: "1.0.0"
});
this.registerTools();
}
private registerTools() {
// Tool for publishing articles
this.server.tool(
"publish-article",
"Publish a new article on Medium",
{
title: z.string().min(1, "Title is required"),
content: z.string().min(10, "Content must be at least 10 characters"),
tags: z.array(z.string()).max(5, "Maximum 5 tags allowed").optional(),
publicationId: z.string().optional(),
publishStatus: z.enum(['public', 'draft', 'unlisted']).optional(),
notifyFollowers: z.boolean().optional()
},
async (args) => {
try {
const publishResult = await this.mediumClient.publishArticle({
title: args.title,
content: args.content,
tags: args.tags,
publicationId: args.publicationId,
publishStatus: args.publishStatus,
notifyFollowers: args.notifyFollowers
});
return {
content: [
{
type: "text",
text: JSON.stringify(publishResult, null, 2)
}
]
};
} catch (error: any) {
return {
isError: true,
content: [
{
type: "text",
text: `Error publishing article: ${error.message}`
}
]
};
}
}
);
// Tool for updating articles
this.server.tool(
"update-article",
"Update an existing article on Medium",
{
articleId: z.string().min(1, "Article ID is required"),
title: z.string().optional(),
content: z.string().optional(),
tags: z.array(z.string()).max(5, "Maximum 5 tags allowed").optional(),
publishStatus: z.enum(['public', 'draft', 'unlisted']).optional()
},
async (args) => {
try {
const updateResult = await this.mediumClient.updateArticle({
articleId: args.articleId,
title: args.title,
content: args.content,
tags: args.tags,
publishStatus: args.publishStatus
});
return {
content: [
{
type: "text",
text: JSON.stringify(updateResult, null, 2)
}
]
};
} catch (error: any) {
return {
isError: true,
content: [
{
type: "text",
text: `Error updating article: ${error.message}`
}
]
};
}
}
);
// Tool for deleting articles
this.server.tool(
"delete-article",
"Delete an article or draft from Medium",
{
articleId: z.string().min(1, "Article ID is required")
},
async (args) => {
try {
const deleteResult = await this.mediumClient.deleteArticle(args.articleId);
return {
content: [
{
type: "text",
text: `Article ${args.articleId} deleted successfully`
}
]
};
} catch (error: any) {
return {
isError: true,
content: [
{
type: "text",
text: `Error deleting article: ${error.message}`
}
]
};
}
}
);
// Tool for getting article details
this.server.tool(
"get-article",
"Get details of a specific article",
{
articleId: z.string().min(1, "Article ID is required")
},
async (args) => {
try {
const article = await this.mediumClient.getArticle(args.articleId);
return {
content: [
{
type: "text",
text: JSON.stringify(article, null, 2)
}
]
};
} catch (error: any) {
return {
isError: true,
content: [
{
type: "text",
text: `Error retrieving article: ${error.message}`
}
]
};
}
}
);
// Tool for retrieving user publications
this.server.tool(
"get-publications",
"Retrieve user's publications",
{},
async () => {
try {
const publications = await this.mediumClient.getUserPublications();
return {
content: [
{
type: "text",
text: JSON.stringify(publications, null, 2)
}
]
};
} catch (error: any) {
return {
isError: true,
content: [
{
type: "text",
text: `Error retrieving publications: ${error.message}`
}
]
};
}
}
);
// Tool for getting user drafts
this.server.tool(
"get-drafts",
"Retrieve user's draft articles",
{},
async () => {
try {
const drafts = await this.mediumClient.getDrafts();
return {
content: [
{
type: "text",
text: JSON.stringify(drafts, null, 2)
}
]
};
} catch (error: any) {
return {
isError: true,
content: [
{
type: "text",
text: `Error retrieving drafts: ${error.message}`
}
]
};
}
}
);
// Tool for getting user profile
this.server.tool(
"get-user-profile",
"Retrieve authenticated user's profile information",
{},
async () => {
try {
const profile = await this.mediumClient.getUserProfile();
return {
content: [
{
type: "text",
text: JSON.stringify(profile, null, 2)
}
]
};
} catch (error: any) {
return {
isError: true,
content: [
{
type: "text",
text: `Error retrieving user profile: ${error.message}`
}
]
};
}
}
);
// Tool for searching articles
this.server.tool(
"search-articles",
"Search and filter Medium articles",
{
keywords: z.array(z.string()).optional(),
publicationId: z.string().optional(),
tags: z.array(z.string()).optional()
},
async (args) => {
try {
const articles = await this.mediumClient.searchArticles({
keywords: args.keywords,
publicationId: args.publicationId,
tags: args.tags
});
return {
content: [
{
type: "text",
text: JSON.stringify(articles, null, 2)
}
]
};
} catch (error: any) {
return {
isError: true,
content: [
{
type: "text",
text: `Error searching articles: ${error.message}`
}
]
};
}
}
);
}
// Method to start the server
async start() {
// Authenticate first
await this.auth.authenticate();
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("🚀 MediumMCP Server Initialized");
}
}
// Main execution
async function main() {
const server = new MediumMcpServer();
await server.start();
}
main().catch(error => {
console.error("Fatal error:", error);
process.exit(1);
});