#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { MonotypeApiClient } from "./api-client.js";
class MonotypeMcpServer {
constructor() {
this.server = new Server(
{
name: "monotype-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// Initialize token from environment variable (optional)
const token = process.env.MONOTYPE_TOKEN;
// Initialize API client with token (if provided)
this.apiClient = new MonotypeApiClient(token);
if (token) {
console.error("Token initialized successfully from MONOTYPE_TOKEN environment variable");
} else {
console.error("WARNING: MONOTYPE_TOKEN not set. Server will start but API calls will fail until token is provided.");
}
this.setupHandlers();
this.setupErrorHandling();
}
setupHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "invite_user_for_customer",
description:
"Invite a user for a customer. This is equivalent to creating a user for the customer. The user receives an email invitation and then gets onboarded.",
inputSchema: {
type: "object",
properties: {
email: {
type: "string",
description: "Email address of the user to invite",
},
roleId: {
type: "string",
description: "Role ID to assign to the user",
},
teamIds: {
type: "array",
items: {
type: "string",
},
description: "Array of team IDs to assign to the user (optional)",
},
status: {
type: "number",
description: "Invitation status (default: 1)",
default: 1,
},
},
required: ["email", "roleId"],
},
},
{
name: "get_teams_for_customer",
description: "Get all teams for a specific customer ID",
inputSchema: {
type: "object",
properties: {
customerId: {
type: "string",
description:
"Customer ID (optional, uses default from auth if not provided)",
},
},
required: [],
},
},
{
name: "get_roles_for_customer",
description: "Get all roles for a specific customer ID",
inputSchema: {
type: "object",
properties: {
customerId: {
type: "string",
description:
"Customer ID (optional, uses default from auth if not provided)",
},
},
required: [],
},
},
{
name: "get_role_id_by_name",
description: "Get role ID by role name. Searches through all roles for the customer and returns the role ID if found.",
inputSchema: {
type: "object",
properties: {
roleName: {
type: "string",
description: "The display name of the role (e.g., 'Admin', 'Full Access')",
},
customerId: {
type: "string",
description:
"Customer ID (optional, uses default from auth if not provided)",
},
},
required: ["roleName"],
},
},
],
}));
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "invite_user_for_customer":
return await this.handleInviteUserForCustomer(args);
case "get_teams_for_customer":
return await this.handleGetTeamsForCustomer(args);
case "get_roles_for_customer":
return await this.handleGetRolesForCustomer(args);
case "get_role_id_by_name":
return await this.handleGetRoleIdByName(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
});
}
async handleInviteUserForCustomer(args) {
const { email, roleId, teamIds = [], status = 1 } = args;
if (!email || !roleId) {
throw new Error("Email and roleId are required");
}
const result = await this.apiClient.inviteUserForCustomer(
email,
roleId,
teamIds,
status
);
if (!result.success) {
throw new Error(result.error);
}
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: true,
message: "User invitation sent successfully",
data: result.data,
},
null,
2
),
},
],
};
}
async handleGetTeamsForCustomer(args) {
const { customerId } = args;
const result = await this.apiClient.getTeamsForCustomer(customerId);
if (!result.success) {
throw new Error(result.error);
}
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: true,
teams: result.data,
},
null,
2
),
},
],
};
}
async handleGetRolesForCustomer(args) {
const { customerId } = args;
const result = await this.apiClient.getRolesForCustomer(customerId);
if (!result.success) {
throw new Error(result.error);
}
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: true,
roles: result.data,
},
null,
2
),
},
],
};
}
async handleGetRoleIdByName(args) {
const { roleName, customerId } = args;
if (!roleName) {
throw new Error("roleName is required");
}
// First, get all roles for the customer
const result = await this.apiClient.getRolesForCustomer(customerId);
if (!result.success) {
throw new Error(result.error);
}
// Search for the role by name (case-insensitive)
const roles = result.data?.data || [];
const role = roles.find(
(r) => r.displayName?.toLowerCase() === roleName.toLowerCase()
);
if (!role) {
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: false,
error: `Role "${roleName}" not found`,
availableRoles: roles.map((r) => r.displayName),
},
null,
2
),
},
],
};
}
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: true,
roleName: role.displayName,
roleId: role.id,
role: role,
},
null,
2
),
},
],
};
}
setupErrorHandling() {
this.server.onerror = (error) => {
console.error("[MCP Error]", error);
};
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("Monotype MCP server running on stdio");
}
}
const server = new MonotypeMcpServer();
try {
await server.run();
} catch (error) {
console.error("Failed to start server:", error);
process.exit(1);
}