mcp-doppler-server
Version:
Model Context Protocol server for Doppler secret management
383 lines • 16.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const doppler_client_js_1 = require("./doppler-client.js");
const logger_js_1 = require("./logger.js");
const zod_1 = require("zod");
const dotenv_1 = __importDefault(require("dotenv"));
// Load environment variables
dotenv_1.default.config();
// Tool schemas
const ListSecretsSchema = zod_1.z.object({
project: zod_1.z.string().describe('The Doppler project name'),
config: zod_1.z.string().describe('The config/environment name (e.g., dev, staging, production)'),
});
const GetSecretSchema = zod_1.z.object({
project: zod_1.z.string().describe('The Doppler project name'),
config: zod_1.z.string().describe('The config/environment name'),
name: zod_1.z.string().describe('The secret name to retrieve'),
});
const SetSecretSchema = zod_1.z.object({
project: zod_1.z.string().describe('The Doppler project name'),
config: zod_1.z.string().describe('The config/environment name'),
name: zod_1.z.string().describe('The secret name'),
value: zod_1.z.string().describe('The secret value'),
});
const DeleteSecretSchema = zod_1.z.object({
project: zod_1.z.string().describe('The Doppler project name'),
config: zod_1.z.string().describe('The config/environment name'),
secrets: zod_1.z.array(zod_1.z.string()).describe('Array of secret names to delete'),
});
const PromoteSecretsSchema = zod_1.z.object({
project: zod_1.z.string().describe('The Doppler project name'),
sourceConfig: zod_1.z.string().describe('Source config/environment'),
targetConfig: zod_1.z.string().describe('Target config/environment'),
excludeKeys: zod_1.z.array(zod_1.z.string()).optional().describe('Keys to exclude from promotion'),
});
const CreateServiceTokenSchema = zod_1.z.object({
project: zod_1.z.string().describe('The Doppler project name'),
config: zod_1.z.string().describe('The config/environment name'),
name: zod_1.z.string().describe('Token name'),
access: zod_1.z.enum(['read', 'read/write']).optional().default('read').describe('Token access level'),
});
const GetActivityLogsSchema = zod_1.z.object({
project: zod_1.z.string().optional().describe('Filter by project'),
page: zod_1.z.number().optional().default(1).describe('Page number'),
perPage: zod_1.z.number().optional().default(20).describe('Items per page'),
});
// Define available tools
const TOOLS = [
{
name: 'doppler_list_projects',
description: 'List all Doppler projects',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'doppler_list_secrets',
description: 'List all secret names in a project/config',
inputSchema: {
type: 'object',
properties: {
project: { type: 'string', description: 'The Doppler project name' },
config: { type: 'string', description: 'The config/environment name' },
},
required: ['project', 'config'],
},
},
{
name: 'doppler_get_secret',
description: 'Get a specific secret value',
inputSchema: {
type: 'object',
properties: {
project: { type: 'string', description: 'The Doppler project name' },
config: { type: 'string', description: 'The config/environment name' },
name: { type: 'string', description: 'The secret name to retrieve' },
},
required: ['project', 'config', 'name'],
},
},
{
name: 'doppler_set_secret',
description: 'Set or update a secret value',
inputSchema: {
type: 'object',
properties: {
project: { type: 'string', description: 'The Doppler project name' },
config: { type: 'string', description: 'The config/environment name' },
name: { type: 'string', description: 'The secret name' },
value: { type: 'string', description: 'The secret value' },
},
required: ['project', 'config', 'name', 'value'],
},
},
{
name: 'doppler_delete_secrets',
description: 'Delete one or more secrets',
inputSchema: {
type: 'object',
properties: {
project: { type: 'string', description: 'The Doppler project name' },
config: { type: 'string', description: 'The config/environment name' },
secrets: {
type: 'array',
items: { type: 'string' },
description: 'Array of secret names to delete',
},
},
required: ['project', 'config', 'secrets'],
},
},
{
name: 'doppler_promote_secrets',
description: 'Promote secrets from one environment to another',
inputSchema: {
type: 'object',
properties: {
project: { type: 'string', description: 'The Doppler project name' },
sourceConfig: { type: 'string', description: 'Source config/environment' },
targetConfig: { type: 'string', description: 'Target config/environment' },
excludeKeys: {
type: 'array',
items: { type: 'string' },
description: 'Keys to exclude from promotion',
},
},
required: ['project', 'sourceConfig', 'targetConfig'],
},
},
{
name: 'doppler_create_service_token',
description: 'Create a service token for a project/config',
inputSchema: {
type: 'object',
properties: {
project: { type: 'string', description: 'The Doppler project name' },
config: { type: 'string', description: 'The config/environment name' },
name: { type: 'string', description: 'Token name' },
access: {
type: 'string',
enum: ['read', 'read/write'],
description: 'Token access level',
},
},
required: ['project', 'config', 'name'],
},
},
{
name: 'doppler_get_activity_logs',
description: 'Get activity logs for auditing',
inputSchema: {
type: 'object',
properties: {
project: { type: 'string', description: 'Filter by project' },
page: { type: 'number', description: 'Page number' },
perPage: { type: 'number', description: 'Items per page' },
},
},
},
];
class DopplerMCPServer {
server;
dopplerClient;
constructor() {
this.server = new index_js_1.Server({
name: 'doppler-mcp-server',
version: '0.1.0',
}, {
capabilities: {
resources: {},
tools: {},
},
});
// Initialize Doppler client with token from environment
const token = process.env.DOPPLER_TOKEN;
if (!token) {
logger_js_1.logger.error('DOPPLER_TOKEN environment variable is required');
process.exit(1);
}
this.dopplerClient = new doppler_client_js_1.DopplerClient(token);
this.setupHandlers();
}
setupHandlers() {
// Handle tool listing
this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
tools: TOOLS,
}));
// Handle resource listing
this.server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
try {
const projects = await this.dopplerClient.listProjects();
const resources = projects.map((project) => ({
uri: `doppler://project/${project.slug}`,
name: project.name,
description: project.description || `Doppler project: ${project.name}`,
mimeType: 'application/json',
}));
return { resources };
}
catch (error) {
logger_js_1.logger.error('Failed to list resources', error);
return { resources: [] };
}
});
// Handle resource reading
this.server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
const uri = request.params.uri;
const match = uri.match(/^doppler:\/\/project\/([^\/]+)(?:\/config\/([^\/]+))?$/);
if (!match) {
throw new Error('Invalid resource URI format');
}
const [, projectSlug, configName] = match;
try {
if (configName) {
// Get secrets for specific config
const secrets = await this.dopplerClient.listSecrets(projectSlug, configName);
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(secrets, null, 2),
},
],
};
}
else {
// Get project configs
const configs = await this.dopplerClient.listConfigs(projectSlug);
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(configs, null, 2),
},
],
};
}
}
catch (error) {
logger_js_1.logger.error('Failed to read resource', { uri, error });
throw error;
}
});
// Handle tool calls
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'doppler_list_projects': {
const projects = await this.dopplerClient.listProjects();
return {
content: [
{
type: 'text',
text: JSON.stringify(projects, null, 2),
},
],
};
}
case 'doppler_list_secrets': {
const { project, config } = ListSecretsSchema.parse(args);
const secrets = await this.dopplerClient.listSecrets(project, config);
return {
content: [
{
type: 'text',
text: JSON.stringify(secrets, null, 2),
},
],
};
}
case 'doppler_get_secret': {
const { project, config, name: secretName } = GetSecretSchema.parse(args);
const secret = await this.dopplerClient.getSecret(project, config, secretName);
return {
content: [
{
type: 'text',
text: JSON.stringify(secret, null, 2),
},
],
};
}
case 'doppler_set_secret': {
const { project, config, name: secretName, value } = SetSecretSchema.parse(args);
const result = await this.dopplerClient.setSecret(project, config, secretName, value);
return {
content: [
{
type: 'text',
text: `Secret '${secretName}' has been ${result.created ? 'created' : 'updated'} successfully in ${project}/${config}`,
},
],
};
}
case 'doppler_delete_secrets': {
const { project, config, secrets } = DeleteSecretSchema.parse(args);
await this.dopplerClient.deleteSecrets(project, config, secrets);
return {
content: [
{
type: 'text',
text: `Successfully deleted ${secrets.length} secret(s) from ${project}/${config}`,
},
],
};
}
case 'doppler_promote_secrets': {
const { project, sourceConfig, targetConfig, excludeKeys } = PromoteSecretsSchema.parse(args);
const result = await this.dopplerClient.promoteSecrets(project, sourceConfig, targetConfig, excludeKeys);
return {
content: [
{
type: 'text',
text: `Successfully promoted ${result.count} secrets from ${project}/${sourceConfig} to ${project}/${targetConfig}`,
},
],
};
}
case 'doppler_create_service_token': {
const { project, config, name: tokenName, access } = CreateServiceTokenSchema.parse(args);
const token = await this.dopplerClient.createServiceToken(project, config, tokenName, access);
return {
content: [
{
type: 'text',
text: `Service token created successfully:\nToken: ${token.key}\nAccess: ${token.access}\n\nIMPORTANT: Save this token securely, it won't be shown again!`,
},
],
};
}
case 'doppler_get_activity_logs': {
const { project, page, perPage } = GetActivityLogsSchema.parse(args);
const logs = await this.dopplerClient.getActivityLogs(project, page, perPage);
return {
content: [
{
type: 'text',
text: JSON.stringify(logs, null, 2),
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
}
catch (error) {
logger_js_1.logger.error(`Tool execution failed: ${name}`, error);
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
};
}
});
}
async start() {
const transport = new stdio_js_1.StdioServerTransport();
await this.server.connect(transport);
logger_js_1.logger.info('Doppler MCP server started');
}
}
// Start the server
const server = new DopplerMCPServer();
server.start().catch((error) => {
logger_js_1.logger.error('Failed to start server', error);
process.exit(1);
});
//# sourceMappingURL=index.js.map