Skip to main content
Glama

Twilio MCP Server (Docker Edition with Auth Token Support)

by Twine2546
http.ts•8.64 kB
import fetch, { Response } from 'node-fetch'; import FormData from 'form-data'; import qs from 'qs'; import * as fs from 'fs'; import * as path from 'path'; import { HttpMethod } from '@app/types'; import logger from './logger'; function getPackageVersion(): string { try { const packageJsonPath = path.resolve(__dirname, '../../package.json'); const packageJsonData = fs.readFileSync(packageJsonPath, 'utf8'); const packageJson = JSON.parse(packageJsonData); return packageJson.version; } catch (error) { logger.error(`Failed to read package.json: ${error}`); return 'unknown'; } } type SuccessResponse<T> = { ok: true; statusCode: number; data: T; response?: Response; }; type ErrorResponse = { ok: false; statusCode: number; error: Error; response?: Response; }; export type HttpResponse<T> = SuccessResponse<T> | ErrorResponse; type RequestOption = { headers?: Record<string, string>; }; type HttpRequest = RequestOption & { method: HttpMethod; url: string; headers?: Record<string, string>; body?: Record<string, unknown>; }; export type Authorization = | { type: 'Basic'; username: string; password: string; } | { type: 'Bearer'; token: string; } | { type: 'ApiKey'; key: string; value: string; }; export type Configuration = { authorization?: Authorization; }; const arrayRepeatUrls = ['serverless.twilio.com']; /** * Get the authorization header * @param authorization */ function getAuthorization( authorization?: Authorization, ): Record<string, string> { if (!authorization) { return {}; } if (authorization.type === 'Basic') { return { Authorization: `Basic ${Buffer.from( `${authorization.username}:${authorization.password}`, ).toString('base64')}`, }; } if (authorization.type === 'Bearer') { return { Authorization: `Bearer ${authorization.token}`, }; } if (authorization.type === 'ApiKey') { return { [authorization.key]: authorization.value, }; } // @ts-ignore throw new Error(`Unsupported authorization type: ${authorization.type}`); } /** * Interpolate URL with params * @param url * @param params */ export const interpolateUrl = ( url: string, params?: Record<string, unknown>, ) => { if (!params) { return url; } if (Array.isArray(params)) { return url; } return url.replace(/{(.*?)}/g, (_, key) => { const value = params[key]; if (typeof value === 'string') { return value; } if (typeof value === 'number') { return value.toString(); } if (typeof value === 'boolean') { return value.toString(); } return `{${key}}`; }); }; export default class Http { private readonly defaultRequest: RequestInit; private readonly logger; private readonly version: string; constructor(config: Configuration) { this.version = getPackageVersion(); this.defaultRequest = { headers: { 'Content-Type': 'application/json', ...getAuthorization(config.authorization), 'x-mcp-server-id': `twilio-openapi-mcp-server/${this.version}`, 'user-agent': `twilio-openapi-mcp-server/${this.version}`, }, }; this.logger = logger.child({ module: 'Http' }); } /** * Makes a request to the downstream service */ private async make<T>(request: HttpRequest): Promise<HttpResponse<T>> { try { logger.debug(`request object: ${JSON.stringify(request)}`); const options: RequestInit = { ...this.defaultRequest, method: request.method, }; if (request.headers) { // @ts-ignore options.headers = { ...this.defaultRequest?.headers, ...request.headers, }; } // Handle FormData objects specially if (request.body && '__formData' in request.body) { // @ts-ignore if (options.headers['Content-Type']) { // @ts-ignore delete options.headers['Content-Type']; } // @ts-ignore // eslint-disable-next-line no-underscore-dangle options.body = request.body.__formData; } else if (['POST', 'PUT'].includes(request.method) && request.body) { const arrayRepeated = arrayRepeatUrls.some((x) => request.url.includes(x), ); options.body = Http.getBody( request.body, request.headers, arrayRepeated, ); } logger.debug( `Making request to ${request.url} with options ${JSON.stringify( options, )}`, ); const response = await fetch(request.url, options as any); if (!response.ok) { const errorMessage = await response.text(); return { ok: false, statusCode: response.status, error: new Error(errorMessage), response, }; } const data = (await response.json()) as T; const successResponse: SuccessResponse<T> = { ok: true, statusCode: response.status, data, response, }; logger.debug('Request successful'); return successResponse; } catch (error) { logger.error(`Failed to make request ${error}`); return { ok: false, statusCode: 500, error: new Error('An error occurred while making the request'), }; } } /** * Makes a GET request * @param url the url to make the request to * @param options additional options for the request */ public async get<T>( url: string, options?: RequestOption, ): Promise<HttpResponse<T>> { return this.make<T>({ url, method: 'GET', ...options, }); } /** * Makes a POST request * @param url the url to make the request to * @param body the body of the request * @param options additional options for the request */ public async post<T>( url: string, body?: Record<string, unknown>, options?: RequestOption, ): Promise<HttpResponse<T>> { return this.make<T>({ url, body, method: 'POST', ...options, }); } /** * Makes a PUT request * @param url the url to make the request to * @param body the body of the request * @param options additional options for the request */ public async put<T>( url: string, body?: Record<string, unknown>, options?: RequestOption, ): Promise<HttpResponse<T>> { return this.make<T>({ url, body, method: 'PUT', ...options, }); } /** * Makes a DELETE request * @param url the url to make the request to * @param options additional options for the request */ public async delete<T>( url: string, options?: RequestOption, ): Promise<HttpResponse<T>> { return this.make<T>({ url, method: 'DELETE', ...options, }); } /** * Makes a multipart form data upload request * @param url the url to make the request to * @param formData the FormData instance to upload * @param options additional options for the request */ public async upload<T>( url: string, formData: FormData, options?: RequestOption, ): Promise<HttpResponse<T>> { const formHeaders = formData.getHeaders ? formData.getHeaders() : {}; const headers = { ...(options?.headers || {}), ...formHeaders, }; return this.make<T>({ url, method: 'POST', headers, // Use a special symbol or property to indicate this is a FormData object // rather than a regular body that needs serialization body: { __formData: formData }, }); } /** * Returns the body of the request * @param body * @param headers * @private */ private static getBody( body: Record<string, unknown>, headers?: Record<string, string>, arrayRepeat?: boolean, ): string { const contentType = headers?.['Content-Type'] as string; if (contentType === 'application/x-www-form-urlencoded') { if (arrayRepeat) { return qs.stringify(body, { arrayFormat: 'repeat', indices: false, }); } const processedBody: Record<string, unknown> = {}; Object.keys(body).forEach((key) => { const value = body[key]; if (value !== null && typeof value === 'object') { processedBody[key] = JSON.stringify(value); } else { processedBody[key] = value; } }); return qs.stringify(processedBody); } return JSON.stringify(body); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Twine2546/twilio-mcp-docker'

If you have feedback or need assistance with the MCP directory API, please join our Discord server